From f600d0576505af8117a836f0fc4d680235867767 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 24 Feb 2022 23:24:53 +0000 Subject: [PATCH] Import gpac_2.0.0+dfsg1.orig.tar.xz [dgit import orig gpac_2.0.0+dfsg1.orig.tar.xz] --- .gitattributes | 25 + .github/ISSUE_TEMPLATE.md | 7 + .gitignore | 90 + .gitmodules | 5 + .travis.yml | 58 + COPYING | 504 + Changelog | 1035 + Makefile | 376 + README.md | 134 + SECURITY.md | 22 + applications/Makefile | 63 + applications/generators/MPEG4/MPEG4Gen.dsp | 98 + applications/generators/MPEG4/MPEG4Gen.dsw | 29 + applications/generators/MPEG4/Makefile | 52 + applications/generators/MPEG4/main.c | 1851 + applications/generators/MPEG4/skip.txt | 181 + applications/generators/MPEG4/templates1.txt | 1292 + applications/generators/MPEG4/templates10.txt | 53 + applications/generators/MPEG4/templates2.txt | 592 + applications/generators/MPEG4/templates3.txt | 123 + applications/generators/MPEG4/templates4.txt | 129 + applications/generators/MPEG4/templates5.txt | 561 + applications/generators/MPEG4/templates6.txt | 242 + applications/generators/MPEG4/templates7.txt | 235 + applications/generators/MPEG4/templates8.txt | 124 + applications/generators/MPEG4/templates9.txt | 68 + applications/generators/Makefile | 26 + applications/generators/SVG/Makefile | 57 + applications/generators/SVG/SVGGen.dsp | 126 + applications/generators/SVG/SVGGen.dsw | 29 + .../generators/SVG/Tiny-1.2-NG/Tiny-1.2.nvdl | 35 + .../generators/SVG/Tiny-1.2-NG/Tiny-1.2.rng | 55 + .../generators/SVG/Tiny-1.2-NG/animate.rng | 334 + .../SVG/Tiny-1.2-NG/animation-element.rng | 59 + .../generators/SVG/Tiny-1.2-NG/audio.rng | 51 + .../SVG/Tiny-1.2-NG/conditional.rng | 65 + .../SVG/Tiny-1.2-NG/contenttype-attrib.rng | 22 + .../SVG/Tiny-1.2-NG/coordinate-attrib.rng | 42 + .../SVG/Tiny-1.2-NG/core-attrib.rng | 67 + .../generators/SVG/Tiny-1.2-NG/datatypes.rng | 145 + .../generators/SVG/Tiny-1.2-NG/discard.rng | 45 + .../SVG/Tiny-1.2-NG/extensibility.rng | 44 + .../SVG/Tiny-1.2-NG/extresources-attrib.rng | 22 + .../SVG/Tiny-1.2-NG/flowable-text-tiny.rng | 118 + .../SVG/Tiny-1.2-NG/focus-attrib.rng | 86 + .../generators/SVG/Tiny-1.2-NG/font-tiny.rng | 349 + .../SVG/Tiny-1.2-NG/gradient-tiny.rng | 156 + .../SVG/Tiny-1.2-NG/graphics-attrib.rng | 109 + .../generators/SVG/Tiny-1.2-NG/handler.rng | 53 + .../generators/SVG/Tiny-1.2-NG/headers.rng | 67 + .../generators/SVG/Tiny-1.2-NG/hyperlink.rng | 47 + .../generators/SVG/Tiny-1.2-NG/image.rng | 53 + .../generators/SVG/Tiny-1.2-NG/laser-ext.rng | 146 + .../SVG/Tiny-1.2-NG/media-attrib.rng | 40 + .../SVG/Tiny-1.2-NG/opacity-attrib-tiny.rng | 44 + .../SVG/Tiny-1.2-NG/paint-attrib-tiny.rng | 113 + .../generators/SVG/Tiny-1.2-NG/prefetch.rng | 60 + .../generators/SVG/Tiny-1.2-NG/script.rng | 44 + .../generators/SVG/Tiny-1.2-NG/shapes.rng | 230 + .../generators/SVG/Tiny-1.2-NG/solidcolor.rng | 65 + .../SVG/Tiny-1.2-NG/structure-tiny.rng | 257 + .../generators/SVG/Tiny-1.2-NG/text-tiny.rng | 213 + .../SVG/Tiny-1.2-NG/transform-attrib.rng | 25 + .../Tiny-1.2-NG/vectoreffects-attrib-tiny.rng | 26 + .../generators/SVG/Tiny-1.2-NG/video.rng | 80 + .../SVG/Tiny-1.2-NG/viewport-attrib-tiny.rng | 45 + .../SVG/Tiny-1.2-NG/xlink-attrib.rng | 123 + .../generators/SVG/Tiny-1.2-NG/xml-events.rng | 84 + applications/generators/SVG/html.c | 151 + applications/generators/SVG/laser.c | 267 + applications/generators/SVG/main.c | 951 + applications/generators/SVG/svggen.h | 195 + applications/generators/SVG/v1.c | 608 + applications/generators/SVG/v2.c | 460 + applications/generators/SVG/v3.c | 295 + applications/generators/X3D/Makefile | 52 + applications/generators/X3D/X3DGen.dsp | 98 + applications/generators/X3D/X3DGen.dsw | 29 + applications/generators/X3D/main.c | 1275 + applications/generators/X3D/skip.txt | 130 + applications/generators/X3D/templates_X3D.txt | 1644 + applications/gpac/Info.plist | 16 + applications/gpac/Makefile | 70 + applications/gpac/main.c | 4847 ++ applications/mp4box/Makefile | 86 + applications/mp4box/filedump.c | 4492 ++ applications/mp4box/fileimport.c | 3852 ++ applications/mp4box/live.c | 763 + applications/mp4box/main.c | 6588 ++ applications/mp4box/mp4box.h | 192 + applications/mp4client/Makefile | 88 + applications/mp4client/carbon_events.c | 109 + applications/mp4client/main.c | 2811 + applications/mp4client/mp4client.rc | 72 + applications/mp4client/resource.h | 18 + change_version.sh | 53 + check_revision.sh | 37 + configure | 3500 + generate_installer.bat | 107 + gpac.spec | 86 + include/gpac/00_doxy.h | 133 + include/gpac/Remotery.h | 679 + include/gpac/ait.h | 269 + include/gpac/avparse.h | 857 + include/gpac/base_coding.h | 108 + include/gpac/bifs.h | 178 + include/gpac/bitstream.h | 655 + include/gpac/cache.h | 283 + include/gpac/color.h | 296 + include/gpac/compositor.h | 283 + include/gpac/config_file.h | 202 + include/gpac/configuration.h | 345 + include/gpac/constants.h | 1655 + include/gpac/crypt.h | 154 + include/gpac/crypt_tools.h | 296 + include/gpac/dash.h | 1174 + include/gpac/download.h | 568 + include/gpac/dsmcc.h | 665 + include/gpac/dvb_mpe.h | 46 + include/gpac/events.h | 425 + include/gpac/events_constants.h | 528 + include/gpac/evg.h | 1094 + include/gpac/filters.h | 4526 ++ include/gpac/html5_media.h | 387 + include/gpac/html5_mse.h | 251 + include/gpac/ietf.h | 1609 + include/gpac/internal/avilib.h | 427 + include/gpac/internal/bifs_dev.h | 242 + include/gpac/internal/bifs_tables.h | 835 + include/gpac/internal/camera.h | 195 + include/gpac/internal/compositor_dev.h | 2599 + include/gpac/internal/crypt_dev.h | 66 + include/gpac/internal/dvb_mpe_dev.h | 265 + include/gpac/internal/ietf_dev.h | 517 + include/gpac/internal/isomedia_dev.h | 4695 ++ include/gpac/internal/laser_dev.h | 344 + include/gpac/internal/m3u8.h | 144 + include/gpac/internal/media_dev.h | 1135 + include/gpac/internal/mesh.h | 317 + include/gpac/internal/odf_dev.h | 364 + include/gpac/internal/odf_parse_common.h | 51 + include/gpac/internal/ogg.h | 213 + include/gpac/internal/reedsolomon.h | 78 + include/gpac/internal/scenegraph_dev.h | 960 + include/gpac/internal/swf_dev.h | 410 + include/gpac/internal/vobsub.h | 75 + include/gpac/iso639.h | 89 + include/gpac/isomedia.h | 6834 ++ include/gpac/laser.h | 169 + include/gpac/list.h | 235 + include/gpac/main.h | 268 + include/gpac/maths.h | 1175 + include/gpac/media_tools.h | 1464 + include/gpac/mediaobject.h | 416 + include/gpac/module.h | 291 + include/gpac/modules/audio_out.h | 135 + include/gpac/modules/codec.h | 329 + include/gpac/modules/compositor_ext.h | 111 + include/gpac/modules/font.h | 127 + include/gpac/modules/hardcoded_proto.h | 70 + include/gpac/modules/ipmp.h | 148 + include/gpac/modules/video_out.h | 237 + include/gpac/mpd.h | 1238 + include/gpac/mpeg4_odf.h | 2517 + include/gpac/mpegts.h | 1997 + include/gpac/network.h | 688 + include/gpac/nodes_mpeg4.h | 3337 + include/gpac/nodes_svg.h | 338 + include/gpac/nodes_x3d.h | 2020 + include/gpac/options.h | 336 + include/gpac/path2d.h | 624 + include/gpac/route.h | 300 + include/gpac/rtp_streamer.h | 253 + include/gpac/scene_engine.h | 175 + include/gpac/scene_manager.h | 583 + include/gpac/scenegraph.h | 1031 + include/gpac/scenegraph_svg.h | 1167 + include/gpac/scenegraph_vrml.h | 958 + include/gpac/setup.h | 745 + include/gpac/svg_types.h | 1065 + include/gpac/sync_layer.h | 180 + include/gpac/term_info.h | 298 + include/gpac/terminal.h | 328 + include/gpac/thread.h | 378 + include/gpac/token.h | 107 + include/gpac/tools.h | 2158 + include/gpac/user.h | 122 + include/gpac/utf.h | 150 + include/gpac/version.h | 84 + include/gpac/webvtt.h | 126 + include/gpac/xml.h | 429 + include/win32/inttypes.h | 21 + include/win32/stdint.h | 17 + include/wince/errno.h | 104 + manifest.rc | 1 + mkdmg.sh | 128 + modules/Makefile | 104 + modules/alsa/Makefile | 44 + modules/alsa/alsa.c | 370 + modules/dec_openhevc/Makefile | 59 + modules/dektec_out/Makefile | 58 + modules/dektec_out/dektec_video.cpp | 803 + modules/dektec_out/dektec_video.h | 98 + modules/dektec_out/dektec_video_decl.c | 117 + modules/dektec_out/dektec_video_old.cpp | 367 + modules/dektec_out/out_dektec.vcxproj | 294 + modules/demo_is/Makefile | 46 + modules/demo_is/demo-sensor.bt | 86 + modules/demo_is/demo_is.c | 104 + modules/directfb_out/Makefile | 47 + modules/directfb_out/directfb_out.c | 343 + modules/directfb_out/directfb_out.h | 74 + modules/directfb_out/directfb_wrapper.c | 924 + modules/droid_audio/droidaudio.c | 510 + modules/droid_audio/javaenv.c | 49 + modules/droid_audio/javaenv.h | 34 + modules/droid_cam/droid_cam.c | 725 + modules/droid_mpegv/droid_mpegv.c | 495 + modules/droid_out/droid_vout-bitmap.c | 217 + modules/droid_out/droid_vout.c | 898 + modules/dx_hw/Makefile | 59 + modules/dx_hw/collide.cur | Bin 0 -> 2238 bytes modules/dx_hw/dx_2d.c | 883 + modules/dx_hw/dx_audio.c | 503 + modules/dx_hw/dx_hw.h | 239 + modules/dx_hw/dx_hw.rc | 81 + modules/dx_hw/dx_video.c | 846 + modules/dx_hw/dx_window.c | 1129 + modules/dx_hw/hand.cur | Bin 0 -> 2238 bytes modules/dx_hw/resource.h | 23 + modules/filter_export.cpp | 36 + modules/ft_font/Makefile | 57 + modules/ft_font/ft_font.c | 838 + modules/ft_font/ft_font.h | 30 + modules/ios_cam/CameraObject.h | 64 + modules/ios_cam/CameraObject.m | 170 + modules/ios_cam/cam_wrap.h | 55 + modules/ios_cam/cam_wrap.m | 82 + modules/ios_cam/ios_cam.c | 435 + modules/ios_mpegv/SensorAcces.m | 209 + modules/ios_mpegv/SensorAccess.h | 34 + modules/ios_mpegv/ios_mpegv-Prefix.pch | 7 + modules/ios_mpegv/ios_mpegv.c | 306 + modules/ios_mpegv/sensor_wrap.h | 43 + modules/jack/Makefile | 42 + modules/jack/jack.c | 501 + modules/modules_export.cpp | 38 + modules/pulseaudio/Makefile | 42 + modules/pulseaudio/pulseaudio.c | 287 + modules/sdl_out/Makefile | 52 + modules/sdl_out/audio.c | 280 + modules/sdl_out/cursors.c | 63 + modules/sdl_out/sdl_out.c | 96 + modules/sdl_out/sdl_out.h | 128 + modules/sdl_out/video.c | 2078 + modules/sdl_out/video2d.c | 29 + modules/test_filter/Makefile | 43 + modules/test_filter/test_filter.c | 115 + modules/test_filter/test_filter.vcxproj | 292 + modules/validator/Makefile | 49 + modules/validator/README.TXT | 49 + modules/validator/validator.c | 1199 + modules/wav_out/Makefile | 44 + modules/wav_out/wav_out.c | 509 + modules/x11_out/Makefile | 84 + modules/x11_out/x11_out.c | 1656 + modules/x11_out/x11_out.h | 134 + packagers/osx/GPAC.app/Contents/Info.plist | 577 + packagers/osx/GPAC.app/Contents/PkgInfo | 1 + .../osx/GPAC.app/Contents/Resources/osmo.icns | Bin 0 -> 147235 bytes .../Contents/Resources/osmo_audio.icns | Bin 0 -> 209406 bytes .../Contents/Resources/osmo_generic.icns | Bin 0 -> 190916 bytes .../Contents/Resources/osmo_model.icns | Bin 0 -> 236301 bytes .../Contents/Resources/osmo_subs.icns | Bin 0 -> 176846 bytes .../Contents/Resources/osmo_video.icns | Bin 0 -> 221221 bytes .../Contents/Resources/osmo_widget.icns | Bin 0 -> 227171 bytes packagers/osx/SLA.r | 446 + packagers/osx/distribution.xml | 19 + packagers/osx/res/background.png | Bin 0 -> 98776 bytes packagers/osx/res/preamble.txt | 8 + packagers/osx/scripts/postinstall | 14 + packagers/win32_64/nsis/default.out | 0 packagers/win32_64/nsis/gpac_installer.nsi | 753 + packagers/win32_64/nsis/readme.txt | 13 + run_configure.sh | 21 + share/default.cfg | 18 + share/doc/CODING_STYLE | 137 + share/doc/GPAC UPnP.doc | Bin 0 -> 81408 bytes share/doc/ISO 639-2 codes.txt | 487 + share/doc/SceneGenerators | 148 + share/doc/configuration.html | 103 + share/doc/doxyfile | 2433 + share/doc/doxyfile-full | 2321 + share/doc/idl/core.idl | 911 + share/doc/idl/dash_algo.idl | 252 + share/doc/idl/evg.idl | 1920 + share/doc/idl/filtersession.idl | 329 + share/doc/idl/jsf.idl | 1028 + share/doc/idl/nodejs.idl | 1492 + share/doc/idl/scenejs.idl | 426 + share/doc/idl/storage.idl | 57 + share/doc/idl/webgl.idl | 473 + share/doc/idl/xhr.idl | 56 + share/doc/ipmpx_syntax.bt | 237 + share/doc/man/gpac-filters.1 | 10066 +++ share/doc/man/gpac.1 | 4600 ++ share/doc/man/mp4box.1 | 2844 + share/doc/man/mp4client.1 | 266 + share/doc/osmo4.ico | Bin 0 -> 15086 bytes share/gpac.desktop | 18 + share/gui/extensions/H2B2VS/H2B2VS.png | Bin 0 -> 22850 bytes share/gui/extensions/H2B2VS/h2b2vs.js | 462 + share/gui/extensions/H2B2VS/init.js | 15 + share/gui/extensions/H2B2VS/logo_hd.png | Bin 0 -> 16123 bytes share/gui/extensions/H2B2VS/logo_uhd.png | Bin 0 -> 50515 bytes share/gui/extensions/about/info.js | 15 + share/gui/extensions/about/info.svg | 48 + share/gui/extensions/about/init.js | 15 + .../bifs_tests/applications-other.svg | 204 + share/gui/extensions/bifs_tests/bifs_tests.js | 354 + share/gui/extensions/bifs_tests/init.js | 16 + .../player/applications-multimedia.svg | 498 + share/gui/extensions/player/fileopen.js | 260 + share/gui/extensions/player/init.js | 24 + share/gui/extensions/player/player.js | 1874 + share/gui/extensions/player/playlist.js | 387 + share/gui/extensions/player/stats.js | 638 + share/gui/extensions/showroom/init.js | 16 + share/gui/extensions/showroom/osmo.bt | 107 + share/gui/extensions/showroom/showroom.js | 180 + .../widget_manager/applications-system.svg | 247 + share/gui/extensions/widget_manager/init.js | 927 + share/gui/gui.bt | 41 + share/gui/gui.js | 311 + share/gui/gwlib.js | 3636 ++ share/gui/icons/add.svg | 12 + share/gui/icons/app.svg | 35 + share/gui/icons/audio.svg | 19 + share/gui/icons/audio_full.svg | 26 + share/gui/icons/audio_mute.svg | 12 + share/gui/icons/check.svg | 11 + share/gui/icons/close.svg | 16 + share/gui/icons/compass.svg | 45 + share/gui/icons/cross.svg | 13 + share/gui/icons/down.svg | 9 + share/gui/icons/expand.svg | 26 + share/gui/icons/file.svg | 10 + share/gui/icons/film.svg | 27 + share/gui/icons/folder.svg | 15 + share/gui/icons/harddrive.svg | 14 + share/gui/icons/heart.svg | 8 + share/gui/icons/home.svg | 11 + share/gui/icons/image.svg | 14 + share/gui/icons/info.svg | 14 + share/gui/icons/laptop.svg | 16 + share/gui/icons/left.svg | 9 + share/gui/icons/list.svg | 20 + share/gui/icons/live.svg | 21 + share/gui/icons/media_next.svg | 12 + share/gui/icons/media_prev.svg | 12 + share/gui/icons/monitor.svg | 11 + share/gui/icons/more.svg | 9 + share/gui/icons/musical.svg | 14 + share/gui/icons/navigation.svg | 17 + share/gui/icons/network.svg | 27 + share/gui/icons/next.svg | 11 + share/gui/icons/osmo.svg | 26 + share/gui/icons/overflowing.svg | 30 + share/gui/icons/pause.svg | 12 + share/gui/icons/pl_next.svg | 16 + share/gui/icons/pl_prev.svg | 16 + share/gui/icons/play.svg | 10 + share/gui/icons/play_loop.svg | 16 + share/gui/icons/play_shuffle.svg | 17 + share/gui/icons/play_single.svg | 12 + share/gui/icons/power.svg | 14 + share/gui/icons/previous.svg | 11 + share/gui/icons/remove.svg | 10 + share/gui/icons/resize.svg | 14 + share/gui/icons/rewind.svg | 11 + share/gui/icons/right.svg | 9 + share/gui/icons/seek_forward.svg | 11 + share/gui/icons/shrink.svg | 55 + share/gui/icons/sort.svg | 25 + share/gui/icons/speed.svg | 26 + share/gui/icons/star.svg | 11 + share/gui/icons/stop.svg | 10 + share/gui/icons/stop2.svg | 10 + share/gui/icons/trash.svg | 16 + share/gui/icons/tray.svg | 14 + share/gui/icons/tv.svg | 21 + share/gui/icons/up.svg | 9 + share/gui/icons/world.svg | 64 + share/lang/fr.txt | 2440 + share/nodejs/binding.gyp | 8 + share/nodejs/index.js | 2 + share/nodejs/package.json | 23 + share/nodejs/src/gpac_napi.c | 6066 ++ share/nodejs/src/gpac_napi.h | 246 + share/nodejs/test/gpac.js | 602 + share/python/libgpac.py | 4500 ++ share/res/gpac.mp4 | Bin 0 -> 1660 bytes share/res/gpac.png | Bin 0 -> 15681 bytes share/res/gpac_cfg_test.mp4 | Bin 0 -> 132360 bytes share/res/gpac_highres.png | Bin 0 -> 98776 bytes share/scripts/custom_dash.js | 68 + share/scripts/jsf/avgen/init.js | 772 + share/scripts/jsf/avgen/testcard.png | Bin 0 -> 43147 bytes share/scripts/jsf/avmix/help.js | 572 + share/scripts/jsf/avmix/init.js | 7989 +++ share/scripts/jsf/avmix/scenes/clear.js | 42 + share/scripts/jsf/avmix/scenes/clip.js | 42 + share/scripts/jsf/avmix/scenes/mask.js | 55 + share/scripts/jsf/avmix/scenes/shape.js | 1196 + share/scripts/jsf/avmix/transitions/fade.js | 82 + .../transitions/gl-transitions/Bounce.glsl | 29 + .../gl-transitions/BowTieHorizontal.glsl | 120 + .../gl-transitions/BowTieVertical.glsl | 114 + .../gl-transitions/BowTieWithParameter.glsl | 70 + .../gl-transitions/ButterflyWaveScrawler.glsl | 28 + .../gl-transitions/CircleCrop.glsl | 17 + .../gl-transitions/ColourDistance.glsl | 16 + .../gl-transitions/CrazyParametricFun.glsl | 17 + .../transitions/gl-transitions/CrossZoom.glsl | 64 + .../gl-transitions/Directional.glsl | 14 + .../gl-transitions/DoomScreenTransition.glsl | 59 + .../transitions/gl-transitions/Dreamy.glsl | 11 + .../gl-transitions/DreamyZoom.glsl | 40 + .../transitions/gl-transitions/FilmBurn.glsl | 59 + .../gl-transitions/GlitchDisplace.glsl | 79 + .../gl-transitions/GlitchMemories.glsl | 15 + .../transitions/gl-transitions/GridFlip.glsl | 72 + .../gl-transitions/InvertedPageCurl.glsl | 214 + .../transitions/gl-transitions/LeftRight.glsl | 25 + .../gl-transitions/LinearBlur.glsl | 26 + .../transitions/gl-transitions/Mosaic.glsl | 42 + .../gl-transitions/PolkaDotsCurtain.glsl | 10 + .../transitions/gl-transitions/Radial.glsl | 16 + .../gl-transitions/SimpleZoom.glsl | 17 + .../gl-transitions/StereoViewer.glsl | 210 + .../transitions/gl-transitions/Swirl.glsl | 31 + .../transitions/gl-transitions/TVStatic.glsl | 25 + .../transitions/gl-transitions/TopBottom.glsl | 24 + .../transitions/gl-transitions/WaterDrop.glsl | 16 + .../gl-transitions/ZoomInCircles.glsl | 38 + .../transitions/gl-transitions/angular.glsl | 21 + .../transitions/gl-transitions/burn.glsl | 10 + .../gl-transitions/cannabisleaf.glsl | 15 + .../transitions/gl-transitions/circle.glsl | 19 + .../gl-transitions/circleopen.glsl | 13 + .../gl-transitions/colorphase.glsl | 14 + .../gl-transitions/crosshatch.glsl | 16 + .../transitions/gl-transitions/crosswarp.glsl | 7 + .../transitions/gl-transitions/cube.glsl | 66 + .../gl-transitions/directional-easing.glsl | 15 + .../gl-transitions/directionalwarp.glsl | 15 + .../gl-transitions/directionalwipe.glsl | 17 + .../gl-transitions/displacement.glsl | 22 + .../transitions/gl-transitions/doorway.glsl | 50 + .../transitions/gl-transitions/fade.glsl | 10 + .../transitions/gl-transitions/fadecolor.glsl | 10 + .../gl-transitions/fadegrayscale.glsl | 17 + .../transitions/gl-transitions/flyeye.glsl | 17 + .../transitions/gl-transitions/heart.glsl | 16 + .../gl-transitions/hexagonalize.glsl | 74 + .../gl-transitions/kaleidoscope.glsl | 22 + .../transitions/gl-transitions/luma.glsl | 12 + .../gl-transitions/luminance_melt.glsl | 126 + .../transitions/gl-transitions/morph.glsl | 16 + .../gl-transitions/multiply_blend.glsl | 17 + .../transitions/gl-transitions/perlin.glsl | 64 + .../transitions/gl-transitions/pinwheel.glsl | 16 + .../transitions/gl-transitions/pixelize.glsl | 17 + .../gl-transitions/polar_function.glsl | 20 + .../gl-transitions/randomNoisex.glsl | 16 + .../gl-transitions/randomsquares.glsl | 15 + .../transitions/gl-transitions/ripple.glsl | 15 + .../gl-transitions/rotateTransition.glsl | 19 + .../gl-transitions/rotate_scale_fade.glsl | 32 + .../gl-transitions/squareswire.glsl | 20 + .../transitions/gl-transitions/squeeze.glsl | 19 + .../transitions/gl-transitions/swap.glsl | 59 + .../gl-transitions/tangentMotionBlur.glsl | 139 + .../gl-transitions/undulatingBurnOut.glsl | 47 + .../transitions/gl-transitions/wind.glsl | 19 + .../gl-transitions/windowblinds.glsl | 15 + .../gl-transitions/windowslice.glsl | 11 + .../transitions/gl-transitions/wipeDown.glsl | 9 + .../transitions/gl-transitions/wipeLeft.glsl | 9 + .../transitions/gl-transitions/wipeRight.glsl | 9 + .../transitions/gl-transitions/wipeUp.glsl | 9 + .../scripts/jsf/avmix/transitions/gltrans.js | 277 + share/scripts/jsf/avmix/transitions/mix.js | 44 + share/scripts/jsf/avmix/transitions/swipe.js | 544 + share/scripts/ttml-renderer.js | 277 + share/scripts/vout.js | 883 + share/scripts/webvtt-renderer.js | 293 + share/shaders/fragment.glsl | 335 + share/shaders/vertex.glsl | 196 + share/vis/Code/Console.js | 208 + share/vis/Code/DataViewReader.js | 47 + share/vis/Code/PixelTimeRange.js | 61 + share/vis/Code/Remotery.js | 338 + share/vis/Code/SampleWindow.js | 215 + share/vis/Code/ThreadFrame.js | 28 + share/vis/Code/TimelineRow.js | 379 + share/vis/Code/TimelineWindow.js | 284 + share/vis/Code/TitleWindow.js | 71 + share/vis/Code/WebSocketConnection.js | 137 + share/vis/Styles/Remotery.css | 234 + .../extern/BrowserLib/Core/Code/Animation.js | 65 + share/vis/extern/BrowserLib/Core/Code/Bind.js | 92 + .../extern/BrowserLib/Core/Code/Convert.js | 218 + share/vis/extern/BrowserLib/Core/Code/Core.js | 26 + share/vis/extern/BrowserLib/Core/Code/DOM.js | 499 + .../extern/BrowserLib/Core/Code/Keyboard.js | 149 + .../extern/BrowserLib/Core/Code/LocalStore.js | 40 + .../vis/extern/BrowserLib/Core/Code/Mouse.js | 83 + .../BrowserLib/Core/Code/MurmurHash3.js | 68 + .../BrowserLib/WindowManager/Code/Button.js | 131 + .../BrowserLib/WindowManager/Code/ComboBox.js | 237 + .../WindowManager/Code/Container.js | 34 + .../BrowserLib/WindowManager/Code/EditBox.js | 119 + .../BrowserLib/WindowManager/Code/Grid.js | 248 + .../BrowserLib/WindowManager/Code/Label.js | 31 + .../BrowserLib/WindowManager/Code/Treeview.js | 352 + .../WindowManager/Code/TreeviewItem.js | 109 + .../BrowserLib/WindowManager/Code/Window.js | 295 + .../WindowManager/Code/WindowManager.js | 54 + .../WindowManager/Styles/WindowManager.css | 652 + share/vis/index.html | 55 + src/Makefile | 434 + src/bifs/arith_decoder.c | 287 + src/bifs/bifs_codec.c | 541 + src/bifs/bifs_node_tables.c | 1153 + src/bifs/com_dec.c | 1432 + src/bifs/com_enc.c | 988 + src/bifs/conditional.c | 195 + src/bifs/field_decode.c | 977 + src/bifs/field_encode.c | 675 + src/bifs/memory_decoder.c | 1057 + src/bifs/predictive_mffield.c | 451 + src/bifs/quant.h | 122 + src/bifs/quantize.c | 338 + src/bifs/script.h | 112 + src/bifs/script_dec.c | 786 + src/bifs/script_enc.c | 1836 + src/bifs/unquantize.c | 439 + src/compositor/audio_input.c | 343 + src/compositor/audio_mixer.c | 1319 + src/compositor/audio_render.c | 451 + src/compositor/bindable.c | 324 + src/compositor/camera.c | 597 + src/compositor/clock.c | 298 + src/compositor/compositor.c | 4097 ++ src/compositor/compositor_2d.c | 1459 + src/compositor/compositor_3d.c | 271 + src/compositor/compositor_node_init.c | 666 + src/compositor/drawable.c | 1590 + src/compositor/drawable.h | 336 + src/compositor/events.c | 2086 + src/compositor/font_engine.c | 1509 + src/compositor/gl_inc.h | 702 + src/compositor/hardcoded_protos.c | 1792 + src/compositor/hc_flash_shape.c | 478 + src/compositor/media_object.c | 1695 + src/compositor/mesh.c | 2438 + src/compositor/mesh_collide.c | 615 + src/compositor/mesh_tesselate.c | 507 + src/compositor/mpeg4_animstream.c | 205 + src/compositor/mpeg4_audio.c | 644 + src/compositor/mpeg4_background.c | 588 + src/compositor/mpeg4_background2d.c | 528 + src/compositor/mpeg4_bitmap.c | 323 + src/compositor/mpeg4_composite.c | 921 + src/compositor/mpeg4_form.c | 695 + src/compositor/mpeg4_geometry_2d.c | 819 + src/compositor/mpeg4_geometry_3d.c | 677 + src/compositor/mpeg4_geometry_ifs2d.c | 357 + src/compositor/mpeg4_geometry_ils2d.c | 330 + src/compositor/mpeg4_gradients.c | 662 + src/compositor/mpeg4_grouping.c | 828 + src/compositor/mpeg4_grouping.h | 196 + src/compositor/mpeg4_grouping_2d.c | 429 + src/compositor/mpeg4_grouping_3d.c | 409 + src/compositor/mpeg4_inline.c | 795 + src/compositor/mpeg4_inputsensor.c | 999 + src/compositor/mpeg4_layer_2d.c | 372 + src/compositor/mpeg4_layer_3d.c | 589 + src/compositor/mpeg4_layout.c | 861 + src/compositor/mpeg4_lighting.c | 175 + src/compositor/mpeg4_mediacontrol.c | 710 + src/compositor/mpeg4_mediasensor.c | 288 + src/compositor/mpeg4_path_layout.c | 282 + src/compositor/mpeg4_sensors.c | 1637 + src/compositor/mpeg4_sound.c | 290 + src/compositor/mpeg4_text.c | 745 + src/compositor/mpeg4_textures.c | 680 + src/compositor/mpeg4_timesensor.c | 211 + src/compositor/mpeg4_viewport.c | 661 + src/compositor/navigate.c | 954 + src/compositor/nodes_stacks.h | 365 + src/compositor/object_manager.c | 2041 + src/compositor/offscreen_cache.c | 893 + src/compositor/offscreen_cache.h | 59 + src/compositor/scene.c | 3292 + src/compositor/scene_node_init.c | 361 + src/compositor/scene_ns.c | 580 + src/compositor/svg_base.c | 375 + src/compositor/svg_external.c | 187 + src/compositor/svg_filters.c | 451 + src/compositor/svg_font.c | 566 + src/compositor/svg_geometry.c | 704 + src/compositor/svg_grouping.c | 1447 + src/compositor/svg_media.c | 907 + src/compositor/svg_paint_servers.c | 878 + src/compositor/svg_text.c | 1535 + src/compositor/texturing.c | 414 + src/compositor/texturing.h | 109 + src/compositor/texturing_gl.c | 1313 + src/compositor/visual_manager.c | 316 + src/compositor/visual_manager.h | 287 + src/compositor/visual_manager_2d.c | 977 + src/compositor/visual_manager_2d.h | 150 + src/compositor/visual_manager_2d_draw.c | 828 + src/compositor/visual_manager_3d.c | 2179 + src/compositor/visual_manager_3d.h | 320 + src/compositor/visual_manager_3d_gl.c | 4028 ++ src/compositor/x3d_geometry.c | 1080 + src/crypto/g_crypt.c | 105 + src/crypto/g_crypt_openssl.c | 303 + src/crypto/g_crypt_tinyaes.c | 270 + src/crypto/tiny_aes.c | 587 + src/crypto/tiny_aes.h | 93 + src/evg/ftgrays.c | 886 + src/evg/rast_soft.h | 600 + src/evg/raster3d.c | 1394 + src/evg/raster_565.c | 659 + src/evg/raster_argb.c | 755 + src/evg/raster_rgb.c | 387 + src/evg/raster_yuv.c | 2259 + src/evg/stencil.c | 3317 + src/evg/surface.c | 1551 + src/export.cpp | 2678 + src/filter_core/filter.c | 4736 ++ src/filter_core/filter_pck.c | 1872 + src/filter_core/filter_pid.c | 8069 +++ src/filter_core/filter_props.c | 1885 + src/filter_core/filter_queue.c | 352 + src/filter_core/filter_register.c | 316 + src/filter_core/filter_session.c | 4252 ++ src/filter_core/filter_session.h | 1131 + src/filter_core/filter_session_js.c | 1440 + src/filters/base_filter_example.c | 168 + src/filters/bs_agg.c | 925 + src/filters/bs_split.c | 1487 + src/filters/bsrw.c | 752 + src/filters/compose.c | 1113 + src/filters/dasher.c | 9561 +++ src/filters/dec_ac52.c | 301 + src/filters/dec_bifs.c | 302 + src/filters/dec_faad.c | 477 + src/filters/dec_img.c | 176 + src/filters/dec_j2k.c | 630 + src/filters/dec_laser.c | 287 + src/filters/dec_mad.c | 375 + src/filters/dec_mediacodec.c | 1295 + src/filters/dec_mediacodec.h | 128 + src/filters/dec_mediacodec_jni.c | 408 + src/filters/dec_nvdec.c | 1598 + src/filters/dec_nvdec_sdk.c | 712 + src/filters/dec_nvdec_sdk.h | 2898 + src/filters/dec_odf.c | 535 + src/filters/dec_openhevc.c | 1435 + src/filters/dec_opensvc.c | 612 + src/filters/dec_theora.c | 318 + src/filters/dec_ttml.c | 546 + src/filters/dec_ttxt.c | 1373 + src/filters/dec_vorbis.c | 317 + src/filters/dec_vtb.c | 2065 + src/filters/dec_vtb_glctx.m | 7 + src/filters/dec_webvtt.c | 516 + src/filters/dec_xvid.c | 466 + src/filters/decrypt_cenc_isma.c | 2173 + src/filters/dmx_avi.c | 679 + src/filters/dmx_dash.c | 3447 + src/filters/dmx_gsf.c | 1394 + src/filters/dmx_m2ts.c | 1325 + src/filters/dmx_mpegps.c | 498 + src/filters/dmx_nhml.c | 1737 + src/filters/dmx_nhnt.c | 524 + src/filters/dmx_ogg.c | 1001 + src/filters/dmx_saf.c | 470 + src/filters/dmx_vobsub.c | 486 + src/filters/enc_jpg.c | 417 + src/filters/enc_png.c | 395 + src/filters/encrypt_cenc_isma.c | 2650 + src/filters/ff_avf.c | 1053 + src/filters/ff_common.c | 1424 + src/filters/ff_common.h | 100 + src/filters/ff_dec.c | 1392 + src/filters/ff_dmx.c | 1228 + src/filters/ff_enc.c | 1896 + src/filters/ff_mx.c | 1190 + src/filters/ff_rescale.c | 640 + src/filters/filelist.c | 2962 + src/filters/hevcmerge.c | 1656 + src/filters/hevcsplit.c | 966 + src/filters/in_dvb4linux.c | 444 + src/filters/in_file.c | 610 + src/filters/in_http.c | 643 + src/filters/in_pipe.c | 576 + src/filters/in_route.c | 982 + src/filters/in_rtp.c | 835 + src/filters/in_rtp.h | 358 + src/filters/in_rtp_rtsp.c | 370 + src/filters/in_rtp_sdp.c | 405 + src/filters/in_rtp_signaling.c | 898 + src/filters/in_rtp_stream.c | 742 + src/filters/in_sock.c | 588 + src/filters/inspect.c | 4425 ++ src/filters/io_fcryp.c | 653 + src/filters/isoffin.h | 240 + src/filters/isoffin_load.c | 1649 + src/filters/isoffin_read.c | 1633 + src/filters/isoffin_read_ch.c | 982 + src/filters/jsfilter.c | 5046 ++ src/filters/load_bt_xmt.c | 970 + src/filters/load_svg.c | 472 + src/filters/load_text.c | 3684 ++ src/filters/mux_avi.c | 699 + src/filters/mux_gsf.c | 1333 + src/filters/mux_isom.c | 7184 ++ src/filters/mux_ts.c | 1997 + src/filters/out_audio.c | 730 + src/filters/out_file.c | 764 + src/filters/out_http.c | 3754 ++ src/filters/out_pipe.c | 516 + src/filters/out_route.c | 2230 + src/filters/out_rtp.c | 1236 + src/filters/out_rtp.h | 110 + src/filters/out_rtsp.c | 1345 + src/filters/out_sock.c | 595 + src/filters/out_video.c | 2184 + src/filters/reframe_ac3.c | 612 + src/filters/reframe_adts.c | 1026 + src/filters/reframe_amr.c | 623 + src/filters/reframe_av1.c | 1323 + src/filters/reframe_flac.c | 708 + src/filters/reframe_h263.c | 758 + src/filters/reframe_img.c | 438 + src/filters/reframe_latm.c | 687 + src/filters/reframe_mhas.c | 910 + src/filters/reframe_mp3.c | 899 + src/filters/reframe_mpgvid.c | 1324 + src/filters/reframe_nalu.c | 3949 ++ src/filters/reframe_prores.c | 702 + src/filters/reframe_qcp.c | 743 + src/filters/reframe_rawpcm.c | 343 + src/filters/reframe_rawvid.c | 509 + src/filters/reframe_truehd.c | 700 + src/filters/reframer.c | 2600 + src/filters/resample_audio.c | 501 + src/filters/restamp.c | 480 + src/filters/rewind.c | 262 + src/filters/rewrite_adts.c | 466 + src/filters/rewrite_mhas.c | 279 + src/filters/rewrite_mp4v.c | 183 + src/filters/rewrite_nalu.c | 624 + src/filters/rewrite_obu.c | 457 + src/filters/tileagg.c | 533 + src/filters/tilesplit.c | 596 + src/filters/tssplit.c | 496 + src/filters/unit_test_filter.c | 991 + src/filters/vcrop.c | 628 + src/filters/vflip.c | 508 + src/filters/write_generic.c | 1662 + src/filters/write_nhml.c | 981 + src/filters/write_nhnt.c | 407 + src/filters/write_qcp.c | 392 + src/filters/write_vtt.c | 356 + src/ietf/rtcp.c | 604 + src/ietf/rtp.c | 1059 + src/ietf/rtp_depacketizer.c | 2014 + src/ietf/rtp_packetizer.c | 630 + src/ietf/rtp_pck_3gpp.c | 821 + src/ietf/rtp_pck_mpeg12.c | 245 + src/ietf/rtp_pck_mpeg4.c | 942 + src/ietf/rtp_streamer.c | 838 + src/ietf/rtsp_command.c | 596 + src/ietf/rtsp_common.c | 388 + src/ietf/rtsp_response.c | 705 + src/ietf/rtsp_session.c | 750 + src/ietf/sdp.c | 1163 + src/isomedia/avc_ext.c | 3757 ++ src/isomedia/box_code_3gpp.c | 1228 + src/isomedia/box_code_adobe.c | 791 + src/isomedia/box_code_apple.c | 976 + src/isomedia/box_code_base.c | 13145 ++++ src/isomedia/box_code_drm.c | 1922 + src/isomedia/box_code_meta.c | 868 + src/isomedia/box_dump.c | 6427 ++ src/isomedia/box_funcs.c | 2173 + src/isomedia/data_map.c | 698 + src/isomedia/drm_sample.c | 1979 + src/isomedia/hint_track.c | 1029 + src/isomedia/hinting.c | 1140 + src/isomedia/iff.c | 1968 + src/isomedia/isom_intern.c | 1544 + src/isomedia/isom_read.c | 6072 ++ src/isomedia/isom_store.c | 2575 + src/isomedia/isom_write.c | 8570 +++ src/isomedia/media.c | 1178 + src/isomedia/media_odf.c | 532 + src/isomedia/meta.c | 2031 + src/isomedia/movie_fragments.c | 3283 + src/isomedia/sample_descs.c | 1758 + src/isomedia/stbl_read.c | 622 + src/isomedia/stbl_write.c | 2221 + src/isomedia/track.c | 1747 + src/isomedia/ttml.c | 135 + src/isomedia/tx3g.c | 939 + src/jsmods/WebGLRenderingContextBase.c | 2140 + src/jsmods/core.c | 3685 ++ src/jsmods/evg.c | 7880 +++ src/jsmods/scene_js.c | 1940 + src/jsmods/storage.c | 231 + src/jsmods/webgl.c | 2492 + src/jsmods/webgl.h | 169 + src/jsmods/xhr.c | 1498 + src/laser/lsr_dec.c | 6139 ++ src/laser/lsr_enc.c | 4457 ++ src/laser/lsr_tables.c | 1256 + src/media_tools/ait.c | 809 + src/media_tools/av_parsers.c | 11586 ++++ src/media_tools/avilib.c | 3145 + src/media_tools/crypt_tools.c | 828 + src/media_tools/dash_client.c | 10237 +++ src/media_tools/dash_segmenter.c | 1230 + src/media_tools/dsmcc.c | 1925 + src/media_tools/dvb_mpe.c | 1249 + src/media_tools/gpac_ogg.c | 1393 + src/media_tools/html5_media.c | 419 + src/media_tools/html5_mse.c | 1092 + src/media_tools/img.c | 705 + src/media_tools/isom_hinter.c | 1344 + src/media_tools/isom_tools.c | 4420 ++ src/media_tools/m2ts_mux.c | 3418 + src/media_tools/m3u8.c | 1293 + src/media_tools/media_export.c | 1549 + src/media_tools/media_import.c | 1581 + src/media_tools/mpd.c | 5711 ++ src/media_tools/mpeg2_ps.c | 2039 + src/media_tools/mpeg2_ps.h | 194 + src/media_tools/mpegts.c | 3329 + src/media_tools/reedsolomon.c | 577 + src/media_tools/route_dmx.c | 2047 + src/media_tools/saf.c | 346 + src/media_tools/vobsub.c | 664 + src/media_tools/webvtt.c | 1542 + src/odf/desc_private.c | 711 + src/odf/descriptors.c | 1824 + src/odf/ipmpx_code.c | 2170 + src/odf/ipmpx_dump.c | 895 + src/odf/ipmpx_parse.c | 712 + src/odf/oci_codec.c | 425 + src/odf/odf_code.c | 3404 + src/odf/odf_codec.c | 736 + src/odf/odf_command.c | 657 + src/odf/odf_dump.c | 1989 + src/odf/odf_parse.c | 728 + src/odf/qos.c | 447 + src/odf/slc.c | 425 + src/quickjs/GPAC_README.md | 20 + src/quickjs/cutils.c | 635 + src/quickjs/cutils.h | 360 + src/quickjs/libbf.c | 8466 +++ src/quickjs/libbf.h | 535 + src/quickjs/libregexp-opcode.h | 58 + src/quickjs/libregexp.c | 2614 + src/quickjs/libregexp.h | 92 + src/quickjs/libunicode-table.h | 4368 ++ src/quickjs/libunicode.c | 1556 + src/quickjs/libunicode.h | 124 + src/quickjs/list.h | 100 + src/quickjs/quickjs-atom.h | 273 + src/quickjs/quickjs-libc.c | 4404 ++ src/quickjs/quickjs-libc.h | 60 + src/quickjs/quickjs-opcode.h | 365 + src/quickjs/quickjs.c | 54268 ++++++++++++++++ src/quickjs/quickjs.h | 1096 + src/scene_manager/encode_isom.c | 1453 + src/scene_manager/loader_bt.c | 3809 ++ src/scene_manager/loader_isom.c | 453 + src/scene_manager/loader_qt.c | 187 + src/scene_manager/loader_svg.c | 2225 + src/scene_manager/loader_xmt.c | 3212 + src/scene_manager/scene_dump.c | 3618 ++ src/scene_manager/scene_engine.c | 1134 + src/scene_manager/scene_manager.c | 795 + src/scene_manager/scene_stats.c | 698 + src/scene_manager/swf_bifs.c | 2265 + src/scene_manager/swf_parse.c | 2700 + src/scene_manager/swf_svg.c | 559 + src/scene_manager/text_to_bifs.c | 538 + src/scenegraph/base_scenegraph.c | 2527 + src/scenegraph/commands.c | 999 + src/scenegraph/dom_events.c | 977 + src/scenegraph/dom_js.c | 3121 + src/scenegraph/html5_media_js.c | 1711 + src/scenegraph/html5_mse_js.c | 956 + src/scenegraph/mpeg4_animators.c | 822 + src/scenegraph/mpeg4_nodes.c | 40415 ++++++++++++ src/scenegraph/mpeg4_valuator.c | 539 + src/scenegraph/qjs_common.h | 261 + src/scenegraph/smil_anim.c | 1552 + src/scenegraph/smil_timing.c | 1065 + src/scenegraph/svg_attributes.c | 6635 ++ src/scenegraph/svg_js.c | 2805 + src/scenegraph/svg_properties.c | 1447 + src/scenegraph/svg_types.c | 522 + src/scenegraph/vrml_interpolators.c | 719 + src/scenegraph/vrml_js.c | 4408 ++ src/scenegraph/vrml_proto.c | 1365 + src/scenegraph/vrml_route.c | 523 + src/scenegraph/vrml_script.c | 313 + src/scenegraph/vrml_tools.c | 1854 + src/scenegraph/x3d_nodes.c | 16440 +++++ src/scenegraph/xml_ns.c | 1176 + src/terminal/terminal.c | 1743 + src/utils/Remotery.c | 7425 +++ src/utils/alloc.c | 988 + src/utils/base_encoding.c | 451 + src/utils/bitstream.c | 1721 + src/utils/cache.c | 1076 + src/utils/color.c | 4383 ++ src/utils/configfile.c | 595 + src/utils/constants.c | 2211 + src/utils/dlmalloc.c | 5713 ++ src/utils/downloader.c | 6522 ++ src/utils/error.c | 1896 + src/utils/gltools.c | 1785 + src/utils/gzio.c | 989 + src/utils/list.c | 844 + src/utils/math.c | 2733 + src/utils/module.c | 618 + src/utils/module_wrap.h | 100 + src/utils/os_config_init.c | 2395 + src/utils/os_divers.c | 2830 + src/utils/os_file.c | 1795 + src/utils/os_module.c | 343 + src/utils/os_net.c | 1943 + src/utils/os_thread.c | 888 + src/utils/path2d.c | 1378 + src/utils/path2d_stroker.c | 1797 + src/utils/sha1.c | 774 + src/utils/symbian_net.cpp | 1322 + src/utils/symbian_os.cpp | 833 + src/utils/token.c | 137 + src/utils/uni_bidi.c | 1491 + src/utils/unicode.c | 109 + src/utils/url.c | 536 + src/utils/utf.c | 770 + src/utils/xml_parser.c | 2463 + src/utils/zlib_symbian_ext.h | 200 + src/utils/zutil.c | 344 + src/utils/zutil.h | 277 + static.mak | 218 + version.bat | 46 + 969 files changed, 934298 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100644 COPYING create mode 100644 Changelog create mode 100644 Makefile create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 applications/Makefile create mode 100644 applications/generators/MPEG4/MPEG4Gen.dsp create mode 100644 applications/generators/MPEG4/MPEG4Gen.dsw create mode 100644 applications/generators/MPEG4/Makefile create mode 100644 applications/generators/MPEG4/main.c create mode 100644 applications/generators/MPEG4/skip.txt create mode 100644 applications/generators/MPEG4/templates1.txt create mode 100644 applications/generators/MPEG4/templates10.txt create mode 100644 applications/generators/MPEG4/templates2.txt create mode 100644 applications/generators/MPEG4/templates3.txt create mode 100644 applications/generators/MPEG4/templates4.txt create mode 100644 applications/generators/MPEG4/templates5.txt create mode 100644 applications/generators/MPEG4/templates6.txt create mode 100644 applications/generators/MPEG4/templates7.txt create mode 100644 applications/generators/MPEG4/templates8.txt create mode 100644 applications/generators/MPEG4/templates9.txt create mode 100644 applications/generators/Makefile create mode 100644 applications/generators/SVG/Makefile create mode 100644 applications/generators/SVG/SVGGen.dsp create mode 100644 applications/generators/SVG/SVGGen.dsw create mode 100644 applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.nvdl create mode 100644 applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/animate.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/animation-element.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/audio.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/conditional.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/contenttype-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/coordinate-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/core-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/datatypes.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/discard.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/extensibility.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/extresources-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/flowable-text-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/focus-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/font-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/gradient-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/graphics-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/handler.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/headers.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/hyperlink.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/image.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/laser-ext.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/media-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/opacity-attrib-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/paint-attrib-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/prefetch.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/script.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/shapes.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/solidcolor.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/structure-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/text-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/transform-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/vectoreffects-attrib-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/video.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/viewport-attrib-tiny.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/xlink-attrib.rng create mode 100644 applications/generators/SVG/Tiny-1.2-NG/xml-events.rng create mode 100644 applications/generators/SVG/html.c create mode 100644 applications/generators/SVG/laser.c create mode 100644 applications/generators/SVG/main.c create mode 100644 applications/generators/SVG/svggen.h create mode 100644 applications/generators/SVG/v1.c create mode 100644 applications/generators/SVG/v2.c create mode 100644 applications/generators/SVG/v3.c create mode 100644 applications/generators/X3D/Makefile create mode 100644 applications/generators/X3D/X3DGen.dsp create mode 100644 applications/generators/X3D/X3DGen.dsw create mode 100644 applications/generators/X3D/main.c create mode 100644 applications/generators/X3D/skip.txt create mode 100644 applications/generators/X3D/templates_X3D.txt create mode 100644 applications/gpac/Info.plist create mode 100644 applications/gpac/Makefile create mode 100644 applications/gpac/main.c create mode 100644 applications/mp4box/Makefile create mode 100644 applications/mp4box/filedump.c create mode 100644 applications/mp4box/fileimport.c create mode 100644 applications/mp4box/live.c create mode 100644 applications/mp4box/main.c create mode 100644 applications/mp4box/mp4box.h create mode 100644 applications/mp4client/Makefile create mode 100644 applications/mp4client/carbon_events.c create mode 100644 applications/mp4client/main.c create mode 100644 applications/mp4client/mp4client.rc create mode 100644 applications/mp4client/resource.h create mode 100755 change_version.sh create mode 100755 check_revision.sh create mode 100755 configure create mode 100644 generate_installer.bat create mode 100644 gpac.spec create mode 100644 include/gpac/00_doxy.h create mode 100644 include/gpac/Remotery.h create mode 100644 include/gpac/ait.h create mode 100644 include/gpac/avparse.h create mode 100644 include/gpac/base_coding.h create mode 100644 include/gpac/bifs.h create mode 100644 include/gpac/bitstream.h create mode 100644 include/gpac/cache.h create mode 100644 include/gpac/color.h create mode 100644 include/gpac/compositor.h create mode 100644 include/gpac/config_file.h create mode 100644 include/gpac/configuration.h create mode 100644 include/gpac/constants.h create mode 100644 include/gpac/crypt.h create mode 100644 include/gpac/crypt_tools.h create mode 100644 include/gpac/dash.h create mode 100644 include/gpac/download.h create mode 100644 include/gpac/dsmcc.h create mode 100644 include/gpac/dvb_mpe.h create mode 100644 include/gpac/events.h create mode 100644 include/gpac/events_constants.h create mode 100644 include/gpac/evg.h create mode 100644 include/gpac/filters.h create mode 100644 include/gpac/html5_media.h create mode 100644 include/gpac/html5_mse.h create mode 100644 include/gpac/ietf.h create mode 100644 include/gpac/internal/avilib.h create mode 100644 include/gpac/internal/bifs_dev.h create mode 100644 include/gpac/internal/bifs_tables.h create mode 100644 include/gpac/internal/camera.h create mode 100644 include/gpac/internal/compositor_dev.h create mode 100644 include/gpac/internal/crypt_dev.h create mode 100644 include/gpac/internal/dvb_mpe_dev.h create mode 100644 include/gpac/internal/ietf_dev.h create mode 100644 include/gpac/internal/isomedia_dev.h create mode 100644 include/gpac/internal/laser_dev.h create mode 100644 include/gpac/internal/m3u8.h create mode 100644 include/gpac/internal/media_dev.h create mode 100644 include/gpac/internal/mesh.h create mode 100644 include/gpac/internal/odf_dev.h create mode 100644 include/gpac/internal/odf_parse_common.h create mode 100644 include/gpac/internal/ogg.h create mode 100644 include/gpac/internal/reedsolomon.h create mode 100644 include/gpac/internal/scenegraph_dev.h create mode 100644 include/gpac/internal/swf_dev.h create mode 100644 include/gpac/internal/vobsub.h create mode 100644 include/gpac/iso639.h create mode 100644 include/gpac/isomedia.h create mode 100644 include/gpac/laser.h create mode 100644 include/gpac/list.h create mode 100644 include/gpac/main.h create mode 100644 include/gpac/maths.h create mode 100644 include/gpac/media_tools.h create mode 100644 include/gpac/mediaobject.h create mode 100644 include/gpac/module.h create mode 100644 include/gpac/modules/audio_out.h create mode 100644 include/gpac/modules/codec.h create mode 100644 include/gpac/modules/compositor_ext.h create mode 100644 include/gpac/modules/font.h create mode 100644 include/gpac/modules/hardcoded_proto.h create mode 100644 include/gpac/modules/ipmp.h create mode 100644 include/gpac/modules/video_out.h create mode 100644 include/gpac/mpd.h create mode 100644 include/gpac/mpeg4_odf.h create mode 100644 include/gpac/mpegts.h create mode 100644 include/gpac/network.h create mode 100644 include/gpac/nodes_mpeg4.h create mode 100644 include/gpac/nodes_svg.h create mode 100644 include/gpac/nodes_x3d.h create mode 100644 include/gpac/options.h create mode 100644 include/gpac/path2d.h create mode 100644 include/gpac/route.h create mode 100644 include/gpac/rtp_streamer.h create mode 100644 include/gpac/scene_engine.h create mode 100644 include/gpac/scene_manager.h create mode 100644 include/gpac/scenegraph.h create mode 100644 include/gpac/scenegraph_svg.h create mode 100644 include/gpac/scenegraph_vrml.h create mode 100644 include/gpac/setup.h create mode 100644 include/gpac/svg_types.h create mode 100644 include/gpac/sync_layer.h create mode 100644 include/gpac/term_info.h create mode 100644 include/gpac/terminal.h create mode 100644 include/gpac/thread.h create mode 100644 include/gpac/token.h create mode 100644 include/gpac/tools.h create mode 100644 include/gpac/user.h create mode 100644 include/gpac/utf.h create mode 100644 include/gpac/version.h create mode 100644 include/gpac/webvtt.h create mode 100644 include/gpac/xml.h create mode 100644 include/win32/inttypes.h create mode 100644 include/win32/stdint.h create mode 100644 include/wince/errno.h create mode 100644 manifest.rc create mode 100755 mkdmg.sh create mode 100644 modules/Makefile create mode 100644 modules/alsa/Makefile create mode 100644 modules/alsa/alsa.c create mode 100644 modules/dec_openhevc/Makefile create mode 100644 modules/dektec_out/Makefile create mode 100644 modules/dektec_out/dektec_video.cpp create mode 100644 modules/dektec_out/dektec_video.h create mode 100644 modules/dektec_out/dektec_video_decl.c create mode 100644 modules/dektec_out/dektec_video_old.cpp create mode 100644 modules/dektec_out/out_dektec.vcxproj create mode 100644 modules/demo_is/Makefile create mode 100644 modules/demo_is/demo-sensor.bt create mode 100644 modules/demo_is/demo_is.c create mode 100644 modules/directfb_out/Makefile create mode 100755 modules/directfb_out/directfb_out.c create mode 100755 modules/directfb_out/directfb_out.h create mode 100644 modules/directfb_out/directfb_wrapper.c create mode 100644 modules/droid_audio/droidaudio.c create mode 100644 modules/droid_audio/javaenv.c create mode 100644 modules/droid_audio/javaenv.h create mode 100644 modules/droid_cam/droid_cam.c create mode 100644 modules/droid_mpegv/droid_mpegv.c create mode 100644 modules/droid_out/droid_vout-bitmap.c create mode 100644 modules/droid_out/droid_vout.c create mode 100644 modules/dx_hw/Makefile create mode 100644 modules/dx_hw/collide.cur create mode 100644 modules/dx_hw/dx_2d.c create mode 100644 modules/dx_hw/dx_audio.c create mode 100644 modules/dx_hw/dx_hw.h create mode 100644 modules/dx_hw/dx_hw.rc create mode 100644 modules/dx_hw/dx_video.c create mode 100644 modules/dx_hw/dx_window.c create mode 100644 modules/dx_hw/hand.cur create mode 100644 modules/dx_hw/resource.h create mode 100644 modules/filter_export.cpp create mode 100644 modules/ft_font/Makefile create mode 100644 modules/ft_font/ft_font.c create mode 100644 modules/ft_font/ft_font.h create mode 100644 modules/ios_cam/CameraObject.h create mode 100644 modules/ios_cam/CameraObject.m create mode 100644 modules/ios_cam/cam_wrap.h create mode 100644 modules/ios_cam/cam_wrap.m create mode 100644 modules/ios_cam/ios_cam.c create mode 100644 modules/ios_mpegv/SensorAcces.m create mode 100644 modules/ios_mpegv/SensorAccess.h create mode 100644 modules/ios_mpegv/ios_mpegv-Prefix.pch create mode 100644 modules/ios_mpegv/ios_mpegv.c create mode 100644 modules/ios_mpegv/sensor_wrap.h create mode 100644 modules/jack/Makefile create mode 100644 modules/jack/jack.c create mode 100644 modules/modules_export.cpp create mode 100644 modules/pulseaudio/Makefile create mode 100644 modules/pulseaudio/pulseaudio.c create mode 100644 modules/sdl_out/Makefile create mode 100644 modules/sdl_out/audio.c create mode 100644 modules/sdl_out/cursors.c create mode 100644 modules/sdl_out/sdl_out.c create mode 100644 modules/sdl_out/sdl_out.h create mode 100644 modules/sdl_out/video.c create mode 100644 modules/sdl_out/video2d.c create mode 100644 modules/test_filter/Makefile create mode 100644 modules/test_filter/test_filter.c create mode 100644 modules/test_filter/test_filter.vcxproj create mode 100644 modules/validator/Makefile create mode 100644 modules/validator/README.TXT create mode 100644 modules/validator/validator.c create mode 100644 modules/wav_out/Makefile create mode 100644 modules/wav_out/wav_out.c create mode 100644 modules/x11_out/Makefile create mode 100644 modules/x11_out/x11_out.c create mode 100644 modules/x11_out/x11_out.h create mode 100644 packagers/osx/GPAC.app/Contents/Info.plist create mode 100644 packagers/osx/GPAC.app/Contents/PkgInfo create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_audio.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_generic.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_model.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_subs.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_video.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_widget.icns create mode 100644 packagers/osx/SLA.r create mode 100644 packagers/osx/distribution.xml create mode 100755 packagers/osx/res/background.png create mode 100644 packagers/osx/res/preamble.txt create mode 100755 packagers/osx/scripts/postinstall create mode 100644 packagers/win32_64/nsis/default.out create mode 100644 packagers/win32_64/nsis/gpac_installer.nsi create mode 100644 packagers/win32_64/nsis/readme.txt create mode 100755 run_configure.sh create mode 100644 share/default.cfg create mode 100644 share/doc/CODING_STYLE create mode 100644 share/doc/GPAC UPnP.doc create mode 100644 share/doc/ISO 639-2 codes.txt create mode 100644 share/doc/SceneGenerators create mode 100644 share/doc/configuration.html create mode 100644 share/doc/doxyfile create mode 100644 share/doc/doxyfile-full create mode 100644 share/doc/idl/core.idl create mode 100644 share/doc/idl/dash_algo.idl create mode 100644 share/doc/idl/evg.idl create mode 100644 share/doc/idl/filtersession.idl create mode 100644 share/doc/idl/jsf.idl create mode 100644 share/doc/idl/nodejs.idl create mode 100644 share/doc/idl/scenejs.idl create mode 100644 share/doc/idl/storage.idl create mode 100644 share/doc/idl/webgl.idl create mode 100644 share/doc/idl/xhr.idl create mode 100644 share/doc/ipmpx_syntax.bt create mode 100644 share/doc/man/gpac-filters.1 create mode 100644 share/doc/man/gpac.1 create mode 100644 share/doc/man/mp4box.1 create mode 100644 share/doc/man/mp4client.1 create mode 100644 share/doc/osmo4.ico create mode 100644 share/gpac.desktop create mode 100644 share/gui/extensions/H2B2VS/H2B2VS.png create mode 100644 share/gui/extensions/H2B2VS/h2b2vs.js create mode 100644 share/gui/extensions/H2B2VS/init.js create mode 100644 share/gui/extensions/H2B2VS/logo_hd.png create mode 100644 share/gui/extensions/H2B2VS/logo_uhd.png create mode 100644 share/gui/extensions/about/info.js create mode 100644 share/gui/extensions/about/info.svg create mode 100644 share/gui/extensions/about/init.js create mode 100644 share/gui/extensions/bifs_tests/applications-other.svg create mode 100644 share/gui/extensions/bifs_tests/bifs_tests.js create mode 100644 share/gui/extensions/bifs_tests/init.js create mode 100644 share/gui/extensions/player/applications-multimedia.svg create mode 100644 share/gui/extensions/player/fileopen.js create mode 100644 share/gui/extensions/player/init.js create mode 100644 share/gui/extensions/player/player.js create mode 100644 share/gui/extensions/player/playlist.js create mode 100644 share/gui/extensions/player/stats.js create mode 100644 share/gui/extensions/showroom/init.js create mode 100644 share/gui/extensions/showroom/osmo.bt create mode 100644 share/gui/extensions/showroom/showroom.js create mode 100644 share/gui/extensions/widget_manager/applications-system.svg create mode 100644 share/gui/extensions/widget_manager/init.js create mode 100644 share/gui/gui.bt create mode 100644 share/gui/gui.js create mode 100644 share/gui/gwlib.js create mode 100644 share/gui/icons/add.svg create mode 100644 share/gui/icons/app.svg create mode 100644 share/gui/icons/audio.svg create mode 100644 share/gui/icons/audio_full.svg create mode 100644 share/gui/icons/audio_mute.svg create mode 100644 share/gui/icons/check.svg create mode 100644 share/gui/icons/close.svg create mode 100644 share/gui/icons/compass.svg create mode 100644 share/gui/icons/cross.svg create mode 100644 share/gui/icons/down.svg create mode 100644 share/gui/icons/expand.svg create mode 100644 share/gui/icons/file.svg create mode 100644 share/gui/icons/film.svg create mode 100644 share/gui/icons/folder.svg create mode 100644 share/gui/icons/harddrive.svg create mode 100644 share/gui/icons/heart.svg create mode 100644 share/gui/icons/home.svg create mode 100644 share/gui/icons/image.svg create mode 100644 share/gui/icons/info.svg create mode 100644 share/gui/icons/laptop.svg create mode 100644 share/gui/icons/left.svg create mode 100644 share/gui/icons/list.svg create mode 100644 share/gui/icons/live.svg create mode 100644 share/gui/icons/media_next.svg create mode 100644 share/gui/icons/media_prev.svg create mode 100644 share/gui/icons/monitor.svg create mode 100644 share/gui/icons/more.svg create mode 100644 share/gui/icons/musical.svg create mode 100644 share/gui/icons/navigation.svg create mode 100644 share/gui/icons/network.svg create mode 100644 share/gui/icons/next.svg create mode 100644 share/gui/icons/osmo.svg create mode 100644 share/gui/icons/overflowing.svg create mode 100644 share/gui/icons/pause.svg create mode 100644 share/gui/icons/pl_next.svg create mode 100644 share/gui/icons/pl_prev.svg create mode 100644 share/gui/icons/play.svg create mode 100644 share/gui/icons/play_loop.svg create mode 100644 share/gui/icons/play_shuffle.svg create mode 100644 share/gui/icons/play_single.svg create mode 100644 share/gui/icons/power.svg create mode 100644 share/gui/icons/previous.svg create mode 100644 share/gui/icons/remove.svg create mode 100644 share/gui/icons/resize.svg create mode 100644 share/gui/icons/rewind.svg create mode 100644 share/gui/icons/right.svg create mode 100644 share/gui/icons/seek_forward.svg create mode 100644 share/gui/icons/shrink.svg create mode 100644 share/gui/icons/sort.svg create mode 100644 share/gui/icons/speed.svg create mode 100644 share/gui/icons/star.svg create mode 100644 share/gui/icons/stop.svg create mode 100644 share/gui/icons/stop2.svg create mode 100644 share/gui/icons/trash.svg create mode 100644 share/gui/icons/tray.svg create mode 100644 share/gui/icons/tv.svg create mode 100644 share/gui/icons/up.svg create mode 100644 share/gui/icons/world.svg create mode 100644 share/lang/fr.txt create mode 100644 share/nodejs/binding.gyp create mode 100644 share/nodejs/index.js create mode 100644 share/nodejs/package.json create mode 100644 share/nodejs/src/gpac_napi.c create mode 100644 share/nodejs/src/gpac_napi.h create mode 100644 share/nodejs/test/gpac.js create mode 100644 share/python/libgpac.py create mode 100644 share/res/gpac.mp4 create mode 100644 share/res/gpac.png create mode 100644 share/res/gpac_cfg_test.mp4 create mode 100755 share/res/gpac_highres.png create mode 100644 share/scripts/custom_dash.js create mode 100644 share/scripts/jsf/avgen/init.js create mode 100644 share/scripts/jsf/avgen/testcard.png create mode 100644 share/scripts/jsf/avmix/help.js create mode 100644 share/scripts/jsf/avmix/init.js create mode 100644 share/scripts/jsf/avmix/scenes/clear.js create mode 100644 share/scripts/jsf/avmix/scenes/clip.js create mode 100644 share/scripts/jsf/avmix/scenes/mask.js create mode 100644 share/scripts/jsf/avmix/scenes/shape.js create mode 100644 share/scripts/jsf/avmix/transitions/fade.js create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/Bounce.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/BowTieHorizontal.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/BowTieVertical.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/BowTieWithParameter.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/ButterflyWaveScrawler.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/CircleCrop.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/ColourDistance.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/CrazyParametricFun.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/CrossZoom.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/Directional.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/DoomScreenTransition.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/Dreamy.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/DreamyZoom.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/FilmBurn.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/GlitchDisplace.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/GlitchMemories.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/GridFlip.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/InvertedPageCurl.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/LeftRight.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/LinearBlur.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/Mosaic.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/PolkaDotsCurtain.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/Radial.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/SimpleZoom.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/StereoViewer.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/Swirl.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/TVStatic.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/TopBottom.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/WaterDrop.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/ZoomInCircles.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/angular.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/burn.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/cannabisleaf.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/circle.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/circleopen.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/colorphase.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/crosshatch.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/crosswarp.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/cube.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/directional-easing.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/directionalwarp.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/directionalwipe.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/displacement.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/doorway.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/fade.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/fadecolor.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/fadegrayscale.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/flyeye.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/heart.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/hexagonalize.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/kaleidoscope.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/luma.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/luminance_melt.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/morph.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/multiply_blend.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/perlin.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/pinwheel.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/pixelize.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/polar_function.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/randomNoisex.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/randomsquares.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/ripple.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/rotateTransition.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/rotate_scale_fade.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/squareswire.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/squeeze.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/swap.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/tangentMotionBlur.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/undulatingBurnOut.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/wind.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/windowblinds.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/windowslice.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/wipeDown.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/wipeLeft.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/wipeRight.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gl-transitions/wipeUp.glsl create mode 100644 share/scripts/jsf/avmix/transitions/gltrans.js create mode 100644 share/scripts/jsf/avmix/transitions/mix.js create mode 100644 share/scripts/jsf/avmix/transitions/swipe.js create mode 100644 share/scripts/ttml-renderer.js create mode 100644 share/scripts/vout.js create mode 100644 share/scripts/webvtt-renderer.js create mode 100644 share/shaders/fragment.glsl create mode 100644 share/shaders/vertex.glsl create mode 100644 share/vis/Code/Console.js create mode 100644 share/vis/Code/DataViewReader.js create mode 100644 share/vis/Code/PixelTimeRange.js create mode 100644 share/vis/Code/Remotery.js create mode 100644 share/vis/Code/SampleWindow.js create mode 100644 share/vis/Code/ThreadFrame.js create mode 100644 share/vis/Code/TimelineRow.js create mode 100644 share/vis/Code/TimelineWindow.js create mode 100644 share/vis/Code/TitleWindow.js create mode 100644 share/vis/Code/WebSocketConnection.js create mode 100644 share/vis/Styles/Remotery.css create mode 100644 share/vis/extern/BrowserLib/Core/Code/Animation.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Bind.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Convert.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Core.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/DOM.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Keyboard.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/LocalStore.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Mouse.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/MurmurHash3.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Button.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Container.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/EditBox.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Grid.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Label.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Treeview.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Window.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css create mode 100644 share/vis/index.html create mode 100644 src/Makefile create mode 100644 src/bifs/arith_decoder.c create mode 100644 src/bifs/bifs_codec.c create mode 100644 src/bifs/bifs_node_tables.c create mode 100644 src/bifs/com_dec.c create mode 100644 src/bifs/com_enc.c create mode 100644 src/bifs/conditional.c create mode 100644 src/bifs/field_decode.c create mode 100644 src/bifs/field_encode.c create mode 100644 src/bifs/memory_decoder.c create mode 100644 src/bifs/predictive_mffield.c create mode 100644 src/bifs/quant.h create mode 100644 src/bifs/quantize.c create mode 100644 src/bifs/script.h create mode 100644 src/bifs/script_dec.c create mode 100644 src/bifs/script_enc.c create mode 100644 src/bifs/unquantize.c create mode 100644 src/compositor/audio_input.c create mode 100644 src/compositor/audio_mixer.c create mode 100644 src/compositor/audio_render.c create mode 100644 src/compositor/bindable.c create mode 100644 src/compositor/camera.c create mode 100644 src/compositor/clock.c create mode 100644 src/compositor/compositor.c create mode 100644 src/compositor/compositor_2d.c create mode 100644 src/compositor/compositor_3d.c create mode 100644 src/compositor/compositor_node_init.c create mode 100644 src/compositor/drawable.c create mode 100644 src/compositor/drawable.h create mode 100644 src/compositor/events.c create mode 100644 src/compositor/font_engine.c create mode 100644 src/compositor/gl_inc.h create mode 100644 src/compositor/hardcoded_protos.c create mode 100644 src/compositor/hc_flash_shape.c create mode 100644 src/compositor/media_object.c create mode 100644 src/compositor/mesh.c create mode 100644 src/compositor/mesh_collide.c create mode 100644 src/compositor/mesh_tesselate.c create mode 100644 src/compositor/mpeg4_animstream.c create mode 100644 src/compositor/mpeg4_audio.c create mode 100644 src/compositor/mpeg4_background.c create mode 100644 src/compositor/mpeg4_background2d.c create mode 100644 src/compositor/mpeg4_bitmap.c create mode 100644 src/compositor/mpeg4_composite.c create mode 100644 src/compositor/mpeg4_form.c create mode 100644 src/compositor/mpeg4_geometry_2d.c create mode 100644 src/compositor/mpeg4_geometry_3d.c create mode 100644 src/compositor/mpeg4_geometry_ifs2d.c create mode 100644 src/compositor/mpeg4_geometry_ils2d.c create mode 100644 src/compositor/mpeg4_gradients.c create mode 100644 src/compositor/mpeg4_grouping.c create mode 100644 src/compositor/mpeg4_grouping.h create mode 100644 src/compositor/mpeg4_grouping_2d.c create mode 100644 src/compositor/mpeg4_grouping_3d.c create mode 100644 src/compositor/mpeg4_inline.c create mode 100644 src/compositor/mpeg4_inputsensor.c create mode 100644 src/compositor/mpeg4_layer_2d.c create mode 100644 src/compositor/mpeg4_layer_3d.c create mode 100644 src/compositor/mpeg4_layout.c create mode 100644 src/compositor/mpeg4_lighting.c create mode 100644 src/compositor/mpeg4_mediacontrol.c create mode 100644 src/compositor/mpeg4_mediasensor.c create mode 100644 src/compositor/mpeg4_path_layout.c create mode 100644 src/compositor/mpeg4_sensors.c create mode 100644 src/compositor/mpeg4_sound.c create mode 100644 src/compositor/mpeg4_text.c create mode 100644 src/compositor/mpeg4_textures.c create mode 100644 src/compositor/mpeg4_timesensor.c create mode 100644 src/compositor/mpeg4_viewport.c create mode 100644 src/compositor/navigate.c create mode 100644 src/compositor/nodes_stacks.h create mode 100644 src/compositor/object_manager.c create mode 100644 src/compositor/offscreen_cache.c create mode 100644 src/compositor/offscreen_cache.h create mode 100644 src/compositor/scene.c create mode 100644 src/compositor/scene_node_init.c create mode 100644 src/compositor/scene_ns.c create mode 100644 src/compositor/svg_base.c create mode 100644 src/compositor/svg_external.c create mode 100644 src/compositor/svg_filters.c create mode 100644 src/compositor/svg_font.c create mode 100644 src/compositor/svg_geometry.c create mode 100644 src/compositor/svg_grouping.c create mode 100644 src/compositor/svg_media.c create mode 100644 src/compositor/svg_paint_servers.c create mode 100644 src/compositor/svg_text.c create mode 100644 src/compositor/texturing.c create mode 100644 src/compositor/texturing.h create mode 100644 src/compositor/texturing_gl.c create mode 100644 src/compositor/visual_manager.c create mode 100644 src/compositor/visual_manager.h create mode 100644 src/compositor/visual_manager_2d.c create mode 100644 src/compositor/visual_manager_2d.h create mode 100644 src/compositor/visual_manager_2d_draw.c create mode 100644 src/compositor/visual_manager_3d.c create mode 100644 src/compositor/visual_manager_3d.h create mode 100644 src/compositor/visual_manager_3d_gl.c create mode 100644 src/compositor/x3d_geometry.c create mode 100644 src/crypto/g_crypt.c create mode 100644 src/crypto/g_crypt_openssl.c create mode 100644 src/crypto/g_crypt_tinyaes.c create mode 100644 src/crypto/tiny_aes.c create mode 100644 src/crypto/tiny_aes.h create mode 100644 src/evg/ftgrays.c create mode 100644 src/evg/rast_soft.h create mode 100644 src/evg/raster3d.c create mode 100644 src/evg/raster_565.c create mode 100644 src/evg/raster_argb.c create mode 100644 src/evg/raster_rgb.c create mode 100644 src/evg/raster_yuv.c create mode 100644 src/evg/stencil.c create mode 100644 src/evg/surface.c create mode 100644 src/export.cpp create mode 100644 src/filter_core/filter.c create mode 100644 src/filter_core/filter_pck.c create mode 100644 src/filter_core/filter_pid.c create mode 100644 src/filter_core/filter_props.c create mode 100644 src/filter_core/filter_queue.c create mode 100644 src/filter_core/filter_register.c create mode 100644 src/filter_core/filter_session.c create mode 100644 src/filter_core/filter_session.h create mode 100644 src/filter_core/filter_session_js.c create mode 100644 src/filters/base_filter_example.c create mode 100644 src/filters/bs_agg.c create mode 100644 src/filters/bs_split.c create mode 100644 src/filters/bsrw.c create mode 100644 src/filters/compose.c create mode 100644 src/filters/dasher.c create mode 100644 src/filters/dec_ac52.c create mode 100644 src/filters/dec_bifs.c create mode 100644 src/filters/dec_faad.c create mode 100644 src/filters/dec_img.c create mode 100644 src/filters/dec_j2k.c create mode 100644 src/filters/dec_laser.c create mode 100644 src/filters/dec_mad.c create mode 100644 src/filters/dec_mediacodec.c create mode 100644 src/filters/dec_mediacodec.h create mode 100644 src/filters/dec_mediacodec_jni.c create mode 100644 src/filters/dec_nvdec.c create mode 100644 src/filters/dec_nvdec_sdk.c create mode 100644 src/filters/dec_nvdec_sdk.h create mode 100644 src/filters/dec_odf.c create mode 100644 src/filters/dec_openhevc.c create mode 100644 src/filters/dec_opensvc.c create mode 100644 src/filters/dec_theora.c create mode 100644 src/filters/dec_ttml.c create mode 100644 src/filters/dec_ttxt.c create mode 100644 src/filters/dec_vorbis.c create mode 100644 src/filters/dec_vtb.c create mode 100644 src/filters/dec_vtb_glctx.m create mode 100644 src/filters/dec_webvtt.c create mode 100644 src/filters/dec_xvid.c create mode 100644 src/filters/decrypt_cenc_isma.c create mode 100644 src/filters/dmx_avi.c create mode 100644 src/filters/dmx_dash.c create mode 100644 src/filters/dmx_gsf.c create mode 100644 src/filters/dmx_m2ts.c create mode 100644 src/filters/dmx_mpegps.c create mode 100644 src/filters/dmx_nhml.c create mode 100644 src/filters/dmx_nhnt.c create mode 100644 src/filters/dmx_ogg.c create mode 100644 src/filters/dmx_saf.c create mode 100644 src/filters/dmx_vobsub.c create mode 100644 src/filters/enc_jpg.c create mode 100644 src/filters/enc_png.c create mode 100644 src/filters/encrypt_cenc_isma.c create mode 100644 src/filters/ff_avf.c create mode 100644 src/filters/ff_common.c create mode 100644 src/filters/ff_common.h create mode 100644 src/filters/ff_dec.c create mode 100644 src/filters/ff_dmx.c create mode 100644 src/filters/ff_enc.c create mode 100644 src/filters/ff_mx.c create mode 100644 src/filters/ff_rescale.c create mode 100644 src/filters/filelist.c create mode 100644 src/filters/hevcmerge.c create mode 100644 src/filters/hevcsplit.c create mode 100644 src/filters/in_dvb4linux.c create mode 100644 src/filters/in_file.c create mode 100644 src/filters/in_http.c create mode 100644 src/filters/in_pipe.c create mode 100644 src/filters/in_route.c create mode 100644 src/filters/in_rtp.c create mode 100644 src/filters/in_rtp.h create mode 100644 src/filters/in_rtp_rtsp.c create mode 100644 src/filters/in_rtp_sdp.c create mode 100644 src/filters/in_rtp_signaling.c create mode 100644 src/filters/in_rtp_stream.c create mode 100644 src/filters/in_sock.c create mode 100644 src/filters/inspect.c create mode 100644 src/filters/io_fcryp.c create mode 100644 src/filters/isoffin.h create mode 100644 src/filters/isoffin_load.c create mode 100644 src/filters/isoffin_read.c create mode 100644 src/filters/isoffin_read_ch.c create mode 100644 src/filters/jsfilter.c create mode 100644 src/filters/load_bt_xmt.c create mode 100644 src/filters/load_svg.c create mode 100644 src/filters/load_text.c create mode 100644 src/filters/mux_avi.c create mode 100644 src/filters/mux_gsf.c create mode 100644 src/filters/mux_isom.c create mode 100644 src/filters/mux_ts.c create mode 100644 src/filters/out_audio.c create mode 100644 src/filters/out_file.c create mode 100644 src/filters/out_http.c create mode 100644 src/filters/out_pipe.c create mode 100644 src/filters/out_route.c create mode 100644 src/filters/out_rtp.c create mode 100644 src/filters/out_rtp.h create mode 100644 src/filters/out_rtsp.c create mode 100644 src/filters/out_sock.c create mode 100644 src/filters/out_video.c create mode 100644 src/filters/reframe_ac3.c create mode 100644 src/filters/reframe_adts.c create mode 100644 src/filters/reframe_amr.c create mode 100644 src/filters/reframe_av1.c create mode 100644 src/filters/reframe_flac.c create mode 100644 src/filters/reframe_h263.c create mode 100644 src/filters/reframe_img.c create mode 100644 src/filters/reframe_latm.c create mode 100644 src/filters/reframe_mhas.c create mode 100644 src/filters/reframe_mp3.c create mode 100644 src/filters/reframe_mpgvid.c create mode 100644 src/filters/reframe_nalu.c create mode 100644 src/filters/reframe_prores.c create mode 100644 src/filters/reframe_qcp.c create mode 100644 src/filters/reframe_rawpcm.c create mode 100644 src/filters/reframe_rawvid.c create mode 100644 src/filters/reframe_truehd.c create mode 100644 src/filters/reframer.c create mode 100644 src/filters/resample_audio.c create mode 100644 src/filters/restamp.c create mode 100644 src/filters/rewind.c create mode 100644 src/filters/rewrite_adts.c create mode 100644 src/filters/rewrite_mhas.c create mode 100644 src/filters/rewrite_mp4v.c create mode 100644 src/filters/rewrite_nalu.c create mode 100644 src/filters/rewrite_obu.c create mode 100644 src/filters/tileagg.c create mode 100644 src/filters/tilesplit.c create mode 100644 src/filters/tssplit.c create mode 100644 src/filters/unit_test_filter.c create mode 100644 src/filters/vcrop.c create mode 100644 src/filters/vflip.c create mode 100644 src/filters/write_generic.c create mode 100644 src/filters/write_nhml.c create mode 100644 src/filters/write_nhnt.c create mode 100644 src/filters/write_qcp.c create mode 100644 src/filters/write_vtt.c create mode 100644 src/ietf/rtcp.c create mode 100644 src/ietf/rtp.c create mode 100644 src/ietf/rtp_depacketizer.c create mode 100644 src/ietf/rtp_packetizer.c create mode 100644 src/ietf/rtp_pck_3gpp.c create mode 100644 src/ietf/rtp_pck_mpeg12.c create mode 100644 src/ietf/rtp_pck_mpeg4.c create mode 100644 src/ietf/rtp_streamer.c create mode 100644 src/ietf/rtsp_command.c create mode 100644 src/ietf/rtsp_common.c create mode 100644 src/ietf/rtsp_response.c create mode 100644 src/ietf/rtsp_session.c create mode 100644 src/ietf/sdp.c create mode 100644 src/isomedia/avc_ext.c create mode 100644 src/isomedia/box_code_3gpp.c create mode 100644 src/isomedia/box_code_adobe.c create mode 100644 src/isomedia/box_code_apple.c create mode 100644 src/isomedia/box_code_base.c create mode 100644 src/isomedia/box_code_drm.c create mode 100644 src/isomedia/box_code_meta.c create mode 100644 src/isomedia/box_dump.c create mode 100644 src/isomedia/box_funcs.c create mode 100644 src/isomedia/data_map.c create mode 100644 src/isomedia/drm_sample.c create mode 100644 src/isomedia/hint_track.c create mode 100644 src/isomedia/hinting.c create mode 100644 src/isomedia/iff.c create mode 100644 src/isomedia/isom_intern.c create mode 100644 src/isomedia/isom_read.c create mode 100644 src/isomedia/isom_store.c create mode 100644 src/isomedia/isom_write.c create mode 100644 src/isomedia/media.c create mode 100644 src/isomedia/media_odf.c create mode 100644 src/isomedia/meta.c create mode 100644 src/isomedia/movie_fragments.c create mode 100644 src/isomedia/sample_descs.c create mode 100644 src/isomedia/stbl_read.c create mode 100644 src/isomedia/stbl_write.c create mode 100644 src/isomedia/track.c create mode 100644 src/isomedia/ttml.c create mode 100644 src/isomedia/tx3g.c create mode 100644 src/jsmods/WebGLRenderingContextBase.c create mode 100644 src/jsmods/core.c create mode 100644 src/jsmods/evg.c create mode 100644 src/jsmods/scene_js.c create mode 100644 src/jsmods/storage.c create mode 100644 src/jsmods/webgl.c create mode 100644 src/jsmods/webgl.h create mode 100644 src/jsmods/xhr.c create mode 100644 src/laser/lsr_dec.c create mode 100644 src/laser/lsr_enc.c create mode 100644 src/laser/lsr_tables.c create mode 100644 src/media_tools/ait.c create mode 100644 src/media_tools/av_parsers.c create mode 100644 src/media_tools/avilib.c create mode 100644 src/media_tools/crypt_tools.c create mode 100644 src/media_tools/dash_client.c create mode 100644 src/media_tools/dash_segmenter.c create mode 100644 src/media_tools/dsmcc.c create mode 100644 src/media_tools/dvb_mpe.c create mode 100644 src/media_tools/gpac_ogg.c create mode 100644 src/media_tools/html5_media.c create mode 100644 src/media_tools/html5_mse.c create mode 100644 src/media_tools/img.c create mode 100644 src/media_tools/isom_hinter.c create mode 100644 src/media_tools/isom_tools.c create mode 100644 src/media_tools/m2ts_mux.c create mode 100644 src/media_tools/m3u8.c create mode 100644 src/media_tools/media_export.c create mode 100644 src/media_tools/media_import.c create mode 100644 src/media_tools/mpd.c create mode 100644 src/media_tools/mpeg2_ps.c create mode 100644 src/media_tools/mpeg2_ps.h create mode 100644 src/media_tools/mpegts.c create mode 100644 src/media_tools/reedsolomon.c create mode 100644 src/media_tools/route_dmx.c create mode 100644 src/media_tools/saf.c create mode 100644 src/media_tools/vobsub.c create mode 100644 src/media_tools/webvtt.c create mode 100644 src/odf/desc_private.c create mode 100644 src/odf/descriptors.c create mode 100644 src/odf/ipmpx_code.c create mode 100644 src/odf/ipmpx_dump.c create mode 100644 src/odf/ipmpx_parse.c create mode 100644 src/odf/oci_codec.c create mode 100644 src/odf/odf_code.c create mode 100644 src/odf/odf_codec.c create mode 100644 src/odf/odf_command.c create mode 100644 src/odf/odf_dump.c create mode 100644 src/odf/odf_parse.c create mode 100644 src/odf/qos.c create mode 100644 src/odf/slc.c create mode 100644 src/quickjs/GPAC_README.md create mode 100644 src/quickjs/cutils.c create mode 100644 src/quickjs/cutils.h create mode 100644 src/quickjs/libbf.c create mode 100644 src/quickjs/libbf.h create mode 100644 src/quickjs/libregexp-opcode.h create mode 100644 src/quickjs/libregexp.c create mode 100644 src/quickjs/libregexp.h create mode 100644 src/quickjs/libunicode-table.h create mode 100644 src/quickjs/libunicode.c create mode 100644 src/quickjs/libunicode.h create mode 100644 src/quickjs/list.h create mode 100644 src/quickjs/quickjs-atom.h create mode 100644 src/quickjs/quickjs-libc.c create mode 100644 src/quickjs/quickjs-libc.h create mode 100644 src/quickjs/quickjs-opcode.h create mode 100644 src/quickjs/quickjs.c create mode 100644 src/quickjs/quickjs.h create mode 100644 src/scene_manager/encode_isom.c create mode 100644 src/scene_manager/loader_bt.c create mode 100644 src/scene_manager/loader_isom.c create mode 100644 src/scene_manager/loader_qt.c create mode 100644 src/scene_manager/loader_svg.c create mode 100644 src/scene_manager/loader_xmt.c create mode 100644 src/scene_manager/scene_dump.c create mode 100644 src/scene_manager/scene_engine.c create mode 100644 src/scene_manager/scene_manager.c create mode 100644 src/scene_manager/scene_stats.c create mode 100644 src/scene_manager/swf_bifs.c create mode 100644 src/scene_manager/swf_parse.c create mode 100644 src/scene_manager/swf_svg.c create mode 100644 src/scene_manager/text_to_bifs.c create mode 100644 src/scenegraph/base_scenegraph.c create mode 100644 src/scenegraph/commands.c create mode 100644 src/scenegraph/dom_events.c create mode 100644 src/scenegraph/dom_js.c create mode 100644 src/scenegraph/html5_media_js.c create mode 100644 src/scenegraph/html5_mse_js.c create mode 100644 src/scenegraph/mpeg4_animators.c create mode 100644 src/scenegraph/mpeg4_nodes.c create mode 100644 src/scenegraph/mpeg4_valuator.c create mode 100644 src/scenegraph/qjs_common.h create mode 100644 src/scenegraph/smil_anim.c create mode 100644 src/scenegraph/smil_timing.c create mode 100644 src/scenegraph/svg_attributes.c create mode 100644 src/scenegraph/svg_js.c create mode 100644 src/scenegraph/svg_properties.c create mode 100644 src/scenegraph/svg_types.c create mode 100644 src/scenegraph/vrml_interpolators.c create mode 100644 src/scenegraph/vrml_js.c create mode 100644 src/scenegraph/vrml_proto.c create mode 100644 src/scenegraph/vrml_route.c create mode 100644 src/scenegraph/vrml_script.c create mode 100644 src/scenegraph/vrml_tools.c create mode 100644 src/scenegraph/x3d_nodes.c create mode 100644 src/scenegraph/xml_ns.c create mode 100644 src/terminal/terminal.c create mode 100644 src/utils/Remotery.c create mode 100644 src/utils/alloc.c create mode 100644 src/utils/base_encoding.c create mode 100644 src/utils/bitstream.c create mode 100644 src/utils/cache.c create mode 100644 src/utils/color.c create mode 100644 src/utils/configfile.c create mode 100644 src/utils/constants.c create mode 100644 src/utils/dlmalloc.c create mode 100644 src/utils/downloader.c create mode 100644 src/utils/error.c create mode 100644 src/utils/gltools.c create mode 100644 src/utils/gzio.c create mode 100644 src/utils/list.c create mode 100644 src/utils/math.c create mode 100644 src/utils/module.c create mode 100644 src/utils/module_wrap.h create mode 100644 src/utils/os_config_init.c create mode 100644 src/utils/os_divers.c create mode 100644 src/utils/os_file.c create mode 100644 src/utils/os_module.c create mode 100644 src/utils/os_net.c create mode 100644 src/utils/os_thread.c create mode 100644 src/utils/path2d.c create mode 100644 src/utils/path2d_stroker.c create mode 100644 src/utils/sha1.c create mode 100644 src/utils/symbian_net.cpp create mode 100644 src/utils/symbian_os.cpp create mode 100644 src/utils/token.c create mode 100644 src/utils/uni_bidi.c create mode 100644 src/utils/unicode.c create mode 100644 src/utils/url.c create mode 100644 src/utils/utf.c create mode 100644 src/utils/xml_parser.c create mode 100644 src/utils/zlib_symbian_ext.h create mode 100644 src/utils/zutil.c create mode 100644 src/utils/zutil.h create mode 100644 static.mak create mode 100644 version.bat diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f32d776 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +* text=auto + +*.c text +*.cpp text +*.h text +*.ttx text +Makefile text + +#unix +*.sh text eol=lf +configure text eol=lf + +#windows +*.bat text eol=crlf +*.sln text eol=crlf +*.vcproj text eol=crlf +*.vcxproj text eol=crlf +*.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 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..2c5bf2e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +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 http://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: http://gpac.io/2013/07/16/how-to-file-a-bug-properly/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eef1582 --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +*.o +*.suo +*.user +*.userosscache +*.sln.docstates +*.userprefs +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile +*~ +*.plg +*.opt +*.gcno +*.gcda +*.gcov +*.DS_Store + +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + + +.DS_Store .AppleDouble .LSOverride + +*.class + +*.so +*.dylib +*.dll +*.lai +*.la +*.a +*.lib +*.exe +*.out +*.app +*.apk +*.dmg +*.gz +*.deb + +.depend +.deps/ +.dep + +#GPAC specific +config.h +include/gpac/revision.h +include/gpac/revision.h.new +extra_lib/lib/ +extra_lib/include/ +config.mak +configure-stamp +gpac.pc +tests/external_media/ +tests/results/ +tests/hash_refs/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0daa934 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,5 @@ +[submodule "testsuite"] + path = testsuite + url = https://github.com/gpac/testsuite.git + branch = master + ignore = dirty diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5c6b79a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,58 @@ +#changes for GPAC filters (0.9+) for travis CI: GPAC hosts its own buildbot for building on various platforms, running tests, coverage and safety checks +#TravisCI is only used for checking sanity of PRs. Consequently: +#- OSX and mingw no longer compiled +#- linux only compiled for +# - mp4box and gpac static +# - release mode with mem tracking and gcov + +language: c +compiler: gcc +os: + - linux +# - osx +dist: bionic +services: + - xvfb + +before_install: + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y update -qq ; fi +install: + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install build-essential fakeroot dpkg-dev devscripts ccache debhelper pkg-config g++ mesa-utils lcov ; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install -y 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 libxv-dev x11proto-video-dev libgl1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps ; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -qq -y install time; fi +# - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install -y gcc-mingw-w64-i686 g++-mingw-w64-i686 binutils-mingw-w64-i686 gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-x86-64-dev ; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then sudo chown -R "$USER":admin /usr/local; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install gnu-time gnu-sed gnu-tar xz lcov ; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install faad2 sdl freetype libvorbis theora openjpeg libmad xvid libogg spidermonkey ffmpeg ; fi +env: + - GPAC_CONFIGURE_OPTIONS="--prefix=build/mp4box --enable-debug --static-bin" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="make install" DOTRAVIS="" + - GPAC_CONFIGURE_OPTIONS="--enable-debug --static-modules" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="" DOTRAVIS="" + - GPAC_CONFIGURE_OPTIONS="--enable-debug --static-build" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="" DOTRAVIS="" +matrix: + include: + - os: linux + before_script: + #git submodule update is called by travis, just checkout filter branch + - "git -C ./testsuite checkout filters" + - xvfb-run -e /dev/stdout --auto-servernum --server-num=1 glxinfo # check glx status + env: GPAC_CONFIGURE_OPTIONS="--enable-mem-track --enable-gcov" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="sudo make install" DOTRAVIS="make travis" AUDIODEV=null + +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--prefix=build/x86_64-w64-mingw32 --enable-debug --static-bin --use-zlib=no --target-os=mingw32 --cross-prefix=i686-w64-mingw32- --extra-ldflags=-Lbuild/x86_64-w64-mingw32/lib" GPAC_CONFIGURE_ECFLAGS="-Ibuild/x86_64-w64-mingw32/include -w -fPIC" DOINSTALL="" DOTRAVIS="" +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--prefix=build/x86_64-w64-mingw32 --enable-debug --static-bin --use-zlib=no --target-os=mingw32 --cross-prefix=x86_64-w64-mingw32- --extra-ldflags=-Lbuild/x86_64-w64-mingw32/lib" GPAC_CONFIGURE_ECFLAGS="-Ibuild/x86_64-w64-mingw32/include -w -fPIC" DOINSTALL="" DOTRAVIS="" +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--prefix=build/all --enable-debug --disable-all" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="make install" DOTRAVIS="" +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--enable-debug --use-js=no --use-mad=no --use-xvid=no --use-ogg=no --use-vorbis=no --use-theora=no --use-openjpeg=no --disable-streaming --disable-isoff-frag --disable-isoff-hint --disable-isoff-write --disable-loader-xmt --disable-loader-bt --disable-loader-isoff --disable-scene-encode --disable-mcrypt --disable-od-dump --disable-scene-dump --disable-scene-stats --disable-swf --disable-export --disable-import --disable-m2ps --disable-ogg -disable-avi --disable-qtvr --disable-seng --disable-smgr --disable-x3d --disable-3d --disable-ssl --disable-jack --disable-pulse --use-a52=no --disable-odf --disable-isoff --disable-m2ts-mux --disable-dvbx --disable-saf --disable-vobsub --disable-ttxt --disable-od-parse --disable-atsc" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="" DOTRAVIS="" +# - os: osx +# env: GPAC_CONFIGURE_OPTIONS="--enable-mem-track" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="sudo make install" DOTRAVIS="" AUDIODEV=null +script: + - ./configure --extra-cflags=''"$GPAC_CONFIGURE_ECFLAGS"'' $GPAC_CONFIGURE_OPTIONS && make && $DOINSTALL && ( if [ -n "$DOTRAVIS" ]; then xvfb-run -e /dev/stdout --auto-servernum --server-num=1 --server-args="-screen 0 1024x768x24" $DOTRAVIS ; fi ) + +notifications: + email: + recipients: + - travisci@gpac.io + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..223ede7 --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library 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 of the License, or (at your option) any later version. + + This library 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; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..ac35f81 --- /dev/null +++ b/Changelog @@ -0,0 +1,1035 @@ +22/02/2022: GPAC 2.0 + +## Adaptive Streaming +- Low Latency HLS (LL-HLS, generation and playback) +- Custom forwarding modes of DASH reader, allowing for example to encrypt/decrypt a live DASH/HLS session +- DASHing now possible using inband cues (generated by flist, dashin or dasher filters) +- Cue-generation only mode for dasher +- HLS/DASH signaling of intra-only representations +- playback improvements for SRD (HEVC tiling and independent streams) +- CMAF-compatible signaling +- PPS injection for inband parameter set modes +- Improved SmoothStreaming support +- DASH period continuity support (from playlists or reframer) +- DASH MPD Chaining support +- Text segments in native format (WebVTT, TTML) in dasher +- User tags injection for HLS master and variant playlists + +## Image File Format (HEIF) +- Grid creation and other derived images +- Encryption +- Time range in -add-image +- Item to sample data reference (no copy) +- Item to item data reference (no copy) +- Generic auxiliary image tagging +- Image replacement in HEIF collection +- AV1 (AVIF) and VVC support + +## MP4Box +- In-place editing (no file remultiplexing) +- Improved splitting, including sample-accurate split +- ISOBMFF edit lists modification through :edits and -edits options +- Track extraction from non ISOBMFF sources in MP4Box +- Most import options can now be applied on an existing ISOBMF file or track +- Multiple filter chains per source in -dash or -add modes +- Chunk interleaving dumper + +## Filters +- Simplified gpac command line (backward compatible syntax) avoiding links directives for common cases +- Added avmix, a playlist-based audio video mixer/compositor with GPU or software rendering +- Python bindings for filter session +- NodeJS bindings for filter session +- Custom HAS adaptation algorithms (JS, Python and NodeJS) +- Custom Remotery callbacks (JS, Python and NodeJS) +- Added avgen, a simple counter generation +- Splicing support in playlists: raw and compressed domain, vod and live splicing +- Tile splitting now available as a filter +- Added restamp, a stream timestamp rewriter filter +- Added bssplit and bsagg, compressed bitstream splitter and aggregator filters +- Aspect ratio support in rescaler (ffsws) +- Raw (uncompressed) modes for flist and reframer +- Flip and rotate in vout +- Simple UI for vout to test seeking and speed modes +- Improved color space support in rendering (OpenGL only) +- Improved round-trip audio decode +- Improved audio resampler +- Added back old arch compositor features (TEMI support, HEVC tiling JS monitoring, MPEG-4 SegmentDescriptor) +- Reading back frame interface data (GPU, decoder mem) in JS or Python sink filters +- Memory storage in httpout server file sink +- FileIO wrapper available in QJS, Python and NodeJS + +## Protocols +- HTTP/2 (client and server) through nghttp2 +- ROUTE multiplexer and low-latency ROUTE (mux and demux) + +## Media Formats +- Improved MPEG-H audio mux/demux/dashing +- VVC parsing, inspecting, mux/demux (ISOBMFF, M2TS), RTP, DASH and encryption +- VUI color info rewrite for AVC, HEVC and VVC (mp4box and bsrw filter) +- Dolby TrueHD +- DolbyVision muxing +- Improved multichannel AAC (>=8) support +- Improved raw video in ISOBMFF support +- Bitstream dumping for inspect analyze mode +- Improved TTML support: metrics, image embedding (IMSC1), TTML sample merging while exporting, subtitle zero in TTML +- YouTube VR meta-data +- yuv4mpeg format read/write +- Extended NHML syntax for properties, reconfiguration, subsamples and sample auxiliary data + +## Encryption +- Multi-key per sample encryption +- HLS full segment encryption and decryption +- Per-segment or per-period key roll +- Master/leaf key schemes + +## Misc +- Improved HTTP rate limiter and chunk-transfer rate estimator +- Cleanup of mod-dirs and js-dirs usage, JS filters can now be included in the default available filters +- Support for windows long path +- Moved to latest QuickJS (2021-03-27) +- Added QJS-libc modules, support for exec/waitpid/kill and Workers on most platforms +- Support for FFmpeg 4.4 + +- many bug fixes, improvements and security patches + + +# 10/09/2020: GPAC 1.0.1 +This release fixes build and installation issues in 1.0.0, as well as various bugs introduced during the migration to the filters architecture. + +It also adds several small features: +- better ttml import +- better support for MPEGH audio +- support fur DASH UTCTiming +- manifest generation from pre-fragmented DASH/HLS mp4 +- speed optimization in isobmf reading (normal and fragmented) +- improved JS API for the filter session +- core tools exposed as JS module (file io, bitstream, etc ...) +- android fixes + +# 16/06/2020: GPAC 1.0 + - Complete rewrite of GPAC streaming core: + * addition of a filter-based architecture, used by MP4Client and MP4Box. + * moving all decoders and demuxer plugins of MP4Client and most of MP4Box import/export code as filters for this new architecture, + * moving DASH/HLS segmenter to a filter + * moving MP4Client compositor and most of the GF_Terminal internals to a filter + * addition of a new application gpac, whose only purpose is to create and run filter chains + * removal of MP42TS and DashCast applications since these functionalities are provided by gpac + * deprecation of some features (widget management, MSE draft implementation for SVG media, UPnP, TEMI player support). +- Profile system allowing to override through a static file default options of all filters and libgpac core +- Alias system for gpac app to simplify your command lines +- Enhanced DASHer: + * Support for HLS and dual HLS / DASH generation + * Support for any input + * True low-latency mode for DASH + * Support for multiple periods + * Support for other segment formats (raw, mkv, webm currently tested) +- Input and outputs + * Generic pipe, TCP, UDP, and Unix Domain socket input and output + * RTSP server output + * HTTP output (client and server), supporting low latency DASH access + * Ad-hoc stream format called GSF to allow serialization to file, pipe or socket of a session (for distributed filter chains), supporting AES-128 CBC encryption. +- Raw audio (PCM) and video (RGB, YUV) reframers and exporters +- 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 +- Support for QuickJS (ES2002) and bindings for: + * Complete filter API + * GPAC software rasterizer (EVG) + * WebGL 1.0 Core + * XmlHttpRequest and uDOM APIs + * Storage +- Inspect and analyze filter +- MPEG-2 TS splitter +- Video cropper filter with zero-copy mode +- Video flip filter +- Source concatenator filter +- Simple audio and video output filters +- Experimental audio and video rewinder filter +- Encryption + * On-the-fly encryption and decryption, now available as filters + * Segment-based encryption and decryption +- ISOBMFF + * box customization + * Better QT support, prores parsing and dumping + * Support for raw media (QT style or ISOBMFF for audio) + * Simplify HEIF batch conversion through item to track mapping + * Reading from pipes (fragmented or progressive files) + * Writing to packets rather than files + * Fast interleaved file creation mode with less storage requirements +- FileIO wrapper for cases where files are not stored in a file system known by GPAC +- Testing and Documentation + * Live doc generation (man and wiki) + * Improved coverage + * Split test suite as dedicated repo + * Moved all resource to https://wiki.gpac.io + * Started howto pages on wiki + * Many bug fixes + +27/06/2019: GPAC 0.8.0 + - General + * Many security fixes (static compile and fuzzing through AFL, always ongoing). + * Many bugs fixes + * Added :ncl option in log levels to disable color logs + * More tests and coverage + - File Formats + * Better support of QTFF / ProRes files + * Support for AV1 + - import and export + - source formats: OBUs (Section 5), IVF and AnnexB + - AV1 in HEIF + * Support for color (nclc, nclx and ICC profiles) in HEIF and ISOBMFF + * Support for HDR (mdcv, clli) info in HEIF and ISOBMFF + * Support for alpha in HEIF + * Support for enforcing pasp presence even for 1:1 ratios + * HEVC temporal sublayer split in MP4Box + * Allow meta storage before mdat for meta-only files (heif and co) + * Added option to keep AU delimiter in isobmff samples + * Support for opus import + * Support for pixi and ccst in HEIF + * DolbyVision 'dvcC' and partial 'dvhe' boxes for HDR + * Support for VP9 import and playback + * Sample dependency in avc and hevc importers, and track thinner for non-refs images + * Support for audio_roll signaling + * New audio import mode to control AudioSampleEntry creation (v0, v1 ISOBMFF, v1 QTFF) + * xHE-AAC import with detection of sync samples + * Added support for MPEG-H 3D audio boxes (no import yet) + * Handle Vobsub empty SPU packets + * Added auxv and pict support + - Common Encryption + * Fully compliant CENC supporting cenc, cens, cbc1 and cbcs + * CENC for AV1 + * Improved DASH+CENC support, pssh in MPD + * ForceClear mode for CENC to skip encryption without sample groups + * Made senc in movie fragments always stored before truns + * Added default values handling for cbcs and possibility to set protection system per track + * Compatibility with OpenSSL 1.1.x + - Streaming and Adaptive Streaming + * Support for ATSC3.0 both US and Korean versions ! + * Support for for live splices (xlink period insertions) in DASH client + * Automatic period continuity in DASH when no codec change between periods + * Added DASH cue-base segmentation (XML based) and -dsap option to generate cue files from source + * Support for BBA-0 and BOLA implementations + * Write fragment defaults in trex even when not using them + * Support for simple ssix for keyframe data byterange at the start of a segment + * Moved segment template at AdaptationSet level if only one representation + * Changed default bsmode in dasher if single input file + * Added init-seg-ext option + * Added -mvex-after-traks option to MP4Box when dashing for CMAF + * Added segmentation option to insert a tfdt per traf + * Added -closest mode for DASH segmentation + * Added -bound option to use audio segmenting method for video + * Renamed -dash-run-for to -run-for + * Added '=' in dash templates + * Improved bandwith estimation when using HTTP 1.1 chunk transfer + * Add option to force moof base offsets + - MP4Box + * Added -catpl to concatenate from playlist in MP4Box + * Added options to set movie timescale at import and dash time + * Added mpd rip option and top-level box compressor in MP4Box + * Made -dts skip timing check and added -dtsc for that + * Made force-cat option more agressive + * Support for MovieFragmentRandomAccess using -mfra option + * Added -dtsx to dump timing without offset + * Added -dnalc opt for nal CRC dump + * Added chunk extraction up to time until end + * ISOBMFF single track import now removes references by default + - Decoders + * Updated ffmpeg to 4.0.2 + * Moved to openHEVC 3.0 API + * Added nvdec support (windows, linux) with reuse of decoder context for tiled VR + * Added HEVC support to mediacodec on android + * AV1 playback through ffmpeg + * Opus playback through ffmpeg + - 3D, VR and 360 + * Added vrhud for multiviewpoint 360 + * Added forced visibility mode of tiles in VR + * Added tile visibility debug mode + * Added forced stereo output for openhevc + * Disable face nav if mouse grabbed + * Added simple face tracking vr navigation based on udp commands + * Added PSVR support + * Added mouse move emulation at window border to force sphere rotation when inactive + * Changed tile visibility algo to sample points in mesh + - Players (Mobile and Desktop) + * Added about extension + * Added multiple audio objects in dynamic scene + * Added addon splicing of main content + * Added mosaic://v1:.:vN url support + * Added gaze simulation through mouse and gaze-sphere visibility test + - Subtitles + * Allow * as argument of -srt|ttxt to dump all possible tracks (#925) + * Improved support for WebVTT import + * Improved support for WebVTT DASHing/fragmentation + - Misc + * OSX install now done through PKG and modify PATH env in/etc/paths + * Added initial PMT version and disc marker to TS muxer + * Moved dektec output to matrix API, added SDI clipping + * Added temi periodic toggle and manual toggle in MP42TS + +# 26/04/2017: GPAC 0.7.1 + - Full changelog at https://github.com/gpac/gpac/wiki/GPAC-0.7.0 + - Many security fixes (static compile and fuzzing through [AFL](http://lcamtuf.coredump.cx/afl/), always ongoing) + - Colorized log. + - Fix pkg-config Private.libs. + - Changed default audio volume to 100% instead of 75%. + - Expose more experimental options through the [documentation](http://htmlpreview.github.io/?https://github.com/gpac/gpac/blob/master/doc/configuration.html). + - Improved GLES renderer on mobile platforms + - YUV422 and YUV444 8 and 10 bit support in GLES renderer + - Improvements on AVI dump. + - SAT>IP modified RTSP client. + - Added L-HEVC File Format support (SHVC/MV-HEVC tracks and HEVC Tile Tracks). + - Added MPEG IFF (image File Format) support. + - Range extension support for AVC and HEVC. + - SHVC and MV-HEVC importers and playback: moved to final spec version (SHM6+). + - Support of HLS with fragmented MP4 playback. + - APIs: gf_mpd_() functions and new segmenter API. + - Improved alternate groups. + - More support for PIFF PSEC and Smooth Streaming ( file format & playback). + - DASH client: pluggable algorithms + improvements with scalable contents. + - The counter source from the DASH sequences added to the public content. + - HLS and DASH playback minor fixes. + - Cleanup of DASH client logs. + - Added support for DASH SRD in 360 for independent videos videos (NxM partial spheres) [more](https://gpac.wp.imt.fr/2016/05/25/srd/) + - Added support for DASH SRD in 360 for HEV Ctiled videos (NxM tiles on one sphere) - [checkout tuto](https://gpac.wp.imt.fr/2017/02/01/hevc-tile-based-adaptation-guide/) + - Apple VideoToolBox hardware decoding support for OSX and iOS for AVC|H264. + - Android hardware decoding hardware acceleration for AVC|H264 (HEVC on its way). + - Android build is based on Android Studio. + - Android: new File Manager. + - Import of TTML via NHML according to MPEG-4 part 30 improved. + +# 19/02/2016: GPAC 0.6.0 + Many things added, improved and fixed - see https://github.com/gpac/gpac/releases/tag/v0.6.0 + +# 25/05/2012: GPAC 0.5.0 + - MPEG-DASH and Apple HLS support in GPAC Clients + - MPEG-DASH segmenter for ISO files and MPEG-2 TS in MP4Box + - MP42TS generator now supports HLS output + - Support for MPEG-U and W3C widgets + - UPnP and DLNA support in the player through Platinum libraries, interfaced in JavaScript + - Better support for AVC and SVC muxing in MP4 + - Support for OpenSVC decoder + - Stereo and Multi-view renderer for auto-stereoscopic screens + - iOS and Android support (but Symbian support has been dropped) + - Camera input through "camera://default" URLs on Windows, OSX 32bit, Linux V4L v1 and Android + - experimental audio filters + - Better T-DMB support + - experimental DVB-MPE and DSM-CC support + - BIFS ExtendedCore2D profile support + - more work on GUI + - and many many fixes and improvements in players and MP4Box + +# 02/12/08: GPAC 0.4.5 + - Support for AC3 in ISO Media, AC3 decoder (liba52) RTP hinting + - Support for MediaAccessEvent (spec still under development ??) + - Added support for user extensions (global class only) in JS through modules - cf gpac_js for sample code. + - Initial support for 3GPP DIMS (file creation and dumping + playback) + * Currently only SVG as overlay is supported for DIMS files, we are not sure about the specification on this topic + * RTP hinting /playback is not supported yet + - Support for SVG foreignObject - all GPAC supported content can be played in a foreign object + - Added automatic switch between 2D and 3D context for VRML/BIFS Inline node + - Basic support for DCCI queries: + * the DCCI object is exposed in scripts as 'DCCIRoot' + * the related document can be specified in GPAC configuration file in the [General] section with the 'EnvironmentFile' key + pointing to an XML file to use as the ontology context + * DCCI's propertyFilters are NOT supported yet. + - Added support for YCbCr OpenGL texturing (only tested with GL_MESA_ycbcr_texture) + - Fixed bugs in OSS audio (100% CPU usage when no audio to play) and ALSA (broken playback) + - Initial support for YUV overlays on Win32 and Linux (Needs XVideo) + * only one overlay at the moment + * Vector graphics /images can be drawn above the overlay if the video output supports dest color keying + - Changed 3D mesh object to be slightly more compact (colors and normal) + - Unified focus model for SVG and MPEG-4. MPEG-4 focus can only be used through tab/-tab (no north/east/.. navigation) + * The MPEG-4 focus is based on the groups (2D/3D) presenting interactive features (sensors) + * keyboard mapping for sensors is: + isOver triggered when focus moves in(isOver=TRUE) and out (isOver=FALSE) + isActive triggered when ENTER key is pressed (isActive=TRUE) and released (isActive=TRUE) + direction keys with or without SHIFT to simulate mouse move + * Layout node is considered as a sensor when scroll_rate is 0, so that the user can scroll the layout + * The focus is by default on the UA (GPAC), not on the content. + - Added support for inplace text edition in SVGT and in MPEG4 + * support for clipboard paste at current caret pos (win32, wxWidgets - no WinCE/symbian yet) + * VRML/MPEG4/X3D: this is not standard, enabled with "EDITABLE" present in text->fontStyle->styles. + Added because StringSensor does not allow for in-place editing of the string + - Added support for text selection in MPEG4 (non standard) / SVGT with clipboard copy (win32, wxWidgets - no copy for text+tspan - no WinCE/symbian yet) + - Better support for SVG , added support for SVG->svg linking (ElementID and svgView) and many svg fixes + - Added support for MPEG-4 over MPEG-2 TS in mp42ts (MPEG-4 SL in MPEG-2 PES) + - Added support for MPEG-4 AAC/LOAS/LATM/ over MPEG-2 TS for import in ISO Media and in the player. + Only basic DVB config is supported for LATM + - Fixed support for AVC/H264 over MPEG-2 TS import in ISO Media and in the player. Only one PPS/SPS is allowed + - Added support for offscreen GL rendering on symbian using PBuffers + - SVG fonts should now be quite stable, but does not support kerning + - Moved all Font modules to the new glyph-based architecture + - Moved SVG text to new glyph-based renderer, added support for OpenGL drawing and textured mode + - Added support for AVC/H264 in mp4_streamer + - Added support of MPEG-4 systems over MPEG-2 + - Added extended version of "-node" switch in MP4Box, to get the list of possible nodes. For insatnce, "MP4Box -node Anchor.children" + - New glyph-based font manager + - Misc fixes in SVG rendering with OpenGL + - Renderer modules have been removed from GPAC. The new integrated 2D+3D renderer has been moved to libgpac and is now + called "compositor". Some renaming has been done in the process to clarify a bit some functions. + NOTE: EVC3 and MSVC8 projects have not been updated yet + - added support for TrackSelection (tsel) - (c) 2007 ENST & RresonateMP4 - cf MP4Box -h general + - added support for PASP in ISO files + - misc fixes in render_full module + - committed patch fixing VobSub extraction bug + - added support for http playback in ffmpeg demuxer + +# 31/05/07: GPAC 0.4.4 + - Added support for XMLHttpRequest for both VRML/MPEG-4 and SVG. All methods should be supported, but only GET/HEAD have been tested + - Added a basic subset of DOM Core for xml doc (XMLHttpRequest, SVG) and moved the uDOM implementation in it. + - Added support for SVG focus & navigation in 2D renderer + - new SVG scene graph implementation with much lower memory usage + - Updated LASeR to new SVG implementation and fixed many bugs in codec + current LASeR binary version should now be in sync with the latest spec (IS+COR, AMD still to be done) + - Added support for some LASeR tools: + - conditional + - clipBegin/clipEnd and syncReference on audio/video elements + - WARNING: + * other LASeR (non-SVG) v1 elements and all v2 elements are NOT supported + * LASeR save/restore and sendEvents are NOT supported + * SAF handling is likely not conformant yet + - Moved default SVG implementation to dynamic attribute allocation + - ALSA output module + - DVB support for Linux - connection URL is dvb://ChannelName, cf doc/configuration.html + - Moved ISMA decryption to run-time module for basic DRM tests + - Added support for NAT Keep Alive (RTP streaming only) + - Two new test apps: + mp4_streamer: RTP unicaster/mulitcaster + mp42ts: MPEG-2 TS sample gateway, can output to file or to RTP. Supports ISO file and SDPs (RTP only, no RTSP) as input. + - Changed default behavior in MP4Box when adding AAC-SBR with explicit signaling: full SBR Samplerate is now used in media track. + - Added JP2 and MJP2 support for mux/demux and playback - latest version of openjpeg (1.1) used for decoding (Win32 only). + Win32 user: if you don't want to install openjpeg, remove GPAC_HAS_JP2 from img_in project settings + - large code rewrite in scenegraph and 2D renderer to lower memory usage + - added support for OMA DRM2 packaging in MP4Box (doc to come) + - Symbian OS now supported. Build instructions are in doc/INSTALL.symbian. Network is NOT working on symbian yet + - Major speed improvement of XML SAX parser + - added unthreaded mode in MP4Client (-no-thread switch) to test behavior on symbian + - added fullscreen startup mode in MP4Client (-fs switch) + - better handling of iTunes tags in MP4Box (set/get) and in players (title display) + - added support for multiple RTSP sessions in an SDP description + - added support for faked broadcast mode in SDP (forbids any timeline control by player except initial play/final stop) + syntax is (in media SDP section, per stream): a=gpac-broadcast:1 + - added ffmpeg support for WinCE devices (ffmpeg for WinCE is available in gpac_extra_libs) + - added GPAC log system + + - moved key/mouse event subsystem to DOM3 model + - Fixed handling of QT V1 and V2 audio descriptions + - Misc source code reorganization for RTP (depacketizers now in libgpac, no longer in plugins) + - Fixed support for IPV6 and for multicast (IPV4 and IPV6). Some issues remain with IPV6 on Win32 XP, so IPV6 is disabled by default on Win32 + - Fixed handling of negative delays when adding/appending media tracks with MP4Box + - moved ffmpeg headers to latest CVS version (05/01/2007). + - fixes in MPEG2 TS import and demuxer + - added detection of FPS for raw AVC import when present + - fixed a display freezing bug in osmo4 for PocketPC 2003. + - added support for svg animation and svg use with external resources + - cleanup of MPEG-4 RemoteOD + support for segment identifiers at scene level + - some workarounds for iPod file producing (special UUID needed) (-ipod option, auto on for .m4a and .m4v extensions) + - added windowless mode on Win32: transparent background color is defined in the config file ( [Rendering] ColorKey ) + - major speed improvements in MPEG-4/VRML scripts and SVG uDOM regarding node creation + - and a lot more bug fixes... + +# 21/07/06: GPAC 0.4.2 + - commit of GPAX (GPAC ActiveX) - controller only works in IE and ActiveX control tester for now. + - API changes to Osmozilla to keep in sync with GPAX. Sample html file can be found in applications/GPAX. + - both plugin now support browser navigation (ie link to html within MPEG-4 content) + - Plugins can be used to modify the presentation from a parent HTML doc + - check sample files in regression tests for more details + - iTune tagging support, (patch from Andrew Voznytsa with slight modif). Tagging can be done with MP4Box -itags option. The tags are passed in a single string, separated by ':', formatted as 'tag_name=tag_value'. Supported tags names are: album, artist, comment, compilation, composer, created, disk, encoder, genre, name, tempo, track, tracknum, writer. + NOTE: to make sure you mp4 is importable on an iPod, you must: + - use .m4a extension + - specify the right brands: MP4Box -brand "M4A " -ab mp42 + This process is automated in MP4Box for all file with extension .m4a + - added MPEG-1/2 raw importing (extensions: .m1v and .m2v). + - cleanup of all Makefiles: + no more recursive makes for libgpac + gpac can now be compiled outside the main source tree with gcc, eg $ ~/cvs/test>../gpac/configure + - Support for VobSub import and export (.idx) thanks to a great patch from Falco ! + - initial version of MPEG-2 TS demuxer (MP4Box and client plugin) + program-based import for MPEG-2 TS streams (MP4Box -add file.ts#program=ProgName). + - initial IPV6 support + - MP42AVI is now deprecated. MP4Client can now be used to produce uncompressed bmp/raw/AVI, dumping the complete presentattion rather than just BIFS scenes (audio is currently not extracted). Usage: + MP4Client -bmp 1-2.5-3 file.mp4 take screenshots of file.mp4 aty T=1, 2.5 and 3 seconds + MP4Client -avi -fps 15.0 -size 176x144 file.mp4 produces an uncompressed AVI of resolution 176x144, framerate 15 + MP4Client -avi 4-10 file.mp4 produces an uncompressed AVI of the scene between 4 and 10 seconds + Check MP4Client man page for more details. + - added support for major brand versioning in MP4Box: "-brand GPAC:2" will set the major brand to GPAC, with a version of 2. + - regression tests (.bt and .x3dv) are now part of GPAC source tree + changed all audio and video media in regression tests. + - added 2D/3D selection param for osmozilla: use3d="true" or use3d="false" to force renderer used. + - added support for importing AMR/SMV/EVRC file missing their magic number + - moved all language handling to both ISO 639-1 (2 char code) and 639-2 (3 char code), as 639-1 is used in SVG. + - removed old XML parser, all parsing now relies on a new GPAC SAX parser (avoid dependency on libxml when unneeded). + - support for progressive loading support for XMT and X3D files. + - new svg_in module using gpac sax parser (supports progressive loading too) + - cf configuration.html or man gpac for more info on progressive loading control + - added 'define' support in BT - to use it just do: + #define symbol blab labl a + and reuse the symbol in the BT text. This may be quite buggy, but it can be useful + - support for Scene Carousel in hinters, core and rtp reassembler. Currently only BIFS and BIFS+AV can be use the scene carousel, carouseling of + static data (eg images) is not supported. The OD data must be embedded in the IOD a la ISMA. + - added FPS and size info dumping for MPEG and AVI file import (MP4Box -info file.mpg) + - improved 3D renderer while checking the X3D conformance suite + - changed MPEG-4 SP -> AVI to add VOSH before each I-frame + - Experimental support for LASeR (encoder, decoder and decoder module). + - Far from behing complete or usable at this time, binary syntax not 100% safe and COR to standard is in edition stage... + - LASeR RAP generation in MP4Box + - added SAF (LASeR Simple Aggregation Format) support: mux and demux (MP4Box) + SAF input plugin. + Basic LASeR usage: + encoding: MP4Box -mp4 file.svg, MP4Box -saf file.svg + decoding: MP4Box -svg file.mp4, MP4Box -xsr file.mp4 (dumps to LASeRML format) + - added patch from FT R&D for simple anim mask encoding needed for FAP/BAP streams on FDT/BDP nodes. + - added patch for drift-controled interleaving (interleaves while trying to keep chunk synchronized). This is now the storage mode of MP4Box, old interleaving is possible with -old-inter option. + - added support for tight interleaving without hinting in MP4Box. + - added support for delayed concatenation (-cat file.mp4:delay=2000 test.mp4) + - added "-name" option to MP4Box (track import and general cmd line opt) for setting the track handler name + - More SVG improvements and features: + - support for SVG scripting (ecmascript through SpiderMonkey) - a good subset of microDOM is supported (!! presentation traits are missing). + - support for SVG events (DOM) + - support for SVG scene dumping + - SVG Tiny 1.2 gradients + gradient matrix (1.1 feature) + - SVG.preserveAspectRatio support + - system color paint + - basic conditional processing (switch) + - SMIL anim events (begin, end, restart) + - added PAR modification support to MP4Box (import time and file based) + - improved precision of IsoMedia file splitting + - added NHML import/export. NHML is an XML representation of the NHNT file, with add-ons and a more flexible way of integrating media. Doc to come on web site + - clean-up of 2D direct rendering mode: + * no more bounds tracking for less memory usage + * automatically mode switch to direct rendering when using slide navigation (pan&zoom) + - clean-up of soft raster: + * removed all bezier curbs handling, let gpac core handle those + * paths are now always flatten to reduce rastering times + * misc optimizations of scanline converter (no more Y-sorting, only X-sorting used) + - speed improvements in BT/WRL loader + - updated makefiles for compilation under GPE + - made FreeType plugin log unknown fonts in general cfg file to speed up font selection. + - changed module naming - all modules are now prefixed with "gm_", and module is loaded/unloaded at run-time + - changed isomedia file open API for better support of temporary directories + - simplified 2D blitter for non DirectX output: no more in-middle surface used, direct stretch, blit and yuv conversion to back buffer + - added software and hardware support for MaterialKey in 2D renderer + - Support for SP2003 and PPC2003 + - added all project files for evc4 + - added osmophone to gpac/applications, demo player for Smartphone devices (tested on SPV C500 and PocketPC) + - support for GDI drawing on WinCE to avoid weird menu behaviour in windowed mode + - added support for OpenGL-ES in 3D renderer, in DX and in GAPI plugins + * tested with Hybrid Graphics and Vincent3D implementations + * Klimt could compile but no decent results + * Result tested on PocketPC/SmartPhone 2003 and regular windows + - added LASeR XML to SVG loader + - improved SRT -> 3GPP convertion - now accepts any number of , , tags at random places. + + - fixed AVC/H264 parsing (pic size detection on interleaved stream and cropping were broken) + - fixes on X3D scene parsing and dumping + - fixed mp4 root OD creating when fragmenting file (PLs were not copied over) + - fixed bug in large MP4 file (> 4GB) writing (chunk offset table was corrupted) + - fixes in InputSensor handling. + - fixes in XMT proto+script parsing + - fixed ISOMedia track duration in case of ctts table + - fixed PAR issus at import time setting wrong track sizes for visual media. + - fixed handling of 64-bit timing in GPAC (clients and MP4Box). Impacts on a few APIs (libisomedia, scene manager and terminal). + - fixed bugs in BIFS RAP generation (BT and XMT-A loaders) + - fixed support for node insert and delete in laser + - fixed AVC import bug with multiple PPSs per stream + - fixed stts bug in file concatenation (first inserted sample had a wrong duration) + - fixed v5 and v7 MPEG-4 node templates + - fixed ttxt extraction (fontID was missing in styles) + - fixed MP4Box -add file1 -cat file2 use case (broken CTTS compute) + - fixed 3GP brand when using AVC (3GP6 shall be used) + - fixed misc issues with text import/info display on track sizes and 3GP text track importing. + - fixed bug in gradient raster (spread/pad/repeat modes were broken) + - fixed bevel stroking bug and many SVG rendering ones + - fixed bug in MP4Box track concatenation of MPEG-4 user streams + - fixed bug in meta items storage (existing items were corrupted on file rewrite) + - fixed splitx option to support end times larger than file duration + - fixed globalQP context loading issues + - fixed display bug when switching between MPEG-4 and SVG scenes + - fixed absolute URLs handling on WinCE devices + - fixed redraw bug in 2D renderer for simple video drawing (background was always repaint regardless of video transparency) + - fixed UTF8 vs Win-CP text handling (BT and XMT parsers, subs->bifs and subs->ttxt converters) + - fixed bug in unknown stsd boxes handling + - fixes for gpac compilation on 64 bits platforms + - fixed bugs related to cache in file downloader + +# 03/08/05: GPAC 0.4.0 RC2 + - fixed Invalid versioning of previous release (was still 0.4.0-DEV...) + - fixed MinGW compilation + - fixed MP4Box handling of full MSDOS paths (C:\) + - fixed GCC 4 compilation issues + - added MacOS X (DARWIN) compilation patches + - fixed OSS audio compilation (seems to perform perfectly on 50% of systems tested and dramatically fail on the rest...) + - fixed MP4A-LATM support at client side + - fixed X11 module in embedded mode (should work well with both Osmo4/wx and Osmozilla) + +# 28/07/05: GPAC 0.4.0 release + ** GPAC is now licensed under LGPL. ** + Massive code rewrite and repository reorganization in order to comply to some base coding style (cf gpac/doc/CODING_STYLE) have taken place + since previous release. Documentation is still a work in progress. + APIs are not backward compatible, but should now be in a frozen state/spelling for the most common tools (utils, MPEG-4 OD, IsoMedia and terminal APIs) + + - Fixed FAAD multichannel support with latest FAAD CVS version (bug due to FAAD inner channel creation). Latest version should + support both multichannel and AAC radios. + - added support for normal drawing (for debug purposes) in 3D renderer + - adedd support for single instance of Osmo4/w32 + - changed audio configuration options to "Number of buffers" and "TotalDuration" + - fixed old bug in audio renderer screwing up the audio from time to time + - moved ffmpeg to latest CVS version (25/07/05). THIS VERSION IS NO LONGER COMPATIBLE with previous ones used in GPAC, update + your binaries!! Also updated ffmpeg plugin to new ffmpeg API + - moved all fullscreen handling to dedicated windows, refinement of wxOsmo4. + - more work on x11 plugin (events handling, 3D support) - THIS IS EXPERIMENTAL AT THIS RELEASE LEVEL, CHECKOUT CVS FOR BETTER SUPPORT... + - fixed anamorphic video handling in GPAC, both playing (2D and 3D) and MP4Box parsers (only done for MPEG-4 Visual, not AVC). Video is + now only rescaled at blit time + - changed ';' separators to ':' seprators in MP4Box (meta options and track import options) for linux prompt compatibility. + - misc fixes in tx3g track import, tx3g bt dump and couple of issues introduced in 0.3.0->0.4.0 migration + - misc cleanup of GF_VideoOutput interface. + - started doc manager using doxygen (doxyfile added in gpac/doc). APIs may be slightly reworked during the documentation phase. + Documentation will only be produced for libgpac, eg all exported headers in gpac/include/gpac + Development APIs documentation (gpac/include/gpac/internal) will come later. + - cleaned up IsoMedia reading for more efficient and more reliable parsing + - misc fixes in MP4Box option parsing, in RTP multicast setup + - Osmozilla should now be much more stable on Win32 - Linux version to come. + + +# 20/06/05: GPAC 0.3.0 release + - fixed bug in 4GB file writing in interleaving mode + - fixed bug in absolute path usage in MP4Box + - added -cat file*.mpg syntax support + - added support for mass encryption in ISMACrypt drm_file (trackID="*" means all tracks get encrypted with the desired key) + - fixed compilation of wxOsmo4 with wxWidgets+Unicode + - fixed bug in 3GPP text layout when not fully contained in video + - added mapping between OD.ESD.GF_Language and MP4.MediaHeader.Language (BT/XMT <-> MP4) + - fixed bug in SDP playback without RTSP session attached (live casts) + - added language and delay selection at import time (cf MP4Box -h import) + - made -lang able to change all tracks languages by default !erase language specified at import time! + - added -lang option to MP4Box to change track language + - added -delay option to MP4Box to change initil media delay - ALTHOUGH 100% STANDARD, THIS MAY NOT BE SUPPORTED BY SOME PLAYERS + - updated configure for opengl disabling and fixed-point configuration + - misc fixes in ffmpeg decoder and MP4Box ISMA for PAR - this is disabled by default due to some unsolved crashes, cf gpac/doc/configuration.html or man gpac.s + - misc updates in MP4Box meta handling for item extraction. + - fixed MPEG-2 aac importing and info dumping + - 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 + - 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 + - fixed bt/xmt to MP4 encoding for IOD with single BIFS ESD.URLString set + - fixed SVG outline handling and SVG/SMIL anim on appearance/geometry + - fixed svg_loader compilation on winCE (ARM binaries and include to follow in next release of gpac_extra_libs) + - commit of SVG fixed-point version + - Fixed MP4Box spliter crash introduced by MetaBox support. + - Merged MP21 and MP4 handling, added meta support in MP4Box (creation/extraction - cf MP4Box -h meta) + + FROM THIS DAY ON, ALL WORK ADDED TO GPAC IS COPYLEFT ENST + +# 15/05/05: + - Updated makefiles and configure for MacOSX support + - commit of GPAC fixed-point version - may not be completely stable yet :) + - changed all project files location for soon-to-come EVC4 and Visual .NET support + - changed 2D path object to directly support cubic and quadratic bezier (outliner do support them too), inspired from FreeType. + - MSVC Users: moved to wxWidgets 2.6.0 !! + - more fixes on CTTS in H264/AVC importer (p-frame reorder and some I/B sequences with neg POC) + - fixed last sample duration in mp4 for BIFS/OD/Text + - completely rewrote AVC CTS computing to handle b-refs properly + - fixed improper instanciation of externProto with VRML files not declaring default field values in externProtos + - reworked file splitter to support duration and size based splittings + - fixes in file concatanation + - support for "self" parameter in Anchor to replace only inline scenes on anchors + - fixed H263 raw importer (frame boundary detection and frame size) - changed default H263 frame rate to 15 fps (more used than 25 in 3GP). + - added support for new chaper format: CHAPTERX=HH:MM:SS[:ms or .ms] and CHAPTERXNAME=string + - committed AVC parser patch from bobolobo (SEI parsing and recovery points) + - added support for nero chapters in MP4 ('chpl' box), in MP4Box (-chap file.chp) and selection in players (chapters mapped to MPEG-4 SegmentDescriptors). + note regarding chp files: not sure about file syntax, currently support for (one chapter entry per line): + ZoomPlayer syntax: AddChapter (-fps for import framerate selection), AddChapterBySecond and AddChapterByTime + Regular time code … la SRT: HH:MM:SS[:ms or .ms] [Chapter Name] + SMPTE time code: HH:MM:SS;fr/fps [Chapter Name] - if fps is omitted, use '-fps' if specified or 25 fps default. + + - fixed avi packed bitstream flag removal bug with beta versions of DivX. + - fixed bugs in AVC CTTS compute + - wxOsmo4 now single-windowed on win32 with DirectX output driver (SDL one buggy). + - fixed bugs in media exporter for non-MPEG4 streams (AMR & co). + - added '-dts' option to MP4Box - dumps samp num, DTS and CTS for all tracks and performs sanity check of CTS offsets. + - added '-split time_sec' option to MP4Box - splits a simple AV IsoMedia file (no systems, only ONE VIDEO stream supported) into several movies of time_sec duration. + - added '-cat' option to MP4Box - concatenates several input file (IsoMedia or not) to a single IsoMedia file. Same options as -add. + ** Streams with different sample descriptions are imported as distinct tracks ** + - fixed bugs in OD and IPMPX parsers (ipmpDescrPtr and IPMP_ToolID) + +# 30/03/05: GPAC 0.2.4 release + - Added TeXML import (QT XML format for 3GPP text) + - Added SRT extraction for text tracks. + - Added SUB subtitles support (SUB->3GPP text, SUB->BIFS) + - New lifting for MP4Box - nicer progress notifications and help screens. + - changed MP4Box to OVERRIDE FILES BY DEFAULT if '-out' is not specified. + - changed MP4Box to use 0.5sec interleaving storage by default on all operations. + - added file associations for Osmo4/Win32 (not for wx version) + - added volume control and playlist restoring (Osmo4_w32 and Osmo4_wx) as well as fullscreen restoring when changing file through playlist + - added '-unhint' option in MP4Box to remove all hinting from file. + - many rewrite and fixes in WinCE version (Osmo4 and gapi), much more stable and usable player. + - changed SBR import in MP4Box: -sbr for backward compatible HE-AAC (implicit) signaling, -sbrx for non-backward compatible (explicit) signaling. + - added bandwidth signaling in SDP (b=AS:X) for some players compatibility + - added H263 and MPEG4 Visual(CMP) importers in MP4Box + - added extra SDP lines support in MP4Box - PLEASE refer to rfc2327 before complaining :) + - added hinters for QCELP (RFC 2658) and EVRC/SMV (RFC 3558). Not tested (no decoder available) - QCELP hinter works with QT6.5. + - added QCP importer/export in MP4Box and raw EVRC and SMV importers (not tested) + - added support for 3GPP2 extensions in IsoMedia (EVRC, QCELP and SMV config) + - added basic media cache for stream recording (rtp, internet radios). Only MP4 cache available, but other formats possible (plugin) + - added raw samples export and avi track to raw export in MP4Box + - More H264: import/export/rtp hinter in MP4Box, rtp reassembler + - Basic ISMACryp support in client (RTP and file) + - ISMACryp hinter in MP4Box + - ISMACryp support in MP4Box - cf "MP4Box -h crypt" for more details. Selective encryption (sample-based) is supported but not key switching (only one key at this time). + - added smaller version of libmcrypt to M4Systems library for ISMA AES-128 CTR (but kept other algos for future IPMPX usage) + - added simple converter from cubic QTVR to MP4 + - added 3GGP AMR NB and WB (float code) plugin (Fixed Point AMRWB is just so slow ....) + - moved 3GGP AMR NB decoder to 600 release (old lib should still be compatible) + - moved to latest official ffmpeg tarball for AVC B-frames decoding support (older ffmpeg versions should still work despite some API changes) + - added IPMPX base code (read/write and BT/XMT dump/parse) + - added chunk encoding in MP4Box (uses 2 input files, one with base scene, one with BIFS updates only) + - changed OGG muxing in MP4 - one single OTI (0xDD) used for all OGG streams - DSI format same as before. + - Fixed bug in CTTS computing in MPEG4 Video import caused by consecutive I frames. + - fixed MPA hinter RTP agregation mode (was always on). + - misc updates in importers, exporters and ISMACryp APIs + - cleaning of ISMA and 3GP related stuff in m4systems, forced 3GP file to be branded '3GP5' for QT compatibility... + - rewrote AMR hinter for RTP agregation, added maxptime hint param for speech rtp payloads (AMR/QCELP/EVRC/SMV). + - Fixed MediaControl/Inline URL changing + - fixed video RTP timescale in hinters for QT compatibility + - fixed language-based alternate stream selection in ODs + - cleanup of m4_config.h (removing all unneeded includes, misc cleanup for FreeBSD port) + - brought XMT-A in line with spec regarding MFString/MFURL/MFScript (parsers and dumpers) + - added support for "od://" in XMT-A urls according to XMT spec + - fixed raw aac export bug + - fixed DIV5 import issues with PLs + +# 05/01/05: GPAC 0.2.3 release + - new regression tests (X3D and some other features) + - MPEG-4 playback from BT/XMT now supports proper startTime/stopTime behaviour + - found a port of XVID for WinCE/ARM, removed OpenDivX from distribution (seemed no longer maintained). This XviD port is BTW much faster & more reliable than old OpenDivx + - split codec_pack in xvid_dec, img_in and mp3_in plugins + - fixed ffmpeg H264 support + - scene time for VRML/X3D now complies with spec (VRML uses absolute UTC timing since 1970) + - fixed 3D spot/point lighting bug + - added support for raw AMR file format (AMR and AMR-WB, multichannel AMR not supported) in MP4Box (import/extract) and reader plugin. + - added MP4Box '-tmp directory': forces temp file creation to given directory rather than OS-specific temp file storage. + - integrated the very nice mpeg ps parser from MPEG4IP - MP4Box now supports MPEG PS import to MP4 (ATM only one audio, one video, and only MP3 audio) + - added basic MPEG 1/2 Video RTP hinter and reassembler, checked MPEG files hinted to MP4 compatibility with QT player. + - fixed color transform and texturing issues. + - various fixes due to "dynamic scenes" in RTP stack and ESM module + - fixed copy/paste in Osmo4 address bar, mp4 on http issues, MP4Box -add pbs with IsoMedia + - Freezing TTXT format version 1.0 - documentation avail on gpac.sf.net + - Updated MP4Box documentation on gpac.sf.net to 0.2.3 version. + - Changed NetClientPluggin API (got rid of status query, status handled internally in ESM and in plugin if needed) + - Updated evc3 projects, made M4Systems READ/WRITE on CE to enable timedtext parsing (and future streaming cache, who knows ...). + - Added 3GPP text hinting in MP4Box and 3GPP text reassembler in rtp plugin. + - Bug fixes and testing of 3GP timed text with some 3GP files + - GF_InlineScene now supports MediaControl with mediaStartTime and mediaStopTime + - AAC SBR import now uses backward compatible decoder config. + - 3GPP/MPEG4 timed text decoder - supports everything except soft wrap & dynamic highlighting (karaoke) - vertical text not fully tested + - new "ttxt" format (for "timed text"): XML representation of 3GPP/MPEG4 timed text streams - importer (from ttxt and from srt) and dumper (to ttxt only) support. + - MP4Box "-ttxt" option: converts an SRT file to a TTXT one. + - Input plugin for ttxt and srt files. + - Support for "dynamic scenes", eg gpac will now generate on the fly a scene description when none is found, and allows stream selection in GUI. + - Modified Osmo4 & wxOsmo4 GUI: stream selection & subtitle adding + - Changed MP4Box "-import" to "-convert" ("-import" is kept for backward compatibility). + - Added MP4Box -nosys (removes all MPEG-4 systems streams and writes an empty IOD) + - Added MP4Box "-add": adds any supported format to an mp4 file (same input conventions as -convert). Several input can be specified (ex: -add audio1.aac -add sub1.srt video.mp4 -out full_movie.mp4) + - fixed a nasty bug in RTSP stack screwing up all mediaControl & rtsp session. + - 3D NonLinearDeformer (AFX) support (taper, twister and bender) + - Completed X3D geometry set: LineSet, (Indexed)TriangleSet, (Indexed)TriangleStripSet, (Indexed)TriangleFanSet + - Full multichannel audio mixing and resampling (and better audio speed support). Multichannel->stereo conversion (not configurable atm). + - Yet Another Announcement of a stable ffmpeg demuxer - stable with avi & mpeg, still sync issues with some QT files (same issues with ffplay). + - added SBR mode for aac importing ('-sbr' switch) in MP4Box. + - fixed: MP4Box hinters (empty tracks), SDL -> BIFS coord mapping in fullscreen, rounding segmentDescriptor.startTime pbs in seeking with mp4menu + - Added Playlist and brower-like navigation to Osmo4 and wxOsmo4 + - AAC/ADTS file & streaming input (radios) - FAAD decoder moved to aac_in plugin + - ADTS import/extraction in MP4Box + - fixed OD XMT-A for ESD.OCR_ES_ID and ESD.dependsOn_ES_ID (now use IDREFs and not binary IDs) + - integrated ogg lib in gpac (), and added ogg/vorbis and ogg/theora importers to mp4 - THIS IS NOT STANDARD AT ALL. + The current syntax is to put needed headers in the MPEG-4 DecoderSpecificInfo, with the following syntax: + u16 sizeof_header; + char *header_data; + for each header (quite similar to what is found in qtcomponents). Vorbis ObjectTypeIndication is 0xDE, theora ObjectTypeIndication is 0xDF. + Need some more testing before filing a request to mp4ra.org. + Streaming possible as MPEG-4 streams, Vorbis RTP packetizer still to be done. + +# 09/11/04: GPAC 0.2.2 release + - 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). + - 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. + - nicer Osmo4/wxGTK + - shoutcast support in mp3 reader + - Install doc cleanup + - MP4Box "-single" option now work for 3GP files + - MP4Box "-rem TrackID" option to remove a track + - made 3GP hinter produce QT-compatible streams (as usual QT only accepts specific RTP timescales) + - moved M4Systems to a dynamic lib (static is still first built & used for MP4Box) + - moved all scene decoders (bifs, OD and context loader) to real plugins + - moved BT & XMT parsers from stdio to ZLIB io, in order to support GZIPed VRML & X3D (and consequently, BT and XMT-A ;). GPAC CAN NO LONGER COMPILE WITHOUT ZLIB + - massive cleanups in scenegraph & MPEG-4 node naming convention + - !!!scene graph no longer customizable without code hacking!!! + - X3D scene graph generator (decided NOT TO SUPPORT VRML 97 extensions, since they are in X3D and with a different format...) + - updated vrml tools for X3D support (Double precision coords & RGBA colors) + - culling of AABB tree against frustum at draw stage in 3D renderer - greatly speeds up large meshes & terrains rendering + - support for weird cyclic graphs in scenegraph (mainly to support encoding of conditionals). Works with nodes as well, configurable through scenegraph but this can + likely crash a renderer other than GPACs (tested with blaxxun & old GPAC versions, crash works each time:) + - lighting and transformations now properly set in 3D renderer + - improved gravity (ground detection) and added jump in walk mode (right click or 'j') + - support for streams without systems timing knowledge (non MPEG4), eg streams where timing is computed after decoding (and not given by transport layer) for simpler ogg support. + - support for multiple URL in VRML nodes (including remote script) + - '#Viewpoint' and 'URL#Viewpoint' support in Anchor and Inline - #segment_name is not supported and should not be used (not standard IMHO) + - ColorRGBA support for X3D + - texture generator (only "SPHERE-LCOAL" and "COORD" supported for now) + - XML scene dumper now supports X3D (scene dumping API has changed) + - X3D support in BT and XMT-A loader + - updated 3D renderer & ESM for X3D support + - added X3D geometry2D nodes in 3D renderer + - Anchor.activate now works with no children + - plethora of bug fixes + + +# 15/10/04: GPAC 0.2.1 release + - massive fixes in javascript (assignment was only 50% working), new faster, less memory-hungry implementation (and Othello reg test now passes!!) + - CreateVrmlFromScript support for VRML content. + - collision detection + - gravity (not really working) + - Layer3D fully supported (including navigation!) + - InputSensor in non-encoded scenes (bt/xmt) + - aabb tree in 3D meshes for faster picking/collision - needs more cfg options though + - user input in composite texture 2D/3D, and completed TouchSensor (hitNormal & texCoords event out) in 3D renderer + - added full blending support in 3D renderer (depth sorting) + - fixed handling of clipers (layout/form) and layer2D. + - added text change checking in spidermonkey to avoid rebuilding text + - support for usual coords in 2D renderer (window top-left in 0, 0 and decreasing y) for SVG integration + - 3D normal renormalization for GL when scaling (eg now there's a proper lighting) + - 4/3 and 16/9 aspect ratio modes now work as expected + - interactions with fullscreen SDL now happen where they should + - LinearGradient and RadialGradient in 3D renderer + - hide all scene context importing through a single ContextLoader object to get first frame and progress info for swf loading in player + - added Orbit navigation mode (orbit around the user look point) & updated interfaces + - massive cleanup of scene handling & decoders to enable run-time modules for scene decoding. APIs have changed, 2 new ones for scene decoders and media decoders + - finished BT/XMT/WRL scene handler and added support for SWF. + - added toolbar for Osmo4_w32 (_wx one to come) + - added PlanarExtrusion hardcoded proto (2D shape extruded by 2D shape) + - added bt/wrl support in MP4Client, update scene graph accordingly to use a single graph & lots of related fixes in ESM + - added texture text support in 3D renderer (graphics plugin being now loaded by main renderer) + - upgraded avilib to latest version from ogmtools (should support OpenDML 2.0) + - updated configure, Makefiles, & EVC3 project files + - textured text support (eg render to texture then blit) in 2D renderer + - ElevationGrid, Extrusion, VisibilitySensor, LOD support + - Sound support (stereo spatializer only) and reworked generic audio renderer for Sound/Sound2D support + - MPEG-4 V5 animators (not complete) + - reworked hardcoded proto mechanism to comply with VRML recommendations - some doc to come soon + - hardcoded proto PathExtrusion for any 2D shape extrusion except Bitmap and PointSet2D - eg EXTRUDED TEXT SUPPORT :) + - fixed Win32 large file support in GPAC, needs testing of AVI importer... + - cleaned up multichannel support, fixed faad channel reordering + - added background support: sky & ground dome, cube image (6 sides) + - added navigation support (WALK/FLY/EXAMINE/ + extra modes a la blaxxun) and user selection in Osmo4/wxOsmo4 and MP4Client + - added animation between viewpoints + - changed handling of empty boundable stacks according to VRML: no more default backColor, and real 3D scenes (top=Group or Layer3D) no longer may use orthographic projections + - normal smoothing for IFS. + - full lighting support (spot, point and directional) + - fog and billboard support. + - basic AVC support in MP4 (only AVCSampleEntry and child atoms) + - VRML/MPEG-4 interaction sensor support (added ProximitySensor, SphereSensor, PlaneSensor and CylinderSensors). still some work to do on TouchSensor hitNormal & hitTex + - SWF parser updates: PlaceObject2 and defineShape2.NewStyle bugs, support for soundStream + - added user interaction in 3D renderer (ray casting) in both ortho and projection modes + - frustum is now only recomputed when change in size or viewpoint + - viewport/viewpoint selection to osmo4/wxOsmo4 GUIs + - BT parser supports basic VRML (uncompressed .WRL should now be imported fine) + - plethora of bug fixes + +# 02/09/04: GPAC 0.2.0 release + - updated cfg file doc man pages & makefiles + - massive re-arch of renderer(s) to be able to load at run-time either 2D or 3D renderer, instead of static linking + - new 2D and 3D renderers + - re-organized development headers + - added basic file<->plugin association by file extension (cf doc) + - Osmo4/wxWidgets version in Applications/Osmo4_wx + - put zoom & pan back in place in 2D render plugin + - fixed V4Studio (upgrade to wxWidgets 2.5.2) + - MP42AVI supports 3D scenes duming + - updated templates for v5 & v6. WARNING: it seems that some nodes will be removed from v5 (AFX COR) which will invalidate v6 bitstreams + - still more fixes related to scenegraph arch changes + - reworked importers & exporters + - updated new regression test suite (no 3D yet) + - bug fixes in MediaControl, MediaSensor & inline scene control + - more fixes related to scenegraph arch changes + - cleanup of SWF importer: + * shape importing is OK, gradient so-so (wrong matrix mapping), no bitmap fill + * font & text OK. + * sprites should work + * sound work - sound stream not done yet + there will likely not be any new SWF feature supported, ActionScript & buttons are out of scope for GPAC. + - added swf support from MP4Box (cf -swf switch) + - fixed XMT-A syntax of script and proto SF/MFNode field, and BIFS/XMT UTF-16 support + - fixed avg/max rate compute on large files for AVI and MP3 importers + - Background2D texturing uses hw blitter when possible (2D renderer only) + - better inline scene & segment descriptors handling + - encoding of IOD extra descriptors now supported + - configure script fixes + - changed all 3D handling, encapsulating all GL calls in a single file + - moved all primitives in 3D renderer to use a single mesh object (rendering done through vertex arrays) + - added IFS, ILS, Sphere + - optimized AVI importer, fixed vbr mp3 in avi import + - changed scenegraph: + * all nodes now keep track of their parents in order to signal sub-tree changes for frustum culling + * no more "SimulationTime" used in scenegraph, using node modif flags instead + * updated 2D and 3D renderer accordingly + - added support for rectangular textures in GL (most graphic cards support this, at least on win32) + - added frustum culling + - added SFTime->SFString formatting in valuator (current MPEG-4 COR) + - plethora of bug fixes + +# 11/05/04: JLF: dev version 0.1.9 + - changed source code architecture: rendering code is now in a dedicated static link outside libm4systems + - intergated draft 3D renderer: + - Most 2D features have been ported to the new renderer (missing: viewport, colorTransform and gradients) + - no user interaction, no viewpoint, no background, no lighting, NO ETC !!!! + - Box, cylinder, cone working + - texture mapping working (still images and video) + - changed video plugin API accordingly + - changed graphics API: all path handling (building & flatening , outlining & dashing) moved to authoring subproject + - changed font API: no more graphics driver used to get font outline + - cf install documentation to recompile. + - updated all node tables to COMPLETE BIFS (eg all nodes from v1 to v6) + - updated v6 template & code to final amendment version + - fixed inline scene start/stop when modifying inline URL. + - updated TODO :) + - uploaded ultra basic SWF convertor (so that it doesn't get lost on my hard drive:) + - plethora of bug fixes + +# 28/04/04: GPAC Release 0.1.4 + - MP4Box avi import fixes (key frames, VOL removal, n-VOP handling) + - Fixed MP4 and NHNT importers (visual tracks sizes) + - MP4Box avi extractor for visual tracks + - XMT-A parser fixes (unicode, DEF routes, proto namespaces) + - better ExternProto resolving + - added VRML addressing (eg url "protolib#protoName" and "protolib#protoID") + - added pause/resume in Layout scrolling + - fixed rendering of layout nodes (layout, Form and Layer2D) in layout nodes + - fixed port reuse bug: 2 instances of the client may now use RTP streaming (UDP or TCP) at the same time + - fixed video Profile and Levels handling (was old 2000 spec version) + - added -merge option in MP4Box (merges 2 files into a single AV file) + - Fixes in SDL surfaces handling + - better "end of presentation" support + - plethora of bug fixes + +# 18/04/04: JLF: GPAC Release 0.1.3 rc2 + - MP4Box default hinting is now QT compatible - b-frame hinting support + - DecodingBuffer and CompositionMemory occupancey in ODInfo + - layout text splitting (word boundaries only) + - linux install and man pages + - OCR stream support in (authoring/playing) - only tested with MP4. MediaControl supported on OCR + - MP4Box B-frame import now works with data referencing + - fixed many seeking and buffering issues + - multi-framed audio units (needed for most formats understood by ffmpeg demux) + - multiplexed input streams now properly setup + - networking under linux (http and rtp) + - fixed udp streaming on win32/winCE, rtp reordering (was never on), default udp buffer size. + - changed config file for reordering + - changed audio driver interface (retrieve the HW config on setup for SDL) + some audio resync addons + - support for console events detection in MP4Client linux (a la win32 kbhit()) + - anchor (mp4 navigation only) support in MP4Client + - uploaded linux version of extra libs + - added non-system SDL support in GCC configure script (cf --sdl-cfg) + - default font handling in freetype plugin: some text will be diplayed if at least one font is present + - better MPEG-4 video import (B-frame and packed bitstream) + - updated MP4 with full trackHeader (was old v1) and fixed MP4Box compatibility issues with 3ivx splitter + - moved all windowing architecture to the video output plugins (keyboard and mouse events, WM messages) in order to support SDL, moved old input functions to new event model + - modified jconfig.h to support both win32 and MinGW. + - updated V4Studio to work with new event/windowing architecture (work with SDL and DX) + - UDP autoconfig option (if no UDP traffic is received, restart with TCP) + - moved to FAAD2.1 (CVS), fixed faad2 compil on MinGW & winCE + - cleaned up install docs + - added support for SDL with software YUV->RGB and software stretching + - fixed ffmpeg audio support + - added ffmpeg JPEG support + - moved to FreeType 2.1.7 + - moved to libmad 0.15.1b + - MinGW support in all extra libs + - MinGW compilation supported - including DirectX under mingw + - added support for xvid 1.0.0 + - plethora of bug fixes + +# 28/01/04: GPAC Release 0.1.2 + - codec selection in Osmo4 GUI + - 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. + - 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 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 + - 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") + - loader (local/network) for JPEG and PNG files + - loader (local/network) for MP3 files. Would be nice to add SHOUTCAST/ICECAST too... + - support for ESD URLs and non-inline OD URLs (supports any service, local file, download and streaming) + - MP4Box no longer needs SpiderMonkey + - added MatteTexture support (needs testing and debug) + - cleanup of exported headers (install in ) + - move all config info to a single include file and updated some compil macro to avoid touching the file + - persistant associations font name / file name for freetype in GPAC.cfg + - support for conditional in proto creating node interfacing with the proto + - complete XMT-A parser (script, proto, externProto and extendedUpdates) + - edit list support in MP4 reader plugin + - BT->XMT-A and XMT-A->BT in MP4Box + - support for MP4 track start offset in BT/MP4Box (edit Lists) + - BIFS random access point generation in MP4Box (sync shadow or regular RAP) for cartoons + - AMR NB support (payload parsing and 3GP codec plugin) + - plethora of bug fixes + +# 20/11/03: GPAC Release 0.1.1 + - 3GP rtsp streaming support + - GPAC uses latest stable extra libs: faad2 2.0rc3, FreeType 2.1.5 and mad 0.15.0b, and dev version (20031117) of xvid + - support for MediaSegment ranges ("#seg+" and "#seg1-seg2") in MediaControl/MediaSensor + - flow control for RTP over RTSP and session restart when RTSP TEARDOWN not acknowledged + - 3GPP hinting (AMR NB and H263) contribution by Andrew Voznytsa + - playback support for basic 3GPP files (that is only MPEG-4 visual and AAC audio) + - support for MPA payload format - RFC 2250 (hinting and playback) + - support for mode detection (CELP and AAC) in hinting since QT6 understands only these modes + - support for several mediaSensors per OD + - AudioBuffer + - removed DirectDraw display resolution changes when switching to fullscreen + - BIFS Extended Updates from AFX/MU amendment (integrated in GF_SceneGraph, dumping, memory decoding and encoding) + - BT parser completed - complete BIFS syntax is now supported except PMF + - SRT importer through BT (converts SRT file to a BIFS animationStream) + - better audio playback/sync + - ffmpeg plugin, decoder seems OK (MPEG1 and MPEG4 visual tested), demuxer not working yet. + - added AVI/mp3/MP4/NHNT import to mp4box + - BIFS encoder initial release (missing: PMF, BIFS extended Updates), bt parser almost done, bt->mp4 working + - exporters in MP4Box (cmp, aac, mp3, jpg, png and nhnt) + - mp4 XML reports for hint tracks and atoms in MP4Box + - merged old soft rasterizer with charcoal + freetype (using ftgrays standalone raster) - better quality, faster ... + - added BIFS stat tools in MP4Box + - lots of updates in dirty rect algo. + - changed GraphicsDriver2D API + - scene graph dumper in Osmo4 win32 + - New app MP42AVI for BIFS to video (AVI, BMP, RAW) conversion, single-frame dump support + - MP4 dumping to XMT-A and bt (BIFS, OD and OCI) - cf MP4Box + - PredictiveMFField support (only IPPPPPPP...) + - segment descriptors in MediaControl + - ported wav audio for WinCE - TEST AND FIXING NEEDEED , DOESN'T WORK ON ALL IPAQs + - support for hardcoded proto testing + - WinCE port + GAPI video renderer (for iPaq only) + - externProto support (only tested with local proto libs) + - InputSensor (keySensor, Mouse and StringSensor) + - plethora of bug fixes + +# version 0.1.0: + initial release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9a09be6 --- /dev/null +++ b/Makefile @@ -0,0 +1,376 @@ +# +# Main gpac Makefile +# +include config.mak + +ifndef SRC_PATH +override SRC_PATH = . +endif + +vpath %.c $(SRC_PATH) + +all: version + $(MAKE) -C src all + $(MAKE) -C applications all +ifneq ($(STATIC_BINARY),yes) + $(MAKE) -C modules all +endif + + +config.mak: + @echo "running default configure" + @./configure + + +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)-//") +BRANCH:=$(shell git --git-dir=$(SRC_PATH)/.git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "UNKNOWN") + +version: + @if [ -d $(SRC_PATH)/".git" ]; then \ + echo "#define GPAC_GIT_REVISION \"$(VERSION)-$(BRANCH)\"" > $(GITREV_PATH).new; \ + if ! diff -q $(GITREV_PATH) $(GITREV_PATH).new >/dev/null ; then \ + mv $(GITREV_PATH).new $(GITREV_PATH); \ + fi; \ + else \ + echo "No GIT Version found" ; \ + fi + +lib: version + $(MAKE) -C src all + +apps: + $(MAKE) -C applications all + +sggen: + $(MAKE) -C applications sggen + +mods: + $(MAKE) -C modules all + +instmoz: + $(MAKE) -C applications/osmozilla install + +depend: + $(MAKE) -C src dep + $(MAKE) -C applications dep + $(MAKE) -C modules dep + +clean: + $(MAKE) -C src clean + $(MAKE) -C applications clean + $(MAKE) -C modules clean + +distclean: + $(MAKE) -C src distclean + $(MAKE) -C applications distclean + $(MAKE) -C modules distclean + rm -f config.mak config.h config.log + @find . -type f -name '*.gcno*' -delete + @find . -type f -name '*.gcda*' -delete + @rm -f coverage.info 2> /dev/null + @rm -f bin/gcc/gm_*$(DYN_LIB_SUFFIX) 2> /dev/null + @rm -f bin/gcc/gf_*$(DYN_LIB_SUFFIX) 2> /dev/null + +doc: + @cd $(SRC_PATH)/share/doc && doxygen + +man: + @cd $(SRC_PATH)/share/doc/man && MP4Box -genman && MP4Client -genman && gpac -genman + +test_suite: + @cd $(SRC_PATH)/testsuite && ./make_tests.sh -precommit -p=0 + +lcov_clean: + lcov --directory . --zerocounters + +lcov_only: + @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 + @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 .. + @echo "Done - coverage.info ready" + +lcov: lcov_only + @rm -rf coverage/ + @genhtml -q -o coverage coverage.info + +travis_tests: + @echo "Running tests in $(SRC_PATH)/testsuite" + @cd $(SRC_PATH)/testsuite && ./make_tests.sh -precommit -p=0 + +travis_deploy: + @echo "Deploying results" + @cd $(SRC_PATH)/testsuite && ./ghp_deploy.sh + +travis: travis_tests lcov travis_deploy + +dep: depend + +install: + $(INSTALL) -d "$(DESTDIR)$(prefix)" + + $(MAKE) install-lib + + $(INSTALL) -d "$(DESTDIR)$(prefix)/bin" + if [ -f bin/gcc/MP4Box$(EXE_SUFFIX) ] ; then \ + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/gpac$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ + fi +ifeq ($(DISABLE_ISOFF),no) + if [ -f bin/gcc/MP4Box$(EXE_SUFFIX) ] ; then \ + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/MP4Box$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ + fi +endif +ifneq ($(STATIC_BINARY),yes) +ifeq ($(DISABLE_PLAYER),no) + if [ -f bin/gcc/MP4Client$(EXE_SUFFIX) ] ; then \ + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/MP4Client$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ + fi +endif +endif + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)/$(moddir)" +ifneq ($(STATIC_BINARY),yes) + $(INSTALL) bin/gcc/gm_*$(DYN_LIB_SUFFIX) "$(DESTDIR)$(prefix)/$(lib_dir)/$(moddir)" || true + $(INSTALL) bin/gcc/gf_*$(DYN_LIB_SUFFIX) "$(DESTDIR)$(prefix)/$(lib_dir)/$(moddir)" || true +ifeq ($(CONFIG_OPENHEVC),yes) + cp -a bin/gcc/libopenhevc* $(DESTDIR)$(prefix)/$(lib_dir)/ || true +endif + +endif + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(man_dir)" + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(man_dir)/man1" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/mp4box.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/mp4client.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/gpac.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/gpac-filters.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/res" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/gui" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/gui/icons" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/gui/extensions" + $(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) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/default.cfg $(DESTDIR)$(prefix)/share/gpac/ + +ifneq ($(CONFIG_DARWIN),yes) + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/icons/hicolor/128x128/apps" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/applications" + + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/res/gpac.png "$(DESTDIR)$(prefix)/share/icons/hicolor/128x128/apps/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gpac.desktop "$(DESTDIR)$(prefix)/share/applications/" +endif + + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gui.bt "$(DESTDIR)$(prefix)/share/gpac/gui/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gui.js "$(DESTDIR)$(prefix)/share/gpac/gui/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gwlib.js "$(DESTDIR)$(prefix)/share/gpac/gui/" + + +ifeq ($(CONFIG_DARWIN),yes) + 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/" +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/ + cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/shaders/* $(DESTDIR)$(prefix)/share/gpac/shaders/ + 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/ +endif + +lninstall: + $(INSTALL) -d "$(DESTDIR)$(prefix)" + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" + $(INSTALL) -d "$(DESTDIR)$(prefix)/bin" + ln -sf $(BUILD_PATH)/bin/gcc/gpac$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/gpac$(EXE_SUFFIX) +ifeq ($(DISABLE_ISOFF),no) + ln -sf $(BUILD_PATH)/bin/gcc/MP4Box$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/MP4Box$(EXE_SUFFIX) +endif +ifneq ($(STATIC_BINARY),yes) +ifeq ($(DISABLE_PLAYER),no) + ln -sf $(BUILD_PATH)/bin/gcc/MP4Client$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/MP4Client$(EXE_SUFFIX) +endif +endif +ifeq ($(CONFIG_DARWIN),yes) + ln -s $(BUILD_PATH)/bin/gcc/libgpac$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_MAJOR)$(DYN_LIB_SUFFIX) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX) + + ln -s $(BUILD_PATH)/bin/gcc/ $(DESTDIR)$(prefix)/$(lib_dir)/gpac + ln -s $(SRC_PATH)/share/ $(DESTDIR)$(prefix)/share/gpac +else + ln -s $(BUILD_PATH)/bin/gcc/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so.$(VERSION_MAJOR) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so + ln -s $(BUILD_PATH)/bin/gcc/ $(DESTDIR)$(prefix)/$(lib_dir)/gpac + + ln -s $(SRC_PATH)/share/ $(DESTDIR)$(prefix)/share/gpac + ln -sf $(DESTDIR)$(prefix)/share/gpac/res/gpac.png $(DESTDIR)/usr/share/icons/hicolor/128x128/apps/gpac.png + ln -sf $(SRC_PATH)/share/gpac.desktop $(DESTDIR)/usr/share/applications/ + +ifeq ($(DESTDIR)$(prefix),$(prefix)) + ldconfig || true +endif + +endif + +uninstall: + $(MAKE) -C applications uninstall + $(MAKE) uninstall-lib + rm -rf $(DESTDIR)$(prefix)/$(lib_dir)/$(moddir) + rm -rf $(DESTDIR)$(prefix)/bin/MP4Box + rm -rf $(DESTDIR)$(prefix)/bin/MP4Client + rm -rf $(DESTDIR)$(prefix)/bin/gpac + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/mp4box.1 + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/mp4client.1 + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/gpac.1 + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/gpac-filters.1 + rm -rf $(DESTDIR)$(prefix)/share/gpac + rm -rf $(DESTDIR)$(prefix)/share/icons/hicolor/128x128/apps/gpac.png + rm -rf $(DESTDIR)$(prefix)/share/applications/gpac.desktop + + +installdylib: +ifneq ($(STATIC_BINARY),yes) + + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" + +ifeq ($(CONFIG_WIN32),yes) + $(INSTALL) -d "$(DESTDIR)$(prefix)/bin" + + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac.dll.a $(DESTDIR)$(prefix)/$(lib_dir) + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac.dll $(DESTDIR)$(prefix)/bin +else + +ifeq ($(DEBUGBUILD),no) + $(STRIP) -S bin/gcc/libgpac$(DYN_LIB_SUFFIX) +endif + +ifeq ($(CONFIG_DARWIN),yes) + $(INSTALL) -m 755 bin/gcc/libgpac$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) + ln -sf libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_MAJOR)$(DYN_LIB_SUFFIX) + ln -sf libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX) +else + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) + ln -sf libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so.$(VERSION_MAJOR) + ln -sf libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so +ifeq ($(DESTDIR)$(prefix),$(prefix)) + ldconfig || true +endif +endif + +endif + +endif + + +uninstalldylib: + rm -rf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac* +ifeq ($(CONFIG_WIN32),yes) + rm -rf "$(DESTDIR)$(prefix)/bin/libgpac*" +endif + +install-lib: + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac" + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac/internal" + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac/modules" + + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/*.h "$(DESTDIR)$(prefix)/include/gpac" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/internal/*.h "$(DESTDIR)$(prefix)/include/gpac/internal" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/modules/*.h "$(DESTDIR)$(prefix)/include/gpac/modules" + + $(INSTALL) $(INSTFLAGS) -m 644 config.h "$(DESTDIR)$(prefix)/include/gpac/configuration.h" || true + +ifeq ($(GPAC_ENST),yes) + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac/enst" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/enst/*.h "$(DESTDIR)$(prefix)/include/gpac/enst" +endif + + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" + $(INSTALL) $(INSTFLAGS) -m 644 "./bin/gcc/libgpac_static.a" "$(DESTDIR)$(prefix)/$(lib_dir)" || true + + $(INSTALL) -d $(DESTDIR)$(prefix)/$(lib_dir)/pkgconfig + $(INSTALL) $(INSTFLAGS) -m 644 gpac.pc "$(DESTDIR)$(prefix)/$(lib_dir)/pkgconfig" + + $(MAKE) installdylib + + +uninstall-lib: + rm -rf "$(DESTDIR)$(prefix)/include/gpac/internal" + rm -rf "$(DESTDIR)$(prefix)/include/gpac/modules" + rm -rf "$(DESTDIR)$(prefix)/include/gpac/enst" + rm -rf "$(DESTDIR)$(prefix)/include/gpac" + rm -f "$(DESTDIR)$(prefix)/$(lib_dir)/libgpac_static.a" + rm -f "$(DESTDIR)$(prefix)/$(lib_dir)/pkgconfig/gpac.pc" + $(MAKE) uninstalldylib + +ifeq ($(CONFIG_DARWIN),yes) +dmg: +# @if [ ! -z "$(shell git diff FETCH_HEAD)" ]; then \ +# echo "Local revision and remote revision are not congruent; you may have local commit."; \ +# echo "Please consider pushing your commit before generating an installer"; \ +# exit 1; \ +# fi + rm "bin/gcc/MP4Client" + $(MAKE) -C applications/mp4client + ./mkdmg.sh $(arch) +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 + fakeroot debian/rules configure + fakeroot debian/rules binary + rm -rf debian/ + git checkout debian +endif + +help: + @echo "Input to GPAC make:" + @echo "depend/dep: builds dependencies (dev only)" + @echo "all (void): builds main library, programs and plugins" + @echo "lib: builds GPAC library only (libgpac.so)" + @echo "apps: builds programs only" + @echo "modules: builds modules only" + @echo "instmoz: build and local install of osmozilla" + @echo "sggen: builds scene graph generators" + @echo + @echo "clean: clean src repository" + @echo "distclean: clean src repository and host config file" + @echo + @echo "install: install applications and modules on system" + @echo "uninstall: uninstall applications and modules" +ifeq ($(CONFIG_DARWIN),yes) + @echo "dmg: creates DMG package file for OSX" +endif +ifeq ($(CONFIG_LINUX),yes) + @echo "deb: creates DEB package file for debian based systems" +endif + @echo + @echo "install-lib: install gpac library (dyn and static) and headers , and " + @echo "uninstall-lib: uninstall gpac library (dyn and static) and headers" + @echo + @echo "test_suite: run all tests. For more info, check https://github.com/gpac/testsuite" + @echo + @echo "doc: build libgpac documentation in gpac/doc" + @echo "man: build gpac man files in gpac/doc/man (must have latest build binaries installed)" + @echo + @echo "lcov: generate lcov files" + @echo "lcov_clean: clean all lcov/gcov files" + + +-include .depend diff --git a/README.md b/README.md new file mode 100644 index 0000000..48326b6 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +[![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) +[![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) +[![Tests](https://tests.gpac.io/testres/badge/tests/win64)](https://tests.gpac.io/) + +[![Build Status](https://tests.gpac.io/testres/badge/build/windows32)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/win32)](https://tests.gpac.io/) + +[![Build Status](https://tests.gpac.io/testres/badge/build/macos)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/macos)](https://tests.gpac.io/) + +[![Build Status](https://tests.gpac.io/testres/badge/build/ios)](https://buildbot.gpac.io/#/grid?branch=master) +[![Build Status](https://tests.gpac.io/testres/badge/build/android)](https://buildbot.gpac.io/#/grid?branch=master) + +[![Coverage](https://tests.gpac.io/testres/badge/cov/linux64?branch=master)](https://tests.gpac.io/testres/) +[![Coverage](https://tests.gpac.io/testres/badge/covfn/linux64?branch=master)](https://tests.gpac.io/testres/) + +![License](https://img.shields.io/badge/license-LGPL-blue.svg) +[![OpenHub](https://www.openhub.net/p/gpac/widgets/project_thin_badge.gif)](https://www.openhub.net/p/gpac) + + +# GPAC Introduction +Latest Release: 2.0 + +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. +GPAC is best-known for its wide MP4/ISOBMFF capabilities and is popular among video enthusiasts, academic researchers, standardization bodies, and professional broadcasters. + +For more information, visit [GPAC website](http://gpac.io) + +GPAC is distributed under the LGPL v2.1 or later, and is also available, for most of it, under a [commercial license](https://www.gpac-licensing.com). + +Please ! _cite_ ! our work in your research: +- "GPAC Filters" (https://doi.org/10.1145/3339825.3394929) for recent versions (0.9 or above) +- "GPAC: open source multimedia framework" (https://doi.org/10.1145/1291233.1291452) for older versions. + + +# Features + +GPAC can process, analyse, package, stream, encode, decode and playback a wide variety of contents. Selected feature list: +- Audio: MPEG audio (mp1/2/3, aac), AC3, E-AC3, Opus, FLAC, … +- Video: MPEG 1 / 2 / 4 (H264/AVC) / H (HEVC), VVC, AV1, VP9, Theora, ... +- Subtitles: WebVTT, TTML (full, EBU-TTD, …), 3GPP/Apple Timed Text, … +- Encryption: CENC, PIFF, ISMA, OMA, ... +- Containers: MP4/fMP4/CMAF/Quicktime MOV/ProRes MOV, AVI, MPG, OGG, MKV, ... +- Streaming: MPEG-2 Transport Stream, RTP, RTSP, HTTP, Apple HLS, MPEG-DASH, ATSC 3.0 ROUTE, ... +- Supported IOs: local files, pipes, UDP/TCP, HTTP(S), custom IO +- Presentation formats: MPEG-4 BIFS, SVG Tiny 1.2, VRML/X3D +- JS scripting through QuickJS for both SVG/BIFS/VRML and extending GPAC framework tools +- 3D support (360 videos, WebGL JS filters…) +- Inputs: microphone, camera, desktop grabbing +- Highly configurable media processing pipeline +- 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://github.com/gpac/gpac/wiki/Filters). +- to get the full list of playback features, check [the dedicated wiki page](https://github.com/gpac/gpac/wiki/Player-Features). + + +# 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). + + +## 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 and MP4Client. +The gpac application is a direct interface to the filter engine of GPAC, allowing any combinaison 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. + +## MP4Client (deprecated) +MP4Client is a media player built upon libgpac, featuring a rich media interactive composition engine with MPEG-4 BIFS, SVG, VRML/X3D support. +For GPAC configuration instruction, check `MP4Client -h` , `man MP4Client` or [our wiki](https://wiki.gpac.io/mp4client). + +__Warning__ +MP4Client is deprecated and will be removed in the next release. Start modifying your scripts: +- replace `MP4Client URL` with `gpac -play URL` (audio/video playback only) or `gpac -mp4c URL` (if compositor is needed) +- replace `MP4Client -gui URL` with `gpac -gui URL`. + +# Getting started +## Download +Stable and nightly builds installers for Windows, Linux, OSX, Android, iOS are available on [gpac.io](https://gpac.wp.imt.fr/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. + +## Documentation +The general GPAC framework documentation is available on [wiki.gpac.io](https://wiki.gpac.io), including [HowTos](https://github.com/gpac/gpac/wiki/Howtos). + +GPAC tools are mostly wrappers around an underlying library called libgpac which can easily be embedded in your projects. The libgpac developer documentation is available at [doxygen.gpac.io](https://doxygen.gpac.io), including documentation of [JS APIs](https://doxygen.gpac.io/group__jsapi__grp.html), [Python APIs](https://doxygen.gpac.io/group__pyapi__grp.html) and [NodeJS APIs](https://doxygen.gpac.io/group__nodejs__grp.html). + + +## Testing +GPAC has a test suite exercising most features of the framework. The test suite is in a separate repository [https://github.com/gpac/testsuite/](https://github.com/gpac/testsuite/), but is available as a submodule of the GPAC main repository. To initialize the testsuite submodule, do `git submodule update --init`. + +For more details on the test suite, read [this page](https://github.com/gpac/gpac/wiki/GPAC_tests) and check the [testsuite readme](https://github.com/gpac/testsuite). + +Per-commit [build](https://buildbot.gpac.io/) and [tests results](https://tests.gpac.io) are available. + + +## Support, ongoing tasks and bugs + +Please use [github](https://github.com/gpac/gpac/issues) for feature requests and bug reports. When filing a request there, please tag it as _feature-request_. + +## Contributing +A complex project like GPAC wouldn’t exist and persist without the support of its community. Please contribute: a nice message, supporting us in our communication, reporting issues when you see them… any gesture, even the smallest ones, counts. + +If you want to contribute to GPAC, you can find ideas at [GSoC page](https://gpac.wp.imt.fr/jobs/google-summer-of-code-ideas/) or look for a [good first issue](https://github.com/gpac/gpac/labels/good%20first%20issue). In any doubt please feel free to [contact us](mailto:contact@gpac.io). + +# Team +GPAC is brought to you by an experienced team of developers with a wide track-record on media processing. + +The project is mainly developed in the MultiMedia group of [Telecom Paris](https://www.telecom-paris.fr/) with the help of many [great contributors](https://github.com/gpac/gpac/graphs/contributors). + +GPAC has a peculiar story: started as a startup in NYC, GPAC gained traction from research and a nascent multimedia community as it was open-sourced in 2003. Since then we have never stopped transforming GPAC into a useful and up-to-date project, with many industrial R&D collaborations and a community of tens of thousands of users. This makes GPAC one of the few open-source multimedia projects that gathers so much diversity. + + +# Roadmap +Users are encouraged to use the latest tag or the master branch. + +The v0.8.X release (the last one using the legacy architecture) is officially deprecated. + +## V2.X +Targets: +- [ ] drop MP4Client and GF_Terminal API +- [ ] User authentication for HTTP and RTSP servers +- [ ] DASH event support +- [ ] Web integration (emscripten, Remotery UI) +- [ ] GUI cleanup ? + + + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..be18292 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Email Contact + +security@gpac.io + +## Supported Versions + +GPAC is under constant development using a continuous integration and deployment process. As a consequence the `HEAD` of the `master` branch is always considered as the _current version_ at any point. + +Thus only reports that are confirmed reproducible on the current `HEAD` of the `master` branch will receive a patch. + + +## Reporting a Vulnerability + +Vulnerabilities (as well as other bugs) should be reported directly using the [Github issue trakcer](https://github.com/gpac/gpac/issues). + +Please include all information needed to reproduce the issue, including a sample file. + +Sample files can be joined directly via github (preferred way) or uploaded to the [GPAC file drop](https://www.mediafire.com/filedrop/filedrop_hosted.php?drop=eec9e058a9486fe4e99c33021481d9e1826ca9dbc242a6cfaab0fe95da5e5d95). + +However if public disclosure seems unreasonable, or if confidential information needs to be shared, you can contact security@gpac.io for private disclosure. diff --git a/applications/Makefile b/applications/Makefile new file mode 100644 index 0000000..195d362 --- /dev/null +++ b/applications/Makefile @@ -0,0 +1,63 @@ +include ../config.mak + +APPDIRS=gpac + +ifeq ($(STATIC_BINARY),yes) +APPDIRS+=mp4box +else + +ifeq ($(DISABLE_PLAYER),no) +APPDIRS+=mp4client +endif + +ifeq ($(DISABLE_ISOFF),no) +APPDIRS+=mp4box + +endif + +V4STUDIODIR= +INSTDIRS=mp4client +ifeq ($(CONFIG_XUL),no) +else +#INSTDIRS+=osmozilla +#APPDIRS+=osmozilla +endif + +#disable due to version incompatibilities +ifeq ($(USE_WXWIDGETS),yes) +#APPDIRS+=osmo4_wx +#V4STUDIODIR=V4Studio +#INSTDIRS+=osmo4_wx +endif + +#STATIC_BINARY +endif + +ALLDIRS=$(APPDIRS) + +all: apps + +apps: + set -e; for i in $(APPDIRS) ; do $(MAKE) -C $$i all; done + +sggen: + $(MAKE) -C generators all + +V4Studio: + set -e; for i in $(V4STUDIODIR) ; do $(MAKE) -C $$i dep; done + +dep: + set -e; for i in $(ALLDIRS) ; do $(MAKE) -C $$i dep; done + +clean: + set -e; for i in $(ALLDIRS) ; do $(MAKE) -C $$i clean; done + +distclean: + set -e; for i in $(ALLDIRS) ; do $(MAKE) -C $$i distclean; done + +install: + set -e; for i in $(INSTDIRS) ; do $(MAKE) -C $$i install; done + +uninstall: + set -e; for i in $(INSTDIRS) ; do $(MAKE) -C $$i uninstall; done + diff --git a/applications/generators/MPEG4/MPEG4Gen.dsp b/applications/generators/MPEG4/MPEG4Gen.dsp new file mode 100644 index 0000000..e5a7d9c --- /dev/null +++ b/applications/generators/MPEG4/MPEG4Gen.dsp @@ -0,0 +1,98 @@ +# Microsoft Developer Studio Project File - Name="MPEG4Gen" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=MPEG4Gen - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "MPEG4Gen.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "MPEG4Gen.mak" CFG="MPEG4Gen - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "MPEG4Gen - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "MPEG4Gen - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "MPEG4Gen - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Obj/W32Rel" +# PROP Intermediate_Dir "Obj/W32Rel" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../../include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "MPEG4Gen - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Obj/W32Deb" +# PROP Intermediate_Dir "Obj/W32Deb" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../../../include" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "MPEG4Gen - Win32 Release" +# Name "MPEG4Gen - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\src\utils\list.c +# End Source File +# Begin Source File + +SOURCE=.\main.c +# End Source File +# End Group +# End Target +# End Project diff --git a/applications/generators/MPEG4/MPEG4Gen.dsw b/applications/generators/MPEG4/MPEG4Gen.dsw new file mode 100644 index 0000000..e3bd23b --- /dev/null +++ b/applications/generators/MPEG4/MPEG4Gen.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "MPEG4Gen"=.\MPEG4Gen.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/applications/generators/MPEG4/Makefile b/applications/generators/MPEG4/Makefile new file mode 100644 index 0000000..b3f0391 --- /dev/null +++ b/applications/generators/MPEG4/Makefile @@ -0,0 +1,52 @@ +include ../../../config.mak + +vpath %.c $(SRC_PATH)/applications/generators/MPEG4 + +CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= main.o + +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=MPEG4Gen$(EXE) +else +EXT= +PROG=MPEG4Gen +endif + +SRCS := $(OBJS:.o=.c) + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o $@ $(OBJS) $(EXTRALIBS) -L../../../bin/gcc -L../../../extra_lib/lib/gcc -lgpac $(LDFLAGS) + + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + + +clean: + rm -f $(OBJS) $(PROG) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/generators/MPEG4/main.c b/applications/generators/MPEG4/main.c new file mode 100644 index 0000000..390bdd5 --- /dev/null +++ b/applications/generators/MPEG4/main.c @@ -0,0 +1,1851 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / MPEG4 Scene Graph Generator 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 +#include +#include + +#undef _DEBUG +#undef DEBUG + +#include + + +#include + + +#define COPYRIGHT_SCENE "/*\n * GPAC - Multimedia Framework C SDK\n *\n * Authors: Jean Le Feuvre\n * Copyright (c) Telecom ParisTech 2000-2012\n * All rights reserved\n *\n * This file is part of GPAC / Scene Graph sub-project\n *\n * GPAC is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation; either version 2, or (at your option)\n * any later version.\n *\n * GPAC is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details. \n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; see the file COPYING. If not, write to\n * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.\n *\n */\n" +#define COPYRIGHT_BIFS "/*\n * GPAC - Multimedia Framework C SDK\n *\n * Authors: Jean Le Feuvre\n * Copyright (c) Telecom ParisTech 2000-2012\n * All rights reserved\n *\n * This file is part of GPAC / BIFS codec sub-project\n *\n * GPAC is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation; either version 2, or (at your option)\n * any later version.\n *\n * GPAC is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details. \n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; see the file COPYING. If not, write to\n * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.\n *\n */\n" + +static char *CurrentLine; + +void PrintUsage() +{ + printf("MPEG4Gen [-p file] [template_file_v1 (template_file_v2 ...) ]\n" + "\nGPAC MPEG4 Scene Graph generator. Usage:\n" + "-p: listing file of nodes to exclude from tables\n" + "Template files MUST be fed in order\n" + "\n" + "Generated Files are directly updated in the GPAC distribution - do NOT try to change this\n\n" + "Written by Jean Le Feuvre - Copyright (c) Telecom ParisTech 2000-2012\n" + ); +} + +//a node field +typedef struct +{ + char type[50]; + //SFxxx, MFxxx + char familly[50]; + //name + char name[1000]; + //default value + char def[100]; + //bounds + u32 hasBounds; + char b_min[20]; + char b_max[20]; + //Quant + u32 hasQuant; + char quant_type[50]; + char qt13_bits[50]; + //Anim + u32 hasAnim; + u32 AnimType; + +} BField; + +//NDTs + +//a BIFS node +typedef struct +{ + char name[1000]; + //NDT info. NDT are created in alphabetical order + GF_List *NDT; + //0: normal, 1: special + u32 codingType; + u32 version; + + GF_List *Fields; + + //coding types + u8 hasDef, hasIn, hasOut, hasDyn; + u8 hasAQInfo; + + u8 hasDefault; + + u8 skip_impl; + + char Child_NDT_Name[1000]; +} BNode; + + +void skip_sep(char *sep) +{ + //skip separaors + while (*CurrentLine && strchr(sep, *CurrentLine)) { + CurrentLine = CurrentLine + 1; + //end of line - no token + if (*CurrentLine == '\n') return; + } +} + +//note that we increment the line no matter what +u32 GetNextToken(char *token, char *sep) +{ + u32 i , j = 0; + + strcpy(token, ""); + + //skip separaors + while (*CurrentLine && strchr(sep, *CurrentLine)) { + CurrentLine = CurrentLine + 1; + j ++; + //end of line - no token + if (*CurrentLine == '\n') return 0; + } + + //copy token until next blank + i=0; + while (1) { + //bad line + if (! *CurrentLine) { + token[i] = 0; + return 0; + } + //end of token or end of line + if (strchr(sep, *CurrentLine) || (*CurrentLine == '\n') ) { + token[i] = 0; + CurrentLine = CurrentLine + 1; + return i; + } else { + token[i] = *CurrentLine; + } + CurrentLine = CurrentLine + 1; + i++; + j++; + } + return 1; +} + + +char szFixedVal[5000]; +char *GetFixedPrintout(char *val) +{ + if (!strcmp(val, "FIX_MIN") || !strcmp(val, "FIX_MAX")) return val; + /*composite texture...*/ + if (!strcmp(val, "65535")) return "FIX_MAX /*WARNING: modified to allow 16.16 fixed point version!!*/"; + sprintf(szFixedVal, "FLT2FIX(%s)", val); + return szFixedVal; +} + +BField *BlankField() +{ + BField *n = gf_malloc(sizeof(BField)); + memset(n, 0, sizeof(BField)); + return n; +} + + +BNode *BlankNode() +{ + BNode *n = gf_malloc(sizeof(BNode)); + memset(n, 0, sizeof(BNode)); + n->NDT = gf_list_new(); + n->Fields = gf_list_new(); + return n; +} + +u8 IsNDT(GF_List *NDTs, char *famName) +{ + u32 i; + for (i=0; i\n\n"); + fprintf(f, "#ifndef GPAC_DISABLE_VRML\n\n"); + + + //write all tags + fprintf(f, "\n\nenum {\n"); + + for (i=0; iname); + else + fprintf(f, "\tTAG_MPEG4_%s = GF_NODE_RANGE_FIRST_MPEG4", n->name); + } + fprintf(f, ",\n\tTAG_LastImplementedMPEG4\n};\n\n"); + + for (i=0; iskip_impl) continue; + + fprintf(f, "typedef struct _tag%s\n{\n", n->name); + fprintf(f, "\tBASE_NODE\n"); + + /*write children field*/ + for (j=0; jFields); j++) { + bf = gf_list_get(n->Fields, j); + if (!stricmp(bf->name, "addChildren") || !strcmp(bf->name, "removeChildren")) continue; + if (!strcmp(bf->name, "children") && stricmp(n->name, "audioBuffer")) { + fprintf(f, "\tVRML_CHILDREN\n"); + break; + } + } + for (j=0; jFields); j++) { + bf = gf_list_get(n->Fields, j); + + if (!strcmp(bf->name, "addChildren") || !strcmp(bf->name, "removeChildren")) continue; + if (!strcmp(bf->name, "children") && stricmp(n->name, "audioBuffer")) continue; + + //write remaining fields + //eventIn fields are handled as pointer to functions, called by the route manager + if (!strcmp(bf->type, "eventIn")) { + if (strstr(bf->familly, "Node")) { + fprintf(f, "\tGF_Node *%s;\t/*eventIn*/\n",bf->name); + } else { + fprintf(f, "\t%s %s;\t/*eventIn*/\n", bf->familly, bf->name); + } + fprintf(f, "\tvoid (*on_%s)(GF_Node *pThis, struct _route *route);\t/*eventInHandler*/\n", bf->name); + } else if (!strcmp(bf->type, "eventOut")) { + //eventOut fields are handled as an opaque stack pointing to the route manager + //this will be refined once the route is in place + //we will likely need a function such as: + // void SignalRoute(route_stack, node, par) + fprintf(f, "\t%s %s;\t/*eventOut*/\n", bf->familly, bf->name); + } else if (strstr(bf->familly, "Node")) { + //this is a POINTER to a node + if (strstr(bf->familly, "SF")) { + fprintf(f, "\tGF_Node *%s;\t/*%s*/\n", bf->name, bf->type); + } else { + //this is a POINTER to a chain + fprintf(f, "\tGF_ChildNodeItem *%s;\t/*%s*/\n", bf->name, bf->type); + } + } else { + fprintf(f, "\t%s %s;\t/*%s*/\n", bf->familly, bf->name, bf->type); + } + } + if (!strcmp(n->name, "CacheTexture")) { + fprintf(f, "\t/*GPAC private*/\n"); + fprintf(f, "\tu8 *data;\n"); + fprintf(f, "\tu32 data_len;\n"); + } + if (!strcmp(n->name, "BitWrapper")) { + fprintf(f, "\t/*GPAC private*/\n"); + fprintf(f, "\tu32 buffer_len;\n"); + } + fprintf(f, "} M_%s;\n\n\n", n->name); + } + + + hasViewport = 0; + //all NDTs are defined in v1 + fprintf(f, "/*NodeDataType tags*/\nenum {\n"); + for (i=0; i not generated + if (!hasViewport) fprintf(f, ",\n\tNDT_SFViewportNode"); + fprintf(f, "\n};\n\n"); + + + fprintf(f, "/*All BIFS versions handled*/\n"); + fprintf(f, "#define GF_BIFS_NUM_VERSION\t\t%d\n\n", NumVersions); + fprintf(f, "enum {\n"); + for (i=0; iNDT); i++) { + char *ndt = gf_list_get(node->NDT, i); + if (!strcmp(ndt, NDTName)) return 1; + } + return 0; +} + +u32 GetBitsCount(u32 MaxVal) +{ + u32 k=0; + + while ((s32) MaxVal > ((1<version != Version) continue; + if (!IsNodeInTable(n, NDTName)) continue; + nodeCount++; + } + return nodeCount; + +} + +void WriteNDT_H(FILE *f, GF_List *BNodes, GF_List *NDTs, u32 Version) +{ + u32 i, j, first, count; + BNode *n; + + + fprintf(f, "\n\n/* NDT BIFS Version %d */\n\n", Version); + + //for all NDTs + for (i=0; i 2) { + count -= 1; + } + if (!count) continue; + + //numBits + fprintf(f, "#define %s_V%d_NUMBITS\t\t%d\n", NDTName, Version, GetBitsCount(count + ( (Version == 2) ? 1 : 0) ) ); + fprintf(f, "#define %s_V%d_Count\t%d\n\n", NDTName, Version, count); + + fprintf(f, "static const u32 %s_V%d_TypeToTag[%d] = {\n", NDTName, Version, count); + first = 1; + //browse each node. + for (j=0; jversion != Version) continue; + if (!IsNodeInTable(n, NDTName)) continue; + + if (first) { + fprintf(f, " TAG_MPEG4_%s", n->name); + first = 0; + } else { + fprintf(f, ", TAG_MPEG4_%s", n->name); + } + } + fprintf(f, "\n};\n\n"); + + } + + fprintf(f, "\nu32 NDT_V%d_GetNumBits(u32 NDT_Tag);\n", Version); + fprintf(f, "u32 NDT_V%d_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType);\n", Version); + fprintf(f, "u32 NDT_V%d_GetNodeType(u32 NDT_Tag, u32 NodeTag);\n", Version); + + fprintf(f, "\n\n"); +} + +//write the NDTs functions for v1 nodes +//all our internal handling is in TAG_MPEG4_#nodename because we need an homogeneous +//namespace for all nodes (v1, v2, v3 and v4) +//the NDT functions will perform the translation from the NDT value to the absolute +//TAG of the node +void WriteNDT_Dec(FILE *f, GF_List *BNodes, GF_List *NDTs, u32 Version) +{ + char *NDTName; + u32 i, count; + + //NodeTag complete translation + fprintf(f, "\n\n\nu32 NDT_V%d_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType)\n{\n\tif (!NodeType) return 0;\n", Version); + + //handle version + fprintf(f, "\t/* adjust according to the table version */\n"); + if (Version == 2) { + fprintf(f, "\t/* v2: 0 reserved for extensions, 1 reserved for protos */\n"); + fprintf(f, "\tif (NodeType == 1) return 0;\n"); + fprintf(f, "\tNodeType -= 2;\n"); + } else { + fprintf(f, "\t/* v%d: 0 reserved for extensions */\n", Version); + fprintf(f, "\tNodeType -= 1;\n"); + } + + fprintf(f, "\tswitch (Context_NDT_Tag) {\n"); + + for (i=0; i 2) { + count -= 1; + } + if (!count) continue; + + fprintf(f, "\tcase NDT_%s:\n\t\tif (NodeType >= %s_V%d_Count) return 0;\n", NDTName, NDTName, Version); + fprintf(f, "\t\treturn %s_V%d_TypeToTag[NodeType];\n", NDTName, Version); + } + fprintf(f, "\tdefault:\n\t\treturn 0;\n\t}\n}"); + + //NDT codec bits + fprintf(f, "\n\n\nu32 NDT_V%d_GetNumBits(u32 NDT_Tag)\n{\n\tswitch (NDT_Tag) {\n", Version); + + for (i=0; i 2) { + count -= 1; + } + if (!count) continue; + + fprintf(f, "\tcase NDT_%s:\n\t\treturn %s_V%d_NUMBITS;\n", NDTName, NDTName, Version); + } + /*all tables have 1 node in v2 for proto coding*/ + fprintf(f, "\tdefault:\n\t\treturn %d;\n\t}\n}\n\n", (Version==2) ? 1 : 0); +} + + +void WriteNDT_Enc(FILE *f, GF_List *BNodes, GF_List *NDTs, u32 Version) +{ + u32 i, count; + + fprintf(f, "u32 NDT_V%d_GetNodeType(u32 NDT_Tag, u32 NodeTag)\n{\n\tif(!NDT_Tag || !NodeTag) return 0;\n\tswitch(NDT_Tag) {\n", Version); + for (i=0; i 2) { + count -= 1; + } + if (!count) continue; + fprintf(f, "\tcase NDT_%s:\n\t\treturn ALL_GetNodeType(%s_V%d_TypeToTag, %s_V%d_Count, NodeTag, GF_BIFS_V%d);\n", NDTName, NDTName, Version, NDTName, Version, Version); + } + fprintf(f, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); +} + + + +void WriteNodeFields(FILE *f, BNode *n) +{ + u32 i, first; + BField *bf; + u32 NbDef, NbIn, NbOut, NbDyn, hasAQ; + + NbDef = NbIn = NbOut = NbDyn = hasAQ = 0; + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + if (!strcmp(bf->type, "field") || !strcmp(bf->type, "exposedField")) { + NbDef += 1; + } + if (!strcmp(bf->type, "eventIn") || !strcmp(bf->type, "exposedField")) { + NbIn += 1; + //check for anim + if (bf->hasAnim) NbDyn += 1; + } + if (!strcmp(bf->type, "eventOut") || !strcmp(bf->type, "exposedField")) { + NbOut += 1; + } + if (bf->hasAnim || bf->hasQuant) hasAQ = 1; + } + + n->hasAQInfo = hasAQ; + + //write the def2all table + if (NbDef) { + first = 1; + fprintf(f, "static const u16 %s_Def2All[] = { ", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + if (strcmp(bf->type, "field") && strcmp(bf->type, "exposedField")) continue; + if (first) { + fprintf(f, "%d", i); + first = 0; + } else { + fprintf(f, ", %d", i); + } + } + fprintf(f, "};\n"); + } + //write the in2all table + if (NbIn) { + first = 1; + fprintf(f, "static const u16 %s_In2All[] = { ", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + if (strcmp(bf->type, "eventIn") && strcmp(bf->type, "exposedField")) continue; + if (first) { + fprintf(f, "%d", i); + first = 0; + } else { + fprintf(f, ", %d", i); + } + } + fprintf(f, "};\n"); + } + //write the out2all table + if (NbOut) { + first = 1; + fprintf(f, "static const u16 %s_Out2All[] = { ", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + if (strcmp(bf->type, "eventOut") && strcmp(bf->type, "exposedField")) continue; + if (first) { + fprintf(f, "%d", i); + first = 0; + } else { + fprintf(f, ", %d", i); + } + } + fprintf(f, "};\n"); + } + //then write the dyn2all table + if (NbDyn) { + first = 1; + fprintf(f, "static const u16 %s_Dyn2All[] = { ", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + if (strcmp(bf->type, "eventIn") && strcmp(bf->type, "exposedField")) continue; + if (!bf->hasAnim) continue; + if (first) { + fprintf(f, "%d", i); + first = 0; + } else { + fprintf(f, ", %d", i); + } + } + fprintf(f, "};\n"); + } + + n->hasDef = NbDef; + n->hasDyn = NbDyn; + n->hasIn = NbIn; + n->hasOut = NbOut; + + + fprintf(f, "\nstatic u32 %s_get_field_count(GF_Node *node, u8 IndexMode)\n{\n", n->name); + fprintf(f, "\tswitch(IndexMode) {\n"); + + fprintf(f, "\tcase GF_SG_FIELD_CODING_IN: return %d;\n", NbIn); + fprintf(f, "\tcase GF_SG_FIELD_CODING_DEF: return %d;\n", NbDef); + fprintf(f, "\tcase GF_SG_FIELD_CODING_OUT: return %d;\n", NbOut); + fprintf(f, "\tcase GF_SG_FIELD_CODING_DYN: return %d;\n", NbDyn); + fprintf(f, "\tdefault:\n"); + fprintf(f, "\t\treturn %d;\n", gf_list_count(n->Fields)); + fprintf(f, "\t}"); + + fprintf(f, "\n}\n"); + + fprintf(f, "\nstatic GF_Err %s_get_field_index(GF_Node *n, u32 inField, u8 IndexMode, u32 *allField)\n{\n", n->name); + fprintf(f, "\tswitch(IndexMode) {\n"); + + if (NbIn) { + fprintf(f, "\tcase GF_SG_FIELD_CODING_IN:\n"); + fprintf(f, "\t\t*allField = %s_In2All[inField];\n\t\treturn GF_OK;\n", n->name); + } + if (NbDef) { + fprintf(f, "\tcase GF_SG_FIELD_CODING_DEF:\n"); + fprintf(f, "\t\t*allField = %s_Def2All[inField];\n\t\treturn GF_OK;\n", n->name); + } + if (NbOut) { + fprintf(f, "\tcase GF_SG_FIELD_CODING_OUT:\n"); + fprintf(f, "\t\t*allField = %s_Out2All[inField];\n\t\treturn GF_OK;\n", n->name); + } + if (NbDyn) { + fprintf(f, "\tcase GF_SG_FIELD_CODING_DYN:\n"); + fprintf(f, "\t\t*allField = %s_Dyn2All[inField];\n\t\treturn GF_OK;\n", n->name); + } + + fprintf(f, "\tdefault:\n"); + fprintf(f, "\t\treturn GF_BAD_PARAM;\n"); + fprintf(f, "\t}"); + + fprintf(f, "\n}\n"); + + + fprintf(f, "static GF_Err %s_get_field(GF_Node *node, GF_FieldInfo *info)\n{\n\tswitch (info->fieldIndex) {\n", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + + fprintf(f, "\tcase %d:\n", i); + + fprintf(f, "\t\tinfo->name = \"%s\";\n", (bf->name[0]=='_') ? bf->name+1 : bf->name); + + //skip all eventIn + if (!strcmp(bf->type, "eventIn")) { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_IN;\n"); + fprintf(f, "\t\tinfo->on_event_in = ((M_%s *)node)->on_%s;\n", n->name, bf->name); + } + else if (!strcmp(bf->type, "eventOut")) { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_OUT;\n"); + } + else if (!strcmp(bf->type, "field")) { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_FIELD;\n"); + } + else { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_EXPOSED_FIELD;\n"); + } + + if (strstr(bf->familly, "Node")) { + if (strstr(bf->familly, "MF")) { + fprintf(f, "\t\tinfo->fieldType = GF_SG_VRML_MFNODE;\n"); + } else { + fprintf(f, "\t\tinfo->fieldType = GF_SG_VRML_SFNODE;\n"); + } + //always remove the SF or MF, as all NDTs are SFXXX + fprintf(f, "\t\tinfo->NDTtype = NDT_SF%s;\n", bf->familly+2); + fprintf(f, "\t\tinfo->far_ptr = & ((M_%s *)node)->%s;\n", n->name, bf->name); + } else { + char szName[20]; + strcpy(szName, bf->familly); + strupr(szName); + //no ext type + fprintf(f, "\t\tinfo->fieldType = GF_SG_VRML_%s;\n", szName); + fprintf(f, "\t\tinfo->far_ptr = & ((M_%s *) node)->%s;\n", n->name, bf->name); + } + fprintf(f, "\t\treturn GF_OK;\n"); + } + fprintf(f, "\tdefault:\n\t\treturn GF_BAD_PARAM;\n\t}\n}\n\n"); + + fprintf(f, "\nstatic s32 %s_get_field_index_by_name(char *name)\n{\n", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + fprintf(f, "\tif (!strcmp(\"%s\", name)) return %d;\n", (bf->name[0]=='_') ? bf->name+1 : bf->name, i); + } + fprintf(f, "\treturn -1;\n\t}\n"); +} + + +//write the Quantization info for each node field(Quant and BIFS-Anim info) +void WriteNodeQuant(FILE *f, BNode *n) +{ + u32 i; + fprintf(f, "static Bool %s_get_aq_info(GF_Node *n, u32 FieldIndex, u8 *QType, u8 *AType, Fixed *b_min, Fixed *b_max, u32 *QT13_bits)\n{\n\tswitch (FieldIndex) {\n", n->name); + + for (i=0; iFields) ; i++ ) { + BField *bf = gf_list_get(n->Fields, i); + if (!bf->hasAnim && !bf->hasQuant) continue; + + fprintf(f, "\tcase %d:\n", i); + //Anim Type + fprintf(f, "\t\t*AType = %d;\n", bf->AnimType); + //Quant Type + fprintf(f, "\t\t*QType = %s;\n", bf->quant_type); + if (!strcmp(bf->quant_type, "13")) + fprintf(f, "\t\t*QT13_bits = %s;\n", bf->qt13_bits); + + //Bounds + if (bf->hasBounds) { + if (!strcmp(bf->b_min, "+I") || !strcmp(bf->b_min, " +I") || !strcmp(bf->b_min, "I")) { + fprintf(f, "\t\t*b_min = FIX_MAX;\n"); + } else if (!strcmp(bf->b_min, "-I") || !strcmp(bf->b_min, "-65536")) { + fprintf(f, "\t\t*b_min = FIX_MIN;\n"); + } else { + fprintf(f, "\t\t*b_min = %s;\n", GetFixedPrintout(bf->b_min)); + } + if (!strcmp(bf->b_max, "+I") || !strcmp(bf->b_max, " +I") || !strcmp(bf->b_max, "I") || !strcmp(bf->b_max, "65535")) { + fprintf(f, "\t\t*b_max = FIX_MAX;\n"); + } else { + fprintf(f, "\t\t*b_max = %s;\n", GetFixedPrintout(bf->b_max)); + } + } + fprintf(f, "\t\treturn 1;\n"); + } + fprintf(f, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); +} + +void WriteNodeCode(GF_List *BNodes) +{ + FILE *f; + char token[20], tok[20]; + char *store; + u32 i, j, k, go; + BField *bf; + BNode *n; + + f = BeginFile("mpeg4_nodes", 1); + + fprintf(f, "#include \n\n"); + fprintf(f, "\n#include \n"); + + fprintf(f, "\n#ifndef GPAC_DISABLE_VRML\n"); + + + for (k=0; kskip_impl) continue; + + fprintf(f, "\n/*\n\t%s Node deletion\n*/\n\n", n->name); + fprintf(f, "static void %s_Del(GF_Node *node)\n{\n\tM_%s *p = (M_%s *) node;\n", n->name, n->name, n->name); + + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + //nothing on child events + if (!strcmp(bf->name, "addChildren")) continue; + if (!strcmp(bf->name, "removeChildren")) continue; + + //delete all children node + if (!strcmp(bf->name, "children") && stricmp(n->name, "audioBuffer")) { + is_parent = 1; + continue; + } + + //delete ALL fields that must be deleted: this includes eventIn and out since + //all fields are defined in the node + if (!strcmp(bf->familly, "MFInt") + || !strcmp(bf->familly, "MFFloat") + || !strcmp(bf->familly, "MFDouble") + || !strcmp(bf->familly, "MFBool") + || !strcmp(bf->familly, "MFInt32") + || !strcmp(bf->familly, "MFColor") + || !strcmp(bf->familly, "MFRotation") + || !strcmp(bf->familly, "MFString") + || !strcmp(bf->familly, "MFTime") + || !strcmp(bf->familly, "MFVec2f") + || !strcmp(bf->familly, "MFVec3f") + || !strcmp(bf->familly, "MFVec4f") + || !strcmp(bf->familly, "MFVec2d") + || !strcmp(bf->familly, "MFVec3d") + || !strcmp(bf->familly, "MFURL") + || !strcmp(bf->familly, "MFScript") + || !strcmp(bf->familly, "SFString") + || !strcmp(bf->familly, "SFURL") + || !strcmp(bf->familly, "SFImage") + || !strcmp(bf->familly, "MFAttrRef") + ) { + char szName[500]; + strcpy(szName, bf->familly); + strlwr(szName); + fprintf(f, "\tgf_sg_%s_del(p->%s);\n", szName, bf->name); + } + else if (!strcmp(bf->familly, "SFCommandBuffer")) { + fprintf(f, "\tgf_sg_sfcommand_del(p->%s);\n", bf->name); + } + else if (strstr(bf->familly, "Node")) { + //this is a POINTER to a node + if (strstr(bf->familly, "SF")) { + fprintf(f, "\tgf_node_unregister((GF_Node *) p->%s, (GF_Node *) p);\t\n", bf->name); + } else { + //this is a POINTER to a chain + fprintf(f, "\tgf_node_unregister_children((GF_Node *) p, p->%s);\t\n", bf->name); + } + } + } + if (!strcmp(n->name, "CacheTexture")) { + fprintf(f, "\tif (p->data) gf_free(p->data);\n"); + } + if (is_parent) fprintf(f, "\tgf_sg_vrml_parent_destroy((GF_Node *) p);\t\n"); + fprintf(f, "\tgf_node_free((GF_Node *) p);\n}\n\n"); + + //node fields + WriteNodeFields(f, n); + WriteNodeQuant(f, n); + + // + // Constructor + // + + fprintf(f, "\n\nGF_Node *%s_Create()\n{\n\tM_%s *p;\n\tGF_SAFEALLOC(p, M_%s);\n", n->name, n->name, n->name); + fprintf(f, "\tif(!p) return NULL;\n"); + fprintf(f, "\tgf_node_setup((GF_Node *)p, TAG_MPEG4_%s);\n", n->name); + + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + //setup all children node + if (!strcmp(bf->name, "children") && stricmp(n->name, "audioBuffer")) { + fprintf(f, "\tgf_sg_vrml_parent_setup((GF_Node *) p);\n"); + break; + } + else if ( strstr(bf->familly, "Node") && strncmp(bf->type, "event", 5) ) { +#if 0 + //this is a POINTER to a node + if (strstr(bf->familly, "MF")) { + //this is a POINTER to a chain + fprintf(f, "\tp->%s = gf_list_new();\t\n", bf->name); + } +#endif + } + /*special case for SFCommandBuffer: we also create a command list*/ + if (!stricmp(bf->familly, "SFCommandBuffer")) { + fprintf(f, "\tp->%s.commandList = gf_list_new();\t\n", bf->name); + } + } + + //check if we have a child node + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + if ( !strcmp(bf->name, "children") || + ( !strstr(bf->type, "event") && strstr(bf->familly, "MF") && strstr(bf->familly, "Node")) ) { + sprintf(n->Child_NDT_Name, "NDT_SF%s", bf->familly+2); + break; + } + } + + fprintf(f, "\n\t/*default field values*/\n"); + + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + + //nothing on eventIn or Out + if (!strcmp(bf->type, "eventIn")) continue; + if (!strcmp(bf->type, "eventOut")) continue; + + if (!strcmp(bf->def, "")) continue; + + //no default on nodes + if (strstr(bf->familly, "Node")) continue; + //extract default falue + + // + // SF Fields + // + + //SFBool + if (!strcmp(bf->familly, "SFBool")) { + if (!strcmp(bf->def, "1") || !strcmp(bf->def, "TRUE")) + fprintf(f, "\tp->%s = 1;\n", bf->name); + } + //SFFloat + else if (!strcmp(bf->familly, "SFFloat")) { + fprintf(f, "\tp->%s = %s;\n", bf->name, GetFixedPrintout(bf->def)); + } + //SFTime + else if (!strcmp(bf->familly, "SFTime")) { + fprintf(f, "\tp->%s = %s;\n", bf->name, bf->def); + } + //SFInt32 + else if (!strcmp(bf->familly, "SFInt32")) { + fprintf(f, "\tp->%s = %s;\n", bf->name, bf->def); + } + //SFURL + else if (!strcmp(bf->familly, "SFURL")) { + if (strcmp(bf->def, "NULL")) + fprintf(f, "\tp->%s = %s;\n", bf->name, bf->def); + } + //SFColor + else if (!strcmp(bf->familly, "SFColor")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + + fprintf(f, "\tp->%s.red = %s;\n", bf->name, GetFixedPrintout(token)); + GetNextToken(token, " "); + fprintf(f, "\tp->%s.green = %s;\n", bf->name, GetFixedPrintout(token)); + GetNextToken(token, " "); + fprintf(f, "\tp->%s.blue = %s;\n", bf->name, GetFixedPrintout(token)); + } + //SFVec2f + else if (!strcmp(bf->familly, "SFVec2f")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.x = %s;\n", bf->name, GetFixedPrintout(token)); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.y = %s;\n", bf->name, GetFixedPrintout(token)); + } + //SFVec3f + else if (!strcmp(bf->familly, "SFVec3f")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.x = %s;\n", bf->name, GetFixedPrintout(token)); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.y = %s;\n", bf->name, GetFixedPrintout(token)); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.z = %s;\n", bf->name, GetFixedPrintout(token)); + } + //SFVec4f & SFRotation + else if (!strcmp(bf->familly, "SFVec4f") || !strcmp(bf->familly, "SFRotation")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.x = %s;\n", bf->name, GetFixedPrintout(token)); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.y = %s;\n", bf->name, GetFixedPrintout(token)); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.z = %s;\n", bf->name, GetFixedPrintout(token)); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(f, "\tp->%s.q = %s;\n", bf->name, GetFixedPrintout(token)); + } + //SFString + else if (!strcmp(bf->familly, "SFString")) { + fprintf(f, "\tp->%s.buffer = (char*)gf_malloc(sizeof(char) * %u);\n", bf->name, (u32) strlen(bf->def)+1); + fprintf(f, "\tstrcpy(p->%s.buffer, \"%s\");\n", bf->name, bf->def); + } + + // + // MF Fields + // + //MFFloat + else if (!strcmp(bf->familly, "MFFloat")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, " ,")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (SFFloat*)gf_malloc(sizeof(SFFloat)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, " ,")) go = 0; + TranslateToken(token); + fprintf(f, "\tp->%s.vals[%d] = %s;\n", bf->name, j, GetFixedPrintout(token)); + j+=1; + } + } + //MFVec2f + else if (!strcmp(bf->familly, "MFVec2f")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (SFVec2f*)gf_malloc(sizeof(SFVec2f)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].x = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].y = %s;\n", bf->name, j, GetFixedPrintout(tok)); + j+=1; + CurrentLine = store; + } + } + //MFVec3f + else if (!strcmp(bf->familly, "MFVec3f")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (SFVec3f *)gf_malloc(sizeof(SFVec3f)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].x = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].y = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].z = %s;\n", bf->name, j, GetFixedPrintout(tok)); + j+=1; + CurrentLine = store; + } + } + //MFVec4f + else if (!strcmp(bf->familly, "MFVec4f") || !strcmp(bf->familly, "MFRotation")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (GF_Vec4*)gf_malloc(sizeof(GF_Vec4)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].x = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].y = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].z = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d].q = %s;\n", bf->name, j, GetFixedPrintout(tok)); + j+=1; + CurrentLine = store; + } + } + //MFInt32 + else if (!strcmp(bf->familly, "MFInt32")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (SFInt32*)gf_malloc(sizeof(SFInt32)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + fprintf(f, "\tp->%s.vals[%d] = %s;\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFColor + else if (!strcmp(bf->familly, "MFColor")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (SFColor*)gf_malloc(sizeof(SFColor)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + fprintf(f, "\tp->%s.vals[%d].red = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + fprintf(f, "\tp->%s.vals[%d].green = %s;\n", bf->name, j, GetFixedPrintout(tok)); + GetNextToken(tok, " "); + fprintf(f, "\tp->%s.vals[%d].blue = %s;\n", bf->name, j, GetFixedPrintout(tok)); + j+=1; + CurrentLine = store; + } + } + //MFString + else if (!strcmp(bf->familly, "MFString")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (char**)gf_malloc(sizeof(SFString)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " \""); + fprintf(f, "\tp->%s.vals[%d] = (char*)gf_malloc(sizeof(char) * %u);\n", bf->name, j, (u32) strlen(tok)+1); + fprintf(f, "\tstrcpy(p->%s.vals[%d], \"%s\");\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFTime + else if (!strcmp(bf->familly, "MFTime")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(f, "\tp->%s.vals = (SFTime*)gf_malloc(sizeof(SFTime)*%d);\n", bf->name, j); + fprintf(f, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " \""); + TranslateToken(tok); + fprintf(f, "\tp->%s.vals[%d] = %s;\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + + //other nodes + else if (!strcmp(bf->familly, "SFImage")) { + //we currently only have SFImage, with NO texture so do nothing + } + //unknown init (for debug) + else { + fprintf(f, "UNKNOWN FIELD (%s);\n", bf->familly); + + } + } + fprintf(f, "\treturn (GF_Node *)p;\n}\n\n"); + + } + + fprintf(f, "\n\n\n"); + + //creator function + fprintf(f, "GF_Node *gf_sg_mpeg4_node_new(u32 NodeTag)\n{\n\tswitch (NodeTag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s:\n\t\treturn %s_Create();\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn NULL;\n\t}\n}\n\n"); + + fprintf(f, "const char *gf_sg_mpeg4_node_get_class_name(u32 NodeTag)\n{\n\tswitch (NodeTag) {\n"); + for (i=0; iskip_impl) fprintf(f, "\tcase TAG_MPEG4_%s:\n\t\treturn \"%s\";\n", n->name, n->name); + } + fprintf(f, "\tdefault:\n\t\treturn \"Unknown Node\";\n\t}\n}\n\n"); + + fprintf(f, "void gf_sg_mpeg4_node_del(GF_Node *node)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s:\n\t\t%s_Del(node); return;\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn;\n\t}\n}\n\n"); + + fprintf(f, "u32 gf_sg_mpeg4_node_get_field_count(GF_Node *node, u8 code_mode)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s:return %s_get_field_count(node, code_mode);\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); + + fprintf(f, "GF_Err gf_sg_mpeg4_node_get_field(GF_Node *node, GF_FieldInfo *field)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s: return %s_get_field(node, field);\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn GF_BAD_PARAM;\n\t}\n}\n\n"); + + fprintf(f, "GF_Err gf_sg_mpeg4_node_get_field_index(GF_Node *node, u32 inField, u8 code_mode, u32 *fieldIndex)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s: return %s_get_field_index(node, inField, code_mode, fieldIndex);\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn GF_BAD_PARAM;\n\t}\n}\n\n"); + + fprintf(f, "Bool gf_sg_mpeg4_node_get_aq_info(GF_Node *node, u32 FieldIndex, u8 *QType, u8 *AType, Fixed *b_min, Fixed *b_max, u32 *QT13_bits)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s: return %s_get_aq_info(node, FieldIndex, QType, AType, b_min, b_max, QT13_bits);\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); + + fprintf(f, "u32 gf_sg_mpeg4_node_get_child_ndt(GF_Node *node)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl && strlen(n->Child_NDT_Name) ) { + fprintf(f, "\tcase TAG_MPEG4_%s: return %s;\n", n->name, n->Child_NDT_Name); + } + } + fprintf(f, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); + + + fprintf(f, "\nu32 gf_node_mpeg4_type_by_class_name(const char *node_name)\n{\n\tif(!node_name) return 0;\n"); + for (i=0; iskip_impl) continue; + fprintf(f, "\tif (!strcmp(node_name, \"%s\")) return TAG_MPEG4_%s;\n", n->name, n->name); + } + fprintf(f, "\treturn 0;\n}\n\n"); + + fprintf(f, "s32 gf_sg_mpeg4_node_get_field_index_by_name(GF_Node *node, char *name)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(f, "\tcase TAG_MPEG4_%s: return %s_get_field_index_by_name(name);\n", n->name, n->name); + } + } + fprintf(f, "\tdefault:\n\t\treturn -1;\n\t}\n}\n\n"); + + fprintf(f, "\n#endif /*GPAC_DISABLE_VRML*/\n"); + + EndFile(f, "", 1); +} + +void ParseTemplateFile(FILE *nodes, GF_List *BNodes, GF_List *NDTs, u32 version) +{ + char sLine[2000]; + char token[100]; + char *p; + BNode *n; + BField *f; + u32 j, i, k; + + //get lines one by one + n = NULL; + while (!feof(nodes)) { + fgets(sLine, 2000, nodes); + //skip comment and empty lines + if (sLine[0] == '#') continue; + if (sLine[0] == '\n') continue; + + CurrentLine = sLine; + + //parse the line till end of line + while (GetNextToken(token, " \t")) { + + //this is a new node + if (!strcmp(token, "PROTO") ) { + n = BlankNode(); + n->version = version; + gf_list_add(BNodes, n); + + //get its name + GetNextToken(n->name, " \t["); + //extract the NDTs + GetNextToken(token, "\t[ %#="); + if (strcmp(token, "NDT")) { + printf("Corrupted template file\n"); + return; + } + while (1) { + GetNextToken(token, "=, \t"); + //done with NDTs + if (token[0] == '%') break; + + //update the NDT list + CheckInTable(token, NDTs); + p = gf_malloc(strlen(token)+1); + strcpy(p, token); + gf_list_add(n->NDT, p); + } + + //extract the coding type + if (strcmp(token, "%COD")) { + printf("Corrupted template file\n"); + return; + } else { + GetNextToken(token, "= "); + if (token[0] == 'N') { + n->codingType = 0; + } else { + n->codingType = 1; + } + } + } + //this is NOT a field + else if (token[0] == ']' || token[0] == '{' || token[0] == '}' ) { + break; + } + //parse a field + else { + if (!n) { + printf("Corrupted template file\n"); + return; + } + f = BlankField(); + gf_list_add(n->Fields, f); + + //get the field type + strcpy(f->type, token); + GetNextToken(f->familly, " \t"); + GetNextToken(f->name, " \t"); + //fix for our own code :( + if (!strcmp(f->name, "tag")) strcpy(f->name, "_tag"); + if (!strcmp(f->name, "auto")) strcpy(f->name, "_auto"); + + //has default + skip_sep(" \t"); + if (GetNextToken(token, "#\t")) { + j=0; + while (token[j] == ' ') j+=1; + if (token[j] == '[') j+=1; + if (token[j] == '"') j+=1; + + if (token[j] != '"' && token[j] != ']') { + strcpy(f->def, token+j); + j=1; + while (j) { + switch (f->def[strlen(f->def)-1]) { + case ' ': + case '"': + case ']': + f->def[strlen(f->def)-1] = 0; + break; + default: + j=0; + break; + } + } + } else { + strcpy(f->def, ""); + } + if (!strcmp(f->familly, "SFFloat")) { + if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { + strcpy(f->def, "FIX_MAX"); + } else if (!strcmp(f->def, "-I")) { + strcpy(f->def, "FIX_MIN"); + } + } else if (!strcmp(f->familly, "SFTime")) { + if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { + strcpy(f->def, "FIX_MAX"); + } else if (!strcmp(f->def, "-I")) { + strcpy(f->def, "FIX_MIN"); + } + } else if (!strcmp(f->familly, "SFInt32")) { + if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { + strcpy(f->def, "0x80000000"); + } else if (!strcmp(f->def, "-I")) { + strcpy(f->def, "GF_INT_MIN"); + } + } + } + //has other + while (GetNextToken(token, " \t#%=")) { + switch (token[0]) { + //bounds + case 'b': + f->hasBounds = 1; + GetNextToken(f->b_min, "[(,"); + GetNextToken(f->b_max, ")]"); + break; + case 'q': + f->hasQuant = 1; + GetNextToken(f->quant_type, " \t"); + if (!strcmp(f->quant_type, "13")) + GetNextToken(f->qt13_bits, " \t"); + break; + case 'a': + f->hasAnim = 1; + GetNextToken(token, " \t"); + f->AnimType = atoi(token); + break; + default: + break; + } + } + } + } + } + + + for (k=0; kFields); i++) { + f = gf_list_get(n->Fields, i); + //nothing on events + if (!strcmp(f->type, "eventIn")) continue; + if (!strcmp(f->type, "eventOut")) continue; + if (!strcmp(f->def, "")) continue; + if (strstr(f->familly, "Node")) continue; + n->hasDefault = 1; + } + } +} + + +void WriteNodeDump(FILE *f, BNode *n) +{ + u32 i; + + fprintf(f, "static const char *%s_FieldName[] = {\n", n->name); + for (i=0; iFields); i++) { + BField *bf = gf_list_get(n->Fields, i); + if (!i) { + fprintf(f, " \"%s\"", bf->name); + } else { + fprintf(f, ", \"%s\"", bf->name); + } + } + fprintf(f, "\n};\n\n"); +} + +void parse_profile(GF_List *nodes, FILE *prof) +{ + char sLine[2000]; + BNode *n; + Bool found; + u32 i; + + while (!feof(prof)) { + fgets(sLine, 2000, prof); + //skip comment and empty lines + if (sLine[0] == '#') continue; + if (sLine[0] == '\n') continue; + if (strstr(sLine, "Proximity")) + found = 0; + found = 1; + while (found) { + switch (sLine[strlen(sLine)-1]) { + case '\n': + case '\r': + case ' ': + sLine[strlen(sLine)-1] = 0; + break; + default: + found = 0; + break; + } + } + +// if (0 && !stricmp(sLine, "Appearance") || !stricmp(sLine, "Shape") || !stricmp(sLine, "Sound2D") ) { + if (0) { + printf("Warning: cannot disable node %s (required in all BIFS profiles)\n", sLine); + } else { + found = 0; + for (i=0; iname, sLine)) { + n->skip_impl = 1; + found = 1; + break; + } + } + if (!found) printf("cannot disable %s: node not found\n", sLine); + } + } +} + +char *format_bit_string(char *str, u32 val, u32 nb_bits) +{ + u32 i, len; + strcpy(str, ""); + while (nb_bits) { + strcat(str, (val%2) ? "1" : "0"); + val>>=1; + nb_bits--; + } + len = strlen(str); + for (i=0; i ((1<\n"\ + "\n"\ + "\n"\ + "NdtListV%d.html\n"\ + "\n"\ + "\n"\ + "Node Coding Tables for BIFS Version %d group\n" + ,gf_gpac_version(), i+1, i+1); + + for (j=0; jversion != i+1) continue; + if (!IsNodeInTable(n, ndt)) continue; + nb_in_ndt ++; + } + + if (!nb_in_ndt) continue; + + fprintf(f, "
\n"\ + "\n"\ + "\n"\ + "\n", ndt, ndt, nb_in_ndt); + + nb_bits = GetBitsCount(nb_in_ndt); + fprintf(f, "\n", format_bit_string(szStr, 0, nb_bits)); + + idx = 1; + for (k=0; kversion != i+1) continue; + if (!IsNodeInTable(n, ndt)) continue; + + + n->hasDef = n->hasIn = n->hasOut = n->hasDyn = 0; + for (l=0; lFields); l++) { + BField *bf = gf_list_get(n->Fields, l); + if (!strcmp(bf->type, "field") || !strcmp(bf->type, "exposedField")) { + n->hasDef += 1; + } + if (!strcmp(bf->type, "eventIn") || !strcmp(bf->type, "exposedField")) { + n->hasIn += 1; + //check for anim + if (bf->hasAnim) n->hasDyn += 1; + } + if (!strcmp(bf->type, "eventOut") || !strcmp(bf->type, "exposedField")) { + n->hasOut += 1; + } + } + + fprintf(f, "\n", n->name, format_bit_string(szStr, idx, nb_bits), get_nb_bits(n->hasDef), get_nb_bits(n->hasIn), get_nb_bits(n->hasOut), get_nb_bits(n->hasDyn)); + idx++; + } + + fprintf(f, "
%s%u nodes
reserved

%s

 

 

 

 

%s

%s

%d DEF bits

%d IN bits

%d OUT bits

%d DYN bits

\n"); + } + gf_fclose(f); + } + +} + +int main (int argc, char **argv) +{ + Bool generate_ndt = 0; + char szTempFile[1024]; + FILE *nodes, *ndt_c, *ndt_h, *fskip; + GF_List *BNodes, *NDTs; + u32 i, j, nbVersion; + BNode *n; + BField *bf; + + if (argc < 1) { + PrintUsage(); + return 0; + } + + BNodes = gf_list_new(); + NDTs = gf_list_new(); + + + + fskip = NULL; + if (argc>1) { + i=1; + if (!strcmp(argv[i], "-ndt")) { + generate_ndt = 1; + } else if (argv[i][0]=='-') { + fskip = gf_fopen(argv[i+1], "rt"); + if (!fskip) { + printf("file %s not found\n", argv[i+1]); + return 0; + } + i+=2; + } + } + nbVersion=1; + while (1) { + sprintf(szTempFile, "templates%u.txt", nbVersion); + nodes = gf_fopen(szTempFile, "rt"); + if (!nodes) { + sprintf(szTempFile, "template%u.txt", nbVersion); + nodes = gf_fopen(szTempFile, "rt"); + } + if (!nodes) break; + + //all nodes are in the same list but we keep version info + ParseTemplateFile(nodes, BNodes, NDTs, nbVersion); + + + //special case for viewport: it is present in V1 but empty + if (nbVersion==1) CheckInTable("SFViewportNode", NDTs); + nbVersion++; + gf_fclose(nodes); + } + nbVersion--; + printf("BIFS tables parsed: %d versions\n", nbVersion); + + if (generate_ndt) { + generate_ndts(NDTs, BNodes, nbVersion); + goto exit; + } + + if (fskip) { + parse_profile(BNodes, fskip); + gf_fclose(fskip); + } + + //write the nodes def + WriteNodesFile(BNodes, NDTs, nbVersion); + //write all nodes init stuff + WriteNodeCode(BNodes); + + //write all NDTs + ndt_h = BeginFile("NDT", 0); + ndt_c = BeginFile("NDT", 1); + + fprintf(ndt_h, "#include \n\n"); + fprintf(ndt_h, "\n\n#ifndef GPAC_DISABLE_BIFS\n"); + + fprintf(ndt_c, "\n\n#include \n"); + fprintf(ndt_c, "\n\n#ifndef GPAC_DISABLE_BIFS\n"); + + //prepare the encoding file + fprintf(ndt_h, "\n\nu32 ALL_GetNodeType(const u32 *table, const u32 count, u32 NodeTag, u32 Version);\n\n"); + fprintf(ndt_c, "\n\nu32 ALL_GetNodeType(const u32 *table, const u32 count, u32 NodeTag, u32 Version)\n{\n\tu32 i = 0;"); + fprintf(ndt_c, "\n\twhile (iskip_impl) continue; + for (j=0; jFields); j++) { + bf = gf_list_get(n->Fields, j); + if (!strcmp(bf->name, "children")) { + fprintf(ndt_c, "\tcase TAG_MPEG4_%s:\n\t\treturn NDT_SF%s;\n", n->name, bf->familly+2); + break; + } + } + } + fprintf(ndt_c, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); + fprintf(ndt_c, "\n\n#endif /*GPAC_DISABLE_BIFS*/\n\n"); + + fprintf(ndt_h, "\n\n#endif /*GPAC_DISABLE_BIFS*/\n\n"); + EndFile(ndt_h, "NDT", 0); + EndFile(ndt_c, "", 1); + + +exit: + //free NDTs + while (gf_list_count(NDTs)) { + char *tmp = gf_list_get(NDTs, 0); + gf_free(tmp); + gf_list_rem(NDTs, 0); + } + gf_list_del(NDTs); + //free nodes + while (gf_list_count(BNodes)) { + n = gf_list_get(BNodes, 0); + gf_list_rem(BNodes, 0); + while (gf_list_count(n->NDT)) { + char *tmp = gf_list_get(n->NDT, 0); + gf_free(tmp); + gf_list_rem(n->NDT, 0); + } + gf_list_del(n->NDT); + while (gf_list_count(n->Fields)) { + bf = gf_list_get(n->Fields, 0); + gf_free(bf); + gf_list_rem(n->Fields, 0); + } + gf_list_del(n->Fields); + gf_free(n); + } + gf_list_del(BNodes); + + return 0; +} + diff --git a/applications/generators/MPEG4/skip.txt b/applications/generators/MPEG4/skip.txt new file mode 100644 index 0000000..b064f06 --- /dev/null +++ b/applications/generators/MPEG4/skip.txt @@ -0,0 +1,181 @@ +#MPEG-4 v1.0 + +#Anchor +#AnimationStream +#AudioBuffer +#AudioClip +#AudioDelay +#AudioFX +#AudioMix +#AudioSource +#AudioSwitch +#Background2D +#Bitmap +#Circle +#Curve2D +#Background +#Billboard +#Box +#Color +#ColorInterpolator +#Collision +#CompositeTexture2D +#CompositeTexture3D +#Conditional +#Cone +#Coordinate2D +#Coordinate +#CoordinateInterpolator2D +#CoordinateInterpolator +#Cylinder +#CylinderSensor +#DirectionalLight +#DiscSensor +#ElevationGrid +Expression +#Extrusion +Face +FaceDefMesh +FaceDefTables +FaceDefTransform +FAP +FDP +FIT +#Fog +#FontStyle +#Form +#Group +#ImageTexture +#IndexedFaceSet +#IndexedFaceSet2D +#IndexedLineSet +#IndexedLineSet2D +#Inline +#LOD +#Layer2D +#Layer3D +#Layout +#LineProperties +#ListeningPoint +#Material2D +#Material +#MovieTexture +#NavigationInfo +#Normal +#NormalInterpolator +#OrderedGroup +#OrientationInterpolator +#PointLight +#PointSet +#PointSet2D +#PixelTexture +#PlaneSensor2D +#PlaneSensor +#PositionInterpolator2D +#PositionInterpolator +#ProximitySensor2D +#ProximitySensor +#QuantizationParameter +#Rectangle +#ScalarInterpolator +#Script +#Sound +#Sphere +#SphereSensor +#SpotLight +#Switch +#TermCap +#TextureCoordinate +#TextureTransform +#Text +#TimeSensor +#TouchSensor +#Transform2D +#Transform +#Valuator +#Viewpoint +#VisibilitySensor +Viseme +#WorldInfo + + +#MPEG-4 v2.0 +#AcousticMaterial +#AcousticScene +#ApplicationWindow +BAP +BDP +Body +BodyDefTable +BodySegmentConnectionHint +#DirectiveSound +#Hierarchical3DMesh +#MaterialKey +#PerceptualParameters + +#MPEG-4 v3.0 +#TemporalTransform +#TemporalGroup +#ServerCommand + +#MPEG-4 v4.0 +#InputSensor +#MatteTexture +#MediaBuffer +#MediaControl +#MediaSensor + +#MPEG-4 V5 +BitWrapper +#CoordinateInterpolator4D +DepthImage +FFD +Implicit +XXLFM_Appearance +XXLFM_BlendList +XXLFM_FrameList +XXLFM_LightMap +XXLFM_SurfaceMapList +XXLFM_ViewMapList +MeshGrid +#NonLinearDeformer +NurbsCurve +NurbsCurve2D +NurbsSurface +OctreeImage +XXParticles +XXParticleInitBox +XXPlanarObstacle +XXPointAttractor +PointTexture +#PositionAnimator +#PositionAnimator2D +#PositionInterpolator4D +ProceduralTexture +Quadric +SBBone +SBMuscle +SBSegment +SBSite +SBSkinnedModel +SBVCAnimation +#ScalarAnimator +SimpleTexture +SolidRep +SubdivisionSurface +SubdivSurfaceSector +WaveletSubdivisionSurface + +#MPEG-4 V6 +#Clipper2D +#ColorTransform +#Ellipse +#LinearGradient +#PathLayout +#RadialGradient +SynthesizedTexture +#TransformMatrix2D +#Viewport +#XCurve2D +#XFontStyle +#XLineProperties diff --git a/applications/generators/MPEG4/templates1.txt b/applications/generators/MPEG4/templates1.txt new file mode 100644 index 0000000..a64c34e --- /dev/null +++ b/applications/generators/MPEG4/templates1.txt @@ -0,0 +1,1292 @@ +#-- Version 1 --# +# templates for the BIFS nodes +# ============================= +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# Jan 16, 2003 (MBS) AudioBuffer.length changed to exposedField +# Dec 10, 1997 (Yuval Fisher) SFTimerNode changed to SFTimeSensorNode +# Dec 10, 1997 (Yuval Fisher) SFFitNode changed to SFFITNode +# Dec 10, 1997 (Yuval Fisher) children in Form changed to exposedField +# Dec 11, 1997 (Yuval Fisher) exposedfield changed to exposedField in Form +# +# Dec 19, 1997 (Yuval Fisher - Following Alexandros' Parser errors) +# - FALSE removed from isActive field on AudioClip node +# - %b=(-I,+I) removed from set_fraction on ColorInterpolator +# - justify field value in FontStyle enclosed in [] +# - %b=(-I,+I) removed from set_fraction on ScalarInterpolator +# - removed -1 from duration_changed and FALSE from isActive in VideoObject2D +# - changed renderedFace to exposedField in Face node +# - changed renderedFace to exposedField in Body node -- just temporary anyway +# - animation type on height field of elevation grid changed from 11 to 7 +# - vector field of Normal node, transposed quantization=9 and animation=4 +# - changed eventIn to exposedField in background, fog, navigationInfo,viewpoint +# of Layer3D +# - Same as above for Composite3DTexture +# +# Dec 22, 1997 (Yuval Fisher) added Conditional back. +# March 16: +# Reordered alphabetically +# Sound field spatialize changed to exposedfield +# Sound added addChildren and deleteChildren to all nodes with children +# Changed children2D field to be children in CompositeMap +# Eliminate ucs_2 field from StreamingText +# Do the childrenLayer fields need add and remove eventIns ? +# sound field changed to source in Sound2D +# +# March 24 +# Many changes based on changes adopted in Tokyo +# added bounding boxes to Layer2D and Layer3D +# +# March 25 +# Added Animation to various nodes - Checked template with Julien. +# +# March 30 +# 0 -> FALSE and MFURL:null->[] corrections. +# +# April 13 +# Added TermCap per Joern +# +# May 12 +# add and remove ChildrenLayer in Layer3d was missing 'Layer' +# term cap had int instead of int32 +# +# May 19 +# DiscSensor center had a wrong quantization type +# May 26 +# drawOrderMin and Max were SFVec3f in Quantization Parameter +# June 8 +# Layer2D,3D Has an SFInt32 depth, converted to SFFloat +# Text should hav an MFString +# June 16 +# added to PlaneSensor: +#eventOut SFVec3f translation_changed +# +# +# to do: +# check if SFstring or SFBuffer ins AudioFX +# check children field for AudioSource +# fix FIT node bounds +# ask Eric if Face really has a URL -- try to eliminate it because it's an +# animation stream/can bounds be put on the faceDefMesh parameters why are +# these fields +# +# Oct 28 +# JS: Updated all template to correct DOC +# JCD updated Form, Layout and Valuator +# JS: To check FBA latest nodes +# November 2, 1998 (Mikael Bourges-Sevenier) AudioBuffer has MFAudioNode children not MFNode +# November 2, 1998 (Mikael Bourges-Sevenier) Face node is a SF3DNode also. +# November 2, 1998 (Mikael Bourges-Sevenier) modif I bound -> +I bound +# November 2, 1998 (Mikael Bourges-Sevenier) modif xFInt -> xFInt32 where missing +# November 2, 1998 (Mikael Bourges-Sevenier) removed childrenLayer in Layer2D +# Nov 2: JS: Integrated latest comments from Gabriel and Liam +# Nov 3: Still more bug corrections! The last ones? Added also type correspondance for +# semantic tables. +# JS: Changesd all fap values to be 0 by default +# Feb 9 99: Changed Layer2D/3D to be SF3DNode, suppresed cone anim stuff +# +# Mar 1 1999 +# group -> groups in Form plus other minor fixes +# Mar 8, 1999 +# Add speed to AudioSource +# June 15, 1999 YF +# Changed renderedFace to default [] from NULL & removed blank line +# +# Dec 20, 1999 YF +# FABs get default value of +I +# +# Aug 10, 2000 YF per Steve Woods: +# VisibilitySensor %b= missing from center and size fields +# +# July 10, 2001 YF per DCOR1 +# modify maxAngle, minAngle and diskAngle in CylinderSensor +# minAngle in DiscSensor changed to 0 +# +# Jan 2, 2002 YF per decision at meeting +# revert to capilized letter of factor, offset, sum in Valuator +# +# Aug 13, 2002 -- added activate field to Anchor per COR2 item +# modified field to exposedField in FontStyle per COR2 +# + +PROTO Anchor [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFString description "" +exposedField MFString parameter [] +exposedField MFURL url [] +eventIn SFBool activate +]{ +} + + +PROTO AnimationStream [ #%NDT=SFWorldNode,SF3DNode,SF2DNode,SFStreamingNode %COD=N +exposedField SFBool loop FALSE +exposedField SFFloat speed 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=(-I,+I) +exposedField SFTime stopTime 0 #%b=(-I,+I) +exposedField MFURL url [] +eventOut SFTime duration_changed +eventOut SFBool isActive +] { +} + + +PROTO Appearance [ #%NDT=SFWorldNode,SFAppearanceNode %COD=N +exposedField SFMaterialNode material NULL +exposedField SFTextureNode texture NULL +exposedField SFTextureTransformNode textureTransform NULL +] { +} + +PROTO AudioBuffer [ #%NDT=SFWorldNode,SFAudioNode %COD=N +exposedField SFBool loop FALSE +exposedField SFFloat pitch 1 #%b=[0,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=[0,+I) #%q=0 +exposedField SFTime stopTime 0 #%b=[0,+I) #%q=0 +exposedField MFAudioNode children [] +exposedField SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +exposedField MFInt32 phaseGroup [1] +exposedField SFFloat length 0.0 #%b=[0,+I) #%q=0 +eventOut SFTime duration_changed +eventOut SFBool isActive +] { +} + +PROTO AudioClip [ #%NDT=SFWorldNode,SFAudioNode,SFStreamingNode %COD=N +exposedField SFString description "" +exposedField SFBool loop FALSE +exposedField SFFloat pitch 1.0 #%b=[0,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=(-I,+I) +exposedField SFTime stopTime 0 #%b=(-I,+I) +exposedField MFURL url [] +eventOut SFTime duration_changed +eventOut SFBool isActive +] { +} + +PROTO AudioDelay [ #%NDT=SFWorldNode,SFAudioNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField SFTime delay 0 #%b=[0,+I) +field SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +field MFInt32 phaseGroup [] #%b=[0,255] #%q=13 8 +] { +} + +PROTO AudioFX [ #%NDT=SFWorldNode,SFAudioNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField SFString orch "" +exposedField SFString score "" +exposedField MFFloat params [] #%b=(-I,+I) #%q=0 #%a=7 +field SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +field MFInt32 phaseGroup [] #%b=[0,255] #%q=13 8 +] { +} + +PROTO AudioMix [ #%NDT=SFWorldNode,SFAudioNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField SFInt32 numInputs 1 #%b=[1,255] #%q=13 8 +exposedField MFFloat matrix [] #%b=[0,1] #%q=0 #%a=7 +field SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +field MFInt32 phaseGroup [] #%b=[0,255] #%q=13 8 +] { +} + +PROTO AudioSource [ #%NDT=SFWorldNode,SFAudioNode,SFStreamingNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField MFURL url [] +exposedField SFFloat pitch 1 #%b=[0,+I) #%q=0 #%a=7 +exposedField SFFloat speed 1 #%b=[0,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 +exposedField SFTime stopTime 0 +field SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +field MFInt32 phaseGroup [] #%b=[0,255] #%q=13 8 +] { +} + +PROTO AudioSwitch [ #%NDT=SFWorldNode,SFAudioNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField MFInt32 whichChoice [] #%b=[0,1] #%q=13 1 +field SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +field MFInt32 phaseGroup [] #%b=[0,255] #%q=13 8 +] { +} + +PROTO Background [ #%NDT=SFWorldNode,SF3DNode,SFBackground3DNode %COD=N +eventIn SFBool set_bind +exposedField MFFloat groundAngle [] #%b=[0,1.5707963] #%q=6 #%a=8 +exposedField MFColor groundColor [] #%b=[0,1] #%q=4 #%a=4 +exposedField MFURL backUrl [] +exposedField MFURL bottomUrl [] +exposedField MFURL frontUrl [] +exposedField MFURL leftUrl [] +exposedField MFURL rightUrl [] +exposedField MFURL topUrl [] +exposedField MFFloat skyAngle [] #%b=[0,3.14159265] #%q=6 #%a=8 +exposedField MFColor skyColor [ 0 0 0 ] #%b=[0,1] #%q=4 #%a=4 +eventOut SFBool isBound +] { +} + +PROTO Background2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode,SFBackground2DNode %COD=N +eventIn SFBool set_bind +exposedField SFColor backColor 0 0 0 #%b=[0,1] #%q=4 #%a=4 +exposedField MFURL url [] +eventOut SFBool isBound +] { +} + +PROTO Billboard [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFVec3f axisOfRotation 0 1 0 #%q=9 #%a=9 +] { +} + +# modified JCD +PROTO Bitmap [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec2f scale -1 -1 #%b=[-1,+I) #%q=12 #%a=12 +] { +} + + +PROTO Box [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +field SFVec3f size 2 2 2 #%b=[0,+I) #%q=11 +] { +} + +PROTO Circle [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFFloat radius 1 #%b=[0,+I) #%q=12 #%a=7 +] { +} + +PROTO Collision [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFBool collide TRUE +field SF3DNode proxy NULL +eventOut SFTime collideTime +] { +} + +PROTO Color [ #%NDT=SFWorldNode,SFColorNode %COD=N +exposedField MFColor color [] #%b=[0,1] #%q=4 #%a=4 +] { +} + +PROTO ColorInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFColor keyValue [] #%b=[0,1] #%q=4 +eventOut SFColor value_changed +] { +} + +PROTO CompositeTexture2D [ #%NDT=SFWorldNode,SFTextureNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFInt32 pixelWidth -1 #%b=[0,65535] #%q=13 16 +exposedField SFInt32 pixelHeight -1 #%b=[0,65535] #%q=13 16 +exposedField SFBackground2DNode background NULL +exposedField SFViewportNode viewport NULL +field SFInt32 repeatSandT 3 #%b=[0,3] #%q=13 2 +] { +} + +PROTO CompositeTexture3D [ #%NDT=SFWorldNode,SFTextureNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFInt32 pixelWidth -1 #%b=[0,65535] #%q=13 16 +exposedField SFInt32 pixelHeight -1 #%b=[0,65535] #%q=13 16 +exposedField SFBackground3DNode background NULL +exposedField SFFogNode fog NULL +exposedField SFNavigationInfoNode navigationInfo NULL +exposedField SFViewpointNode viewpoint NULL +field SFBool repeatS TRUE +field SFBool repeatT TRUE +] { +} + + +PROTO Conditional [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +eventIn SFBool activate +eventIn SFBool reverseActivate FALSE +exposedField SFCommandBuffer buffer "" +eventOut SFBool isActive +]{ +} + +PROTO Cone [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +field SFFloat bottomRadius 1 #%b=[0,+I) #%q=11 +field SFFloat height 2 #%b=[0,+I) #%q=11 +field SFBool side TRUE +field SFBool bottom TRUE +] { +} + +PROTO Coordinate [ #%NDT=SFWorldNode,SFCoordinateNode %COD=N +exposedField MFVec3f point [] #%b=(-I,+I) #%q=1 #%a=1 +] { +} + +PROTO Coordinate2D [ #%NDT=SFWorldNode,SFCoordinate2DNode %COD=N +exposedField MFVec2f point [] #%b=(-I,+I) #%q=2 #%a=2 +] { +} + +PROTO CoordinateInterpolator [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec3f keyValue [] #%b=(-I,+I) #%q=1 +eventOut MFVec3f value_changed +] { +} + +PROTO CoordinateInterpolator2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec2f keyValue [] #%b=(-I,+I) #%q=2 +eventOut MFVec2f value_changed +] { +} + + +PROTO Curve2D [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFCoordinate2DNode point [] +exposedField SFFloat fineness 0.5 #%b=[0,1] #%q=0 #%a=7 +exposedField MFInt32 type [] #%b=[0,3] #%q=13 2 +] { +} + +PROTO Cylinder [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +field SFBool bottom TRUE +field SFFloat height 2 #%b=[0,+I) #%q=11 +field SFFloat radius 1 #%b=[0,+I) #%q=11 +field SFBool side TRUE +field SFBool top TRUE +] { +} + +PROTO CylinderSensor [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFBool autoOffset TRUE +exposedField SFFloat diskAngle 0.262 #%b=[0,1.5707963] #%q=6 +exposedField SFBool enabled TRUE +exposedField SFFloat maxAngle -1 #%b=[-6.2831853,6.2831853] #%q=6 +exposedField SFFloat minAngle 0 #%b=[-6.2831853,6.2831853] #%q=6 +exposedField SFFloat offset 0 #%b=[0,6.2831853] #%q=6 +eventOut SFBool isActive +eventOut SFRotation rotation_changed +eventOut SFVec3f trackPoint_changed +] { +} + +PROTO DirectionalLight [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFFloat ambientIntensity 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFColor color 1 1 1 #%b=[0,1] #%q=4 #%a=4 +exposedField SFVec3f direction 0 0 -1 #%q=9 #%a=9 +exposedField SFFloat intensity 1 #%b=[0,1] #%q=4 #%a=8 +exposedField SFBool on TRUE +] { +} + +PROTO DiscSensor [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFBool autoOffset TRUE +exposedField SFBool enabled TRUE +exposedField SFFloat maxAngle -1 #%b=[-6.2831853,6.2831853] #%q=6 +exposedField SFFloat minAngle 0 #%b=[-6.2831853,6.2831853] #%q=6 +exposedField SFFloat offset 0 #%b=[0,6.2831853] #%q=6 +eventOut SFBool isActive +eventOut SFFloat rotation_changed +eventOut SFVec2f trackPoint_changed +] { +} + +PROTO ElevationGrid [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFFloat set_height +exposedField SFColorNode color NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field MFFloat height [] #%b=(-I,+I) #%q=11 #%a=7 +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFFloat creaseAngle 0.0 #%b=[0,6.2831853] #%q=6 +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field SFInt32 xDimension 0 #%b=[0,+I) #%q=11 +field SFFloat xSpacing 1.0 #%b=(0,+I) #%q=11 +field SFInt32 zDimension 0 #%b=[0,+I) #%q=11 +field SFFloat zSpacing 1.0 #%b=[0,+I) #%q=11 +] { +} + + +PROTO Expression [ #%NDT=SFWorldNode,SFExpressionNode %COD=N +exposedField SFInt32 expression_select1 0 #%b=(0,31) #%q=13 5 +exposedField SFInt32 expression_intensity1 0 #%b=(0,63) #%q=13 6 +exposedField SFInt32 expression_select2 0 #%b=(0,31) #%q=13 5 +exposedField SFInt32 expression_intensity2 0 #%b=(0,63) #%q=13 6 +exposedField SFBool init_face FALSE +exposedField SFBool expression_def FALSE +] { +} + +PROTO Extrusion [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFVec2f set_crossSection +eventIn MFRotation set_orientation +eventIn MFVec2f set_scale +eventIn MFVec3f set_spine +field SFBool beginCap TRUE +field SFBool ccw TRUE +field SFBool convex TRUE +field SFFloat creaseAngle 0.0 #%b=[0,6.2831853] #%q=6 +field MFVec2f crossSection [ 1 1, 1 -1, -1 -1, -1 1, 1 1 ] #%b=(-I,+I) #%q=2 +field SFBool endCap TRUE +field MFRotation orientation [0 0 1 0] #%b=(-I,+I) #%q=10 +field MFVec2f scale [1 1] #%b=[0,+I) #%q=7 +field SFBool solid TRUE +field MFVec3f spine [ 0 0 0, 0 1 0 ] #%b=(-I,+I) #%q=1 +] { +} + +PROTO Face [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField SFFAPNode fap NULL +exposedField SFFDPNode fdp NULL +exposedField SFFITNode fit NULL +exposedField SFAudioNode ttsSource NULL +exposedField MF3DNode renderedFace [] +] { +} + + +PROTO FaceDefMesh [ #%NDT=SFWorldNode,SFFaceDefMeshNode %COD=N +field SF3DNode faceSceneGraphNode NULL +field MFInt32 intervalBorders [] #%q=0 +field MFInt32 coordIndex [] #%q=0 +field MFVec3f displacements [] #%q=0 +] { +} + +PROTO FaceDefTables [ #%NDT=SFWorldNode,SFFaceDefTablesNode %COD=N +field SFInt32 fapID 1 #%b=[1, 68] #%q=13 7 +field SFInt32 highLevelSelect 1 #%b=[1, 64] #%q=13 6 +exposedField MFFaceDefMeshNode faceDefMesh [] +exposedField MFFaceDefTransformNode faceDefTransform [] +] { +} + +PROTO FaceDefTransform [ #%NDT=SFWorldNode,SFFaceDefTransformNode %COD=N +field SF3DNode faceSceneGraphNode NULL +field SFInt32 fieldId 1 +field SFRotation rotationDef 0 0 1 0 #%b=(-I,+I) #%q=10 +field SFVec3f scaleDef 1 1 1 #%q=7 +field SFVec3f translationDef 0 0 0 #%q=1 +] { +} + +PROTO FAP [ #%NDT=SFWorldNode,SFFAPNode %COD=N +exposedField SFVisemeNode viseme NULL +exposedField SFExpressionNode expression NULL +exposedField SFInt32 open_jaw +I #%b=[0, +I) #%q=0 +exposedField SFInt32 lower_t_midlip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_b_midlip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 stretch_l_corner +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 stretch_r_corner +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_t_lip_lm +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_t_lip_rm +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_b_lip_lm +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_b_lip_rm +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_l_cornerlip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_r_cornerlip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 thrust_jaw +I #%b=[0,+I) #%q=0 +exposedField SFInt32 shift_jaw +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 push_b_lip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 push_t_lip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 depress_chin +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 close_t_l_eyelid +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 close_t_r_eyelid +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 close_b_l_eyelid +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 close_b_r_eyelid +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 yaw_l_eyeball +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 yaw_r_eyeball +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 pitch_l_eyeball +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 pitch_r_eyeball +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 thrust_l_eyeball +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 thrust_r_eyeball +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 dilate_l_pupil +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 dilate_r_pupil +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_l_i_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_r_i_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_l_m_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_r_m_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_l_o_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_r_o_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 squeeze_l_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 squeeze_r_eyebrow +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 puff_l_cheek +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 puff_r_cheek +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lift_l_cheek +I #%b=[0,+I) #%q=0 +exposedField SFInt32 lift_r_cheek +I #%b=[0,+I) #%q=0 +exposedField SFInt32 shift_tongue_tip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_tongue_tip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 thrust_tongue_tip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_tongue +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 tongue_roll +I #%b=[0,+I) #%q=0 +exposedField SFInt32 head_pitch +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 head_yaw +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 head_roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_t_midlip_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_b_midlip_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 stretch_l_cornerlip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 stretch_r_cornerlip +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_t_lip_lm_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 lower_t_lip_rm_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_b_lip_lm_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_b_lip_rm_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_l_cornerlip_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_r_cornerlip_o +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 stretch_l_nose +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 stretch_r_nose +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_nose +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 bend_nose +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_l_ear +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 raise_r_ear +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 pull_l_ear +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 pull_r_ear +I #%b=(-I,+I) #%q=0 +] { +} + +PROTO FDP [ #%NDT=SFWorldNode,SFFDPNode %COD=N +exposedField SFCoordinateNode featurePointsCoord NULL +exposedField SFTextureCoordinateNode textureCoord NULL +exposedField MFFaceDefTablesNode faceDefTables [] +exposedField MF3DNode faceSceneGraph [] +field SFBool useOrthoTexture FALSE +] { +} + + +PROTO FIT [ #%NDT=SFWorldNode,SFFITNode %COD=N +exposedField MFInt32 FAPs [] #%b=[-1,68] #%q=13 7 +exposedField MFInt32 Graph [] #%b=[0,68] #%q=13 7 +exposedField MFInt32 numeratorExp [] #%b=[0,15] #%q=13 4 +exposedField MFInt32 denominatorExp [] #%b=[0,15] #%q=13 4 +exposedField MFInt32 numeratorImpulse [] #%b=[0,1023] #%q=13 10 +exposedField MFInt32 numeratorTerms [] #%b=[0,10] #%q=13 4 +exposedField MFInt32 denominatorTerms [] #%b=[0,10] #%q=13 4 +exposedField MFFloat numeratorCoefs [] #%b=(-I,+I) +exposedField MFFloat denominatorCoefs [] #%b=(-I,+I) +] { +} + +PROTO Fog [ #%NDT=SFWorldNode,SF3DNode,SFFogNode %COD=N +exposedField SFColor color 1 1 1 #%b=[0,1] #%q=4 #%a=4 +exposedField SFString fogType "LINEAR" +exposedField SFFloat visibilityRange 0 #%b=[0,+I) #%q=11 #a=7 +eventIn SFBool set_bind +eventOut SFBool isBound +] { +} + + +PROTO FontStyle [ #%NDT=SFWorldNode,SFFontStyleNode %COD=N +exposedField MFString family ["SERIF"] +exposedField SFBool horizontal TRUE +exposedField MFString justify ["BEGIN"] +exposedField SFString language "" +exposedField SFBool leftToRight TRUE +exposedField SFFloat size 1.0 #%b=[0,+I) #%q=11 +exposedField SFFloat spacing 1.0 #%b=[0,+I) #%q=11 +exposedField SFString style "PLAIN" +exposedField SFBool topToBottom TRUE +] { +} + +PROTO Form [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFVec2f size -1 -1 #%b=[0,+I) #%q=12 #%a=12 +exposedField MFInt32 groups [] #%b=[-1,1022] #%q=13 10 +exposedField MFString constraints [] +exposedField MFInt32 groupsIndex [] #%b=[-1,1022] #%q=13 10 +]{} + + +PROTO Group [ #%NDT=SFWorldNode,SFTopNode,SF3DNode,SF2DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +] { +} + + +PROTO ImageTexture [ #%NDT=SFWorldNode,SFTextureNode %COD=N +exposedField MFURL url [] +field SFBool repeatS TRUE +field SFBool repeatT TRUE +] { +} + +PROTO IndexedFaceSet [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +eventIn MFInt32 set_normalIndex +eventIn MFInt32 set_texCoordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field MFInt32 colorIndex [] #%b=[-1,+I) #%q=14 +field SFBool colorPerVertex TRUE +field SFBool convex TRUE +field MFInt32 coordIndex [] #%b=[-1,+I) #%q=14 +field SFFloat creaseAngle 0.0 #%b=[0,6.2831853] #%q=6 +field MFInt32 normalIndex [] #%b=[-1,+I) #%q=14 +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field MFInt32 texCoordIndex [] #%b=[-1,+I) #%q=14 +] { +} + +PROTO IndexedFaceSet2D [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +eventIn MFInt32 set_texCoordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinate2DNode coord NULL +exposedField SFTextureCoordinateNode texCoord NULL +field MFInt32 colorIndex [] #%b=[-1,+I) #%q=14 +field SFBool colorPerVertex TRUE +field SFBool convex TRUE +field MFInt32 coordIndex [] #%b=[-1,+I) #%q=14 +field MFInt32 texCoordIndex [] #%b=[-1,+I) #%q=14 +] { +} + +PROTO IndexedLineSet [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +field MFInt32 colorIndex [] #%b=[-1,+I) #%q=14 +field SFBool colorPerVertex TRUE +field MFInt32 coordIndex [] #%b=[-1,+I) #%q=14 +] { +} + +PROTO IndexedLineSet2D [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinate2DNode coord NULL +field MFInt32 colorIndex [] #%b=[-1,+I) #%q=14 +field SFBool colorPerVertex TRUE +field MFInt32 coordIndex [] #%b=[-1,+I) #%q=14 +]{ +} + +PROTO Inline [ #%NDT=SFWorldNode,SF3DNode,SFStreamingNode,SF2DNode %COD=N +exposedField MFURL url [] +] { +} + + +PROTO LOD [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField MF3DNode level [] +field SFVec3f center 0 0 0 #%b=(-I,+I) #%q=1 +field MFFloat range [] #%b=[0,+I] #%q=11 +] { +} + +PROTO Layer2D [ #%NDT=SFWorldNode,SFTopNode,SF2DNode,SF3DNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFVec2f size -1 -1 #%b=(-I,+I) #%q=12 #%a=12 +exposedField SFBackground2DNode background NULL +exposedField SFViewportNode viewport NULL +] { +} + +PROTO Layer3D [ #%NDT=SFWorldNode,SFTopNode,SF2DNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFVec2f size -1 -1 #%b=(-I,+I) #%q=12 #%a=12 +exposedField SFBackground3DNode background NULL +exposedField SFFogNode fog NULL +exposedField SFNavigationInfoNode navigationInfo NULL +exposedField SFViewpointNode viewpoint NULL +] { +} + + +PROTO Layout [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFBool wrap FALSE +exposedField SFVec2f size -1 -1 #%b=[0,+I) #%q=12 #%a=12 +exposedField SFBool horizontal TRUE +exposedField MFString justify ["BEGIN"] +exposedField SFBool leftToRight TRUE +exposedField SFBool topToBottom TRUE +exposedField SFFloat spacing 1 #%b=[0,+I) #%q=0 #%a=7 +exposedField SFBool smoothScroll FALSE +exposedField SFBool loop FALSE +exposedField SFBool scrollVertical TRUE +exposedField SFFloat scrollRate 0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFInt32 scrollMode 0 #%b=[-1,1] #%q=13 2 +]{} + +PROTO LineProperties [ #%NDT=SFWorldNode,SFLinePropertiesNode %COD=N +exposedField SFColor lineColor 0 0 0 #%b=[0,1] #%q=4 #%a=4 +exposedField SFInt32 lineStyle 0 #%b=[0,5] #%q=13 3 +exposedField SFFloat width 1 #%b=[0,+I) #%q=12 #%a=7 +]{ +} + +PROTO ListeningPoint [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFBool set_bind +exposedField SFBool jump TRUE +exposedField SFRotation orientation 0 0 1 0 #%q=10 #%a=10 +exposedField SFVec3f position 0 0 10 #%b=(-I,+I) #%q=1 #%a=1 +field SFString description "" +eventOut SFTime bindTime +eventOut SFBool isBound +] { +} + +PROTO Material [ #%NDT=SFWorldNode,SFMaterialNode %COD=N +exposedField SFFloat ambientIntensity 0.2 #%b=[0,1] #%q=4 #%a=8 +exposedField SFColor diffuseColor 0.8 0.8 0.8 #%b=[0,1] #%q=4 #%a=4 +exposedField SFColor emissiveColor 0 0 0 #%b=[0,1] #%q=4 #%a=4 +exposedField SFFloat shininess 0.2 #%b=[0,1] #%q=4 #%a=8 +exposedField SFColor specularColor 0 0 0 #%b=[0,1] #%q=4 #%a=4 +exposedField SFFloat transparency 0 #%b=[0,1] #%q=4 #%a=8 +] { +} + +PROTO Material2D [ #%NDT=SFWorldNode,SFMaterialNode %COD=N +exposedField SFColor emissiveColor 0.8 0.8 0.8 #%b=[0,1] #%q=4 #%a=4 +exposedField SFBool filled FALSE +exposedField SFLinePropertiesNode lineProps NULL +exposedField SFFloat transparency 0 #%b=[0,1] #%q=4 #%a=8 +]{ +} + +PROTO MovieTexture [ #%NDT=SFWorldNode,SFTextureNode,SFStreamingNode %COD=N +exposedField SFBool loop FALSE +exposedField SFFloat speed 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=(-I,+I) +exposedField SFTime stopTime 0 #%b=(-I,+I) +exposedField MFURL url [] +field SFBool repeatS TRUE +field SFBool repeatT TRUE +eventOut SFTime duration_changed +eventOut SFBool isActive +] { +} + +PROTO NavigationInfo [ #%NDT=SFWorldNode,SF3DNode,SFNavigationInfoNode %COD=N +eventIn SFBool set_bind +exposedField MFFloat avatarSize [0.25, 1.6, 0.75] # %b=[0,+I) #%q=11 +exposedField SFBool headlight TRUE +exposedField SFFloat speed 1.0 # %b=[0,+I) #%q=0 +exposedField MFString type ["WALK", "ANY"] +exposedField SFFloat visibilityLimit 0.0 # %b=[0,+I) #%q=11 #%a=7 +eventOut SFBool isBound +]{ +} + +PROTO Normal [ #%NDT=SFWorldNode,SFNormalNode %COD=N +exposedField MFVec3f vector [] #%q=9 #%a=9 +] { +} + +PROTO NormalInterpolator [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec3f keyValue [] #%b=(-I,+I) #%q=9 +eventOut MFVec3f value_changed +] { +} + +PROTO OrderedGroup [ #%NDT=SFWorldNode,SF3DNode,SF2DNode,SFTopNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField MFFloat order [] #%b=[0,+I) #%q=3 +] { +} + +PROTO OrientationInterpolator [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFRotation keyValue [] #%b=(-I,+I) #%q=10 +eventOut SFRotation value_changed +] { +} + + +PROTO PixelTexture [ #%NDT=SFWorldNode,SFTextureNode %COD=N +exposedField SFImage image 0 0 0 # %q=0 +field SFBool repeatS TRUE +field SFBool repeatT TRUE +] { +} + +PROTO PlaneSensor [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFBool autoOffset TRUE +exposedField SFBool enabled TRUE +exposedField SFVec2f maxPosition -1 -1 #%b=(-I,+I) #%q=2 +exposedField SFVec2f minPosition 0 0 #%b=(-I,+I) #%q=2 +exposedField SFVec3f offset 0 0 0 #%b=(-I,+I) #%q=1 +eventOut SFBool isActive +eventOut SFVec3f trackPoint_changed +eventOut SFVec3f translation_changed +] { +} + +PROTO PlaneSensor2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFBool autoOffset TRUE +exposedField SFBool enabled TRUE +exposedField SFVec2f maxPosition 0 0 #%b=(-I,+I) #%q=2 +exposedField SFVec2f minPosition 0 0 #%b=(-I,+I) #%q=2 +exposedField SFVec2f offset 0 0 #%b=(-I,+I) #%q=12 +eventOut SFBool isActive +eventOut SFVec2f trackPoint_changed +eventOut SFVec2f translation_changed +] { +} + +PROTO PointLight [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFFloat ambientIntensity 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFVec3f attenuation 1 0 0 #%b=[0,+I) #%q=11 #%a=1 +exposedField SFColor color 1 1 1 #%b=[0,1] #%q=4 #%a=4 +exposedField SFFloat intensity 1 #%b=[0,1] #%q=4 #%a=8 +exposedField SFVec3f location 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +exposedField SFBool on TRUE +exposedField SFFloat radius 100 #%b=[0,+I) #%q=11 #%a=7 +] { +} + +PROTO PointSet [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +] { +} + +PROTO PointSet2D [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFColorNode color NULL +exposedField SFCoordinate2DNode coord NULL +]{ +} + + +PROTO PositionInterpolator [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec3f keyValue [] #%b=(-I,+I) #%q=1 +eventOut SFVec3f value_changed +] { +} + +PROTO PositionInterpolator2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec2f keyValue [] #%b=(-I,+I) #%q=2 +eventOut SFVec2f value_changed +] { +} + +PROTO ProximitySensor2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFVec2f center 0 0 #%b=[-1,+I) #%q=2 +exposedField SFVec2f size 0 0 #%b=[0,+I) #%q=12 +exposedField SFBool enabled TRUE +eventOut SFBool isActive +eventOut SFVec2f position_changed +eventOut SFFloat orientation_changed +eventOut SFTime enterTime +eventOut SFTime exitTime +] { +} + +PROTO ProximitySensor [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFVec3f center 0 0 0 #%b=(-I,+I) #%q=1 +exposedField SFVec3f size 0 0 0 #%b=[0,+I) #%q=11 +exposedField SFBool enabled TRUE +eventOut SFBool isActive +eventOut SFVec3f position_changed +eventOut SFRotation orientation_changed +eventOut SFTime enterTime +eventOut SFTime exitTime +] { +} + +PROTO QuantizationParameter [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +field SFBool isLocal FALSE +field SFBool position3DQuant FALSE +field SFVec3f position3DMin -I -I -I #%b=(-I,+I) #%q=0 +field SFVec3f position3DMax +I +I +I #%b=(-I,+I) #%q=0 +field SFInt32 position3DNbBits 16 #%b=[0,31] #%q=13 5 +field SFBool position2DQuant FALSE +field SFVec2f position2DMin -I -I #%b=(-I,+I) #%q=0 +field SFVec2f position2DMax +I +I #%b=(-I,+I) #%q=0 +field SFInt32 position2DNbBits 16 #%b=[0,31] #%q=13 5 +field SFBool drawOrderQuant FALSE +field SFFloat drawOrderMin -I #%b=(-I,+I) #%q=0 +field SFFloat drawOrderMax +I #%b=(-I,+I) #%q=0 +field SFInt32 drawOrderNbBits 8 #%b=[0,31] #%q=13 5 +field SFBool colorQuant TRUE +field SFFloat colorMin 0.0 #%b=[0,1] #%q=0 +field SFFloat colorMax 1.0 #%b=[0,1] #%q=0 +field SFInt32 colorNbBits 8 #%b=[0,31] #%q=13 5 +field SFBool textureCoordinateQuant TRUE +field SFFloat textureCoordinateMin 0 #%b=[0,1] #%q=0 +field SFFloat textureCoordinateMax 1 #%b=[0,1] #%q=0 +field SFInt32 textureCoordinateNbBits 16 #%b=[0,31] #%q=13 5 +field SFBool angleQuant TRUE +field SFFloat angleMin 0.0 #%b=[0,6.2831853] #%q=0 +field SFFloat angleMax 6.2831853 #%b=[0,6.2831853] #%q=0 +field SFInt32 angleNbBits 16 #%b=[0,31] #%q=13 5 +field SFBool scaleQuant FALSE +field SFFloat scaleMin 0.0 #%b=(-I,+I) #%q=0 +field SFFloat scaleMax +I #%b=(-I,+I) #%q=0 +field SFInt32 scaleNbBits 8 #%b=[0,31] #%q=13 5 +field SFBool keyQuant TRUE +field SFFloat keyMin 0.0 #%b=(-I,+I) #%q=0 +field SFFloat keyMax 1.0 #%b=(-I,+I) #%q=0 +field SFInt32 keyNbBits 8 #%b=[0,31] #%q=13 5 +field SFBool normalQuant TRUE +field SFInt32 normalNbBits 8 #%b=[0,31] #%q=13 5 +field SFBool sizeQuant FALSE +field SFFloat sizeMin 0 #%b=(-I,+I) #%q=0 +field SFFloat sizeMax +I #%b=(-I,+I) #%q=0 +field SFInt32 sizeNbBits 8 #%b=[0,31] #%q=13 5 +field SFBool useEfficientCoding FALSE +]{ +} + +PROTO Rectangle [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec2f size 2 2 #%b=[0,+I) #%q=12 #%a=2 +]{ +} + +PROTO ScalarInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFFloat keyValue [] #%b=(-I,+I) #%q=0 +eventOut SFFloat value_changed +]{} + +PROTO Script [#%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=S +exposedField MFScript url [] +field SFBool directOutput FALSE +field SFBool mustEvaluate FALSE +]{ +} + +PROTO Shape [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField SFAppearanceNode appearance NULL +exposedField SFGeometryNode geometry NULL +] { +} + +PROTO Sound [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFVec3f direction 0 0 1 #%b=(-I,+I) #%q=9 +exposedField SFFloat intensity 1 #%b=[0,1] #%q=4 #%a=7 +exposedField SFVec3f location 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +exposedField SFFloat maxBack 10 #%b=[0,+I) #%q=11 #%a=7 +exposedField SFFloat maxFront 10 #%b=[0,+I) #%q=11 #%a=7 +exposedField SFFloat minBack 1 #%b=[0,+I) #%q=11 #%a=7 +exposedField SFFloat minFront 1 #%b=[0,+I) #%q=11 #%a=7 +exposedField SFFloat priority 0 #%b=[0,1] #%q=4 +exposedField SFAudioNode source NULL +field SFBool spatialize TRUE +] { +} + +PROTO Sound2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFFloat intensity 1 #%b=[0,1] #%q=4 #%a=7 +exposedField SFVec2f location 0 0 #%b=(-I,+I) #%q=2 #%a=2 +exposedField SFAudioNode source NULL +field SFBool spatialize TRUE +]{ +} + +PROTO Sphere [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +field SFFloat radius 1 #%b=(0,+I) #%q=11 +] { +} + + +PROTO SphereSensor [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFBool autoOffset TRUE +exposedField SFBool enabled TRUE +exposedField SFRotation offset 0 1 0 0 # %b=(-I,+I) #%q=10 +eventOut SFBool isActive +eventOut SFRotation rotation_changed +eventOut SFVec3f trackPoint_changed +]{ +} + +PROTO SpotLight [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFFloat ambientIntensity 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFVec3f attenuation 1 0 0 #%b=[0,+I) #%q=11 #%a=1 +exposedField SFFloat beamWidth 1.570796 #%b=[0,1.5707963] #%q=6 #%a=8 +exposedField SFColor color 1 1 1 #%b=[0,1] #%q=4 #%a=4 +exposedField SFFloat cutOffAngle 0.785398 #%b=[0,1.5707963] #%q=6 #%a=8 +exposedField SFVec3f direction 0 0 -1 #%b=(-I,+I) #%q=9 #%a=9 +exposedField SFFloat intensity 1 #%b=[0,1] #%q=4 #%a=8 +exposedField SFVec3f location 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +exposedField SFBool on TRUE +exposedField SFFloat radius 100 #%b=[0,+I) #%q=11 #%a=7 +] { +} + +PROTO Switch [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField MF3DNode choice [] +exposedField SFInt32 whichChoice -1 #%b=[-1, 1022] #%q=13 10 +] { +} + +PROTO TermCap [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFTime evaluate +exposedField SFInt32 capability 0 #%b=[0,127] #%q=13 7 +eventOut SFInt32 value 0 #%b=[0,7] #%q=13 3 +] { +} + +PROTO Text [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField MFString string [] +exposedField MFFloat length [] #%b=[0,+I) #%q=11 #%a=7 +exposedField SFFontStyleNode fontStyle NULL +exposedField SFFloat maxExtent 0.0 #%b=[0,+I) #%q=11 #%a=7 +] { +} + +PROTO TextureCoordinate [ #%NDT=SFWorldNode,SFTextureCoordinateNode %COD=N +exposedField MFVec2f point [] #%b=(-I,+I) #%q=5 #%a=2 +]{ +} + +PROTO TextureTransform [ #%NDT=SFWorldNode,SFTextureTransformNode %COD=N +exposedField SFVec2f center 0 0 #%b=(-I,+I) #%q=2 #%a=2 +exposedField SFFloat rotation 0 #%b=[0,6.2831853] #%q=6 #%a=6 +exposedField SFVec2f scale 1 1 #%b=(-I,+I) #%q=7 #%a=12 +exposedField SFVec2f translation 0 0 #%b=(-I,+I) #%q=2 #%a=2 +] { +} + +PROTO TimeSensor [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField SFTime cycleInterval 1 #%b=(0,+I) +exposedField SFBool enabled TRUE +exposedField SFBool loop FALSE +exposedField SFTime startTime 0 #%b=(-I,+I) +exposedField SFTime stopTime 0 #%b=(-I,+I) +eventOut SFTime cycleTime +eventOut SFFloat fraction_changed +eventOut SFBool isActive +eventOut SFTime time +] { +} + +PROTO TouchSensor [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFBool enabled TRUE +eventOut SFVec3f hitNormal_changed +eventOut SFVec3f hitPoint_changed +eventOut SFVec2f hitTexCoord_changed +eventOut SFBool isActive +eventOut SFBool isOver +eventOut SFTime touchTime +] {} + +PROTO Transform [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField SFVec3f center 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +exposedField MF3DNode children [] +exposedField SFRotation rotation 0 0 1 0 #%q=10 #%a=10 +exposedField SFVec3f scale 1 1 1 #%b=(0,+I) #%q=7 #%a=11 +exposedField SFRotation scaleOrientation 0 0 1 0 #%q=10 #%a=10 +exposedField SFVec3f translation 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +] { +} + +PROTO Transform2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFVec2f center 0 0 #%b=(-I,+I) #%q=2 #%a=2 +exposedField SFFloat rotationAngle 0 #%b=[0,6.2831853] #%q=6 #%a=6 +exposedField SFVec2f scale 1 1 #%b=(-I,+I) #%q=7 #%a=12 +exposedField SFFloat scaleOrientation 0 #%b=[0,6.2831853] #%q=6 #%a=6 +exposedField SFVec2f translation 0 0 #%b=(-I,+I) #%q=2 #%a=2 +] { +} + +PROTO Valuator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +eventIn SFBool inSFBool +eventIn SFColor inSFColor +eventIn MFColor inMFColor +eventIn SFFloat inSFFloat +eventIn MFFloat inMFFloat +eventIn SFInt32 inSFInt32 +eventIn MFInt32 inMFInt32 +eventIn SFRotation inSFRotation +eventIn MFRotation inMFRotation +eventIn SFString inSFString +eventIn MFString inMFString +eventIn SFTime inSFTime +eventIn SFVec2f inSFVec2f +eventIn MFVec2f inMFVec2f +eventIn SFVec3f inSFVec3f +eventIn MFVec3f inMFVec3f +eventOut SFBool outSFBool +eventOut SFColor outSFColor +eventOut MFColor outMFColor +eventOut SFFloat outSFFloat +eventOut MFFloat outMFFloat +eventOut SFInt32 outSFInt32 +eventOut MFInt32 outMFInt32 +eventOut SFRotation outSFRotation +eventOut MFRotation outMFRotation +eventOut SFString outSFString +eventOut MFString outMFString +eventOut SFTime outSFTime +eventOut SFVec2f outSFVec2f +eventOut MFVec2f outMFVec2f +eventOut SFVec3f outSFVec3f +eventOut MFVec3f outMFVec3f +exposedField SFFloat Factor1 1.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Factor2 1.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Factor3 1.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Factor4 1.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Offset1 0.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Offset2 0.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Offset3 0.0 #%b=(-I,+I) #%q=0 +exposedField SFFloat Offset4 0.0 #%b=(-I,+I) #%q=0 +exposedField SFBool Sum FALSE +] { +} + +PROTO Viewpoint [ #%NDT=SFWorldNode,SF3DNode,SFViewpointNode %COD=N +eventIn SFBool set_bind +exposedField SFFloat fieldOfView 0.785398 #%b=[0,3.1415927] #%q=6 #%a=8 +exposedField SFBool jump TRUE +exposedField SFRotation orientation 0 0 1 0 #%q=10 #%a=10 +exposedField SFVec3f position 0 0 10 #%b=(-I,+I) #%q=1 #%a=1 +field SFString description "" +eventOut SFTime bindTime +eventOut SFBool isBound +] { +} + + +PROTO VisibilitySensor [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFVec3f center 0 0 0 # %b=(-I,+I) #%q=1 #%a=1 +exposedField SFBool enabled TRUE +exposedField SFVec3f size 0 0 0 # %b=[0,+I) #%q=11 #%a=11 +eventOut SFTime enterTime +eventOut SFTime exitTime +eventOut SFBool isActive +]{ +} + +PROTO Viseme [ #%NDT=SFWorldNode,SFVisemeNode %COD=N +exposedField SFInt32 viseme_select1 0 #%b=(0,31) #%q=13 5 +exposedField SFInt32 viseme_select2 0 #%b=(0,31) #%q=13 5 +exposedField SFInt32 viseme_blend 0 #%b=(0,63) #%q=13 6 +exposedField SFBool viseme_def FALSE +] { +} + +PROTO WorldInfo [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +field MFString info [] +field SFString title "" +] { +} diff --git a/applications/generators/MPEG4/templates10.txt b/applications/generators/MPEG4/templates10.txt new file mode 100644 index 0000000..1c4898a --- /dev/null +++ b/applications/generators/MPEG4/templates10.txt @@ -0,0 +1,53 @@ +PROTO CacheTexture [#%NDT=SFWorldNode,SF2DNode,SF3DNode,SFTextureNode %COD=N +field SFInt32 objectTypeIndication 0 +field SFString decoderSpecificInfo "" +field SFString image "" +field SFString cacheURL "" +field MFURL cacheOD [] +field SFInt32 expirationDate 0 +field SFBool repeatS TRUE +field SFBool repeatT TRUE +]{} + +PROTO EnvironmentTest [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFBool evaluate +exposedField SFBool enabled TRUE +exposedField SFInt32 parameter 0 +exposedField SFString compareValue "" +exposedField SFBool evaluateOnChange TRUE +eventOut SFBool valueLarger +eventOut SFBool valueEqual +eventOut SFBool valueSmaller +eventOut SFString parameterValue +] {} + + +PROTO KeyNavigator [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFBool setFocus +exposedField SF3DNode sensor NULL +exposedField SF2DNode left NULL +exposedField SF2DNode right NULL +exposedField SF2DNode up NULL +exposedField SF2DNode down NULL +exposedField SF2DNode select NULL +exposedField SF2DNode quit NULL +exposedField SFFloat step 0 +eventOut SFBool focusSet +]{} + +PROTO SpacePartition [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFURL SPStream NULL +]{} + +PROTO Storage [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFBool forceSave +eventIn SFBool forceRestore +exposedField SFBool auto TRUE +field SFInt32 expireAfter 0 +field SFString name "" +field MFAttrRef storageList [] +]{} + diff --git a/applications/generators/MPEG4/templates2.txt b/applications/generators/MPEG4/templates2.txt new file mode 100644 index 0000000..b3c013e --- /dev/null +++ b/applications/generators/MPEG4/templates2.txt @@ -0,0 +1,592 @@ +#-- Version 2 --# +# templates for the BIFS nodes of v2 +# ================================== +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# JS: Created on 990726 +# _________________ +# YF: modified 990726 +# added NDT to nodes below, but probably incorrectly. +#BodySegmentConnectionHint [ #%NDT=SFWorldNode +#MaterialKey [#%NDT=SFWorldNode +#PROTO ServerCommand [#%NDT=SFWorldNode +#Hierarchical3DMesh {#%NDT=SFWorldNode +# +# JS: modified 990726 +# Cleaned up NDTs. +# re-ordered in the right order fields and events: eventIn, exposedField, field, eventOut +# => FPDAM needs to be changed accordingly +# corrected case of fields and event types! +# re-aligned stuff with spaces so that we may conserve a kind of alignment. Please be +# careful! did not finish for BAp, too long! +# +# YF: modified 990728 +# YF: fixed typo in Hierarchical3DMesh, finishd BAP allignment +# +# YF: modified 990729 +# YF: many more typos +# +# Aug 23, 1999 (Mikael Bourges-Sevenier) added body animation nodes from Tolga +# Aug 26, 1999 (Mikael Bourges-Sevenier) added body audio nodes from Riitta +# Sep 1, 1999 (MBS) cross-checked everything. +# Dec 20, 1999 YF +# bounds of form (x,y) changed to form [a,b] e.g.: +# numInterpolatorkeys gets new range [2,+I) +# bapIds gets new range [1,296] +# BAPs set to +I default value +# added application window +# +# 23.1.2000 (Riitta) +# Fixed AcousticMaterial, AcousticScene, DirectiveSound +# to comply with the latest spec (FDIS). +# +# Feb 8, 2000 (YF) NULL -> [] in BodySegmentConnectionHint +# remove "," from Vec3f values and put [ ] around MFFloat values +# +# July 27, 2000 (YF) NDT of Hierarchical3DMech changed to SFGeometryNode +# +# August 10, 2000 (YF per Steve Wood): +# Add [] around default values of MF field in AcousticMaterial, AcousticScene +# DirectiveSound, PerceptualParameters +# ApplicationWindow parameter field default value just [] + +#PROTO AcousticMaterial [#%NDT=SFMaterialNode,SFWorldNode %COD=N +#exposedField SFFloat ambientIntensity 0.2 #%b=[0,1] #%q=4 #%a=8 +#exposedField SFColor diffuseColor 0.8, 0.8, 0.8 #%b=[0,1] #%q=4 #%a=8 +#exposedField SFColor emissiveColor 0, 0, 0 #%b=[0,1] #%q=4 #%a=8 +#exposedField SFFloat shininess 0.2 #%b=[0,1] #%q=4 #%a=8 +#exposedField SFColor specularColor 0, 0, 0 #%b=[0,1] #%q=4 #%a=8 +#exposedField SFFloat transparency 0 #%b=[0,1] #%q=4 #%a=8 +#field SFFloat reffunc 0 #%b=(-I,+I) #%q=0 +#field SFFloat transfunc 1 #%b=(-I,+I) #%q=0 +#]{ +#} + +PROTO AcousticMaterial [#%NDT=SFMaterialNode,SFWorldNode %COD=N +exposedField SFFloat ambientIntensity 0.2 #%b=[0,1] #%q=4 #%a=8 +exposedField SFColor diffuseColor 0.8 0.8 0.8 #%b=[0,1] #%q=4 #%a=8 +exposedField SFColor emissiveColor 0 0 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFFloat shininess 0.2 #%b=[0,1] #%q=4 #%a=8 +exposedField SFColor specularColor 0 0 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFFloat transparency 0 #%b=[0,1] #%q=4 #%a=8 +field MFFloat reffunc [0] #%b=(-I,+I) #%q=0 +field MFFloat transfunc [1] #%b=(-I,+I) #%q=0 +field MFFloat refFrequency [0] #%b=(0,+I) #%q=0 +field MFFloat transFrequency [0] #%b=(0,+I) #%q=0 +]{ +} + +#PROTO AcousticScene [#%NDT=SFWorldNode,SF3DNode %COD=N +#exposedField SFFloat reverbLevel 0.4 #%b=(-I,+I) #%q=0 #%a=7 +#exposedField SFTime reverbDelay 0.5 #%b=(-I,+I) #%q=0 +#field SFVec3f center 0 0 0 #%b=(-I,+I) #%q=11 #%a=11 +#field SFVec3f Size -1 -1 -1 #%b=(-I,+I) #%q=1 #%a=1 +#field MFTime reverbTime [0] #%b=(-I,+I) #%q=0 +#field MFFloat reverbFreq [1000] #%b=(0 ,+I) #%q=0 +#]{ +#} + +PROTO AcousticScene [#%NDT=SFWorldNode,SF3DNode %COD=N +field SFVec3f center 0 0 0 #%b=(-I,+I) #%q=1 +field SFVec3f Size -1 -1 -1 #%b=(-I,+I) #%q=11 +field MFTime reverbTime [0] #%b=(0 ,+I) #%q=0 +field MFFloat reverbFreq [1000] #%b=(0 ,+I) #%q=0 +exposedField SFFloat reverbLevel 0.4 #%b=(0 ,+I) #%q=0 #%a=7 +exposedField SFTime reverbDelay 0.5 #%b=(0 ,+I) #%q=0 +]{ +} + +PROTO ApplicationWindow [#%NDT=SFWorldNode,SF2DNode %COD=N +exposedField SFBool isActive FALSE +exposedField SFTime startTime 0 #%b=(-I,+I) #%q=0 +exposedField SFTime stopTime 0 #%b=(-I,+I) #%q=0 +exposedField SFString description "" +exposedField MFString parameter [] +exposedField MFURL url [] +exposedField SFVec2f size 0 0 #%b=(-I,+I) #%q=12 #%a=12 +]{ +} + +PROTO BAP [#%NDT=SFWorldNode,SFBAPNode %COD=N +exposedField SFInt32 sacroiliac_tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 sacroiliac_torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 sacroiliac_roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_hip_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_hip_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_hip_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_hip_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_hip_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_hip_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_knee_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_knee_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_knee_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_knee_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ankle_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ankle_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ankle_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ankle_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_subtalar_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_subtalar_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_midtarsal_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_midtarsal_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_metatarsal_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_metatarsal_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_sternoclavicular_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_sternoclavicular_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_sternoclavicular_rotate +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_sternoclavicular_rotate +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_acromioclavicular_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_acromioclavicular_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_acromioclavicular_rotate +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_acromioclavicular_rotate +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_shoulder_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_shoulder_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_shoulder_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_shoulder_abduct +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_shoulder_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_shoulder_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_elbow_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_elbow_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_elbow_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_elbow_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_wrist_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_wrist_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_wrist_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_wrist_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_wrist_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_wrist_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 skullbase_roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 skullbase_torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 skullbase_tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc1roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc1torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc1tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc2roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc2torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc2tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc3roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc3torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc3tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc4roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc4torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc4tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc5roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc5torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc5tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc6roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc6torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc6tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc7roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc7torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vc7tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt1roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt1torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt1tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt2roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt2torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt2tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt3roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt3torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt3tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt4roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt4torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt4tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt5roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt5torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt5tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt6roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt6torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt6tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt7roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt7torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt7tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt8roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt8torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt8tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt9roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt9torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt9tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt10roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt10torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt10tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt11roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt11torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt11tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt12roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt12torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vt12tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl1roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl1torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl1tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl2roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl2torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl2tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl3roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl3torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl3tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl4roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl4torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl4tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl5roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl5torsion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 vl5tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_pinky0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_pinky0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_pinky1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_pinky1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_pinky1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_pinky1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_pinky1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_pinky1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_pinky2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_pinky2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_pinky3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_pinky3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ring0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ring0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ring1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ring1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ring1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ring1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ring1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ring1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ring2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ring2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_ring3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_ring3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_middle0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_middle0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_middle1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_middle1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_middle1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_middle1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_middle1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_middle1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_middle2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_middle2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_middle3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_middle3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_index0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_index0_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_index1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_index1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_index1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_index1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_index1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_index1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_index2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_index2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_index3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_index3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_thumb1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_thumb1_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_thumb1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_thumb1_pivot +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_thumb1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_thumb1_twisting +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_thumb2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_thumb2_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 l_thumb3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 r_thumb3_flexion +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 HumanoidRoot_tr_vertical +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 HumanoidRoot_tr_lateral +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 HumanoidRoot_tr_frontal +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 HumanoidRoot_rt_body_turn +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 HumanoidRoot_rt_body_roll +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 HumanoidRoot_rt_body_tilt +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap187 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap188 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap189 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap190 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap191 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap192 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap193 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap194 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap195 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap196 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap197 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap198 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap199 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap200 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap201 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap202 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap203 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap204 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap205 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap206 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap207 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap208 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap209 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap210 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap211 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap212 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap213 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap214 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap215 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap216 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap217 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap218 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap219 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap220 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap221 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap222 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap223 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap224 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap225 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap226 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap227 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap228 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap229 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap230 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap231 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap232 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap233 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap234 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap235 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap236 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap237 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap238 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap239 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap240 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap241 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap242 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap243 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap244 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap245 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap246 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap247 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap248 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap249 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap250 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap251 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap252 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap253 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap254 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap255 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap256 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap257 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap258 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap259 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap260 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap261 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap262 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap263 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap264 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap265 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap266 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap267 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap268 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap269 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap270 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap271 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap272 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap273 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap274 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap275 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap276 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap277 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap278 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap279 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap280 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap281 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap282 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap283 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap284 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap285 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap286 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap287 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap288 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap289 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap290 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap291 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap292 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap293 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap294 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap295 +I #%b=(-I,+I) #%q=0 +exposedField SFInt32 extensionBap296 +I #%b=(-I,+I) #%q=0 +] { +} + +PROTO BDP [#%NDT=SFWorldNode,SFBDPNode %COD=N +exposedField MFBodyDefTableNode bodyDefTables [] +exposedField MF3DNode bodySceneGraph [] +] { +} + +PROTO Body [#%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField SFBDPNode bdp NULL +exposedField SFBAPNode bap NULL +exposedField MF3DNode renderedBody [] +] +{ +} + +PROTO BodyDefTable [#%NDT=SFWorldNode,SFBodyDefTableNode %COD=N +exposedField SFString bodySceneGraphNodeName "" +exposedField MFInt32 bapIDs [] #%b=[1,296] #%q=13 9 +exposedField MFInt32 vertexIds [] #%b=[0,+I) #%q=0 +exposedField MFInt32 bapCombinations [] #%b=(-I,+I) #%q=0 +exposedField MFVec3f displacements [] +exposedField SFInt32 numInterpolateKeys 2 #%b=[2,+I) #%q=0 +] { +} + +PROTO BodySegmentConnectionHint [#%NDT=SFWorldNode,SFBodySegmentConnectionHintNode %COD=N +exposedField SFString firstSegmentNodeName "" +exposedField SFString secondSegmentNodeName "" +exposedField MFInt32 firstVertexIdList [] #%b=[0,+I) #%q=0 +exposedField MFInt32 secondVertexIdList [] #%b=[0,+I) #%q=0 +] { +} + +#PROTO DirectiveSound [#%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +#exposedField SFVec3f direction 0 0 -1 #%b=(-I,+I) #%q=9 #%a=9 +#exposedField SFFloat intensity 1 #%b=(-I,+I) #%q=0 #%a=7 +#exposedField SFVec3f location 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +#exposedField SFAudioNode source NULL +#exposedField SFPerceptualParameterNode perceptualParameters NULL +#exposedField SFBool roomEffect FALSE +#exposedField SFBool spatialize TRUE +#field MFFloat angles 1 #%b=[0,3.14159265] #%q=6 #%a=8 +#field MFFloat directivity 1 #%b=(-I,+I) #%q=0 +#field SFFloat speedOfSound 340 #%b=(-I,+I) #%q=1 #%a=1 +#field SFFloat distance 100 #%b=(-I,+I) #%q=0 +#field SFBool useAirabs FALSE +#]{ +#} + +PROTO DirectiveSound [#%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFVec3f direction 0 0 -1 #%b=(-I,+I) #%q=9 #%a=9 +exposedField SFFloat intensity 1 #%b=(0,+I) #%q=0 #%a=7 +exposedField SFVec3f location 0 0 0 #%b=(-I,+I) #%q=1 #%a=1 +exposedField SFAudioNode source NULL +exposedField SFPerceptualParameterNode perceptualParameters NULL +exposedField SFBool roomEffect FALSE +exposedField SFBool spatialize TRUE +field MFFloat directivity 1 #%b=(-I,+I) #%q=0 +field MFFloat angles 1 #%b=[0,3.14159265] #%q=6 +field MFFloat frequency [] #%b=(0,+I) #%q=0 +field SFFloat speedOfSound 340 #%b=(0,+I) #%q=1 +field SFFloat distance 100 #%b=(0,+I) #%q=0 +field SFBool useAirabs FALSE +]{ +} + +PROTO Hierarchical3DMesh [#%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn SFInt32 triangleBudget 1000 #%b=(-1,+I) #%q=0 +exposedField SFFloat level 1 #%b=(-1,+I) #%q=0 +field MFURL url [] +eventOut SFBool doneLoading +]{ +} + +PROTO MaterialKey [#%NDT=SFWorldNode,SFMaterialNode %COD=N +exposedField SFBool isKeyed TRUE +exposedField SFBool isRGB TRUE +exposedField SFColor keyColor 0 0 0 #%b=[0,1] #%q=4 #%a=4 +exposedField SFFloat lowThreshold 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFFloat highThreshold 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFFloat transparency 0 #%b=[0,1] #%q=4 #%a=8 +]{ +} + +PROTO PerceptualParameters [#%NDT=SFWorldNode,SFPerceptualParameterNode %COD=N +exposedField SFFloat sourcePresence 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat sourceWarmth 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat sourceBrilliance 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat roomPresence 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat runningReverberance 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat envelopment 0.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat lateReverberance 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat heavyness 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat liveness 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField MFFloat omniDirectivity [1.0] #%b=(-I,+I) #%q=0 #%a=7 +exposedField MFFloat directFilterGains [1.0, 1.0, 1.0] #%b=(-I,+I) #%q=0 #%a=7 +exposedField MFFloat inputFilterGains [1.0, 1.0, 1.0] #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat refDistance 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat freqLow 250.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFFloat freqHigh 4000.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFTime timeLimit1 0.02 #%b=(-I,+I) #%q=0 +exposedField SFTime timeLimit2 0.04 #%b=(-I,+I) #%q=0 +exposedField SFTime timeLimit3 0.1 #%b=(-I,+I) #%q=0 +exposedField SFTime modalDensity 0.8 #%b=(-I,+I) #%q=0 +]{ +} + +#PROTO ServerCommand [#%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +#eventIn SFBool trigger FALSE +#exposedField SFBool enable FALSE +#exposedField MFURL url [ ] +#eventIn SFString command "" +#]{ +#} + + + diff --git a/applications/generators/MPEG4/templates3.txt b/applications/generators/MPEG4/templates3.txt new file mode 100644 index 0000000..e2a0f4b --- /dev/null +++ b/applications/generators/MPEG4/templates3.txt @@ -0,0 +1,123 @@ +#-- Version 3 --# +# templates for the BIFS nodes of v3 +# ================================== +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 Reserved +# +# %a=y Animation method for fields that can be animated +# +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying +# which node sub types the node belongs to. Moreover, each field of type +# SF/MF3DNode is re assigned a unique correct NodeDataType according to +# specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# 10 Aug 2000 (YF per reflector discussion): +# TemporalGroup children is now an MFTemporalNode +# +# 12 Nov 2001 (YF per reflector discussion): +# reorder fields of ServerCommand to match specification +# +# 6 Jan 02 YF -- add default values for children field of TemporalTransform +# and TemporalGroup +# +# YF: Created on Jun 5, 2000 based on w3383 output from Geneva + +PROTO TemporalTransform [#%NDT=SFWorldNode,SF2DNode,SF3DNode,SFTemporalNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField MFURL url [] +exposedField SFTime startTime -1.0 +exposedField SFTime optimalDuration -1.0 +exposedField SFBool active FALSE +exposedField SFFloat speed 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFVec2f scalability 1.0 1.0 #%b=[-1,+I) #%q=12 #%a=12 +exposedField MFInt32 stretchMode [0] #%b=[0,2] #%q=13 2 +exposedField MFInt32 shrinkMode [0] #%b=[0,1] #%q=13 1 +exposedField SFTime maxDelay 0 +eventOut SFTime actualDuration +] { +} + + +PROTO TemporalGroup [#%NDT=SFWorldNode,SF2DNode,SF3DNode,SFTemporalNode %COD=N +eventIn MFTemporalNode addChildren +eventIn MFTemporalNode removeChildren +exposedField MFTemporalNode children [] +field SFBool costart TRUE +field SFBool coend FALSE +field SFBool meet FALSE +exposedField MFFloat priority [] #%b=[0,+I) #%q=3 +eventOut SFBool isActive +eventOut SFInt32 activeChild +]{ +} + +PROTO ServerCommand [#%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFBool trigger FALSE +exposedField SFBool enable FALSE +exposedField MFURL url [] +exposedField SFString command "" +]{ +} + + diff --git a/applications/generators/MPEG4/templates4.txt b/applications/generators/MPEG4/templates4.txt new file mode 100644 index 0000000..94458e8 --- /dev/null +++ b/applications/generators/MPEG4/templates4.txt @@ -0,0 +1,129 @@ +#-- Version 4 --# +# templates for the BIFS nodes +# ============================= +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# Jan 29, 2000 created for v4 nodes (called group 4 now) +# +# Aug 21, 2001 Changed SFCommandBuffer from SFString buffer in InputSensor +# SFURL -> MFURL in MediaControl + +PROTO InputSensor [#%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFBool enabled TRUE +exposedField SFCommandBuffer buffer "" +exposedField MFURL url "" +eventOut SFTime eventTime +]{ +} + +PROTO MatteTexture [#%NDT=SFWorldNode,SFTextureNode,SF2DNode,SF3DNode %COD=N +field SFTextureNode surfaceA NULL +field SFTextureNode surfaceB NULL +field SFTextureNode alphaSurface NULL +exposedField SFString operation "" +field SFBool overwrite FALSE +exposedField SFFloat fraction 0 +exposedField MFFloat parameter 0 +]{ +} + +PROTO MediaBuffer [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFFloat bufferSize 0.0 #%b=[0,+I) +exposedField MFURL url [] +exposedField SFTime mediaStartTime -1 #%b=(-I,+I) +exposedField SFTime mediaStopTime +I #%b=(-I,+I) +eventOut SFBool isBuffered +exposedField SFBool enabled TRUE +] { +} + +PROTO MediaControl [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField MFURL url [] +exposedField SFTime mediaStartTime -1 #%b=(-I,+I) +exposedField SFTime mediaStopTime +I #%b=(-I,+I) +exposedField SFFloat mediaSpeed 1.0 #%b=(-I,+I) +exposedField SFBool loop FALSE +exposedField SFBool preRoll TRUE +exposedField SFBool mute FALSE +exposedField SFBool enabled TRUE +eventOut SFBool isPreRolled +] { +} + +PROTO MediaSensor [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField MFURL url [] +eventOut SFTime mediaCurrentTime +eventOut SFTime streamObjectStartTime +eventOut SFTime mediaDuration +eventOut SFBool isActive +eventOut MFString info +] { +} + diff --git a/applications/generators/MPEG4/templates5.txt b/applications/generators/MPEG4/templates5.txt new file mode 100644 index 0000000..3d626c0 --- /dev/null +++ b/applications/generators/MPEG4/templates5.txt @@ -0,0 +1,561 @@ +#-- Version 5 --# +# templates for the BIFS nodes +# ============================= +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 SFVec4f +# 16 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# March 18, 2004 [MBS] According to 68th meeting resolutions, removed Light-Field Mapping, Solid modeling and Particle systems related nodes. +# This includes the nodes: +# - +# Aug. 1, 2003 [MBS] According to 65th meeting resolutions, removed Multi-User Worlds nodes MUxxx +# Dec. 19, 2002 [MBS] According to 63rd meeting resolutions, removed Synthetic textures nodes: ColorProfile, SynthesizedTextureXXX, GradientLinear,GradientRadial,Ellipse +# Dec. 8, 2002 [MBS,MH] OctreeImage.image changed from MFTextureNode to MFDepthImageNode. DepthImage now also belongs to SFDepthImageNode context. +# Nov. 6, 2002 [MBS] modified nodes according to study of FPDAM (w5285). +# Oct 22, 2002 [MBS] added GradientRadial.Transform field which was missing but was in the spec. +# Aug 31, 2002 [MBS] +# changed SynthesizedTextureCurve.separatingFlags to MFInt32 since MFBool doesn't exist in VRML and bounds to 0 and 1 on 1 bit. +# For profileType, changed quantizer to 13 1 instead of 13 2 since values are 0 or 1 +# Aug 7, 2002 [MBS] aligned nodes to FDPAM +# May 9, 2002 [AFX] added quantizers, syntax check with PDAM +# January 28, 2002 [MBS] added rest of AFX nodes, alpha order everything +# January 6, 2002 [MBS] added AFX nodes +# December 7, 2001 [MBS, IG] created for AMD4 (v5) nodes + +# +# AFX nodes +# + +PROTO BitWrapper [ #%NDT=SFWorldNode,SF3DNode,SF2DNode,SFGeometryNode %COD=N +field SFWorldNode node NULL +field SFInt32 type 0 +field MFURL url [] +field SFString buffer "" +]{} + +PROTO CoordinateInterpolator4D [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec4f keyValue [] #%b=(-I,+I) #%q=15 +eventOut MFVec4f value_changed +] { +} + +PROTO DepthImage [ #%NDT=SFWorldNode,SF3DNode,SFDepthImageNode %COD=N +field SFDepthTextureNode diTexture NULL +field SFFloat farPlane 100 #%b=[0,+I] +field SFVec2f fieldOfView 0.785398 0.785398 #%b=[0,3.1415927] +field SFFloat nearPlane 10 #%b=[0,+I] +field SFRotation orientation 0 0 1 0 +field SFBool orthographic TRUE +field SFVec3f position 0 0 10 #%b=[-I,+I] +]{} + +PROTO FFD [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField MFVec4f controlPoint [] #%b=[-I,+I] #%q=15 #%a=15 +field SFInt32 uDimension 2 #%b=[2,257] #%q=13 8 +field MFFloat uKnot [] #%b=[-I,+I] +field SFInt32 uOrder 2 #%b=[2,33] #%q=13 5 +field SFInt32 vDimension 2 #%b=[2,257] #%q=13 8 +field MFFloat vKnot [] #%b=[-I,+I] +field SFInt32 vOrder 2 #%b=[2,33] #%q=13 5 +field SFInt32 wDimension 2 #%b=[2,257] #%q=13 8 +field MFFloat wKnot [] #%b=[-I,+I] +field SFInt32 wOrder 2 #%b=[2,33] #%q=13 5 +]{} + +PROTO Implicit [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec3f bboxSize 2 2 2 #%b=[0,+I] #%q=11 #%a=11 +exposedField MFFloat c [] #%b=[-I,+I] #%q=0 #%a=7 +exposedField MFInt32 densities [] #%b=[0,+I] +exposedField SFBool dual FALSE +exposedField SFBool solid FALSE +]{} + +# renamed as XX for deletion +PROTO XXLFM_Appearance [ #%NDT=SFWorldNode,SFAppearanceNode %COD=N +exposedField SFBlendListNode blendList NULL +exposedField MFLightMapNode lightMapList [] +exposedField MFTextureNode tileList [] +exposedField SFFrameListNode vertexFrameList NULL +]{} + +# renamed as XX for deletion +PROTO XXLFM_BlendList [ #%NDT=SFWorldNode,SFBlendListNode %COD=N +exposedField MFInt32 blendMode [] #%b=[0,1] #%q=13 1 +exposedField MFInt32 lightMapIndex [] #%q=14 +]{} + +# renamed as XX for deletion +PROTO XXLFM_FrameList [ #%NDT=SFWorldNode,SFFrameListNode %COD=N +exposedField MFInt32 index [ -1 ] #%q=14 +exposedField MFVec3f frame [ 1 0 0, 0 1 0, 0 0 1 ] #%b=[-1,1] #%q=1 +]{} + +# renamed as XX for deletion +PROTO XXLFM_LightMap [ #%NDT=SFWorldNode,SFLightMapNode %COD=N +exposedField SFVec3f biasRGB 0 0 0 #%b=[-1,1] #%q=7 +exposedField SFInt32 priorityLevel 0 #%b=[0,255] #%q=13 8 +exposedField SFVec3f scaleRGB 1 1 1 #%b=[-1,1] #%q=7 +exposedField SFSurfaceMapNode surfaceMapList NULL +exposedField SFViewMapNode viewMapList NULL +]{} + +# renamed as XX for deletion +PROTO XXLFM_SurfaceMapList [ #%NDT=SFWorldNode,SFSurfaceMapNode %COD=N +exposedField MFInt32 tileIndex [] #%q=14 +exposedField SFTextureCoordinateNode triangleCoordinate NULL +exposedField MFInt32 triangleIndex [] #%q=14 +exposedField MFInt32 viewMapIndex [] #%q=14 +]{} + +# renamed as XX for deletion +PROTO XXLFM_ViewMapList [ #%NDT=SFWorldNode,SFViewMapNode %COD=N +exposedField SFTextureCoordinateNode textureOrigin NULL +exposedField SFTextureCoordinateNode textureSize NULL +exposedField MFInt32 tileIndex [] #%q=14 +exposedField MFInt32 vertexIndex [] #%q=14 +]{} + +PROTO MeshGrid [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +eventIn MFInt32 set_normalIndex +eventIn MFInt32 set_texCoordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFInt32 displayLevel 0 #%b=[0, +I] #%q=13 32 #%a=13 +exposedField SFInt32 filterType 0 #%b=[0, 1] #%q=13 2 #%a=13 +exposedField SFCoordinateNode gridCoord NULL +exposedField SFInt32 hierarchicalLevel 0 #%b=[-1, +I] #%q=13 32 #%a=13 +exposedField MFInt32 nLevels [] #%q=7 #%a=7 +exposedField SFNormalNode normal NULL +exposedField MFInt32 nSlices [] #%q=7 #%a=7 +exposedField SFTextureCoordinateNode texCoord NULL +exposedField MFFloat vertexOffset [] #%b=[0.0, 2.0] #%q=7 #%a=7 +exposedField MFInt32 vertexLink [] #%b=[0, 3] #%q=13 2 +field MFInt32 colorIndex [] #%b=[-1, +I] #%q=14 +field MFInt32 coordIndex [] #%b=[-1, +I] #%q=14 +field MFInt32 normalIndex [] #%b=[-1, +I] #%q=14 +field SFBool solid TRUE +field MFInt32 texCoordIndex [] #%b=[-1, +I] #%q=14 +eventOut SFBool isLoading +eventOut MFInt32 nVertices +]{} + +PROTO NonLinearDeformer [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec3f axis 0 0 1 #%b=[0,1] +exposedField MFFloat extend [] +exposedField SFGeometryNode geometry NULL +exposedField SFFloat param 0 +exposedField SFInt32 type 0 #%b=[0,2] +]{} + +PROTO NurbsCurve [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +exposedField SFColorNode color NULL +exposedField MFVec4f controlPoint [] #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFInt32 tessellation 0 #%b=[0,+I] +field MFInt32 colorIndex [] #%q=14 +field SFBool colorPerVertex TRUE +field MFFloat knot [] #%b=[-I,+I] +field SFInt32 order 4 #%b=[3,34] #%q=13 5 +]{} + +PROTO NurbsCurve2D [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +exposedField SFColorNode color NULL +exposedField MFVec3f controlPoint [] #%b=[-I,+I] #%q=2 #%a=2 +exposedField SFInt32 tessellation 0 #%b=[0,+I] +field MFInt32 colorIndex [] #%q=14 +field SFBool colorPerVertex TRUE +field MFFloat knot [] #%b=[-I,+I] +field SFInt32 order 4 #%b=[3,34] #%q=13 5 +]{} + +PROTO NurbsSurface [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_texColorIndex +exposedField SFColorNode color NULL +exposedField MFVec4f controlPoint [] #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFTextureCoordinateNode texCoord NULL +exposedField SFInt32 uTessellation 0 #%b=[0,+I] +exposedField SFInt32 vTessellation 0 #%b=[0,+I] +field SFBool ccw TRUE +field MFInt32 colorIndex [] #%q=14 +field SFBool colorPerVertex TRUE +field SFBool solid TRUE +field MFInt32 texColorIndex [] #%q=14 +field SFInt32 uDimension 4 #%b=[3,258] #%q=13 8 +field MFFloat uKnot [] #%b=[-I,+I] +field SFInt32 uOrder 4 #%b=[3,34] #%q=13 5 +field SFInt32 vDimension 4 #%b=[3,258] #%q=13 8 +field MFFloat vKnot [] #%b=[-I,+I] +field SFInt32 vOrder 4 #%b=[3,34] #%q=13 5 +]{} + +PROTO OctreeImage [ #%NDT=SFWorldNode,SF3DNode %COD=N +field MFDepthImageNode images [] +field MFInt32 octree [] #%b=[0,255] #%q=13 8 +field SFInt32 octreeResolution 256 #%b=[1,+I] +field MFInt32 voxelImageIndex [] #%b=[0,255] #%q=13 8 +]{} + +# renamed as XX for deletion +PROTO XXParticles [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFFloat creationRate 500 +exposedField SFFloat creationRateVariation 0 +exposedField SFFloat emitAlpha 1.0 +exposedField SFColor emitColor 1 1 1 +exposedField SFColor emitColorVariation 0 0 0 +exposedField SFVec3f emitterPosition 0 3 0 +exposedField SFVec3f emitVelocity 0 0 0 +exposedField SFVec3f emitVelocityVariation 1 1 1 +exposedField SFBool enabled TRUE +exposedField SFFloat fadeAlpha 1.0 +exposedField SFColor fadeColor 0.25 0.25 0.25 +exposedField SFFloat fadeRate 0.25 +exposedField SFVec3f force 0 -9.8 0 +exposedField MFInfluenceNode influences [] +exposedField SFWorldNode init NULL +exposedField SFTime maxLifeTime 5 +exposedField SFFloat maxLifeTimeVariation 0 +exposedField SFInt32 maxParticles 500 +exposedField SFFloat minRange 1 +exposedField SFFloat maxRange -1 +exposedField SFWorldNode primitive NULL +exposedField SFInt32 primitiveType 2 +exposedField SFFloat particleRadius 0.1 +exposedField SFFloat particleRadiusRate 0 +exposedField SFFloat particleRadiusVariation 0 +]{} + +# renamed as XX for deletion +PROTO XXParticleInitBox [ #%NDT=SFWorldNode,SFParticleInitializerNode %COD=N +exposedField SFFloat falloff 0 +exposedField SFVec3f size 1 1 1 +]{} + +# renamed as XX for deletion +PROTO XXPlanarObstacle [ #%NDT=SFWorldNode,SFInfluenceNode %COD=N +exposedField SFVec3f distance 0 0 0 +exposedField SFVec3f normal 0 1 0 +exposedField SFFloat reflection 0 +exposedField SFFloat absorption 0 +]{} + +# renamed as XX for deletion +PROTO XXPointAttractor [ #%NDT=SFWorldNode,SFInfluenceNode %COD=N +exposedField SFFloat innerRadius 10 +exposedField SFFloat outerRadius 100 +exposedField SFVec3f position 0 0 0 +exposedField SFFloat rate 1 +]{} + +PROTO PointTexture [ #%NDT=SFWorldNode,SFDepthTextureNode %COD=N +field MFColor color [] +field MFInt32 depth [] #%b=[0,+I] +field SFInt32 depthNbBits 7 #%b=[0,31] #%q=13 5 +field SFInt32 height 256 #%b=[1,+I] +field SFInt32 width 256 #%b=[1,+I] +]{} + +PROTO PositionAnimator [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField SFVec2f fromTo 0 1 #%q=8 +exposedField MFFloat key [] #%q=8 +exposedField MFRotation keyOrientation [] +exposedField SFInt32 keyType 0 +exposedField MFVec2f keySpline [0 0, 1 1 ] #%q=8 +exposedField MFVec3f keyValue [] #%q=4 +exposedField SFInt32 keyValueType 0 +exposedField SFVec3f offset 0 0 0 #%b=[-I,+I] #%q=1 +exposedField MFFloat weight [] #%b=[-1,1] +eventOut SFVec3f endValue +eventOut SFRotation rotation_changed +eventOut SFVec3f value_changed +]{} + +PROTO PositionAnimator2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField SFVec2f fromTo 0 1 #%q=8 +exposedField MFFloat key [] #%q=8 +exposedField SFInt32 keyOrientation 0 +exposedField SFInt32 keyType 0 +exposedField MFVec2f keySpline [0 0, 1 1 ] #%q=8 +exposedField MFVec2f keyValue [] #%q=4 +exposedField SFInt32 keyValueType 0 +exposedField SFVec2f offset 0 0 #%b=[-I,+I] #%q=2 +exposedField MFFloat weight [] #%b=[-1,1] +eventOut SFVec2f endValue +eventOut SFFloat rotation_changed +eventOut SFVec2f value_changed +]{} + +PROTO PositionInterpolator4D [ #%NDT=SFWorldNode,SF3DNode %COD=N +eventIn SFFloat set_fraction +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFVec4f keyValue [] #%b=(-I,+I) #%q=15 +eventOut SFVec4f value_changed +] { +} + +PROTO ProceduralTexture [ #%NDT=SFWorldNode,SFTextureNode %COD=N +exposedField SFBool aSmooth FALSE +exposedField MFVec2f aWarpmap [0 0, 1 1] #%b=[0,1] #%q=2 +exposedField MFFloat aWeights [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0] #%b=[-1,1] +exposedField SFBool bSmooth FALSE +exposedField MFVec2f bWarpmap [0 0, 1 1] #%b=[0, 1] #%q=2 +exposedField MFFloat bWeights [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0] #%b=[-1,1] +exposedField SFInt32 cellWidth 4 #%b=[0,15] #%q=13 4 +exposedField SFInt32 cellHeight 4 #%b=[0,15] #%q=13 4 +exposedField MFColor color [0.3 0.698 1, 0.8 0.8 0.8, 1 1 1, 0 0 0] #%q=4 +exposedField SFFloat distortion 0 #%b=[0,1] #%q=13 16 +exposedField SFInt32 height 7 #%b=[1,15] #%q=13 4 +exposedField SFInt32 roughness 0 #%b=[0,15] #%q=13 4 +exposedField SFInt32 seed 129093 #%b=[-I,+I] +exposedField SFInt32 type 0 #%b=[0,4] #%q=13 3 +exposedField SFBool xSmooth FALSE +exposedField MFVec2f xWarpmap [] #%b=[0,1] #%q=2 +exposedField SFBool ySmooth FALSE +exposedField MFVec2f yWarpmap [] #%b=[0,1] #%q=2 +exposedField SFInt32 width 7 #%b=[1,15] #%q=13 4 +eventOut SFImage image_changed +]{} + +PROTO Quadric [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec3f bboxSize 2 2 2 #%b=[0,+I] #%q=11 #%a=11 +exposedField MFInt32 densities [] #%b=[0,+I] +exposedField SFBool dual FALSE +exposedField SFVec4f P0 -1 0 0 1 #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFVec4f P1 1 0 0 1 #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFVec4f P2 0 1 0 0 #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFVec4f P3 0 0 1 0 #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFVec4f P4 0 1 0 1 #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFVec4f P5 0 0 1 1 #%b=[-I,+I] #%q=15 #%a=15 +exposedField SFBool solid FALSE +]{} + +PROTO SBBone [ #%NDT=SFWorldNode,SF2DNode,SF3DNode,SFSBBoneNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField SFInt32 boneID 0 #%b=[0,1023] #%q=13 10 +exposedField SFVec3f center 0 0 0 #%q=1 #%a=1 +exposedField MF3DNode children [] +exposedField SFVec3f endpoint 0 0 1 #%q=1 #%a=1 +exposedField SFInt32 falloff 1 #%b=[-1,4] #%q=13 3 +exposedField SFInt32 ikChainPosition 0 #%b=[0,3] #%q=13 2 +exposedField MFFloat ikPitchLimit [] +exposedField MFFloat ikRollLimit [] +exposedField MFFloat ikTxLimit [] +exposedField MFFloat ikTyLimit [] +exposedField MFFloat ikTzLimit [] +exposedField MFFloat ikYawLimit [] +exposedField SFRotation rotation 0 0 1 0 #%q=10 #%a=10 +exposedField SFInt32 rotationOrder 0 #%b=[0,23] #%q=13 5 +exposedField SFVec3f scale 0 0 0 #%q=7 #%a=11 +exposedField SFRotation scaleOrientation 0 0 1 0 #%q=10 #%a=10 +exposedField MFFloat sectionInner [] +exposedField MFFloat sectionOuter [] +exposedField MFFloat sectionPosition [] +exposedField MFInt32 skinCoordIndex [] #%q=14 +exposedField MFFloat skinCoordWeight [] #%b=[-1,1] +exposedField SFVec3f translation 0 0 0 #%q=1 #%a=1 +]{} + +PROTO SBMuscle [ #%NDT=SFWorldNode,SF2DNode,SF3DNode,SFSBMuscleNode %COD=N +exposedField SFInt32 falloff 1 #%b=[-1,4] #%q=13 3 +exposedField SFGeometryNode muscleCurve NULL +exposedField SFInt32 muscleID 0 #%b=[0,1023] #%q=13 10 +exposedField SFInt32 radius 1 #%q=7 #%a=11 +exposedField MFInt32 skinCoordIndex [] #%b=[0,+I] #%q=14 +exposedField MFFloat skinCoordWeight [] #%b=[-1,1] +]{} + +PROTO SBSegment [ #%NDT=SFWorldNode,SF2DNode,SF3DNode,SFSBSegmentNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField SFVec3f centerOfMass 0 0 0 #%q=1 #%a=1 +exposedField MF3DNode children [] +exposedField SFFloat mass 0 +exposedField MFVec3f momentsOfInertia [ 0 0 0, 0 0 0, 0 0 0 ] +exposedField SFString name "" +]{} + +PROTO SBSite [ #%NDT=SFWorldNode,SF2DNode,SF3DNode,SFSBSiteNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField SFVec3f center 0 0 0 #%q=1 #%a=1 +exposedField MF3DNode children [] +exposedField SFString name "" +exposedField SFRotation rotation 0 0 1 0 #%q=10 #%a=10 +exposedField SFVec3f scale 1 1 1 #%q=7 #%a=11 +exposedField SFRotation scaleOrientation 0 0 1 0 #%q=10 #%a=10 +exposedField SFVec3f translation 0 0 0 #%q=1 #%a=1 +]{} + +PROTO SBSkinnedModel [#%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField MFSBBoneNode bones [] +exposedField SFVec3f center 0 0 0 #%q=1 #%a=1 +exposedField MFSBMuscleNode muscles [] +exposedField SFString name "" +exposedField SFRotation rotation 0 0 1 0 #%q=10 #%a=10 +exposedField MFSBSegmentNode segments [] +exposedField SFVec3f scale 1 1 1 #%q=7 #%a=11 +exposedField SFRotation scaleOrientation 0 0 1 0 #%q=10 #%a=10 +exposedField MFSBSiteNode sites [] +exposedField MF3DNode skeleton [] +exposedField MF3DNode skin [] +exposedField SFCoordinateNode skinCoord NULL +exposedField SFNormalNode skinNormal NULL +exposedField SFVec3f translation 0 0 0 #%q=1 #%a=1 +exposedField SF3DNode weighsComputationSkinCoord NULL +]{} + +PROTO SBVCAnimation [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +exposedField MFURL url [] +exposedField MF3DNode virtualCharacters [] +]{} + +PROTO ScalarAnimator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N +eventIn SFFloat set_fraction +exposedField SFVec2f fromTo 0 1 #%q=8 +exposedField MFFloat key [] #%q=8 +exposedField SFInt32 keyType 0 +exposedField MFVec2f keySpline [0 0, 1 1 ] #%q=8 +exposedField MFFloat keyValue [] #%q=0 +exposedField SFInt32 keyValueType 0 +exposedField SFFloat offset 0 +exposedField MFFloat weight [] #%b=[-1,1] +eventOut SFFloat endValue +eventOut SFFloat value_changed +]{} + +PROTO SimpleTexture [ #%NDT=SFWorldNode,SFDepthTextureNode %COD=N +field SFTextureNode depth NULL +field SFTextureNode texture NULL +]{} + +PROTO SolidRep [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec3f bboxSize 2 2 2 #%b=[0,+I] #%q=11 #%a=11 +exposedField MFInt32 densityList [] #%b=[0,+I] +exposedField SF3DNode solidTree NULL +]{} + +PROTO SubdivisionSurface [ #%NDT=SFWorldNode,SFGeometryNode,SFBaseMeshNode %COD=N +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +eventIn MFInt32 set_cornerVertexIndex +eventIn MFInt32 set_creaseEdgeIndex +eventIn MFInt32 set_creaseVertexIndex +eventIn MFInt32 set_dartVertexIndex +eventIn MFInt32 set_texCoordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFTextureCoordinateNode texCoord NULL +exposedField MFSubdivSurfaceSectorNode sectors [] +exposedField SFInt32 subdivisionLevel 0 #%b=[-1,+I] +exposedField SFInt32 subdivisionType 0 #%b=[0,3] #%q=13 2 +exposedField SFInt32 subdivisionSubType 0 #%b=[0,3] #%q=13 2 +field MFInt32 invisibleEdgeIndex [] #%b=[0,+I] +field SFBool ccw TRUE +field MFInt32 colorIndex [] #%b=[-1,+I] +field SFBool colorPerVertex TRUE +field SFBool convex TRUE +field MFInt32 coordIndex [] #%b=[-1,+I] +field MFInt32 cornerVertexIndex [] #%b=[-1,+I] +field MFInt32 creaseEdgeIndex [] #%b=[-1,+I] +field MFInt32 creaseVertexIndex [] #%b=[-1,+I] +field MFInt32 dartVertexIndex [] #%b=[-1,+I] +field SFBool solid TRUE +field MFInt32 texCoordIndex [] #%b=[-1,+I] +]{} + +PROTO SubdivSurfaceSector [ #%NDT=SFWorldNode,SFSubdivSurfaceSectorNode %COD=N +exposedField SFFloat flatness 0 #%b=[0,1] #%q=7 +exposedField SFVec3f normal 0 0 0 #%q=9 +exposedField SFFloat normalTension 0 #%b=[0,1] #%q=7 +exposedField SFInt32 tag 0 #%b=[0,2] #%q=13 2 +exposedField SFFloat theta 0 #%b=[0,6.2831853] #%q=6 +field SFInt32 faceIndex 0 #%q=14 +field SFInt32 vertexIndex 0 #%q=14 +]{} + +PROTO WaveletSubdivisionSurface [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFGeometryNode baseMesh NULL +exposedField SFFloat fieldOfView 0.785398 # pi/4 +exposedField SFFloat frequency 1.0 +exposedField SFInt32 quality 1 +]{} + + + diff --git a/applications/generators/MPEG4/templates6.txt b/applications/generators/MPEG4/templates6.txt new file mode 100644 index 0000000..ab82832 --- /dev/null +++ b/applications/generators/MPEG4/templates6.txt @@ -0,0 +1,242 @@ +#-- Version 6 --# +#-- Beta Advanced Text Graphics --# +# templates for the BIFS nodes +# ============================= +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# July 31, 2003 [CC, ENST] updated to FPDAM2 (w5774) +# April 28, 2003 [JLF, ENST] updated to PDAM (w5645) +# January 9, 2003 [JLF, ENST] updated to WD3.0 (w5475) +# September 17, 2002 [JLF, ENST] created for AdvancedText & Graphics WD 2.0 +#NB: XFontStyle.feature* MFInt32 fields should have some quantization type ? + +PROTO Clipper2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFGeometryNode geometry NULL +exposedField SFBool inside TRUE +exposedField SF2DNode transform NULL +exposedField SFBool XOR FALSE +] { +} + +PROTO ColorTransform [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFFloat mrr 1 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mrg 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mrb 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mra 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat tr 0 #%b=(-I, +I) #%q=4 #%a=7 +exposedField SFFloat mgr 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mgg 1 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mgb 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mga 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat tg 0 #%b=(-I, +I) #%q=4 #%a=7 +exposedField SFFloat mbr 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mbg 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mbb 1 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mba 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat tb 0 #%b=(-I, +I) #%q=4 #%a=7 +exposedField SFFloat mar 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mag 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mab 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat maa 1 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat ta 0 #%b=(-I, +I) #%q=4 #%a=7 +] { +} + +PROTO Ellipse [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFVec2f radius 1 1 #%b=[0,+I) #%q=12 #%a=2 +]{} + +PROTO LinearGradient [ #%NDT=SFWorldNode,SFTextureNode %COD=N +exposedField SFVec2f endPoint 1 0 #%b=(-I, +I) #%q=5 #%a=2 +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFColor keyValue [] #%b=[0,1] #%q=4 +exposedField MFFloat opacity [1] #%b=[0,1] #%q=7 +exposedField SFInt32 spreadMethod 0 #%b=[0,2] #%q=13 2 +exposedField SFVec2f startPoint 0 0 #%b=(-I, +I) #%q=5 #%a=2 +exposedField SF3DNode transform NULL +]{} + +PROTO PathLayout [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFGeometryNode geometry NULL +exposedField MFInt32 alignment [0 0] #%b=[-1,1] #%q=13 2 +exposedField SFFloat pathOffset 0 #%b=[-I,I] #%q=7 #%a=7 +exposedField SFFloat spacing 1.0 #%b=[-I,I] #%q=7 #%a=7 +exposedField SFBool reverseLayout FALSE +exposedField SFInt32 wrapMode 0 #%b=[0,2] #%q=13 2 +exposedField SFBool splitText TRUE +] { +} + +PROTO RadialGradient [ #%NDT=SFWorldNode,SFTextureNode %COD=N +exposedField SFVec2f center 0.5 0.5 #%b=(-I, +I) #%q=5 #%a=2 +exposedField SFVec2f focalPoint 0 0 #%b=(-I, +I) #%q=5 #%a=2 +exposedField MFFloat key [] #%b=[0,1] #%q=8 +exposedField MFColor keyValue [] #%b=[0,1] #%q=4 +exposedField MFFloat opacity [1] #%b=[0,1] #%q=7 +exposedField SFFloat radius 0.5 #%b=[0,+I) #%q=12 #%a=7 +exposedField SFInt32 spreadMethod 0 #%b=[0,2] #%q=13 2 +exposedField SF3DNode transform NULL +]{} + +PROTO SynthesizedTexture [ #%NDT=SFWorldNode,SFTextureNode %COD=N +exposedField MFVec3f translation [] #%b=[-I,+I] #%q=1 #%a=1 +exposedField MFRotation rotation [] #%b=[-I,+I] #%q=10 #%a=10 +exposedField SFInt32 pixelWidth -1 #%b=[0,65535] #%q=13 16 +exposedField SFInt32 pixelHeight -1 #%b=[0,65535] #%q=13 16 +exposedField SFBool loop FALSE +exposedField SFFloat speed 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=(-I,+I) +exposedField SFTime stopTime 0 #%b=(-I,+I) +exposedField MFURL url [] +eventOut SFTime duration_changed +eventOut SFBool isActive +]{ +} + +PROTO TransformMatrix2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode,SFTextureTransformNode %COD=N +eventIn MF2DNode addChildren +eventIn MF2DNode removeChildren +exposedField MF2DNode children [] +exposedField SFFloat mxx 1 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat mxy 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat tx 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat myx 0 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat myy 1 #%b=(-I, +I) #%q=7 #%a=7 +exposedField SFFloat ty 0 #%b=(-I, +I) #%q=7 #%a=7 +] { +} + +PROTO Viewport [ #%NDT=SFWorldNode,SF3DNode,SF2DNode,SFViewportNode %COD=N +eventIn SFBool set_bind +exposedField SFVec2f position 0 0 #%b=(-I,+I) #%q=1 #%a=1 +exposedField SFVec2f size -1 -1 #%b=(-I,+I) #%q=12 #%a=12 +exposedField SFFloat orientation 0 #%b=[0,6.2831853] #%q=6 #%a=6 +exposedField MFInt32 alignment [0 0] #%b=[-1,1] #%q=13 3 +exposedField SFInt32 fit 0 #%b=[0,2] #%q=13 3 +field SFString description "" +eventOut SFTime bindTime +eventOut SFBool isBound + +] { +} + +PROTO XCurve2D [ #%NDT=SFWorldNode,SFGeometryNode %COD=N +exposedField SFCoordinate2DNode point [] +exposedField SFFloat fineness 0.5 #%b=[0,1] #%q=0 #%a=7 +exposedField MFInt32 type [] #%b=[0,15] #%q=13 4 +] { +} + +PROTO XFontStyle [ #%NDT=SFWorldNode,SFFontStyleNode %COD=N +exposedField MFString fontName ["SERIF"] +exposedField SFBool horizontal TRUE +exposedField MFString justify ["BEGIN"] +exposedField SFString language "" +exposedField SFBool leftToRight TRUE +exposedField SFFloat size 1.0 #%b=[0,+I) #%q=11 +exposedField SFString stretch "NORMAL" +exposedField SFFloat letterSpacing 0.0 #%b=[0,+I) #%q=11 +exposedField SFFloat wordSpacing 0.0 #%b=[0,+I) #%q=11 +exposedField SFInt32 weight 400 +exposedField SFBool fontKerning TRUE +exposedField SFString style "PLAIN" +exposedField SFBool topToBottom TRUE +exposedField MFString featureName [""] +exposedField MFInt32 featureStartOffset [] #%b=(-I, +I) +exposedField MFInt32 featureLength [] #%b=(-I, +I) +exposedField MFInt32 featureValue [] #%b=(-I, +I) +] { +} + +PROTO XLineProperties [ #%NDT=SFWorldNode,SFLinePropertiesNode %COD=N +exposedField SFColor lineColor 0 0 0 #%b=[0,1] #%q=4 #%a=4 +exposedField SFInt32 lineStyle 0 #%b=[0,5] #%q=13 3 +exposedField SFBool isCenterAligned TRUE +exposedField SFBool isScalable TRUE +exposedField SFInt32 lineCap 0 #%b=[0,2] #%q=13 3 +exposedField SFInt32 lineJoin 0 #%b=[0,2] #%q=13 3 +exposedField SFFloat miterLimit 4 #%b=[1,+I) #%q=12 +exposedField SFFloat transparency 0 #%b=[0,1] #%q=4 #%a=8 +exposedField SFFloat width 1 #%b=[0,+I) #%q=12 #%a=7 +exposedField SFFloat dashOffset 0 #%b=[0,+I) #%q=12 #%a=7 +exposedField MFFloat dashes [] #%b=[0,+I) #%q=12 #%a=7 +exposedField SFTextureNode texture NULL +exposedField SFTextureTransformNode textureTransform NULL +]{ +} diff --git a/applications/generators/MPEG4/templates7.txt b/applications/generators/MPEG4/templates7.txt new file mode 100644 index 0000000..55b4536 --- /dev/null +++ b/applications/generators/MPEG4/templates7.txt @@ -0,0 +1,235 @@ +#-- Version 7 --# +# +# Beta for AFX/AMD1 +# +# templates for the BIFS nodes +# ============================= +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 SFVec4f +# 16 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# March 18, 2003 [MBS] According to 68th meeting resolutions, created for AFX/AMD1 nodes + +# +# AFX/AMD1 nodes +# + + +PROTO AdvancedAudioBuffer [ #%NDT=SFWorldNode,SFAudioNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField SFBool loop FALSE +exposedField SFFloat pitch 1.0 #%b=[0,+I] #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=[0,+I] #%q=0 +exposedField SFTime stopTime 0 #%b=[0,+I] #%q=0 +exposedField SFTime startLoadTime 0 #%b=[0,+I] #%q=0 +exposedField SFTime stopLoadTime 0 #%b=[0,+I] #%q=0 +exposedField SFInt32 loadMode 0 #%b=[0,4] #%q=13 3 +exposedField SFInt32 numAccumulatedBlocks 0 #%b=[0,65535] #%q=13 16 +exposedField SFInt32 deleteBlock 0 #%b=[-65536, 0] #%q=13 17 +exposedField SFInt32 playBlock 0 #%b=[-65536, 0] #%q=13 17 +exposedField SFFloat length 0.0 #%b=[0,+I] #%q=0 +field SFInt32 numChan 1 #%b=[0,255] #%q=13 8 +field MFInt32 phaseGroup [] #%b=[0,255] #%q=13 8 +eventOut SFTime duration_changed +eventOut SFBool isActive +]{} + + +PROTO AudioChannelConfig [ #%NDT=SFWorldNode,SFAudioNode %COD=N +eventIn MFAudioNode addChildren +eventIn MFAudioNode removeChildren +exposedField MFAudioNode children [] +exposedField SFInt32 generalChannelFormat 0 #%b=[0,4] #%q=13 3 +exposedField SFInt32 fixedPreset 0 #%b=[0,15] #%q=13 4 +exposedField SFInt32 fixedPresetSubset 0 +exposedField SFInt32 fixedPresetAddInf 0 #%b=[0,2] #%q=13 3 +exposedField MFInt32 channelCoordinateSystems [] #%b=[0,6] #%q=13 3 +exposedField MFFloat channelSoundLocation [] #%b=[-I,+I] +exposedField MFInt32 channelDirectionalPattern [] #%b=[0,2] #%q=13 3 +exposedField MFVec3f channelDirection [] +exposedField SFInt32 ambResolution2D 1 #%b=[0,127] #%q=13 7 +exposedField SFInt32 ambResolution3D 0 #%b=[0,15] #%q=13 4 +exposedField SFInt32 ambEncodingConvention 0 #%b=[0,5] #%q=13 3 +exposedField SFFloat ambNfcReferenceDistance 1.5 #%b=[0,+I] +exposedField SFFloat ambSoundSpeed 340.0 #%b=[0,+I] +exposedField SFInt32 ambArrangementRule 0 #%b=[0,7] #%q=13 3 +exposedField SFInt32 ambRecombinationPreset 0 #%b=[0,15] #%q=13 4 +exposedField MFInt32 ambComponentIndex [] #%b=[0,255] #%q=13 8 +exposedField MFFloat ambBackwardMatrix [] #%b=[-I,+I] +exposedField MFInt32 ambSoundfieldResolution [] #%b=[0,127] #%q=13 7 +field SFInt32 numChannel 0 #%b=[0,255] #%q=13 8 +]{} + +PROTO DepthImageV2 [ #%NDT=SFWorldNode,SF3DNode,SFDepthImageNode %COD=N +field SFDepthTextureNode diTexture NULL +field SFFloat farPlane 100 #%b=[0,+I] +field SFVec2f fieldOfView 0.75 0.75 #%b=[0,3.1415927] +field SFFloat nearPlane 10 #%b=[0,+I] +field SFRotation orientation 0 0 1 0 +field SFBool orthographic TRUE +field SFVec3f position 0 0 10 #%b=[-I,+I] +field SFVec2f splatMinMax 0.115 0.975 #%b=[-I,+I] +]{} + + +PROTO MorphShape [ #%NDT=SFWorldNode,SF3DNode,SF2DNode, %COD=N + exposedField SF3DNode baseShape NULL + exposedField SFInt32 morphID 0 #%b=[0, 1023] #%q=13 7 + exposedField MF3DNode targetShapes [ ] + exposedField MFFloat weights [ ] +]{} + +PROTO MultiTexture [ #%NDT=SFWorldNode,SFTextureNode %COD=N + exposedField SFFloat alpha 1 #%b=[0,1] + exposedField SFColor color 1 1 1 #%b=[0,1] + exposedField MFInt32 function [] + exposedField MFInt32 mode [] + exposedField MFInt32 source [] + exposedField MFTextureNode texture [] + exposedField MFVec3f cameraVector [] + exposedField SFBool transparent FALSE +]{} + +PROTO PointTextureV2 [ #%NDT=SFWorldNode,SFDepthTextureNode %COD=N +field MFColor color [] +field MFInt32 depth [] #%b=[0,+I] +field SFInt32 depthNbBits 7 #%b=[0,31] #%q=13 5 +field SFInt32 height 256 #%b=[1,+I] +field SFNormalNode normal NULL #%b=[-I,+I] +field MFVec3f splatU [] #%b=[-I,+I] #%q=1 +field MFVec3f splatV [] #%b=[-I,+I] #%q=1 +field SFInt32 width 256 #%b=[1,+I] +]{} + + +PROTO SBVCAnimationV2 [ #%NDT=SFWorldNode,SF3DNode,SF2DNode %COD=N + exposedField MFInt32 activeUrlIndex [] + exposedField SFBool loop FALSE + exposedField SFFloat speed 1.0 #%b=(-I,+I) #%q=0 #%a=7 + exposedField SFTime startTime 0 #%b=(-I,+I) + exposedField SFTime stopTime 0 #%b=(-I,+I) + exposedField SFFloat transitionTime 0 #%b=[0,+I) #%q=0 #%a=7 + exposedField MFURL url [ ] + exposedField MF3DNode virtualCharacters [ ] + eventOut SFTime duration_changed + eventOut SFBool isActive +]{} + +PROTO SimpleTextureV2 [ #%NDT=SFWorldNode,SFDepthTextureNode %COD=N +field SFTextureNode depth NULL +field SFTextureNode normal NULL +field SFTextureNode splatU NULL +field SFTextureNode splatV NULL +field SFTextureNode texture NULL +]{} + +PROTO SurroundingSound [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFAudioNode source NULL +exposedField SFFloat intensity 1.0 #%b=[0,1] #%q=0 #%a=7 +exposedField SFFloat distance 0.0 #%b=[0,+I] #%q=0 +exposedField SFVec3f location 0.0 0.0 0.0 #%b=[-I,+I] #%q=1 #%a=1 +exposedField SFFloat distortionFactor 0.0 #%b=[-I,+I] +exposedField SFRotation orientation 0.0 0.0 1.0 0.0 #%b=[-I,+I] #%q=10 #%a=10 +exposedField SFBool isTransformable TRUE +]{} + + +PROTO Transform3DAudio [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFFloat thirdCenterCoordinate 0.0 #%b=[-I,+I] #%q=0 #%a=7 +exposedField SFVec3f rotationVector 0.0 0.0 1.0 #%b=[-3.14159265,3.14159265] #%q=7 #%a=11 +exposedField SFFloat thirdScaleCoordinate 0.0 #%b=[0,+I] #%q=0 #%a=7 +exposedField SFVec3f scaleOrientationVector 0.0 0.0 1.0 #%b=[-1,1] #%q=7 #%a=11 +exposedField SFFloat thirdTranslationCoordinate 0.0 #%b=[-I,+I] #%q=0 #%a=7 +exposedField SFRotation coordinateTransform 1.0 0.0 0.0 -1.5707963 #%b=[-3.14159265,3.14159265] #%q=10 +]{} + + +PROTO WideSound [ #%NDT=SFWorldNode,SF3DNode %COD=N +exposedField SFAudioNode source NULL +exposedField SFFloat intensity 1 #%b=[0,1] #%q=0 #%a=7 +exposedField SFVec3f location 0.0 0.0 0.0 #%b=[-I,+I] #%q=1 #%a=1 +exposedField SFBool spatialize TRUE +exposedField SFPerceptualParameterNode perceptualParameters NULL +exposedField SFBool roomEffect FALSE +exposedField SFInt32 shape 0 #%b=[0,4] #%q=13 4 +exposedField MFFloat size 0.0 #%b=[-I,+I] +exposedField SFVec3f direction 0.0 1.0 0.0 #%b=[-I,+I] #%q=9 #%a=9 +exposedField SFFloat density 0.5 #%b=[0,+I] +exposedField SFInt32 diffuseSelect 1 #%b=[0,+I] #%q=0 +exposedField SFFloat decorrStrength 1.0 #%b=[0,1] +field SFFloat speedOfSound 340.0 #%b=[0,+I] #%q=1 +field SFFloat distance 1000.0 #%b=[0,+I] #%q=0 +field SFBool useAirabs FALSE +]{} + diff --git a/applications/generators/MPEG4/templates8.txt b/applications/generators/MPEG4/templates8.txt new file mode 100644 index 0000000..2a907b2 --- /dev/null +++ b/applications/generators/MPEG4/templates8.txt @@ -0,0 +1,124 @@ +#-- Version 8 --# +# +# Beta for Symbolic Music Representation (SMR) +# +# templates for the BIFS nodes +# ============================= +# Notations I = Infinity +# %q=x Quantization method x +# 0 None +# 1 3D Position (SFVec3F) +# 2 2D Position (SFVec2F) +# 3 drawing Order +# 4 Color (SFColor) +# 5 Texture Coordinate +# 6 Angle (SFFloat 0-2PI) +# 7 Scale (SFVec2F or SFVec3F) +# 8 Interpolators keys +# 9 Normals +# 10 Rotations (SFRotation) +# 11 Object Size 3D (SFVec3F and SFFloat) +# 12 Object Size 2D +# 13 Linear Quantization (+ Nb Bits) +# 14 Index (of IndexedFaceSet,...) +# 15 SFVec4f +# 16 Reserved +# +# %a=y Animation method for fields that can be animated +# +## OO 081498 To match BIFS's update numbering +# 0 None +# 1 Position 3D +# 2 Position 2D +# 4 Color +# 6 Angle +# 7 Float +# 8 BoundFloat (intensities, transparencies,...) +# 9 Normal +# 10 Rotation +# 11 Size 3D +# 12 Size 2D +# 13 Integer +# 14 Reserved +## 0 3D Position +## 1 2D positon +## 2 Color (SFColor) +## 3 Angle (SFFloat 0-2pi) +## 4 Normals +## 5 Scale (SFVec2F) +## 6 Rotation (SFRotation) +## 7 Object Size or Scalar (SFFloat) +# +# %b=[min,max] bounds of value +# For each scalar or vectorial value, bounds may be specified. +# This will be used to check if user-specified values are out of bounds. In +# this case, bounds specified in the templates will be used (if not infinity). +# +# %NDT=Node Data Type +# For each node, one or several Node Data Types are assigned, specifying which node sub +# types the node belongs to. Moreover, each field of type SF/MF3DNode is re assigned +# a unique correct NodeDataType according to specify the allowed values of the field +# +# %COD Type of encoding +# N Normal Syntax : The node syntax follos the generic syntax for nodes +# S Special Syntax : The node has a specific syntax +# +# +# NCT => VRML type equivalence +# +# SF/MFxxxNode => SF/MFNode +# SF/MFURL => SF/MFString +# SF/MFCommandBuffer => SF/MFString +# SF/MFScript => SF/MFString +# +# +# Modification History +# ------------------------------------------------ +# October 9, 2006 [MBS] Added SMR nodes based on w8121 + +# +# Symbolic Music Representation (SMR) nodes +# + + +PROTO ScoreShape [ #%NDT=SFWorldNode,SF2DNode,SF3DNode %COD=N +exposedField SFMusicScoreNode score NULL +exposedField SF2DNode geometry NULL +]{} + + +PROTO MusicScore [ #%NDT=SFWorldNode,SFMusicScoreNode %COD=N +eventIn SFBool executeCommand +eventIn SFString gotoLabel +eventIn SFInt32 gotoMeasure +eventIn SFTime highlightTimePosition +eventIn SFVec3f mousePosition +exposedField MFString argumentsOnExecute [] +exposedField SFString commandOnExecute "" +exposedField SFInt32 firstVisibleMeasure 0 +exposedField SFBool hyperlinkEnable TRUE +exposedField SFBool loop FALSE +exposedField MFString partsLyrics [] +exposedField MFInt32 partsShown [] +exposedField SFTime scoreOffset 0.0 +exposedField SFVec2f size -1 -1 +exposedField SFFloat speed 1.0 #%b=(-I,+I) #%q=0 #%a=7 +exposedField SFTime startTime 0 #%b=(-I,+I) +exposedField SFTime stopTime 0 #%b=(-I,+I) +exposedField SFFloat transpose 0.0 +exposedField MFURL url [] +exposedField MFURL urlSA [] +exposedField SFString viewType "" +eventOut SFString activatedLink +eventOut MFString availableCommands +eventOut MFString availableLabels +eventOut MFString availableLyricLanguages +eventOut MFString availableViewTypes +eventOut SFBool isActive +eventOut SFVec3f highlightPosition +eventOut SFInt32 lastVisibleMeasure +eventOut SFInt32 numMeasures +eventOut MFString partNames +]{} + + diff --git a/applications/generators/MPEG4/templates9.txt b/applications/generators/MPEG4/templates9.txt new file mode 100644 index 0000000..a88813b --- /dev/null +++ b/applications/generators/MPEG4/templates9.txt @@ -0,0 +1,68 @@ +PROTO FootPrintSetNode [#%NDT=SFWorldNode,SF3DNode,SFGeometryNode %COD=N +exposedField MFGeometryNode children [] +]{} + +PROTO FootPrintNode [#%NDT=SFWorldNode,SF3DNode,SFGeometryNode %COD=N + +exposedField SFInt32 index -1 #%b=[0,65535] +exposedField SFGeometryNode footprint NULL +]{} + +PROTO BuildingPartNode [#%NDT=SFWorldNode,SF3DNode,SFGeometryNode %COD=N + +exposedField SFInt32 index -1 #%b=[0,65535] +exposedField SFGeometryNode footprint NULL +exposedField SFInt32 buildingIndex -1 #%b=[0,65535] +exposedField SFFloat height 0 #%b=[0,I] +exposedField SFFloat altitude 0 #%b=[0,I] +exposedField MFGeometryNode alternativeGeometry [] +exposedField MFGeometryNode roofs [] +exposedField MFGeometryNode facades [] +]{} + + + +PROTO RoofNode [#%NDT=SFWorldNode,SF3DNode,SFGeometryNode %COD=N + +exposedField SFInt32 Type 0 #%b=[0,65535] +exposedField SFFloat Height 0.0 #%b=[0,I] +exposedField MFFloat SlopeAngle [0.0] #%b=[0,6.2831854] +exposedField SFFloat EaveProjection 0.0 +exposedField SFInt32 EdgeSupportIndex -1 #%b=[0,65535] +exposedField SFURL RoofTextureURL "" +exposedField SFBool IsGenericTexture TRUE +exposedField SFFloat TextureXScale 1.0 #%b=[0,I] +exposedField SFFloat TextureYScale 1.0 #%b=[0,I] +exposedField SFFloat TextureXPosition 0.0 #%b=[0,I] +exposedField SFFloat TextureYPosition 0.0 #%b=[0,I] +exposedField SFFloat TextureRotation 0.0 #%b=[0,I] +]{} + + + +PROTO FacadeNode [#%NDT=SFWorldNode,SF3DNode,SFGeometryNode %COD=N +exposedField SFFloat WidthRatio 1.0 #%b=[-I,I] +exposedField SFFloat XScale 1.0 #%b=[-I,I] +exposedField SFFloat YScale 1.0 #%b=[-I,I] +exposedField SFFloat XPosition 0.0 #%b=[-I,I] +exposedField SFFloat YPosition 0.0 #%b=[-I,I] +exposedField SFFloat XRepeatInterval 0.0 #%b=[-I,I] +exposedField SFFloat YRepeatInterval 0.0 #%b=[-I,I] +exposedField SFBool Repeat FALSE +exposedField SFURL FacadePrimitive "" +exposedField SFInt32 NbStories 0 #%b=[0,65535] +exposedField MFInt32 NbFacadeCellsByStorey 0 +exposedField MFFloat StoreyHeight 1.0 #%b=[0,I] +exposedField MFGeometryNode FacadeCellsArray [] +]{} + + +PROTO Shadow [#%NDT=SFWorldNode,SF3DNode,SFGeometryNode %COD=N +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFBool enabled TRUE +exposedField SFBool cast TRUE +exposedField SFBool receive TRUE +exposedField SFFloat penumbra 0 #%b=[0,I] +]{} diff --git a/applications/generators/Makefile b/applications/generators/Makefile new file mode 100644 index 0000000..a9fdf0c --- /dev/null +++ b/applications/generators/Makefile @@ -0,0 +1,26 @@ +include ../../config.mak + +GENDIRS=MPEG4 X3D + +ifeq ($(HAS_LIBXML2),yes) +#sorry minGW friends, I can't get a stable libxml2 to compile ... +ifeq ($(CONFIG_WIN32),yes) +else +GENDIRS+=SVG +endif +endif + +all: sggen + +sggen: + set -e; for i in $(GENDIRS) ; do $(MAKE) -C $$i all; done + +dep: + set -e; for i in $(GENDIRS) ; do $(MAKE) -C $$i dep; done + +clean: + set -e; for i in $(GENDIRS) ; do $(MAKE) -C $$i clean; done + +distclean: + set -e; for i in $(GENDIRS) ; do $(MAKE) -C $$i distclean; done + diff --git a/applications/generators/SVG/Makefile b/applications/generators/SVG/Makefile new file mode 100644 index 0000000..d326671 --- /dev/null +++ b/applications/generators/SVG/Makefile @@ -0,0 +1,57 @@ +include ../../../config.mak + +vpath %.c $(SRC_PATH)/applications/generators/SVG + +CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= html.o laser.o main.o v1.o v2.o v3.o + +CFLAGS+=-g +LDFLAGS+=-g +CFLAGS+=$(XML2_CFLAGS) + +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=SVGGen$(EXE) +EXTRALIBS+=-lwsock32 -lz +else +EXT= +PROG=SVGGen +endif + +SRCS := $(OBJS:.o=.c) + +all: $(PROG) + +SVGGen$(EXE): $(OBJS) + $(CC) -o $@ $(OBJS) $(XML2_LIBS) $(EXTRALIBS) -L../../../bin/gcc -L../../../extra_lib/lib/gcc -lgpac -lz $(LDFLAGS) + + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + + +clean: + rm -f $(OBJS) $(PROG) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/generators/SVG/SVGGen.dsp b/applications/generators/SVG/SVGGen.dsp new file mode 100644 index 0000000..c76b774 --- /dev/null +++ b/applications/generators/SVG/SVGGen.dsp @@ -0,0 +1,126 @@ +# Microsoft Developer Studio Project File - Name="SVGGen" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=SVGGen - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "SVGGen.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "SVGGen.mak" CFG="SVGGen - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SVGGen - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "SVGGen - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SVGGen - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../../include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 libxml2.lib zlib.lib iconv.lib /nologo /subsystem:console /machine:I386 /libpath:"../../../extra_lib/lib/w32_release" + +!ELSEIF "$(CFG)" == "SVGGen - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../../../include/" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 libxml2.lib zlib.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept /libpath:"../../../extra_lib/lib/w32_deb" + +!ENDIF + +# Begin Target + +# Name "SVGGen - Win32 Release" +# Name "SVGGen - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\src\utils\error.c +# End Source File +# Begin Source File + +SOURCE=.\html.c +# End Source File +# Begin Source File + +SOURCE=.\laser.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\src\utils\list.c +# End Source File +# Begin Source File + +SOURCE=.\main.c +# End Source File +# Begin Source File + +SOURCE=.\v1.c +# End Source File +# Begin Source File + +SOURCE=.\v2.c +# End Source File +# Begin Source File + +SOURCE=.\v3.c +# End Source File +# End Group +# Begin Source File + +SOURCE=.\svggen.h +# End Source File +# End Target +# End Project diff --git a/applications/generators/SVG/SVGGen.dsw b/applications/generators/SVG/SVGGen.dsw new file mode 100644 index 0000000..1b31b26 --- /dev/null +++ b/applications/generators/SVG/SVGGen.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SVGGen"=.\SVGGen.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.nvdl b/applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.nvdl new file mode 100644 index 0000000..0571331 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.nvdl @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.rng b/applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.rng new file mode 100644 index 0000000..90c7adc --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/Tiny-1.2.rng @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/animate.rng b/applications/generators/SVG/Tiny-1.2-NG/animate.rng new file mode 100644 index 0000000..5a49526 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/animate.rng @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + remove + freeze + + + + + + + + + + + + + + + + always + never + whenNotActive + + + + + + + + + + + + + + canSlip + locked + independent + default + + + + + + + + default + + + + + + + + + + + + + + + + + + + + canSlip + locked + independent + inherit + + + + + + + + inherit + + + + + + + + + + + + + + + + + + + + + XML + CSS + auto + + + + + + + + + + + + + + + discrete + linear + paced + spline + + + + + + + + + + + + + + + replace + sum + + + + + + + none + sum + + + + + + + + + + translate + scale + rotate + skewX + skewY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/animation-element.rng b/applications/generators/SVG/Tiny-1.2-NG/animation-element.rng new file mode 100644 index 0000000..8ef01ea --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/animation-element.rng @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/audio.rng b/applications/generators/SVG/Tiny-1.2-NG/audio.rng new file mode 100644 index 0000000..046f0de --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/audio.rng @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/conditional.rng b/applications/generators/SVG/Tiny-1.2-NG/conditional.rng new file mode 100644 index 0000000..03449a8 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/conditional.rng @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/contenttype-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/contenttype-attrib.rng new file mode 100644 index 0000000..b72188c --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/contenttype-attrib.rng @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/coordinate-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/coordinate-attrib.rng new file mode 100644 index 0000000..325982d --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/coordinate-attrib.rng @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/core-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/core-attrib.rng new file mode 100644 index 0000000..15cbdc1 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/core-attrib.rng @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + preserve + + + + + + + + + + + preserve + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/datatypes.rng b/applications/generators/SVG/Tiny-1.2-NG/datatypes.rng new file mode 100644 index 0000000..c330b2a --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/datatypes.rng @@ -0,0 +1,145 @@ + + + + + + + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + self + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/discard.rng b/applications/generators/SVG/Tiny-1.2-NG/discard.rng new file mode 100644 index 0000000..18bd530 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/discard.rng @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/extensibility.rng b/applications/generators/SVG/Tiny-1.2-NG/extensibility.rng new file mode 100644 index 0000000..45309d7 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/extensibility.rng @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/extresources-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/extresources-attrib.rng new file mode 100644 index 0000000..f900756 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/extresources-attrib.rng @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/flowable-text-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/flowable-text-tiny.rng new file mode 100644 index 0000000..dc87152 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/flowable-text-tiny.rng @@ -0,0 +1,118 @@ + + + + + + + + + + + auto + before + center + after + inherit + + + + + + + auto + inherit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + + + + + + + + auto + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/focus-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/focus-attrib.rng new file mode 100644 index 0000000..edd10fc --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/focus-attrib.rng @@ -0,0 +1,86 @@ + + + + + + + + + + auto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + none + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/font-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/font-tiny.rng new file mode 100644 index 0000000..824006d --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/font-tiny.rng @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/gradient-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/gradient-tiny.rng new file mode 100644 index 0000000..a8cd97e --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/gradient-tiny.rng @@ -0,0 +1,156 @@ + + + + + + + + + + + inherit + + + + + + + + inherit + + + + + + + + + + + + + + + + + + + + + + userSpaceOnUse + objectBoundingBox + + + + + + + + + + + pad + reflect + repeat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/graphics-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/graphics-attrib.rng new file mode 100644 index 0000000..7b1028d --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/graphics-attrib.rng @@ -0,0 +1,109 @@ + + + + + + + + + + inline + block + list-item + run-in + compact + marker + table + inline-table + table-row-group + table-header-group + table-footer-group + table-row + table-column-group + table-column + table-cell + table-caption + none + inherit + + + + + + + visible + hidden + inherit + + + + + + + auto + optimizeSpeed + optimizeQuality + inherit + + + + + + + visiblePainted + visibleFill + visibleStroke + visible + painted + fill + stroke + all + none + inherit + + + + + + + auto + optimizeSpeed + crispEdges + geometricPrecision + inherit + + + + + + + auto + optimizeSpeed + optimizeLegibility + geometricPrecision + inherit + + + + + + + + + + whenStarted + always + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/handler.rng b/applications/generators/SVG/Tiny-1.2-NG/handler.rng new file mode 100644 index 0000000..f052fc9 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/handler.rng @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/headers.rng b/applications/generators/SVG/Tiny-1.2-NG/headers.rng new file mode 100644 index 0000000..a112936 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/headers.rng @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/hyperlink.rng b/applications/generators/SVG/Tiny-1.2-NG/hyperlink.rng new file mode 100644 index 0000000..a8e19e5 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/hyperlink.rng @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + _replace + _self + _parent + _top + _blank + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/image.rng b/applications/generators/SVG/Tiny-1.2-NG/image.rng new file mode 100644 index 0000000..1549ddb --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/image.rng @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/laser-ext.rng b/applications/generators/SVG/Tiny-1.2-NG/laser-ext.rng new file mode 100644 index 0000000..ed65b8a --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/laser-ext.rng @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/media-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/media-attrib.rng new file mode 100644 index 0000000..508dc45 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/media-attrib.rng @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + inherit + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/opacity-attrib-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/opacity-attrib-tiny.rng new file mode 100644 index 0000000..00fb018 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/opacity-attrib-tiny.rng @@ -0,0 +1,44 @@ + + + + + + + + + + inherit + + + + + + + + inherit + + + + + + + + + + + inherit + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/paint-attrib-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/paint-attrib-tiny.rng new file mode 100644 index 0000000..d67afaf --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/paint-attrib-tiny.rng @@ -0,0 +1,113 @@ + + + + + + + + + + inherit + + + + + + + + inherit + nonzero + evenodd + + + + + + + inherit + + + + + + + + inherit + none + + + + + + + + inherit + + + + + + + + butt + round + square + inherit + + + + + + + miter + round + bevel + inherit + + + + + + + inherit + + + + + + + + inherit + + + + + + + + inherit + + + + + + + + auto + optimizeSpeed + optimizeQuality + inherit + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/prefetch.rng b/applications/generators/SVG/Tiny-1.2-NG/prefetch.rng new file mode 100644 index 0000000..4f91049 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/prefetch.rng @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/script.rng b/applications/generators/SVG/Tiny-1.2-NG/script.rng new file mode 100644 index 0000000..65204fb --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/script.rng @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/shapes.rng b/applications/generators/SVG/Tiny-1.2-NG/shapes.rng new file mode 100644 index 0000000..1b32e93 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/shapes.rng @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/solidcolor.rng b/applications/generators/SVG/Tiny-1.2-NG/solidcolor.rng new file mode 100644 index 0000000..3e92662 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/solidcolor.rng @@ -0,0 +1,65 @@ + + + + + + + + + + + inherit + + + + + + + + inherit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/structure-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/structure-tiny.rng new file mode 100644 index 0000000..6bc021e --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/structure-tiny.rng @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \s*(none|xMidYMid)\s*(meet)?\s* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + disable + magnify + + + + + + + 1.0 + 1.1 + 1.2 + + + + + + + none + tiny + basic + full + + + + + + + + + + + + none + + + + + + + + onLoad + onStart + + + + + + + all + forwardOnly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/text-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/text-tiny.rng new file mode 100644 index 0000000..074bff0 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/text-tiny.rng @@ -0,0 +1,213 @@ + + + + + + + + + + + inherit + + + + + + + + inherit + + + + + + + + normal + italic + oblique + inherit + + + + + + + normal + small-caps + inherit + + + + + + + normal + bold + bolder + lighter + 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + inherit + + + + + + + start + middle + end + inherit + + + + + + + start + center + end + inherit + + + + + + + none + underline + overline + line-through + blink + inherit + + + + + + + + + + + + + + + + + none + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/transform-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/transform-attrib.rng new file mode 100644 index 0000000..fe379ae --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/transform-attrib.rng @@ -0,0 +1,25 @@ + + + + + + + + + + + none + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/vectoreffects-attrib-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/vectoreffects-attrib-tiny.rng new file mode 100644 index 0000000..ed5c9ad --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/vectoreffects-attrib-tiny.rng @@ -0,0 +1,26 @@ + + + + + + + + + + none + non-scaling-stroke + inherit + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/video.rng b/applications/generators/SVG/Tiny-1.2-NG/video.rng new file mode 100644 index 0000000..b1f885d --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/video.rng @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + geometric + pinned + pinned90 + pinned180 + pinned270 + + + + + + + none + top + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/viewport-attrib-tiny.rng b/applications/generators/SVG/Tiny-1.2-NG/viewport-attrib-tiny.rng new file mode 100644 index 0000000..91473b2 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/viewport-attrib-tiny.rng @@ -0,0 +1,45 @@ + + + + + + + + + + inherit + none + + + + + + + + inherit + + + + + + + + visible + hidden + scroll + auto + inherit + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/xlink-attrib.rng b/applications/generators/SVG/Tiny-1.2-NG/xlink-attrib.rng new file mode 100644 index 0000000..83b4fb8 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/xlink-attrib.rng @@ -0,0 +1,123 @@ + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onLoad + + + + + + + + other + + + + + + + + + embed + + + + + + + + + + + + + + + + + + + new + replace + + + + + + onRequest + + + + + + + + + + + + + + + diff --git a/applications/generators/SVG/Tiny-1.2-NG/xml-events.rng b/applications/generators/SVG/Tiny-1.2-NG/xml-events.rng new file mode 100644 index 0000000..147fed2 --- /dev/null +++ b/applications/generators/SVG/Tiny-1.2-NG/xml-events.rng @@ -0,0 +1,84 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + default + capture + + + + + + + continue + stop + + + + + + + perform + cancel + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
diff --git a/applications/generators/SVG/html.c b/applications/generators/SVG/html.c new file mode 100644 index 0000000..6fdbbee --- /dev/null +++ b/applications/generators/SVG/html.c @@ -0,0 +1,151 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 "svggen.h" +static FILE *BeginHtml() +{ + FILE *f; + char sPath[GF_MAX_PATH]; + + sprintf(sPath, "C:%cUsers%cCyril%ccontent%csvg%cregression%cregression_table.html", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + f = gf_fopen(sPath, "wt"); + + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "Status of the SVG implementation in GPAC\n"); + + { + time_t rawtime; + time(&rawtime); + fprintf(f, "\n\n", asctime(gmtime(&rawtime)), GPAC_VERSION); + } + fprintf(f, "\n", COPYRIGHT); + fprintf(f, "\n"); + + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "

Status of the SVG implementation in GPAC

\n"); + return f; +} + +static void EndHtml(FILE *f) +{ + fprintf(f, "\n"); + fprintf(f, "\n"); + gf_fclose(f); +} + +/* Generates an HTML table */ +void generate_table(GF_List *elements) +{ + u32 i, j; + u32 nbExamples = 0; + FILE *f; + f = BeginHtml(); + + + fprintf(f, "

Legend

\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "
StatusColor
Not supported
Partially supported
Fully supported
\n"); + + fprintf(f, "

SVG Tiny 1.2 Elements

\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + for (i = 0; i < gf_list_count(elements); i++) { + SVGGenElement *elt = gf_list_get(elements, i); + fprintf(f, "\n", elt->implementation_name); + fprintf(f, "\n", elt->svg_name, elt->svg_name); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + } + fprintf(f, "\n"); + fprintf(f, "
Element NameStatusObservationsExample(s)Bug(s)
%s   
\n"); + + for (i = 0; i < gf_list_count(elements); i++) { + SVGGenElement *elt = gf_list_get(elements, i); + fprintf(f, "

%s

\n", elt->implementation_name, elt->svg_name); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n"); + for (j = 0; j < gf_list_count(elt->attributes); j++) { + SVGGenAttribute *att = gf_list_get(elt->attributes, j); + if (!strcmp(att->svg_name, "textContent")) continue; + fprintf(f, "\n"); + fprintf(f, "\n",att->svg_name); + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "\n",++nbExamples); + fprintf(f, "\n"); + fprintf(f, "\n"); + } + fprintf(f, "\n"); + fprintf(f, "
Attribute NameStatusObservationsExample(s)Bug(s)
%s %d -   
\n"); + } + + EndHtml(f); + gf_list_del(elements); +} + diff --git a/applications/generators/SVG/laser.c b/applications/generators/SVG/laser.c new file mode 100644 index 0000000..ad96b9a --- /dev/null +++ b/applications/generators/SVG/laser.c @@ -0,0 +1,267 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 "svggen.h" + +static char *laser_attribute_name_type_list[] = { + "target", "accumulate", "additive", "audio_level", "bandwidth", "begin", "calcMode", "children", "choice", "clipBegin", "clipEnd", "color", "color_rendering", "cx", "cy", "d", "delta", "display", "display_align", "dur", "editable", "lsr_enabled", "end", "event", "externalResourcesRequired", "fill", "fill_opacity", "fill_rule", "focusable", "font_family", "font_size", "font_style", "font_variant", "font_weight", "fullscreen", "gradientUnits", "handler", "height", "image_rendering", "keyPoints", "keySplines", "keyTimes", "line_increment", "listener_target", "mediaCharacterEncoding", "mediaContentEncodings", "mediaSize", "mediaTime", "nav_down", "nav_down_left", "nav_down_right", "nav_left", "nav_next", "nav_prev", "nav_right", "nav_up", "nav_up_left", "nav_up_right", "observer", "offset", "opacity", "overflow", "overlay", "path", "pathLength", "pointer_events", "points", "preserveAspectRatio", "r", "repeatCount", "repeatDur", "requiredExtensions", "requiredFeatures", "requiredFormats", "restart", "rotate", "rotation", "rx", "ry", "scale", "shape_rendering", "size", "solid_color", "solid_opacity", "stop_color", "stop_opacity", "stroke", "stroke_dasharray", "stroke_dashoffset", "stroke_linecap", "stroke_linejoin", "stroke_miterlimit", "stroke_opacity", "stroke_width", "svg_height", "svg_width", "syncBehavior", "syncBehaviorDefault", "syncReference", "syncTolerance", "syncToleranceDefault", "systemLanguage", "text_align", "text_anchor", "text_decoration", "text_display", "text_rendering", "textContent", "transform", "transformBehavior", "translation", "vector_effect", "viewBox", "viewport_fill", "viewport_fill_opacity", "visibility", "width", "x", "x1", "x2", "xlink_actuate", "xlink_arcrole", "xlink_href", "xlink_role", "xlink_show", "xlink_title", "xlink_type", "xml_base", "xml_lang", "y", "y1", "y2", "zoomAndPan", NULL +}; + + + +static char *laser_attribute_rare_type_list[] = { + "_class", "audio_level", "color", "color_rendering", "display", "display_align", "fill_opacity", + "fill_rule", "image_rendering", "line_increment", "pointer_events", "shape_rendering", "solid_color", + "solid_opacity", "stop_color", "stop_opacity", "stroke_dasharray", "stroke_dashoffset", "stroke_linecap", + "stroke_linejoin", "stroke_miterlimit", "stroke_opacity", "stroke_width", "text_anchor", "text_rendering", + "viewport_fill", "viewport_fill_opacity", "vector_effect", "visibility", "requiredExtensions", + "requiredFeatures", "requiredFormats", "systemLanguage", "xml_base", "xml_lang", "xml_space", + "nav_next", "nav_up", "nav_up_left", "nav_up_right", "nav_prev", "nav_down", "nav_down_left", + "nav_down_right", "nav_left", "focusable", "nav_right", "transform","text_decoration", + "extension", /*LASER EXTENSIONS SVG*/ + + "font_variant", "font_family", "font_size", "font_style", "font_weight", "xlink_title", "xlink_type", + "xlink_role", "xlink_arcrole", "xlink_actuate", "xlink_show", "end", "max", "min", + NULL +}; + + +s32 get_lsr_att_name_type(const char *name) +{ + u32 i = 0; + while (laser_attribute_name_type_list[i]) { + if (!strcmp(name, laser_attribute_name_type_list[i])) return i; + i++; + } + return -1; +} + +void generateGenericAttrib(FILE *output, SVGGenElement *elt, u32 index) +{ + int k; + for (k=0; k < generic_attributes[index].array_length; k++) { + char *att_name = generic_attributes[index].array[k]; + SVGGenAttribute *a = findAttribute(elt, att_name); + if (a) { + s32 type = get_lsr_att_name_type(att_name); + /*SMIL anim fill not updatable*/ + if ((index==6) && !strcmp(att_name, "fill")) { + type = -1; + } + fprintf(output, ", %d", type); + } + } +} + +void generate_laser_tables(GF_List *svg_elements) +{ + FILE *output; + u32 i; + u32 special_cases; + + output = BeginFile(2); + if (generation_mode == 1) fprintf(output, "\n#include \n\n"); + else if (generation_mode == 2) fprintf(output, "\n#include \n\n"); + else if (generation_mode == 3) fprintf(output, "\n#include \n\n"); + + for (i=0; iattributes); + + fprintf(output, "static const s32 %s_field_to_attrib_type[] = {\n", elt->implementation_name); + + /*core info: id, xml:id, class, xml:lang, xml:base, xml:space, externalResourcesRequired*/ + fprintf(output, "-1, -1, -1, 125, 124, -1, 24"); + if (elt->has_media_properties) generateGenericAttrib(output, elt, 2); + if (elt->has_properties) generateGenericAttrib(output, elt, 1); + if (elt->has_opacity_properties) generateGenericAttrib(output, elt, 3); + if (elt->has_focus) generateGenericAttrib(output, elt, 4); + if (elt->has_xlink) generateGenericAttrib(output, elt, 5); + if (elt->has_timing) generateGenericAttrib(output, elt, 6); + if (elt->has_sync) generateGenericAttrib(output, elt, 7); + if (elt->has_animation) generateGenericAttrib(output, elt, 8); + if (elt->has_conditional) generateGenericAttrib(output, elt, 9); + /*WATCHOUT - HARDCODED VALUES*/ + if (elt->has_transform) fprintf(output, ", 105"); + if (elt->has_xy) fprintf(output, ", 116, 129"); + + + /*svg.width and svg.height escapes*/ + special_cases = 0; + if (!strcmp(elt->svg_name, "svg")) special_cases = 1; + else if (!strcmp(elt->svg_name, "a")) special_cases = 2; + + for (j=0; jattributes, j); + s32 type = get_lsr_att_name_type(att->svg_name); + if (special_cases==1) { + if (!strcmp(att->svg_name, "width")) + type = 95; + else if (!strcmp(att->svg_name, "height")) + type = 94; + } + if ((special_cases==2) && !strcmp(att->svg_name, "target")) + type = 0; + fprintf(output, ", %d", type); + } + fprintf(output, "\n};\n\n"); + + } + fprintf(output, "s32 gf_lsr_field_to_attrib_type(GF_Node *n, u32 fieldIndex)\n{\n\tif(!n) return -2;\n\tswitch (gf_node_get_tag(n)) {\n"); + for (i=0; iimplementation_name); + fcount = gf_list_count(elt->attributes); + fprintf(output, "\t\treturn %s_field_to_attrib_type[fieldIndex];\n", elt->implementation_name); + } + fprintf(output, "\tdefault:\n\t\treturn -2;\n\t}\n}\n\n"); +} + + +void generate_laser_tables_da(GF_List *atts) +{ + FILE *output; + u32 i, count, j; + + output = BeginFile(2); + + fprintf(output, "\n#include \n\n"); + fprintf(output, "\n\ns32 gf_lsr_anim_type_from_attribute(u32 tag) {\n\tswitch(tag) {\n"); + + count = gf_list_count(atts); + j=0; + while (laser_attribute_name_type_list[j]) { + for (i=0; iimplementation_name, laser_attribute_name_type_list[j])) { + fprintf(output, "\tcase TAG_SVG_ATT_%s: return %d;\n", att->implementation_name, j); + break; + } + } + if (i==count) { + //fprintf(stdout, "Warning: Ignoring %s\n", laser_attribute_name_type_list[j]); + fprintf(output, "\tcase TAG_LSR_ATT_%s: return %d;\n", laser_attribute_name_type_list[j], j); + } + j++; + } + fprintf(output, "\tdefault: return -1;\n\t}\n}\n\n"); + + fprintf(output, "\n\ns32 gf_lsr_rare_type_from_attribute(u32 tag) {\n\tswitch(tag) {\n"); + count = gf_list_count(atts); + j=0; + while (laser_attribute_rare_type_list[j]) { + for (i=0; iimplementation_name, laser_attribute_rare_type_list[j])) { + fprintf(output, "\tcase TAG_SVG_ATT_%s: return %d;\n", att->implementation_name, j); + break; + } + } + if (i==count) { + if (!strcmp(laser_attribute_rare_type_list[j], "extension")) { + fprintf(output, "\tcase TAG_SVG_ATT_syncMaster: return %d;\n", j); + fprintf(output, "\tcase TAG_SVG_ATT_focusHighlight: return %d;\n", j); + fprintf(output, "\tcase TAG_SVG_ATT_initialVisibility: return %d;\n", j); + fprintf(output, "\tcase TAG_SVG_ATT_fullscreen: return %d;\n", j); + fprintf(output, "\tcase TAG_SVG_ATT_requiredFonts: return %d;\n", j); + } else { + fprintf(stdout, "Warning: Ignoring %s\n", laser_attribute_rare_type_list[j]); + } + } + j++; + } + fprintf(output, "\tdefault: return -1;\n\t}\n}\n\n"); + + + + fprintf(output, "\n\ns32 gf_lsr_anim_type_to_attribute(u32 tag) {\n\tswitch(tag) {\n"); + j=0; + while (laser_attribute_name_type_list[j]) { + for (i=0; iimplementation_name, laser_attribute_name_type_list[j])) { + fprintf(output, "\tcase %d: return TAG_SVG_ATT_%s;\n", j, att->implementation_name); + break; + } + } + if (i==count) { + fprintf(output, "\tcase %d: return TAG_LSR_ATT_%s;\n", j, laser_attribute_name_type_list[j]); + } + j++; + } + fprintf(output, "\tdefault: return -1;\n\t}\n}\n\n"); + + fprintf(output, "\n\ns32 gf_lsr_rare_type_to_attribute(u32 tag) {\n\tswitch(tag) {\n"); + j=0; + while (laser_attribute_rare_type_list[j]) { + for (i=0; iimplementation_name, laser_attribute_rare_type_list[j])) { + fprintf(output, "\tcase %d: return TAG_SVG_ATT_%s;\n", j, att->implementation_name); + break; + } + } + j++; + } + fprintf(output, "\tdefault: return -1;\n\t}\n}\n\n"); + + + fprintf(output, "\n\nu32 gf_lsr_same_rare(SVGAllAttributes *elt_atts, SVGAllAttributes *base_atts)\n{\n"); + fprintf(output, "\tGF_FieldInfo f_elt, f_base;\n"); + + j=0; + while (laser_attribute_rare_type_list[j]) { + SVGGenAttribute *att = NULL; + if (!strcmp(laser_attribute_rare_type_list[j], "extension")) { + j++; + continue; + } + for (i=0; iimplementation_name, laser_attribute_rare_type_list[j])) + break; + att = NULL; + } + assert(att); + + fprintf(output, "\tf_elt.fieldType = f_base.fieldType = %s_datatype;\n", att->impl_type); + fprintf(output, "\tf_elt.fieldIndex = f_base.fieldIndex = TAG_SVG_ATT_%s;\n", laser_attribute_rare_type_list[j]); + fprintf(output, "\tf_elt.far_ptr = elt_atts->%s;\n", laser_attribute_rare_type_list[j]); + fprintf(output, "\tf_base.far_ptr = base_atts->%s;\n", laser_attribute_rare_type_list[j]); + fprintf(output, "\tif (!gf_svg_attributes_equal(&f_elt, &f_base)) return 0;\n\n"); + + j++; + } + fprintf(output, "\treturn 1;\n}\n\n"); + + gf_fclose(output); +} diff --git a/applications/generators/SVG/main.c b/applications/generators/SVG/main.c new file mode 100644 index 0000000..1db4d83 --- /dev/null +++ b/applications/generators/SVG/main.c @@ -0,0 +1,951 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 "svggen.h" + +SVGGenAttribute *NewSVGGenAttribute() +{ + SVGGenAttribute *att; + GF_SAFEALLOC(att, SVGGenAttribute) + return att; +} + +void deleteSVGGenAttribute(SVGGenAttribute **p) +{ + xmlFree((*p)->svg_name); + xmlFree((*p)->svg_type); + gf_free(*p); + *p = NULL; +} + +SVGGenAttrGrp *NewSVGGenAttrGrp() +{ + SVGGenAttrGrp *tmp; + GF_SAFEALLOC(tmp, SVGGenAttrGrp) + tmp->attrs = gf_list_new(); + tmp->attrgrps = gf_list_new(); + return tmp; +} + +SVGGenElement *NewSVGGenElement() +{ + SVGGenElement *elt; + GF_SAFEALLOC(elt, SVGGenElement); + if (elt) { + elt->attributes = gf_list_new(); + elt->generic_attributes = gf_list_new(); + } + return elt; +} + +void deleteSVGGenElement(SVGGenElement **p) +{ + u32 i; + xmlFree((*p)->svg_name); + for (i = 0; i < gf_list_count((*p)->attributes); i++) { + SVGGenAttribute *a = gf_list_get((*p)->attributes, i); + deleteSVGGenAttribute(&a); + } + gf_list_del((*p)->attributes); + gf_free(*p); + *p = NULL; +} + +static GF_List *sortElements(GF_List *elements) +{ + u32 i, j; + GF_List *sorted_elements = gf_list_new(); + + for (i = 0; i< gf_list_count(elements); i++) { + u8 is_added = 0; + SVGGenElement *elt = gf_list_get(elements, i); + for (j = 0; j < gf_list_count(sorted_elements); j++) { + SVGGenElement *selt = gf_list_get(sorted_elements, j); + if (strcmp(elt->svg_name, selt->svg_name) < 0) { + gf_list_insert(sorted_elements, elt, j); + is_added = 1; + break; + } + } + if (!is_added) gf_list_add(sorted_elements, elt); + } + + gf_list_del(elements); + return sorted_elements; +} + +static GF_List *sortAttrGrp(GF_List *attgrps) +{ + u32 i, j; + GF_List *sorted_attgrps = gf_list_new(); + + for (i = 0; i< gf_list_count(attgrps); i++) { + u8 is_added = 0; + SVGGenAttrGrp *grp = gf_list_get(attgrps, i); + for (j = 0; j < gf_list_count(sorted_attgrps); j++) { + SVGGenAttrGrp *sgrp = gf_list_get(sorted_attgrps, j); + if (strcmp(grp->name, sgrp->name) < 0) { + gf_list_insert(sorted_attgrps, grp, j); + is_added = 1; + break; + } + } + if (!is_added) gf_list_add(sorted_attgrps, grp); + } + + gf_list_del(attgrps); + return sorted_attgrps; +} + +static GF_List *sortAttr(GF_List *atts) +{ + u32 i, j; + GF_List *sorted_atts = gf_list_new(); + + for (i = 0; i< gf_list_count(atts); i++) { + u8 is_added = 0; + SVGGenAttribute *att = gf_list_get(atts, i); + for (j = 0; j < gf_list_count(sorted_atts); j++) { + SVGGenAttribute *satt = gf_list_get(sorted_atts, j); + if (strcmp(att->svg_name, satt->svg_name) < 0) { + gf_list_insert(sorted_atts, att, j); + is_added = 1; + break; + } + } + if (!is_added) gf_list_add(sorted_atts, att); + } + + gf_list_del(atts); + return sorted_atts; +} + +void svgNameToImplementationName(xmlChar *svg_name, char implementation_name[50]) { + char *tmp; + strcpy(implementation_name, svg_name); + tmp = implementation_name; + while ( (tmp = strchr(tmp, '.')) ) { + *tmp='_'; + tmp++; + } + tmp = implementation_name; + while ( (tmp = strchr(tmp, '-')) ) { + *tmp='_'; + tmp++; + } + tmp = implementation_name; + while ( (tmp = strchr(tmp, ':')) ) { + *tmp='_'; + tmp++; + } +} + +static Bool isGenericAttributesGroup(char *name) +{ + if (!strcmp(name, "svg.Core.attr") || + !strcmp(name, "svg.CorePreserve.attr") || + !strcmp(name, "svg.External.attr") || + !strcmp(name, "svg.Properties.attr") || + !strcmp(name, "svg.Media.attr") || + !strcmp(name, "svg.MediaClip.attr") || + !strcmp(name, "svg.Opacity.attr") || + !strcmp(name, "svg.FocusHighlight.attr") || + !strcmp(name, "svg.Focus.attr") || + !strcmp(name, "svg.AnimateCommon.attr") || + !strcmp(name, "svg.XLinkEmbed.attr") || + !strcmp(name, "svg.XLinkRequired.attr") || + !strcmp(name, "svg.XLinkReplace.attr") || +// !strcmp(name, "svg.ContentType.attr") || + !strcmp(name, "svg.AnimateTiming.attr") || + !strcmp(name, "svg.AnimateTimingNoMinMax.attr") || + !strcmp(name, "svg.AnimateBegin.attr") || + !strcmp(name, "svg.AnimateTimingNoFillNoMinMax.attr") || + !strcmp(name, "svg.AnimateSync.attr") || + !strcmp(name, "svg.AnimateSyncDefault.attr") || + !strcmp(name, "svg.AnimateAttributeCommon.attr") || + !strcmp(name, "svg.AnimateToCommon.attr") || + !strcmp(name, "svg.AnimateValueCommon.attr") || + !strcmp(name, "svg.AnimateAdditionCommon.attr") || + !strcmp(name, "svg.AnimateTypeCommon.attr") || + !strcmp(name, "svg.Conditional.attr") || +// !strcmp(name, "svg.XY.attr") || + !strcmp(name, "svg.Transform.attr")) { + return 1; + } else { + return 0; + } +} + +static Bool setGenericAttributesFlags(char *name, SVGGenElement *e) +{ + Bool ret = 1; + if (!strcmp(name, "svg.Core.attr") || + !strcmp(name, "svg.CorePreserve.attr") || + !strcmp(name, "svg.External.attr")) { + e->has_svg_generic = 1; + e->has_xml_generic = 1; + } else if (!strcmp(name, "svg.Properties.attr")) { + e->has_properties = 1; + e->has_media_properties = 1; + } else if (!strcmp(name, "svg.Media.attr")) { + e->has_media_properties = 1; + } else if (!strcmp(name, "svg.Opacity.attr")) { + e->has_opacity_properties = 1; + } else if (!strcmp(name, "svg.FocusHighlight.attr") || + !strcmp(name, "svg.Focus.attr")) { + e->has_focus = 1; + } else if (!strcmp(name, "svg.AnimateCommon.attr") || + !strcmp(name, "svg.XLinkEmbed.attr") || + !strcmp(name, "svg.XLinkRequired.attr") || + !strcmp(name, "svg.XLinkReplace.attr")) { //|| +// !strcmp(name, "svg.ContentType.attr")) { + e->has_xlink = 1; + } else if (!strcmp(name, "svg.AnimateTiming.attr") || + !strcmp(name, "svg.AnimateTimingNoMinMax.attr") || + !strcmp(name, "svg.AnimateBegin.attr") || + !strcmp(name, "svg.AnimateTimingNoFillNoMinMax.attr")) { + e->has_timing = 1; + } else if (!strcmp(name, "svg.AnimateSync.attr") || + !strcmp(name, "svg.AnimateSyncDefault.attr")) { + e->has_sync= 1; + } else if (!strcmp(name, "svg.AnimateAttributeCommon.attr") || + !strcmp(name, "svg.AnimateToCommon.attr") || + !strcmp(name, "svg.AnimateValueCommon.attr") || + !strcmp(name, "svg.AnimateAdditionCommon.attr") || + !strcmp(name, "svg.AnimateTypeCommon.attr")) { + e->has_animation = 1; + } else if (!strcmp(name, "svg.Conditional.attr")) { + e->has_conditional = 1; + } else if (!strcmp(name, "svg.Transform.attr")) { + e->has_transform = 1; + } else if (!strcmp(name, "svg.XY.attr")) { + e->has_xy = 1; + } else { + ret = 0; + } + return ret; +} + +static void flattenAttributeGroup(SVGGenAttrGrp attgrp, SVGGenElement *e, Bool all); + +static void flattenAttributeGroups(GF_List *attrgrps, SVGGenElement *e, Bool all) +{ + u32 i; + for (i = 0; i < gf_list_count(attrgrps); i ++) { + SVGGenAttrGrp *ag = gf_list_get(attrgrps, i); + flattenAttributeGroup(*ag, e, all); + } +} + +static void flattenAttributeGroup(SVGGenAttrGrp attgrp, SVGGenElement *e, Bool all) +{ + u32 i; + + if (isGenericAttributesGroup(attgrp.name) && !all) { + setGenericAttributesFlags(attgrp.name, e); + flattenAttributeGroups(attgrp.attrgrps, e, 1); + for (i = 0; i < gf_list_count(attgrp.attrs); i++) { + gf_list_add(e->generic_attributes, gf_list_get(attgrp.attrs, i)); + } + } else { + flattenAttributeGroups(attgrp.attrgrps, e, all); + for (i = 0; i < gf_list_count(attgrp.attrs); i++) { + if (all) + gf_list_add(e->generic_attributes, gf_list_get(attgrp.attrs, i)); + else + gf_list_add(e->attributes, gf_list_get(attgrp.attrs, i)); + } + } +} + +SVGGenAttribute *findAttribute(SVGGenElement *e, char *name) +{ + u32 i; + for (i = 0; i < gf_list_count(e->attributes); i++) { + SVGGenAttribute *a = gf_list_get(e->attributes, i); + if (!strcmp(a->svg_name, name)) return a; + } + for (i = 0; i < gf_list_count(e->generic_attributes); i++) { + SVGGenAttribute *a = gf_list_get(e->generic_attributes, i); + if (!strcmp(a->svg_name, name)) return a; + } + return NULL; +} + +static u32 countAttributesAllInGroup(SVGGenAttrGrp *ag) +{ + u32 i, ret = 0; + for (i = 0; i < gf_list_count(ag->attrgrps); i ++) { + SVGGenAttrGrp *agtmp = gf_list_get(ag->attrgrps, i); + ret += countAttributesAllInGroup(agtmp); + } + ret += gf_list_count(ag->attrs); + return ret; +} + +/* XML related functions */ +xmlNodeSetPtr findNodes( xmlXPathContextPtr ctxt, xmlChar * path ) +{ + xmlXPathObjectPtr res = NULL; + + if ( ctxt->node != NULL && path != NULL ) { + xmlXPathCompExprPtr comp; + + xmlDocPtr tdoc = NULL; + xmlNodePtr froot = ctxt->node; + + comp = xmlXPathCompile( path ); + if ( comp == NULL ) { + return NULL; + } + + if ( ctxt->node->doc == NULL ) { + /* if one XPaths a node from a fragment, libxml2 will + refuse the lookup. this is not very useful for XML + scripters. thus we need to create a temporary document + to make libxml2 do it's job correctly. + */ + tdoc = xmlNewDoc( NULL ); + + /* find refnode's root node */ + while ( froot != NULL ) { + if ( froot->parent == NULL ) { + break; + } + froot = froot->parent; + } + xmlAddChild((xmlNodePtr)tdoc, froot); + + ctxt->node->doc = tdoc; + } + + res = xmlXPathCompiledEval(comp, ctxt); + + xmlXPathFreeCompExpr(comp); + + if ( tdoc != NULL ) { + /* after looking through a fragment, we need to drop the + fake document again */ + xmlSetTreeDoc(froot,NULL); + froot->doc = NULL; + tdoc->children = NULL; + tdoc->last = NULL; + froot->parent = NULL; + ctxt->node->doc = NULL; + + xmlFreeDoc( tdoc ); + } + } + if (res && res->type == XPATH_NODESET) + return res->nodesetval; + else + return NULL; +} + + +/* definition of GPAC groups of SVG attributes */ + +void setAttributeType(SVGGenAttribute *att) +{ + if (!att->svg_type) { /* if the type is not given in the RNG, we explicitely set it */ + if (!strcmp(att->svg_name, "textContent")) { + strcpy(att->impl_type, "SVG_TextContent"); + } else if (!strcmp(att->svg_name, "class")) { + strcpy(att->implementation_name, "_class"); + strcpy(att->impl_type, "SVG_String"); + } else if (!strcmp(att->svg_name, "visibility")) { + strcpy(att->impl_type, "SVG_Visibility"); + } else if (!strcmp(att->svg_name, "display")) { + strcpy(att->impl_type, "SVG_Display"); + } else if (!strcmp(att->svg_name, "stroke-linecap")) { + strcpy(att->impl_type, "SVG_StrokeLineCap"); + } else if (!strcmp(att->svg_name, "stroke-dasharray")) { + strcpy(att->impl_type, "SVG_StrokeDashArray"); + } else if (!strcmp(att->svg_name, "stroke-linejoin")) { + strcpy(att->impl_type, "SVG_StrokeLineJoin"); + } else if (!strcmp(att->svg_name, "font-style")) { + strcpy(att->impl_type, "SVG_FontStyle"); + } else if (!strcmp(att->svg_name, "font-weight")) { + strcpy(att->impl_type, "SVG_FontWeight"); + } else if (!strcmp(att->svg_name, "text-anchor")) { + strcpy(att->impl_type, "SVG_TextAnchor"); + } else if (!strcmp(att->svg_name, "fill")) { + strcpy(att->impl_type, "SMIL_Fill"); + } else if (!strcmp(att->svg_name, "fill-rule")) { + strcpy(att->impl_type, "SVG_FillRule"); + } else if (!strcmp(att->svg_name, "font-family")) { + strcpy(att->impl_type, "SVG_FontFamily"); + } else if (!strcmp(att->svg_name, "calcMode")) { + strcpy(att->impl_type, "SMIL_CalcMode"); + } else if (!strcmp(att->svg_name, "values")) { + strcpy(att->impl_type, "SMIL_AnimateValues"); + } else if (!strcmp(att->svg_name, "keyTimes")) { + strcpy(att->impl_type, "SMIL_KeyTimes"); + } else if (!strcmp(att->svg_name, "keySplines")) { + strcpy(att->impl_type, "SMIL_KeySplines"); + } else if (!strcmp(att->svg_name, "keyPoints")) { + strcpy(att->impl_type, "SMIL_KeyPoints"); + } else if (!strcmp(att->svg_name, "from") || + !strcmp(att->svg_name, "to") || + !strcmp(att->svg_name, "by")) { + strcpy(att->impl_type, "SMIL_AnimateValue"); + } else if (!strcmp(att->svg_name, "additive")) { + strcpy(att->impl_type, "SMIL_Additive"); + } else if (!strcmp(att->svg_name, "accumulate")) { + strcpy(att->impl_type, "SMIL_Accumulate"); + } else if (!strcmp(att->svg_name, "begin") || + !strcmp(att->svg_name, "end") + ) { + strcpy(att->impl_type, "SMIL_Times"); + } else if (!strcmp(att->svg_name, "clipBegin") || + !strcmp(att->svg_name, "clipEnd") + ) { + strcpy(att->impl_type, "SVG_Clock"); + } else if (!strcmp(att->svg_name, "min") || + !strcmp(att->svg_name, "max") || + !strcmp(att->svg_name, "dur") || + !strcmp(att->svg_name, "repeatDur") + ) { + strcpy(att->impl_type, "SMIL_Duration"); + } else if (!strcmp(att->svg_name, "repeat")) { + strcpy(att->impl_type, "SMIL_Repeat"); + } else if (!strcmp(att->svg_name, "restart")) { + strcpy(att->impl_type, "SMIL_Restart"); + } else if (!strcmp(att->svg_name, "repeatCount")) { + strcpy(att->impl_type, "SMIL_RepeatCount"); + } else if (!strcmp(att->svg_name, "attributeName")) { + strcpy(att->impl_type, "SMIL_AttributeName"); + } else if (!strcmp(att->svg_name, "type")) { + strcpy(att->impl_type, "SVG_TransformType"); + } else if (!strcmp(att->svg_name, "font-size")) { + strcpy(att->impl_type, "SVG_FontSize"); + } else if (!strcmp(att->svg_name, "viewBox")) { + strcpy(att->impl_type, "SVG_ViewBox"); + } else if (!strcmp(att->svg_name, "preserveAspectRatio")) { + strcpy(att->impl_type, "SVG_PreserveAspectRatio"); + } else if (!strcmp(att->svg_name, "zoomAndPan")) { + strcpy(att->impl_type, "SVG_ZoomAndPan"); + } else if (!strcmp(att->svg_name, "path")) { + strcpy(att->impl_type, "SVG_PathData"); + } else if (!strcmp(att->svg_name, "image-rendering")) { + strcpy(att->impl_type, "SVG_RenderingHint"); + } else if (!strcmp(att->svg_name, "color-rendering")) { + strcpy(att->impl_type, "SVG_RenderingHint"); + } else if (!strcmp(att->svg_name, "text-rendering")) { + strcpy(att->impl_type, "SVG_RenderingHint"); + } else if (!strcmp(att->svg_name, "shape-rendering")) { + strcpy(att->impl_type, "SVG_RenderingHint"); + } else if (!strcmp(att->svg_name, "pointer-events")) { + strcpy(att->impl_type, "SVG_PointerEvents"); + } else if (!strcmp(att->svg_name, "vector-effect")) { + strcpy(att->impl_type, "SVG_VectorEffect"); + } else if (!strcmp(att->svg_name, "display-align")) { + strcpy(att->impl_type, "SVG_DisplayAlign"); + } else if (!strcmp(att->svg_name, "text-align")) { + strcpy(att->impl_type, "SVG_TextAlign"); + } else if (!strcmp(att->svg_name, "propagate")) { + strcpy(att->impl_type, "XMLEV_Propagate"); + } else if (!strcmp(att->svg_name, "defaultAction")) { + strcpy(att->impl_type, "XMLEV_DefaultAction"); + } else if (!strcmp(att->svg_name, "phase")) { + strcpy(att->impl_type, "XMLEV_Phase"); + } else if (!strcmp(att->svg_name, "syncBehavior")) { + strcpy(att->impl_type, "SMIL_SyncBehavior"); + } else if (!strcmp(att->svg_name, "syncBehaviorDefault")) { + strcpy(att->impl_type, "SMIL_SyncBehavior"); + } else if (!strcmp(att->svg_name, "attributeType")) { + strcpy(att->impl_type, "SMIL_AttributeType"); + } else if (!strcmp(att->svg_name, "playbackOrder")) { + strcpy(att->impl_type, "SVG_PlaybackOrder"); + } else if (!strcmp(att->svg_name, "timelineBegin")) { + strcpy(att->impl_type, "SVG_TimelineBegin"); + } else if (!strcmp(att->svg_name, "xml:space")) { + strcpy(att->impl_type, "XML_Space"); + } else if (!strcmp(att->svg_name, "snapshotTime")) { + strcpy(att->impl_type, "SVG_Clock"); + } else if (!strcmp(att->svg_name, "version")) { + strcpy(att->impl_type, "SVG_String"); + } else if (!strcmp(att->svg_name, "gradientUnits")) { + strcpy(att->impl_type, "SVG_GradientUnit"); + } else if (!strcmp(att->svg_name, "baseProfile")) { + strcpy(att->impl_type, "SVG_String"); + } else if (!strcmp(att->svg_name, "focusHighlight")) { + strcpy(att->impl_type, "SVG_FocusHighlight"); + } else if (!strcmp(att->svg_name, "initialVisibility")) { + strcpy(att->impl_type, "SVG_InitialVisibility"); + } else if (!strcmp(att->svg_name, "overlay")) { + strcpy(att->impl_type, "SVG_Overlay"); + } else if (!strcmp(att->svg_name, "transformBehavior")) { + strcpy(att->impl_type, "SVG_TransformBehavior"); + } else if (!strcmp(att->svg_name, "rotate")) { + strcpy(att->impl_type, "SVG_Rotate"); + } else if (!strcmp(att->svg_name, "font-variant")) { + strcpy(att->impl_type, "SVG_FontVariant"); + } else if (!strcmp(att->svg_name, "lsr:enabled")) { + strcpy(att->impl_type, "SVG_Boolean"); + } else if (!strcmp(att->svg_name, "spreadMethod")) { + strcpy(att->impl_type, "SVG_SpreadMethod"); + } else if (!strcmp(att->svg_name, "gradientTransform")) { + strcpy(att->impl_type, "SVG_Transform_Full"); + } else if (!strcmp(att->svg_name, "editable")) { + strcpy(att->impl_type, "SVG_Boolean"); + } else if (!strcmp(att->svg_name, "choice")) { + strcpy(att->impl_type, "LASeR_Choice"); + } else if (!strcmp(att->svg_name, "size") || + !strcmp(att->svg_name, "delta")) { + strcpy(att->impl_type, "LASeR_Size"); + } else if (!strcmp(att->svg_name, "syncReference")) { + strcpy(att->impl_type, "XMLRI"); + } else { + /* For all other attributes, we use String as default type */ + 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 */ + if (!strcmp(att->svg_name, "color")) { + strcpy(att->impl_type, "SVG_Paint"); + } else if (!strcmp(att->svg_name, "viewport-fill")) { + strcpy(att->impl_type, "SVG_Paint"); + } else if (!strcmp(att->svg_name, "syncTolerance")) { + strcpy(att->impl_type, "SMIL_SyncTolerance"); + } else if (!strcmp(att->svg_name, "syncToleranceDefault")) { + strcpy(att->impl_type, "SMIL_SyncTolerance"); + } else if (!strcmp(att->svg_name, "transform")) { + strcpy(att->impl_type, "SVG_Transform"); + } else if (!strcmp(att->svg_name, "gradientTransform")) { + strcpy(att->impl_type, "SVG_Transform"); + } else if (!strcmp(att->svg_name, "focusable")) { + strcpy(att->impl_type, "SVG_Focusable"); + } else if (!strcmp(att->svg_name, "event") || !strcmp(att->svg_name, "ev:event")) { + strcpy(att->impl_type, "XMLEV_Event"); + } else if (!strcmp(att->svg_type, "IRI.datatype")) { + strcpy(att->impl_type, "XMLRI"); + } else if (!strcmp(att->svg_type, "IDREF.datatype")) { + strcpy(att->impl_type, "XML_IDREF"); + } else if (strstr(att->svg_type, "datatype")) { + char *tmp; + sprintf(att->impl_type, "SVG_%s", att->svg_type); + tmp = att->impl_type; + while ( (tmp = strstr(tmp, "-")) ) { + *tmp='_'; + tmp++; + } + tmp = att->impl_type; + while ( (tmp = strstr(tmp, ".")) ) { + *tmp='_'; + tmp++; + } + tmp = att->impl_type; + if ( (tmp = strstr(tmp, "datatype")) ) { + tmp--; + *tmp = 0; + } + } + } +} + +void getAttributeType(xmlDocPtr doc, xmlXPathContextPtr xpathCtx, + xmlNodePtr attributeNode, SVGGenAttribute *a) +{ + + xmlNodeSetPtr refNodes; + xpathCtx->node = attributeNode; + refNodes = findNodes(xpathCtx, ".//rng:ref"); + if (refNodes->nodeNr == 0) { + //a->svg_type = xmlStrdup("0_ref_type"); + } else if (refNodes->nodeNr == 1) { + xmlNodePtr ref = refNodes->nodeTab[0]; + a->svg_type = xmlStrdup(xmlGetProp(ref, "name")); + } else { + //a->svg_type = xmlStrdup("N_ref_type"); + } +} + +void getRealAttributes(xmlDocPtr doc, xmlXPathContextPtr xpathCtx, xmlNodePtr newCtxNode, + GF_List *attributes) +{ + xmlNodeSetPtr attributeNodes; + int k; + u32 j; + + xpathCtx->node = newCtxNode; + attributeNodes = findNodes(xpathCtx, ".//rng:attribute"); + for (k = 0; k < attributeNodes->nodeNr; k++) { + Bool already_exists = 0; + xmlNodePtr attributeNode = attributeNodes->nodeTab[k]; + if (attributeNode->type == XML_ELEMENT_NODE) { + SVGGenAttribute *a = NewSVGGenAttribute(); + a->svg_name = xmlGetProp(attributeNode, "name"); + a->optional = xmlStrEqual(attributeNode->parent->name, "optional"); + svgNameToImplementationName(a->svg_name, a->implementation_name); + getAttributeType(doc, xpathCtx, attributeNode, a); + setAttributeType(a); + for (j=0; jsvg_name, a->svg_name)) { + already_exists = 1; + break; + } + } + if (already_exists) { + deleteSVGGenAttribute(&a); + } else { + //fprintf(stdout, "Adding attribute %s to element %s\n",a->svg_name, e->svg_name); + gf_list_add(attributes, a); + } + } + } +} + +SVGGenAttrGrp *getOneGlobalAttrGrp(xmlDocPtr doc, xmlXPathContextPtr xpathCtx, xmlChar *name) +{ + SVGGenAttrGrp *attgrp = NULL; + xmlNodeSetPtr attrGrpDefNodes; + xmlChar *expr; + u32 j; + int i, l; + + /* attributes group already resolved */ + for (j = 0; j < gf_list_count(globalAttrGrp); j++) { + attgrp = gf_list_get(globalAttrGrp, j); + if (!strcmp(attgrp->name, name)) { + return attgrp; + } + } + + /* new attributes group */ + expr = xmlStrdup("//rng:define[@name=\""); + expr = xmlStrcat(expr, name); + expr = xmlStrcat(expr, "\" and not(rng:empty) and not(rng:notAllowed)]"); + attrGrpDefNodes = findNodes(xpathCtx, expr); + if (!attrGrpDefNodes->nodeNr) { + fprintf(stdout, "Warning: found 0 non-empty or allowed definition for the Group of Attributes: %s\n", name); + return NULL; + } + attgrp = NewSVGGenAttrGrp(); + attgrp->name = gf_strdup(name); + svgNameToImplementationName(attgrp->name, attgrp->imp_name); + gf_list_add(globalAttrGrp, attgrp); + + for (i = 0; i < attrGrpDefNodes->nodeNr; i++) { + xmlNodePtr attrGrp = attrGrpDefNodes->nodeTab[i]; + getRealAttributes(doc, xpathCtx, attrGrp, attgrp->attrs); + + { + xmlNodeSetPtr refNodes; + xpathCtx->node = attrGrp; + refNodes = findNodes(xpathCtx, ".//rng:ref"); + for (l = 0; l < refNodes->nodeNr; l++) { + xmlNodePtr ref = refNodes->nodeTab[l]; + xmlChar *rname = xmlGetProp(ref, "name"); + if (xmlStrstr(rname, ".attr")) { + SVGGenAttrGrp *g2 = getOneGlobalAttrGrp(doc, xpathCtx, rname); + if (g2) { + gf_list_add(attgrp->attrgrps, g2); + } + } + } + } + } + return attgrp; +} + +void getAllGlobalAttrGrp(xmlDocPtr doc, xmlXPathContextPtr xpathCtx) +{ + xmlNodeSetPtr elementNodes = findNodes(xpathCtx, "//rng:define"); + int k; + for (k = 0; k < elementNodes->nodeNr; k++) { + xmlNodePtr elementNode = elementNodes->nodeTab[k]; + if (elementNode->type == XML_ELEMENT_NODE) { + xmlChar *name = NULL; + name = xmlGetProp(elementNode, "name"); + if (xmlStrstr(name, ".attr")) { + getOneGlobalAttrGrp(doc, xpathCtx, name); + } + } + } +} + +GF_List *getElements(xmlDocPtr doc, xmlXPathContextPtr xpathCtx) +{ + xmlChar *expr; + GF_List *elements = gf_list_new(); + xmlNodeSetPtr ATNodes; + xmlNodeSetPtr refNodes, elementNodes = findNodes(xpathCtx, "//rng:element"); + int k, j; + u32 i; + + for (k = 0; k < elementNodes->nodeNr; k++) { + xmlNodePtr elementNode = elementNodes->nodeTab[k]; + if (elementNode->type == XML_ELEMENT_NODE) { + SVGGenElement *e = NewSVGGenElement(); + e->svg_name = xmlStrdup(xmlGetProp(elementNode, "name")); + //fprintf(stdout, "\n\tElement %s\n", e->svg_name); + + svgNameToImplementationName(e->svg_name, e->implementation_name); + + /* getting the */ + expr = xmlStrdup("//rng:define[@name=\""); + if (!xmlStrcmp(e->svg_name, "polygon") || !xmlStrcmp(e->svg_name, "polyline")) { + expr = xmlStrcat(expr, "polyCommon"); + } else { + expr = xmlStrcat(expr, e->svg_name); + } + expr = xmlStrcat(expr, ".AT\"]"); + ATNodes = findNodes(xpathCtx, expr); + if (ATNodes->nodeNr) { + + /* dealing with attributes defined in groups of attributes */ + xpathCtx->node = ATNodes->nodeTab[0]; + refNodes = findNodes(xpathCtx, ".//rng:ref"); + for (j = 0; j nodeNr; j++) { + xmlNodePtr refNode = refNodes->nodeTab[j]; + char *name = xmlGetProp(refNode, "name"); + for (i = 0; i < gf_list_count(globalAttrGrp); i++) { + SVGGenAttrGrp *a = gf_list_get(globalAttrGrp, i); + if (!strcmp(a->name, name)) { + if (isGenericAttributesGroup(a->name)) { + setGenericAttributesFlags(a->name, e); + flattenAttributeGroup(*a, e, 1); + } else { + flattenAttributeGroup(*a, e, 0); + } + break; + } + } + } + + /* dealing with attributes defined directly here */ + getRealAttributes(doc, xpathCtx, ATNodes->nodeTab[0], e->attributes); + } + + /* checking if this element is already present in the list of possible elements + and if not, adding it */ + { + Bool found = 0; + for (i=0; isvg_name, e->svg_name)) { + found = 1; + break; + } + } + if (!found) gf_list_add(elements, e); + } + } + } + return elements; +} + +/*type: 0: header, 1: source*/ +FILE *BeginFile(u32 type) +{ + FILE *f; + + char sPath[GF_MAX_PATH]; + + if (!type) { +#ifdef LOCAL_SVG_NODES + sprintf(sPath, "nodes_svg.h", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); +#else + if (generation_mode == 1) + sprintf(sPath, "..%c..%c..%c..%cinclude%cgpac%cnodes_svg_sa.h", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + else if (generation_mode == 2) + sprintf(sPath, "..%c..%c..%c..%cinclude%cgpac%cnodes_svg_sani.h", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + else if (generation_mode == 3) + sprintf(sPath, "..%c..%c..%c..%cinclude%cgpac%cnodes_svg_da.h", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + +#endif + } else if (type==1) { +#ifdef LOCAL_SVG_NODES + sprintf(sPath, "svg_nodes.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); +#else + if (generation_mode == 1) + sprintf(sPath, "..%c..%c..%c..%csrc%cscenegraph%csvg_nodes_sa.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + else if (generation_mode == 2) + sprintf(sPath, "..%c..%c..%c..%csrc%cscenegraph%csvg_nodes_sani.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + else if (generation_mode == 3) + sprintf(sPath, "..%c..%c..%c..%csrc%cscenegraph%csvg_nodes_da.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); +#endif + } else { +#ifdef LOCAL_SVG_NODES + sprintf(sPath, "lsr_tables.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); +#else + if (generation_mode == 1) + sprintf(sPath, "..%c..%c..%c..%csrc%claser%clsr_tables_sa.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + else if (generation_mode == 2) + sprintf(sPath, "..%c..%c..%c..%csrc%claser%clsr_tables_sani.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + else if (generation_mode == 3) + sprintf(sPath, "..%c..%c..%c..%csrc%claser%clsr_tables.c", GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR, GF_PATH_SEPARATOR); + +#endif + } + + f = gf_fopen(sPath, "wt"); + fprintf(f, "%s\n", COPYRIGHT); + + { + time_t rawtime; + time(&rawtime); + fprintf(f, "\n/*\n\tDO NOT MOFIFY - File generated on GMT %s\n\tBY SVGGen for GPAC Version %s\n*/\n\n", asctime(gmtime(&rawtime)), GPAC_VERSION); + } + + if (!type) { + if (generation_mode == 1) { + fprintf(f, "#ifndef _GF_SVG_SA_NODES_H\n"); + fprintf(f, "#define _GF_SVG_SA_NODES_H\n\n"); + } else if (generation_mode == 2) { + fprintf(f, "#ifndef _GF_SVG_SANI_NODES_H\n"); + fprintf(f, "#define _GF_SVG_SANI_NODES_H\n\n"); + } else if (generation_mode == 3) { + fprintf(f, "#ifndef _GF_SVG_NODES_H\n"); + fprintf(f, "#define _GF_SVG_NODES_H\n\n"); + } + fprintf(f, "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"); + } + return f; +} + +void EndFile(FILE *f, u32 type) +{ + if (!type) { + fprintf(f, "#ifdef __cplusplus\n}\n#endif\n\n"); + if (generation_mode == 1) fprintf(f, "\n\n#endif\t\t/*_GF_SVG_SA_NODES_H*/\n\n"); + if (generation_mode == 2) fprintf(f, "\n\n#endif\t\t/*_GF_SVG_SANI_NODES_H*/\n\n"); + if (generation_mode == 3) fprintf(f, "\n\n#endif\t\t/*_GF_SVG_NODES_H*/\n\n"); + } else { + fprintf(f, "\n"); + } + gf_fclose(f); +} + +void generateAttributes(FILE *output, GF_List *attributes, Bool inDefine) +{ + u32 i; + for (i = 0; iimpl_type, att->implementation_name); + else + fprintf(output, "\t%s %s; \\\n", att->impl_type, att->implementation_name); + else + fprintf(output, "\t%s %s;\n", att->impl_type, att->implementation_name); + } +} + +/* +u32 generateAttributesGroupInfo(FILE *output, char * elt_imp_name, SVGGenAttrGrp *attgrp, u32 i) +{ + u32 att_index = i; + u32 k; + for (k=0; kattrgrps); k++) { + SVGGenAttrGrp *ag = gf_list_get(attgrp->attrgrps, k); + att_index = generateAttributesGroupInfo(output, elt_imp_name, ag, att_index); + } + for (k=0; kattrs); k++) { + SVGGenAttribute *at = gf_list_get(attgrp->attrs, k); + generateAttributeInfo(output, elt_imp_name, at, att_index++); + } + return att_index; +} +*/ + +void replaceIncludes(xmlDocPtr doc, xmlXPathContextPtr xpathCtx) +{ + int k; + xmlNodeSetPtr nodes; + xmlXPathObjectPtr xpathObj; + + /* Get all the RNG elements */ + xpathObj = xmlXPathEvalExpression("//rng:include", xpathCtx); + if(xpathObj == NULL || xpathObj->type != XPATH_NODESET) return; + + nodes = xpathObj->nodesetval; + + for (k = 0; k < nodes->nodeNr; k++) { + xmlNodePtr node = nodes->nodeTab[k]; + if (node->type == XML_ELEMENT_NODE) { + xmlChar *href; + xmlDocPtr sub_doc; + + href = xmlGetNoNsProp(node, "href"); + sub_doc = xmlParseFile(href); + xmlReplaceNode(nodes->nodeTab[k], xmlDocGetRootElement(sub_doc)); + } + } + xmlXPathFreeObject(xpathObj); +} + +int main(int argc, char **argv) +{ + xmlDocPtr doc = NULL; + xmlXPathContextPtr xpathCtx = NULL; + GF_List *svg_elements = NULL; + + xmlInitParser(); + LIBXML_TEST_VERSION + + doc = xmlParseFile(argv[1]); + if (!doc) { + printf("error: could not parse file %s\n", argv[1]); + return -1; + } + + xpathCtx = xmlXPathNewContext(doc); + if(xpathCtx == NULL) { + fprintf(stderr,"Error: unable to create new XPath context\n"); + xmlFreeDoc(doc); + return(-1); + } + xmlXPathRegisterNs(xpathCtx, RNG_PREFIX, RNG_NS); + xmlXPathRegisterNs(xpathCtx, RNGA_PREFIX, RNGA_NS); + xmlXPathRegisterNs(xpathCtx, SVGA_PREFIX, SVGA_NS); + + replaceIncludes(doc, xpathCtx); + xmlSaveFile("completerng_props.xml", doc); + + globalAttrGrp = gf_list_new(); + getAllGlobalAttrGrp(doc, xpathCtx); + + svg_elements = getElements(doc, xpathCtx); + svg_elements = sortElements(svg_elements); + + if (argv[2] && !strcmp(argv[2], "-html")) { + generate_table(svg_elements); + } else { + if (generation_mode == 1) generateSVGCode_V1(svg_elements); + if (generation_mode == 2) generateSVGCode_V2(svg_elements); + if (generation_mode == 3) generateSVGCode_V3(svg_elements); + } + + xmlXPathFreeContext(xpathCtx); + //xmlFreeDoc(doc); + + xmlCleanupParser(); + return 0; +} + + diff --git a/applications/generators/SVG/svggen.h b/applications/generators/SVG/svggen.h new file mode 100644 index 0000000..a2c97e3 --- /dev/null +++ b/applications/generators/SVG/svggen.h @@ -0,0 +1,195 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 _SVGGEN_H_ +#define _SVGGEN_H_ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +/* if defined generates .c/.h directly in the appropriate GPAC source folders */ +#undef LOCAL_SVG_NODES + +/* + Modes for generating SVG code + - 1 means static allocation of attributes (including properties, use Tiny-1.2-NG) + - 2 means static allocation of attributes (only useful properties on nodes, use Tiny-1.2-NG-noproperties) + - 3 means dynamic allocation of attributes (including properties) +*/ +static u32 generation_mode = 3; + +#define RNG_NS "http://relaxng.org/ns/structure/1.0" +#define RNGA_NS "http://relaxng.org/ns/compatibility/annotations/1.0" +#define SVGA_NS "http://www.w3.org/2005/02/svg-annotations" + +#define RNG_PREFIX "rng" +#define RNGA_PREFIX "rnga" +#define SVGA_PREFIX "svg" + +#define COPYRIGHT "/*\n * GPAC - Multimedia Framework C SDK\n *\n * Authors: Cyril Concolato - Jean Le Feuvre\n * Copyright (c) Telecom ParisTech 2000-2012 - All rights reserved\n *\n * This file is part of GPAC / SVG Scene Graph sub-project\n *\n * GPAC is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation; either version 2, or (at your option)\n * any later version.\n *\n * GPAC is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details. \n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; see the file COPYING. If not, write to\n * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.\n *\n */\n" + + +/* + type declarations +*/ + +typedef struct +{ + xmlChar *svg_name; + char implementation_name[50]; + + Bool has_svg_generic; + Bool has_xml_generic; + Bool has_media_properties; + Bool has_properties; + Bool has_opacity_properties; + Bool has_focus; + Bool has_xlink; + Bool has_timing; + Bool has_sync; + Bool has_animation; + Bool has_conditional; + Bool has_transform; + Bool has_xy; + + GF_List *attributes; + GF_List *generic_attributes; + + u32 nb_atts; +} SVGGenElement; + +typedef struct { + xmlChar *svg_name; + char implementation_name[50]; + xmlChar *svg_type; + char impl_type[50]; + u8 animatable; + u8 inheritable; + Bool optional; + xmlChar *default_value; + u32 index; +} SVGGenAttribute; +SVGGenAttribute *NewSVGGenAttribute(); + +typedef struct { + char *name; + char imp_name[50]; + GF_List *attrs; + GF_List *attrgrps; +} SVGGenAttrGrp; + + + +/******************************************* + * Structures needed for static allocation * + *******************************************/ + +static GF_List *globalAttrGrp; + +/* SVG Generic */ +static char *core[] = { "id", "class", "xml:id", "xml:base", "xml:lang", "xml:space", "externalResourceRequired" }; + +/* Media Properties */ +static char *media_properties[] = { + "audio-level", "display", "image-rendering", "pointer-events", "shape-rendering", "text-rendering", + "viewport-fill", "viewport-fill-opacity", "visibility" +}; + +/* others */ +static char *other_properties[] = { + "color", "color-rendering", "display-align", "fill", "fill-opacity", "fill-rule", + "font-family", "font-size", "font-style", "font-weight", "line-increment", + "solid-color", "solid-opacity", "stop-color", "stop-opacity", + "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", + "stroke-opacity", "stroke-width", "text-align", "text-anchor", "vector-effect" +}; + +/* only opacity on image */ +static char *opacity_properties[] = { + "opacity" +}; + +/* Focus */ +static char *focus[] = { + "focusHighlight", "focusable", "nav-down", "nav-down-left", "nav-down-right", + "nav-left", "nav-next", "nav-prev", "nav-right", "nav-up", "nav-up-left", "nav-up-right" +}; + +/* Xlink */ +static char *xlink[] = { + "xlink:href", "xlink:show", "xlink:title", "xlink:actuate", "xlink:role", "xlink:arcrole", "xlink:type" +}; + +/* Timing */ +static char *timing[] = { + "begin", "end", "dur", "repeatCount", "repeatDur", "restart", "min", "max", "fill", "clipBegin", "clipEnd" +}; + +/* Sync */ +static char *sync[] = { + "syncBehavior", "syncBehaviorDefault", "syncTolerance", "syncToleranceDefault", "syncMaster", "syncReference" +}; + +/* Animation */ +static char *anim[] = { + "attributeName", "attributeType", "to", "from", "by", "values", + "type", "calcMode", "keySplines", "keyTimes", "accumulate", "additive", "lsr:enabled" +}; + +/* Conditional Processing */ +static char *conditional[] = { + "requiredExtensions", "requiredFeatures", "requiredFonts", "requiredFormats", "systemLanguage" +}; + +typedef struct { + int array_length; + char **array; // mapping of constructs to the RNG definition +} _atts; + +static _atts generic_attributes[] = { + { 7, core }, + { 26, other_properties }, + { 9, media_properties }, + { 1, opacity_properties }, + { 12, focus }, + { 7, xlink }, + { 11, timing }, + { 6, sync }, + { 13, anim }, + { 5, conditional} +}; + +FILE *BeginFile(u32 type); +void EndFile(FILE *f, u32 type); + + +#endif // _SVGGEN_H_ diff --git a/applications/generators/SVG/v1.c b/applications/generators/SVG/v1.c new file mode 100644 index 0000000..a48bbbd --- /dev/null +++ b/applications/generators/SVG/v1.c @@ -0,0 +1,608 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 "svggen.h" + +void generateNode(FILE *output, SVGGenElement* svg_elt) +{ + fprintf(output, "typedef struct _tagSVG_SA_%sElement\n{\n", svg_elt->implementation_name); + + if (svg_elt->has_transform) { + fprintf(output, "\tTRANSFORMABLE_SVG_ELEMENT\n"); + } else { + fprintf(output, "\tBASE_SVG_ELEMENT\n"); + } + + if (!strcmp(svg_elt->implementation_name, "conditional")) { + fprintf(output, "\tSVGCommandBuffer updates;\n"); + } + + generateAttributes(output, svg_elt->attributes, 0); + + /*special case for handler node*/ + if (!strcmp(svg_elt->implementation_name, "handler")) { + fprintf(output, "\tvoid (*handle_event)(GF_Node *hdl, GF_DOM_Event *event);\n"); + } + fprintf(output, "} SVG_SA_%sElement;\n\n\n", svg_elt->implementation_name); +} + + +void generateAttributeInfo(FILE *output, char * elt_imp_name, SVGGenAttribute *att, u32 i) +{ + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"%s\";\n", att->svg_name); + fprintf(output, "\t\t\tinfo->fieldType = %s_datatype;\n", att->impl_type); + fprintf(output, "\t\t\tinfo->far_ptr = & ((SVG_SA_%sElement *)node)->%s;\n", elt_imp_name, att->implementation_name); + fprintf(output, "\t\t\treturn GF_OK;\n"); +} + +u32 generateCoreInfo(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"id\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_ID_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = gf_node_get_name_address(node);\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"xml:id\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_ID_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = gf_node_get_name_address(node);\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"class\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_String_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SA_Element *)node)->core->_class;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"xml:lang\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_LanguageID_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SA_Element *)node)->core->lang;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"xml:base\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_String_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SA_Element *)node)->core->base;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"xml:space\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = XML_Space_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SA_Element *)node)->core->space;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"externalResourcesRequired\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Boolean_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SA_Element *)node)->core->eRR;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + return i; +} + +void generateAttributeInfoFlat(FILE *output, char *pointer, char *name, char *type, u32 i) +{ + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"%s\";\n", name); + fprintf(output, "\t\t\tinfo->fieldType = %s_datatype;\n", type); + fprintf(output, "\t\t\tinfo->far_ptr = &%s;\n", pointer); + fprintf(output, "\t\t\treturn GF_OK;\n"); +} + +u32 generateTransformInfo(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"transform\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Transform_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVGTransformableElement *)node)->transform;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + return i; +} + +u32 generateMotionTransformInfo(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"motionTransform\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Motion_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = ((SVGTransformableElement *)node)->motionTransform;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + return i; +} + +u32 generateXYInfo(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"x\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Coordinate_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVGTransformableElement *)node)->xy.x;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"y\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Coordinate_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVGTransformableElement *)node)->xy.y;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + return i; +} + +u32 generateGenericInfo(FILE *output, SVGGenElement *elt, u32 index, char *pointer_root, u32 start) +{ + u32 i = start; + int k; + for (k=0; k < generic_attributes[index].array_length; k++) { + char *att_name = generic_attributes[index].array[k]; + SVGGenAttribute *a = findAttribute(elt, att_name); + if (a) { + char pointer[500]; + if (strstr(att_name, "xlink:")) { + sprintf(pointer, "%s%s", pointer_root, att_name+6); + } else if (strstr(att_name, "xml:")) { + sprintf(pointer, "%s%s", pointer_root, att_name+4); + } else { + char imp_name[50]; + svgNameToImplementationName(att_name, imp_name); + sprintf(pointer, "%s%s", pointer_root, imp_name); + } + generateAttributeInfoFlat(output, pointer, a->svg_name, a->impl_type, i); + i++; + } + } + return i; +} + +u32 generateIndexInfo(FILE *output, SVGGenElement *elt, u32 index, u32 start) +{ + u32 i = start; + int k; + for (k=0; k < generic_attributes[index].array_length; k++) { + char *att_name = generic_attributes[index].array[k]; + SVGGenAttribute *a = findAttribute(elt, att_name); + if (a) { + fprintf(output, "\tif(!strcmp(\"%s\", name)) return %d;\n", att_name, i); + i++; + } + } + return i; +} + +void generateNodeImpl(FILE *output, SVGGenElement* svg_elt) +{ + u32 i; + + /***************************************************/ + /* Constructor */ + /***************************************************/ + fprintf(output, "void *gf_svg_new_%s()\n{\n\tSVG_SA_%sElement *p;\n", svg_elt->implementation_name,svg_elt->implementation_name); + fprintf(output, "\tGF_SAFEALLOC(p, SVG_SA_%sElement);\n\tif (!p) return NULL;\n\tgf_node_setup((GF_Node *)p, TAG_SVG_%s);\n\tgf_sg_parent_setup((GF_Node *) p);\n",svg_elt->implementation_name,svg_elt->implementation_name); + + fprintf(output, "\tgf_svg_sa_init_core((SVG_SA_Element *)p);\n"); + if (svg_elt->has_properties || + svg_elt->has_media_properties || + svg_elt->has_opacity_properties) { + fprintf(output, "\tgf_svg_sa_init_properties((SVG_SA_Element *)p);\n"); + } + if (svg_elt->has_focus) { + fprintf(output, "\tgf_svg_sa_init_focus((SVG_SA_Element *)p);\n"); + } + if (svg_elt->has_xlink) { + fprintf(output, "\tgf_svg_sa_init_xlink((SVG_SA_Element *)p);\n"); + } + if (svg_elt->has_timing) { + fprintf(output, "\tgf_svg_sa_init_timing((SVG_SA_Element *)p);\n"); + } + if (svg_elt->has_sync) { + fprintf(output, "\tgf_svg_sa_init_sync((SVG_SA_Element *)p);\n"); + } + if (svg_elt->has_animation) { + fprintf(output, "\tgf_svg_sa_init_anim((SVG_SA_Element *)p);\n"); + } + if (svg_elt->has_conditional) { + fprintf(output, "\tgf_svg_sa_init_conditional((SVG_SA_Element *)p);\n"); + } + + if (svg_elt->has_transform) { + fprintf(output, "\tgf_mx2d_init(p->transform.mat);\n"); + } + + if (!strcmp(svg_elt->implementation_name, "conditional")) { + fprintf(output, "\tgf_svg_sa_init_lsr_conditional(&p->updates);\n"); + fprintf(output, "\tgf_svg_sa_init_timing((SVG_SA_Element *)p);\n"); + + } + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + /* Initialization of complex types */ + if ( !strcmp("SVG_Points", att->impl_type) || + !strcmp("SVG_Coordinates", att->impl_type) || + !strcmp("SMIL_KeyPoints", att->impl_type)) { + fprintf(output, "\tp->%s = gf_list_new();\n", att->implementation_name); + } else if (!strcmp("SVG_PathData", att->impl_type) && !strcmp(svg_elt->svg_name, "animateMotion")) { + fprintf(output, "#ifdef USE_GF_PATH\n"); + fprintf(output, "\tgf_path_reset(&p->path);\n"); + fprintf(output, "#else\n"); + fprintf(output, "\tp->path.commands = gf_list_new();\n"); + fprintf(output, "\tp->path.points = gf_list_new();\n"); + fprintf(output, "#endif\n"); + } else if (!strcmp("SVG_PathData", att->impl_type)) { + fprintf(output, "#ifdef USE_GF_PATH\n"); + fprintf(output, "\tgf_path_reset(&p->d);\n"); + fprintf(output, "#else\n"); + fprintf(output, "\tp->d.commands = gf_list_new();\n"); + fprintf(output, "\tp->d.points = gf_list_new();\n"); + fprintf(output, "#endif\n"); + } else if (!strcmp(att->svg_name, "lsr:enabled")) { + fprintf(output, "\tp->lsr_enabled = 1;\n"); + } + } + /*some default values*/ + if (!strcmp(svg_elt->svg_name, "svg")) { + fprintf(output, "\tp->width.type = SVG_NUMBER_PERCENTAGE;\n"); + fprintf(output, "\tp->width.value = INT2FIX(100);\n"); + fprintf(output, "\tp->height.type = SVG_NUMBER_PERCENTAGE;\n"); + fprintf(output, "\tp->height.value = INT2FIX(100);\n"); + } + else if (!strcmp(svg_elt->svg_name, "solidColor")) { + fprintf(output, "\tp->properties->solid_opacity.value = FIX_ONE;\n"); + } + else if (!strcmp(svg_elt->svg_name, "stop")) { + fprintf(output, "\tp->properties->stop_opacity.value = FIX_ONE;\n"); + } + else if (!strcmp(svg_elt->svg_name, "linearGradient")) { + fprintf(output, "\tp->x2.value = FIX_ONE;\n"); + fprintf(output, "\tgf_mx2d_init(p->gradientTransform.mat);\n"); + } + else if (!strcmp(svg_elt->svg_name, "radialGradient")) { + fprintf(output, "\tp->cx.value = FIX_ONE/2;\n"); + fprintf(output, "\tp->cy.value = FIX_ONE/2;\n"); + fprintf(output, "\tp->r.value = FIX_ONE/2;\n"); + fprintf(output, "\tgf_mx2d_init(p->gradientTransform.mat);\n"); + fprintf(output, "\tp->fx.value = FIX_ONE/2;\n"); + fprintf(output, "\tp->fy.value = FIX_ONE/2;\n"); + } + else if (!strcmp(svg_elt->svg_name, "video") || !strcmp(svg_elt->svg_name, "audio") || !strcmp(svg_elt->svg_name, "animation")) { + fprintf(output, "\tp->timing->dur.type = SMIL_DURATION_MEDIA;\n"); + } + fprintf(output, "\treturn p;\n}\n\n"); + + /***************************************************/ + /* Destructor */ + /***************************************************/ + fprintf(output, "static void gf_svg_sa_%s_del(GF_Node *node)\n{\n", svg_elt->implementation_name); + fprintf(output, "\tSVG_SA_%sElement *p = (SVG_SA_%sElement *)node;\n", svg_elt->implementation_name, svg_elt->implementation_name); + + fprintf(output, "\tgf_svg_sa_reset_base_element((SVG_SA_Element *)p);\n"); + + if (!strcmp(svg_elt->implementation_name, "conditional")) { + fprintf(output, "\tgf_svg_sa_reset_lsr_conditional(&p->updates);\n"); + } + else if (!strcmp(svg_elt->implementation_name, "a")) { + fprintf(output, "\tif (p->target) free(p->target);\n"); + } + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + if (!strcmp("SMIL_KeyPoints", att->impl_type)) { + fprintf(output, "\tgf_smil_delete_key_types(p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_Coordinates", att->impl_type)) { + fprintf(output, "\tgf_svg_delete_coordinates(p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_Points", att->impl_type)) { + fprintf(output, "\tgf_svg_delete_points(p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_PathData", att->impl_type)) { + if (!strcmp(svg_elt->svg_name, "animateMotion")) { + fprintf(output, "\tgf_svg_reset_path(p->path);\n"); + } else { + fprintf(output, "\tgf_svg_reset_path(p->d);\n"); + } + } else if (!strcmp("XMLRI", att->impl_type)) { + fprintf(output, "\tgf_svg_reset_iri(node->sgprivate->scenegraph, &p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_FontFamily", att->impl_type)) { + fprintf(output, "\tif (p->%s.value) free(p->%s.value);\n", att->implementation_name, att->implementation_name); + } else if (!strcmp("SVG_String", att->impl_type) || !strcmp("SVG_ContentType", att->impl_type)) { + fprintf(output, "\tfree(p->%s);\n", att->implementation_name); + } + } + if (svg_elt->has_transform) { + fprintf(output, "\tif (p->motionTransform) free(p->motionTransform);\n"); + } + + fprintf(output, "\tgf_sg_parent_reset((GF_Node *) p);\n"); + fprintf(output, "\tgf_node_free((GF_Node *)p);\n"); + fprintf(output, "}\n\n"); + + /***************************************************/ + /* Attribute Access */ + /***************************************************/ + fprintf(output, "static GF_Err gf_svg_sa_%s_get_attribute(GF_Node *node, GF_FieldInfo *info)\n{\n", svg_elt->implementation_name); + fprintf(output, "\tswitch (info->fieldIndex) {\n"); + svg_elt->nb_atts = 0; + svg_elt->nb_atts = generateCoreInfo(output, svg_elt, svg_elt->nb_atts); + + if (svg_elt->has_media_properties) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 2, "((SVG_SA_Element *)node)->properties->", svg_elt->nb_atts); + if (svg_elt->has_properties) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 1, "((SVG_SA_Element *)node)->properties->", svg_elt->nb_atts); + if (svg_elt->has_opacity_properties) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 3, "((SVG_SA_Element *)node)->properties->", svg_elt->nb_atts); + if (svg_elt->has_focus) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 4, "((SVG_SA_Element *)node)->focus->", svg_elt->nb_atts); + if (svg_elt->has_xlink) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 5, "((SVG_SA_Element *)node)->xlink->", svg_elt->nb_atts); + if (svg_elt->has_timing) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 6, "((SVG_SA_Element *)node)->timing->", svg_elt->nb_atts); + if (svg_elt->has_sync) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 7, "((SVG_SA_Element *)node)->sync->", svg_elt->nb_atts); + if (svg_elt->has_animation) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 8, "((SVG_SA_Element *)node)->anim->", svg_elt->nb_atts); + if (svg_elt->has_conditional) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 9, "((SVG_SA_Element *)node)->conditional->", svg_elt->nb_atts); + if (svg_elt->has_transform) { + svg_elt->nb_atts = generateTransformInfo(output, svg_elt, svg_elt->nb_atts); + svg_elt->nb_atts = generateMotionTransformInfo(output, svg_elt, svg_elt->nb_atts); + } + if (svg_elt->has_xy) + svg_elt->nb_atts = generateXYInfo(output, svg_elt, svg_elt->nb_atts); + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + generateAttributeInfo(output, svg_elt->implementation_name, att, svg_elt->nb_atts++); + } + fprintf(output, "\t\tdefault: return GF_BAD_PARAM;\n\t}\n}\n\n"); + + /***************************************************/ + /* gf_svg_sa_%s_get_attribute_index_from_name */ + /***************************************************/ + fprintf(output, "s32 gf_svg_sa_%s_get_attribute_index_from_name(char *name)\n{\n", svg_elt->implementation_name); + { + u32 att_index = 0; + fprintf(output, "\tif(!strcmp(\"id\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"xml:id\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"class\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"xml:lang\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"xml:base\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"xml:space\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"externalResourcesRequired\", name)) return %d;\n", att_index); + att_index++; + if (svg_elt->has_media_properties) + att_index = generateIndexInfo(output, svg_elt, 2, att_index); + if (svg_elt->has_properties) + att_index = generateIndexInfo(output, svg_elt, 1, att_index); + if (svg_elt->has_opacity_properties) + att_index = generateIndexInfo(output, svg_elt, 3, att_index); + if (svg_elt->has_focus) + att_index = generateIndexInfo(output, svg_elt, 4, att_index); + if (svg_elt->has_xlink) + att_index = generateIndexInfo(output, svg_elt, 5, att_index); + if (svg_elt->has_timing) + att_index = generateIndexInfo(output, svg_elt, 6, att_index); + if (svg_elt->has_sync) + att_index = generateIndexInfo(output, svg_elt, 7, att_index); + if (svg_elt->has_animation) + att_index = generateIndexInfo(output, svg_elt, 8, att_index); + if (svg_elt->has_conditional) + att_index = generateIndexInfo(output, svg_elt, 9, att_index); + if (svg_elt->has_transform) { + fprintf(output, "\tif(!strcmp(\"transform\", name)) return %d;\n", att_index); + att_index++; + /*motionTransform*/ + fprintf(output, "\tif(!strcmp(\"motionTransform\", name)) return %d;\n", att_index); + att_index++; + } + if (svg_elt->has_xy) { + fprintf(output, "\tif(!strcmp(\"x\", name)) return %d;\n", att_index); + att_index++; + fprintf(output, "\tif(!strcmp(\"y\", name)) return %d;\n", att_index); + att_index++; + } + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + fprintf(output, "\tif(!strcmp(\"%s\", name)) return %d;\n", att->svg_name, att_index); + att_index++; + } + } + fprintf(output, "\treturn -1;\n}\n\n"); +} + +void generateSVGCode_V1(GF_List *svg_elements) +{ + FILE *output; + u32 i; + + /***************************************************/ + /***************************************************/ + /*************** Creating .h file ******************/ + /***************************************************/ + /***************************************************/ + output = BeginFile(0); + fprintf(output, "#include \n\n\n"); + fprintf(output, "/* Definition of SVG element internal tags */\n"); + fprintf(output, "/* TAG names are made of \"TAG_SVG\" + SVG element name (with - replaced by _) */\n"); + + /* write all tags */ + fprintf(output, "enum {\n"); + for (i=0; iimplementation_name); + } else { + fprintf(output, ",\n\tTAG_SVG_%s", elt->implementation_name); + } + } + + fprintf(output, ",\n\t/*undefined elements (when parsing) use this tag*/\n\tTAG_SVG_UndefinedElement\n};\n\n"); + + fprintf(output, "/******************************************\n"); + fprintf(output, "* SVG Elements structure definitions *\n"); + fprintf(output, "*******************************************/\n"); + for (i=0; i\n\n"); + + fprintf(output, "#ifndef GPAC_DISABLE_SVG\n\n"); + fprintf(output, "#include \n\n"); + fprintf(output, "#ifdef GPAC_ENABLE_SVG_SA\n\n"); + for (i=0; iimplementation_name,elt->implementation_name); + } + fprintf(output, "\t\tdefault: return NULL;\n\t}\n}\n\n"); + + /***************************************************/ + /* void gf_svg_sa_element_del(SVG_SA_Element *elt) */ + /***************************************************/ + fprintf(output, "void gf_svg_sa_element_del(SVG_SA_Element *elt)\n{\n"); + fprintf(output, "\tGF_Node *node = (GF_Node *)elt;\n"); + fprintf(output, "\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->implementation_name); + } + fprintf(output, "\t\tdefault: return;\n\t}\n}\n\n"); + + /***************************************************/ + /* u32 gf_svg_sa_get_attribute_count(SVG_SA_Element *elt) */ + /***************************************************/ + fprintf(output, "u32 gf_svg_sa_get_attribute_count(GF_Node *node)\n{\n"); + fprintf(output, "\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->nb_atts); + } + fprintf(output, "\t\tdefault: return 0;\n\t}\n}\n\n"); + + /***********************************************************************/ + /* GF_Err gf_svg_sa_get_attribute_info(GF_Node *node, GF_FieldInfo *info) */ + /***********************************************************************/ + fprintf(output, "GF_Err gf_svg_sa_get_attribute_info(GF_Node *node, GF_FieldInfo *info)\n{\n"); + fprintf(output, "\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->implementation_name); + } + fprintf(output, "\t\tdefault: return GF_BAD_PARAM;\n\t}\n}\n\n"); + + /****************************************************************/ + /* u32 gf_svg_sa_node_type_by_class_name(const char *element_name) */ + /****************************************************************/ + fprintf(output, "u32 gf_svg_sa_node_type_by_class_name(const char *element_name)\n{\n\tif (!element_name) return TAG_UndefinedNode;\n"); + for (i=0; isvg_name, elt->implementation_name); + } + fprintf(output, "\treturn TAG_UndefinedNode;\n}\n\n"); + + + /***************************************************/ + /* const char *gf_svg_sa_get_element_name(u32 tag) */ + /***************************************************/ + fprintf(output, "const char *gf_svg_sa_get_element_name(u32 tag)\n{\n\tswitch(tag) {\n"); + for (i=0; iimplementation_name, elt->svg_name); + } + fprintf(output, "\tdefault: return \"UndefinedNode\";\n\t}\n}\n\n"); + + /***************************************************/ + /* const char *gf_svg_sa_get_attribute_index_by_name(u32 tag) */ + /***************************************************/ + fprintf(output, "s32 gf_svg_sa_get_attribute_index_by_name(GF_Node *node, char *name)\n{\n\tswitch(node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->implementation_name); + } + fprintf(output, "\tdefault: return -1;\n\t}\n}\n\n"); + + /***************************************************/ + /* Bool gf_svg_is_element_transformable(u32 tag) */ + /***************************************************/ + fprintf(output, "Bool gf_svg_is_element_transformable(u32 tag)\n{\n\tswitch(tag) {\n"); + for (i=0; iimplementation_name); + if (elt->has_transform) fprintf(output, "return 1;\n"); + else fprintf(output, "return 0;\n"); + } + fprintf(output, "\tdefault: return 0;\n\t}\n}\n"); + + fprintf(output, "#endif /*GPAC_ENABLE_SVG_SA*/\n"); + fprintf(output, "#endif /*GPAC_DISABLE_SVG*/\n\n"); + EndFile(output, 1); + + generate_laser_tables(svg_elements); +} + diff --git a/applications/generators/SVG/v2.c b/applications/generators/SVG/v2.c new file mode 100644 index 0000000..957fd48 --- /dev/null +++ b/applications/generators/SVG/v2.c @@ -0,0 +1,460 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 "svggen.h" + +void generateAttributes2(FILE *output, GF_List *attributes) +{ + u32 i; + for (i = 0; iimplementation_name, "transform")) continue; + fprintf(output, "\t%s %s;\n", att->impl_type, att->implementation_name); + } +} + +void generateNode2(FILE *output, SVGGenElement* svg_elt) +{ + fprintf(output, "typedef struct _tagSVG_SANI_%sElement\n{\n", svg_elt->implementation_name); + + if (svg_elt->has_transform) { + fprintf(output, "\tTRANSFORMABLE_SVG_SANI_ELEMENT\n"); + } else { + fprintf(output, "\tBASE_SVG_SANI_ELEMENT\n"); + } + + if (!strcmp(svg_elt->implementation_name, "conditional")) { + fprintf(output, "\tSVGCommandBuffer updates;\n"); + } + + generateAttributes2(output, svg_elt->attributes); + + /*special case for handler node*/ + if (!strcmp(svg_elt->implementation_name, "handler")) { + fprintf(output, "\tvoid (*handle_event)(GF_Node *hdl, GF_DOM_Event *event);\n"); + } + fprintf(output, "} SVG_SANI_%sElement;\n\n\n", svg_elt->implementation_name); +} + +void generateAttributeInfo2(FILE *output, char * elt_imp_name, SVGGenAttribute *att, u32 i) +{ + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"%s\";\n", att->svg_name); + fprintf(output, "\t\t\tinfo->fieldType = %s_datatype;\n", att->impl_type); + fprintf(output, "\t\t\tinfo->far_ptr = & ((SVG_SANI_%sElement *)node)->%s;\n", elt_imp_name, att->implementation_name); + fprintf(output, "\t\t\treturn GF_OK;\n"); +} + +u32 generateTransformInfo2(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"transform\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Transform_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SANI_TransformableElement *)node)->transform;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + return i; +} + +u32 generateMotionTransformInfo2(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"motionTransform\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Transform_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = ((SVG_SANI_TransformableElement *)node)->motionTransform;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + return i; +} + +u32 generateXYInfo2(FILE *output, SVGGenElement *elt, u32 start) +{ + u32 i = start; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"x\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Coordinate_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SANI_TransformableElement *)node)->xy.x;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + + fprintf(output, "\t\tcase %d:\n", i); + fprintf(output, "\t\t\tinfo->name = \"y\";\n"); + fprintf(output, "\t\t\tinfo->fieldType = SVG_Coordinate_datatype;\n"); + fprintf(output, "\t\t\tinfo->far_ptr = &((SVG_SANI_TransformableElement *)node)->xy.y;\n"); + fprintf(output, "\t\t\treturn GF_OK;\n"); + i++; + return i; +} + +void generateNodeImpl2(FILE *output, SVGGenElement* svg_elt) +{ + u32 i; + + /* Constructor */ + fprintf(output, "void *gf_svg_sani_new_%s()\n{\n\tSVG_SANI_%sElement *p;\n", svg_elt->implementation_name,svg_elt->implementation_name); + fprintf(output, "\tGF_SAFEALLOC(p, SVG_SANI_%sElement);\n\tif (!p) return NULL;\n\tgf_node_setup((GF_Node *)p, TAG_SVG_SANI_%s);\n\tgf_sg_parent_setup((GF_Node *) p);\n",svg_elt->implementation_name,svg_elt->implementation_name); + + fprintf(output, "\tgf_svg_sani_init_core((SVG_SANI_Element *)p);\n"); + if (svg_elt->has_focus) { + fprintf(output, "\tgf_svg_sani_init_focus((SVG_SANI_Element *)p);\n"); + } + if (svg_elt->has_xlink) { + fprintf(output, "\tgf_svg_sani_init_xlink((SVG_SANI_Element *)p);\n"); + } + if (svg_elt->has_timing) { + fprintf(output, "\tgf_svg_sani_init_timing((SVG_SANI_Element *)p);\n"); + } + if (svg_elt->has_sync) { + fprintf(output, "\tgf_svg_sani_init_sync((SVG_SANI_Element *)p);\n"); + } + if (svg_elt->has_animation) { + fprintf(output, "\tgf_svg_sani_init_anim((SVG_SANI_Element *)p);\n"); + } + if (svg_elt->has_conditional) { + fprintf(output, "\tgf_svg_sani_init_conditional((SVG_SANI_Element *)p);\n"); + } + + if (svg_elt->has_transform) { + fprintf(output, "\tgf_mx2d_init(p->transform.mat);\n"); + } + + if (!strcmp(svg_elt->implementation_name, "conditional")) { + fprintf(output, "\tgf_svg_sa_init_lsr_conditional(&p->updates);\n"); + fprintf(output, "\tgf_svg_sani_init_timing((SVG_SANI_Element *)p);\n"); + + } + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + + /* forcing initialization of old-properties */ + if (!strcmp("audio-level", att->svg_name)) { + fprintf(output, "\tp->audio_level.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->audio_level.value = FIX_ONE;\n"); + } else if (!strcmp("display", att->svg_name)) { + fprintf(output, "\tp->display = SVG_DISPLAY_INLINE;\n"); + } else if (!strcmp("display-align", att->svg_name)) { + fprintf(output, "\tp->display_align = SVG_DISPLAYALIGN_AUTO;\n"); + } else if (!strcmp("fill", att->svg_name)) { + fprintf(output, "\tp->fill.type = SVG_PAINT_COLOR;\n"); + fprintf(output, "\tp->fill.color.type = SVG_COLOR_RGBCOLOR;\n"); + } else if (!strcmp("fill-opacity", att->svg_name)) { + fprintf(output, "\tp->fill_opacity.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->fill_opacity.value = FIX_ONE;\n"); + } else if (!strcmp("fill-rule", att->svg_name)) { + fprintf(output, "\tp->fill_rule = SVG_FILLRULE_NONZERO;\n"); + } else if (!strcmp("font-family", att->svg_name)) { + fprintf(output, "\tp->font_family.type = SVG_FONTFAMILY_VALUE;\n"); + fprintf(output, "\tp->font_family.value = strdup(\"Arial\");\n"); + } else if (!strcmp("font-size", att->svg_name)) { + fprintf(output, "\tp->font_size.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->font_size.value = 12*FIX_ONE;\n"); + } else if (!strcmp("font-style", att->svg_name)) { + fprintf(output, "\tp->font_style = SVG_FONTSTYLE_NORMAL;\n"); + } else if (!strcmp("font-variant", att->svg_name)) { + fprintf(output, "\tp->font_variant = SVG_FONTVARIANT_NORMAL;\n"); + } else if (!strcmp("font-weight", att->svg_name)) { + fprintf(output, "\tp->font_weight = SVG_FONTWEIGHT_NORMAL;\n"); + } else if (!strcmp("line-increment", att->svg_name)) { + fprintf(output, "\tp->line_increment.type = SVG_NUMBER_AUTO;\n"); + fprintf(output, "\tp->line_increment.value = FIX_ONE;\n"); + } else if (!strcmp("opacity", att->svg_name)) { + fprintf(output, "\tp->opacity.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->opacity.value = FIX_ONE;\n"); + } else if (!strcmp("solid-color", att->svg_name)) { + fprintf(output, "\tp->solid_color.type = SVG_PAINT_COLOR;\n"); + fprintf(output, "\tp->solid_color.color.type = SVG_COLOR_RGBCOLOR;\n"); + } else if (!strcmp("solid-opacity", att->svg_name)) { + fprintf(output, "\tp->solid_opacity.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->solid_opacity.value = FIX_ONE;\n"); + } else if (!strcmp("stop-opacity", att->svg_name)) { + fprintf(output, "\tp->stop_opacity.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->stop_opacity.value = FIX_ONE;\n"); + } else if (!strcmp("stroke", att->svg_name)) { + fprintf(output, "\tp->stroke.type = SVG_PAINT_NONE;\n"); + fprintf(output, "\tp->stroke.color.type = SVG_COLOR_RGBCOLOR;\n"); + } else if (!strcmp("stroke-dasharray", att->svg_name)) { + fprintf(output, "\tp->stroke_dasharray.type = SVG_STROKEDASHARRAY_NONE;\n"); + } else if (!strcmp("stroke-dashoffset", att->svg_name)) { + fprintf(output, "\tp->stroke_dashoffset.type = SVG_NUMBER_VALUE;\n"); + } else if (!strcmp("stroke-linecap", att->svg_name)) { + fprintf(output, "\tp->stroke_linecap = SVG_STROKELINECAP_BUTT;\n"); + } else if (!strcmp("stroke-linejoin", att->svg_name)) { + fprintf(output, "\tp->stroke_linejoin = SVG_STROKELINEJOIN_MITER;\n"); + } else if (!strcmp("stroke-miterlimit", att->svg_name)) { + fprintf(output, "\tp->stroke_miterlimit.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->stroke_miterlimit.value = 4*FIX_ONE;\n"); + } else if (!strcmp("stroke-opacity", att->svg_name)) { + fprintf(output, "\tp->stroke_opacity.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->stroke_opacity.value = FIX_ONE;\n"); + } else if (!strcmp("stroke-width", att->svg_name)) { + fprintf(output, "\tp->stroke_width.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->stroke_width.value = FIX_ONE;\n"); + } else if (!strcmp("text-align", att->svg_name)) { + fprintf(output, "\tp->text_align = SVG_TEXTALIGN_START;\n"); + } else if (!strcmp("text-anchor", att->svg_name)) { + fprintf(output, "\tp->text_anchor = SVG_TEXTANCHOR_START;\n"); + } else if (!strcmp("vector-effect", att->svg_name)) { + fprintf(output, "\tp->vector_effect = SVG_VECTOREFFECT_NONE;\n"); + } else if (!strcmp("viewport-fill", att->svg_name)) { + fprintf(output, "\tp->viewport_fill.type = SVG_PAINT_NONE;\n"); + } else if (!strcmp("viewport-fill-opacity", att->svg_name)) { + fprintf(output, "\tp->viewport_fill_opacity.type = SVG_NUMBER_VALUE;\n"); + fprintf(output, "\tp->viewport_fill_opacity.value = FIX_ONE;\n"); + } else if (!strcmp("visibility", att->svg_name)) { + fprintf(output, "\tp->visibility = SVG_VISIBILITY_VISIBLE;\n"); + } + + /* Initialization of complex types */ + if ( !strcmp("SVG_Points", att->impl_type) || + !strcmp("SVG_Coordinates", att->impl_type) || + !strcmp("SMIL_KeyPoints", att->impl_type)) { + fprintf(output, "\tp->%s = gf_list_new();\n", att->implementation_name); + } else if (!strcmp("SVG_PathData", att->impl_type) && !strcmp(svg_elt->svg_name, "animateMotion")) { + fprintf(output, "#ifdef USE_GF_PATH\n"); + fprintf(output, "\tgf_path_reset(&p->path);\n"); + fprintf(output, "#else\n"); + fprintf(output, "\tp->path.commands = gf_list_new();\n"); + fprintf(output, "\tp->path.points = gf_list_new();\n"); + fprintf(output, "#endif\n"); + } else if (!strcmp("SVG_PathData", att->impl_type)) { + fprintf(output, "#ifdef USE_GF_PATH\n"); + fprintf(output, "\tgf_path_reset(&p->d);\n"); + fprintf(output, "#else\n"); + fprintf(output, "\tp->d.commands = gf_list_new();\n"); + fprintf(output, "\tp->d.points = gf_list_new();\n"); + fprintf(output, "#endif\n"); + } else if (!strcmp(att->svg_name, "lsr:enabled")) { + fprintf(output, "\tp->lsr_enabled = 1;\n"); + } + } + /*some default values*/ + if (!strcmp(svg_elt->svg_name, "svg")) { + fprintf(output, "\tp->width.type = SVG_NUMBER_PERCENTAGE;\n"); + fprintf(output, "\tp->width.value = INT2FIX(100);\n"); + fprintf(output, "\tp->height.type = SVG_NUMBER_PERCENTAGE;\n"); + fprintf(output, "\tp->height.value = INT2FIX(100);\n"); + } + else if (!strcmp(svg_elt->svg_name, "linearGradient")) { + fprintf(output, "\tp->x2.value = FIX_ONE;\n"); + fprintf(output, "\tgf_mx2d_init(p->gradientTransform.mat);\n"); + } + else if (!strcmp(svg_elt->svg_name, "radialGradient")) { + fprintf(output, "\tp->cx.value = FIX_ONE/2;\n"); + fprintf(output, "\tp->cy.value = FIX_ONE/2;\n"); + fprintf(output, "\tp->r.value = FIX_ONE/2;\n"); + fprintf(output, "\tgf_mx2d_init(p->gradientTransform.mat);\n"); + fprintf(output, "\tp->fx.value = FIX_ONE/2;\n"); + fprintf(output, "\tp->fy.value = FIX_ONE/2;\n"); + } + else if (!strcmp(svg_elt->svg_name, "video") || !strcmp(svg_elt->svg_name, "audio") || !strcmp(svg_elt->svg_name, "animation")) { + fprintf(output, "\tp->timing->dur.type = SMIL_DURATION_MEDIA;\n"); + } + fprintf(output, "\treturn p;\n}\n\n"); + + /* Destructor */ + fprintf(output, "static void gf_svg_sani_%s_del(GF_Node *node)\n{\n", svg_elt->implementation_name); + fprintf(output, "\tSVG_SANI_%sElement *p = (SVG_SANI_%sElement *)node;\n", svg_elt->implementation_name, svg_elt->implementation_name); + fprintf(output, "\tgf_svg_sani_reset_base_element((SVG_SANI_Element *)p);\n"); + + if (!strcmp(svg_elt->implementation_name, "conditional")) { + fprintf(output, "\tgf_svg_sa_reset_lsr_conditional(&p->updates);\n"); + } + else if (!strcmp(svg_elt->implementation_name, "a")) { + fprintf(output, "\tif (p->target) free(p->target);\n"); + } + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + if (!strcmp("SMIL_KeyPoints", att->impl_type)) { + fprintf(output, "\tgf_smil_delete_key_types(p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_Coordinates", att->impl_type)) { + fprintf(output, "\tgf_svg_delete_coordinates(p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_Points", att->impl_type)) { + fprintf(output, "\tgf_svg_delete_points(p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_PathData", att->impl_type)) { + if (!strcmp(svg_elt->svg_name, "animateMotion")) { + fprintf(output, "\tgf_svg_reset_path(p->path);\n"); + } else { + fprintf(output, "\tgf_svg_reset_path(p->d);\n"); + } + } else if (!strcmp("XMLRI", att->impl_type)) { + fprintf(output, "\tgf_svg_reset_iri(node->sgprivate->scenegraph, &p->%s);\n", att->implementation_name); + } else if (!strcmp("SVG_FontFamily", att->impl_type)) { + fprintf(output, "\tif (p->%s.value) free(p->%s.value);\n", att->implementation_name, att->implementation_name); + } else if (!strcmp("SVG_String", att->impl_type) || !strcmp("SVG_ContentType", att->impl_type)) { + fprintf(output, "\tfree(p->%s);\n", att->implementation_name); + } + } + if (svg_elt->has_transform) { + fprintf(output, "\tif (p->motionTransform) free(p->motionTransform);\n"); + } + + fprintf(output, "\tgf_sg_parent_reset((GF_Node *) p);\n"); + fprintf(output, "\tgf_node_free((GF_Node *)p);\n"); + fprintf(output, "}\n\n"); + + /* Attribute Access */ + fprintf(output, "static GF_Err gf_svg_sani_%s_get_attribute(GF_Node *node, GF_FieldInfo *info)\n{\n", svg_elt->implementation_name); + fprintf(output, "\tswitch (info->fieldIndex) {\n"); + svg_elt->nb_atts = 0; + svg_elt->nb_atts = generateCoreInfo(output, svg_elt, svg_elt->nb_atts); + + if (svg_elt->has_focus) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 4, "((SVG_SANI_Element *)node)->focus->", svg_elt->nb_atts); + if (svg_elt->has_xlink) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 5, "((SVG_SANI_Element *)node)->xlink->", svg_elt->nb_atts); + if (svg_elt->has_timing) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 6, "((SVG_SANI_Element *)node)->timing->", svg_elt->nb_atts); + if (svg_elt->has_sync) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 7, "((SVG_SANI_Element *)node)->sync->", svg_elt->nb_atts); + if (svg_elt->has_animation) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 8, "((SVG_SANI_Element *)node)->anim->", svg_elt->nb_atts); + if (svg_elt->has_conditional) + svg_elt->nb_atts = generateGenericInfo(output, svg_elt, 9, "((SVG_SANI_Element *)node)->conditional->", svg_elt->nb_atts); + if (svg_elt->has_transform) { + svg_elt->nb_atts = generateTransformInfo2(output, svg_elt, svg_elt->nb_atts); + svg_elt->nb_atts = generateMotionTransformInfo2(output, svg_elt, svg_elt->nb_atts); + } + if (svg_elt->has_xy) + svg_elt->nb_atts = generateXYInfo2(output, svg_elt, svg_elt->nb_atts); + + for (i = 0; i < gf_list_count(svg_elt->attributes); i++) { + SVGGenAttribute *att = gf_list_get(svg_elt->attributes, i); + generateAttributeInfo2(output, svg_elt->implementation_name, att, svg_elt->nb_atts++); + } + fprintf(output, "\t\tdefault: return GF_BAD_PARAM;\n\t}\n}\n\n"); + +} + +void generateSVGCode_V2(GF_List *svg_elements) +{ + FILE *output; + u32 i; + + output = BeginFile(0); + fprintf(output, "#include \n\n\n"); + fprintf(output, "/* Definition of SVG 2 Alternate element internal tags */\n"); + fprintf(output, "/* TAG names are made of \"TAG_SVG_SANI_\" + SVG element name (with - replaced by _) */\n"); + + /* write all tags */ + fprintf(output, "enum {\n"); + for (i=0; iimplementation_name); + } else { + fprintf(output, ",\n\tTAG_SVG_SANI_%s", elt->implementation_name); + } + } + + fprintf(output, ",\n\t/*undefined elements (when parsing) use this tag*/\n\tTAG_SVG_SANI_UndefinedElement\n};\n\n"); + + fprintf(output, "/******************************************\n"); + fprintf(output, "* SVG_SANI_ Elements structure definitions *\n"); + fprintf(output, "*******************************************/\n"); + for (i=0; i\n\n"); + + fprintf(output, "#ifndef GPAC_DISABLE_SVG\n\n"); + fprintf(output, "#include \n\n"); + for (i=0; iimplementation_name,elt->implementation_name); + } + fprintf(output, "\t\tdefault: return NULL;\n\t}\n}\n\n"); + + fprintf(output, "void gf_svg_sani_element_del(SVG_SANI_Element *elt)\n{\n"); + fprintf(output, "\tGF_Node *node = (GF_Node *)elt;\n"); + fprintf(output, "\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->implementation_name); + } + fprintf(output, "\t\tdefault: return;\n\t}\n}\n\n"); + + fprintf(output, "u32 gf_svg_sani_get_attribute_count(GF_Node *node)\n{\n"); + fprintf(output, "\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->nb_atts); + } + fprintf(output, "\t\tdefault: return 0;\n\t}\n}\n\n"); + + fprintf(output, "GF_Err gf_svg_sani_get_attribute_info(GF_Node *node, GF_FieldInfo *info)\n{\n"); + fprintf(output, "\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iimplementation_name, elt->implementation_name); + } + fprintf(output, "\t\tdefault: return GF_BAD_PARAM;\n\t}\n}\n\n"); + + fprintf(output, "u32 gf_svg_sani_type_by_class_name(const char *element_name)\n{\n\tif (!element_name) return TAG_UndefinedNode;\n"); + for (i=0; isvg_name, elt->implementation_name); + } + fprintf(output, "\treturn TAG_UndefinedNode;\n}\n\n"); + + fprintf(output, "const char *gf_svg_sani_get_element_name(u32 tag)\n{\n\tswitch(tag) {\n"); + for (i=0; iimplementation_name, elt->svg_name); + } + fprintf(output, "\tdefault: return \"UndefinedNode\";\n\t}\n}\n\n"); + + fprintf(output, "Bool gf_svg_sani_is_element_transformable(u32 tag)\n{\n\tswitch(tag) {\n"); + for (i=0; iimplementation_name); + if (elt->has_transform) fprintf(output, "return 1;\n"); + else fprintf(output, "return 0;\n"); + } + fprintf(output, "\tdefault: return 0;\n\t}\n}\n"); + + fprintf(output, "#endif /*GPAC_DISABLE_SVG*/\n\n"); + EndFile(output, 1); + + generate_laser_tables(svg_elements); +} diff --git a/applications/generators/SVG/v3.c b/applications/generators/SVG/v3.c new file mode 100644 index 0000000..292844d --- /dev/null +++ b/applications/generators/SVG/v3.c @@ -0,0 +1,295 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2012 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph Generator 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 "svggen.h" + + +void buildGlobalAttributeList(GF_List *svg_elements, GF_List *all_atts) +{ + u32 i, j, k; + Bool added = 0; + + for (i=0; igeneric_attributes); j++) { + SVGGenAttribute *att = gf_list_get(elt->generic_attributes, j); + added = 0; + if (!strcmp(att->impl_type, "SMIL_Fill")) { + strcpy(att->implementation_name, "smil_fill"); + } else if (!strcmp(att->impl_type, "SVG_TransformType")) { + strcpy(att->implementation_name, "transform_type"); + } + for (k = 0; k < gf_list_count(all_atts); k++) { + SVGGenAttribute *a = gf_list_get(all_atts, k); + if (!strcmp(a->implementation_name, att->implementation_name) + && !strcmp(a->impl_type, att->impl_type)) { + added = 1; + break; + } + } + if (!added) { + gf_list_add(all_atts, att); + } + } + for (j = 0; j < gf_list_count(elt->attributes); j++) { + SVGGenAttribute *att = gf_list_get(elt->attributes, j); + added = 0; + if (!strcmp(elt->svg_name, "text")) { + if (!strcmp(att->implementation_name, "x")) { + strcpy(att->implementation_name, "text_x"); + } else if (!strcmp(att->implementation_name, "y")) { + strcpy(att->implementation_name, "text_y"); + } else if (!strcmp(att->implementation_name, "rotate")) { + strcpy(att->implementation_name, "text_rotate"); + } + } else if (!strcmp(elt->svg_name, "listener") && !strcmp(att->implementation_name, "target")) { + strcpy(att->implementation_name, "listener_target"); + } else if (!strcmp(elt->svg_name, "a") && !strcmp(att->implementation_name, "target")) { + strcpy(att->impl_type, "SVG_String"); + } else if (!strcmp(elt->svg_name, "cursorManager")) { + if (!strcmp(att->implementation_name, "x")) { + strcpy(att->implementation_name, "cursorManager_x"); + } else if (!strcmp(att->implementation_name, "y")) { + strcpy(att->implementation_name, "cursorManager_y"); + } + } + for (k = 0; k < gf_list_count(all_atts); k++) { + SVGGenAttribute *a = gf_list_get(all_atts, k); + if (!strcmp(a->implementation_name, att->implementation_name) + && !strcmp(a->impl_type, att->impl_type)) { + added = 1; + break; + } + } + if (!added) { + gf_list_add(all_atts, att); + } + } + } + /*motionTransform is not parsed in rng*/ + { + SVGGenAttribute *att = NewSVGGenAttribute(); + strcpy(att->implementation_name, "motionTransform"); + strcpy(att->impl_type, "SVG_Motion"); + att->svg_name = "motionTransform"; + att->svg_type = "SVG_Motion"; + gf_list_add(all_atts, att); + } +} + +void generateSVGCode_V3(GF_List *svg_elements) +{ + FILE *output; + u32 i; + GF_List *all_atts = gf_list_new(); + + buildGlobalAttributeList(svg_elements, all_atts); + + /***************************************************/ + /***************************************************/ + /*************** Creating .h file ******************/ + /***************************************************/ + /***************************************************/ + output = BeginFile(0); + fprintf(output, "#include \n\n\n"); + + /* Generation of ELEMENT tags */ + fprintf(output, "/* Definition of SVG 3 Alternate element internal tags */\n"); + fprintf(output, "/* TAG names are made of \"TAG_SVG\" + SVG element name (with - replaced by _) */\n"); + fprintf(output, "enum {\n"); + for (i=0; iimplementation_name); + } else { + fprintf(output, ",\n\tTAG_SVG_%s", elt->implementation_name); + } + } + fprintf(output, ",\n\t/*undefined elements (when parsing) use this tag*/\n\tTAG_SVG_UndefinedElement\n};\n\n"); + + /* Generation of ATTRIBUTE tags */ + fprintf(output, "/* Definition of SVG 3 attribute internal tags - %d defined */\n", gf_list_count(all_atts)); + fprintf(output, "/* TAG names are made of \"TAG_SVG_ATT_\" + SVG attribute name (with - replaced by _) */\n"); + fprintf(output, "enum {\n"); + + for (i=0; iimplementation_name); + else fprintf(output, "\tTAG_SVG_ATT_%s = TAG_SVG_ATT_RANGE_FIRST,\n", att->implementation_name); + } + fprintf(output, "\t/*undefined attributes (when parsing) use this tag*/\n\tTAG_SVG_ATT_Unknown\n};\n\n"); + + /* Generation of the flatten structure pointing to all possible attributes in SVG */ + fprintf(output, "struct _all_atts {\n"); + for (i=0; iimpl_type, att->implementation_name); + } + fprintf(output, "};\n"); + + EndFile(output, 0); + + /***************************************************/ + /***************************************************/ + /*************** Creating .c file ******************/ + /***************************************************/ + /***************************************************/ + output = BeginFile(1); + fprintf(output, "#ifndef GPAC_DISABLE_SVG\n\n"); + fprintf(output, "#include \n\n"); + fprintf(output, "#include \n\n"); + + + /****************************************************************/ + /* u32 gf_svg_get_attribute_tag(u32 element_tag, const char *attribute_name) */ + /****************************************************************/ + fprintf(output, "u32 gf_svg_get_attribute_tag(u32 element_tag, const char *attribute_name)\n{\n\tif (!attribute_name) return TAG_SVG_ATT_Unknown;\n"); + for (i=0; iimpl_type, "SMIL_Fill")) continue; + if (!strcmp(att->impl_type, "SVG_ContentType")) continue; + if (!strcmp(att->implementation_name, "text_x")) continue; + if (!strcmp(att->implementation_name, "text_y")) continue; + if (!strcmp(att->implementation_name, "text_rotate")) continue; + if (!strcmp(att->implementation_name, "cursorManager_x")) continue; + if (!strcmp(att->implementation_name, "cursorManager_y")) continue; + + if (!strcmp(att->svg_name, "x") || !strcmp(att->svg_name, "y")) { + fprintf(output, "\tif (!stricmp(attribute_name, \"%s\")) {\n", att->svg_name); + fprintf(output, "\t\tif (element_tag == TAG_SVG_text) return TAG_SVG_ATT_text_%s;\n", att->implementation_name); + fprintf(output, "\t\telse if (element_tag == TAG_SVG_cursorManager) return TAG_SVG_ATT_cursorManager_%s;\n", att->implementation_name); + fprintf(output, "\t\telse return TAG_SVG_ATT_%s;\n", att->implementation_name); + fprintf(output, "\t}\n"); + } else if (!strcmp(att->svg_name, "rotate")) { + fprintf(output, "\tif (!stricmp(attribute_name, \"%s\")) {\n", att->svg_name); + fprintf(output, "\t\tif (element_tag == TAG_SVG_text) return TAG_SVG_ATT_text_%s;\n", att->implementation_name); + fprintf(output, "\t\telse return TAG_SVG_ATT_%s;\n", att->implementation_name); + fprintf(output, "\t}\n"); + } else if (!strcmp(att->svg_name, "type")) { + fprintf(output, "\tif (!stricmp(attribute_name, \"%s\")) {\n", att->svg_name); + fprintf(output, "\t\tif (element_tag == TAG_SVG_animateTransform) return TAG_SVG_ATT_transform_type;\n"); + fprintf(output, "\t\telse return TAG_SVG_ATT_%s;\n", att->implementation_name); + fprintf(output, "\t}\n"); + } else if (!strcmp(att->svg_name, "fill")) { + fprintf(output, "\tif (!stricmp(attribute_name, \"%s\")) {\n", att->svg_name); + fprintf(output, "\t\tif (element_tag == TAG_SVG_animate || element_tag == TAG_SVG_animateColor || element_tag == TAG_SVG_animateMotion || element_tag == TAG_SVG_animateTransform || element_tag == TAG_SVG_animation || element_tag == TAG_SVG_audio || element_tag == TAG_SVG_video || element_tag == TAG_SVG_set) return TAG_SVG_ATT_smil_fill;\n"); + fprintf(output, "\t\telse return TAG_SVG_ATT_%s;\n", att->implementation_name); + fprintf(output, "\t}\n"); + } else { + fprintf(output, "\tif (!stricmp(attribute_name, \"%s\")) return TAG_SVG_ATT_%s;\n", att->svg_name, att->implementation_name); + } + } + fprintf(output, "\treturn TAG_SVG_ATT_Unknown;\n}\n\n"); + + /****************************************************************/ + /* u32 gf_svg_get_attribute_type(u32 tag) */ + /****************************************************************/ + fprintf(output, "u32 gf_svg_get_attribute_type(u32 tag)\n{\n"); + fprintf(output, "\tswitch(tag) {\n"); + for (i=0; iimplementation_name, att->impl_type); + } + fprintf(output, "\t\tdefault: return SVG_Unknown_datatype;\n"); + fprintf(output, "\t}\n"); + fprintf(output, "\treturn TAG_SVG_ATT_Unknown;\n}\n\n"); + + /****************************************************************/ + /* const char* gf_svg_get_attribute_name(u32 tag) */ + /****************************************************************/ + fprintf(output, "const char*gf_svg_get_attribute_name(u32 tag)\n{\n"); + fprintf(output, "\tswitch(tag) {\n"); + for (i=0; iimplementation_name, att->svg_name); + } + fprintf(output, "\t\tdefault: return \"unknown\";\n"); + fprintf(output, "\t}\n"); + fprintf(output, "}\n\n"); + + /***************************************************/ + /* SVGAttribute *gf_svg_create_attribute(GF_Node *node, u32 tag) */ + /***************************************************/ + fprintf(output, "SVGAttribute *gf_svg_create_attribute(GF_Node *node, u32 tag)\n{\n\tswitch(tag) {\n"); + for (i=0; iimplementation_name, att->impl_type); + } + fprintf(output, "\tdefault: return NULL;\n\t}\n}\n\n"); + + /****************************************************************/ + /* void gf_svg_flatten_attributes(SVG_Element *e, SVGAllAttributes *all_atts) */ + /****************************************************************/ + fprintf(output, "void gf_svg_flatten_attributes(SVG_Element *e, SVGAllAttributes *all_atts)\n"); + + fprintf(output, "{\n"); + fprintf(output, "\tSVGAttribute *att;\n"); + fprintf(output, "\tmemset(all_atts, 0, sizeof(SVGAllAttributes));\n"); + fprintf(output, "\tif (e->sgprivate->tag <= GF_NODE_FIRST_DOM_NODE_TAG) return;\n"); + fprintf(output, "\tatt = e->attributes;\n"); + fprintf(output, "\twhile (att) {\n"); + fprintf(output, "\t\tswitch(att->tag) {\n"); + for (i=0; i%s = (%s *)att->data; break;\n", att->implementation_name, att->implementation_name, att->impl_type); + } + fprintf(output, "\t\t}\n"); + fprintf(output, "\tatt = att->next;\n"); + fprintf(output, "\t}\n"); + fprintf(output, "}\n"); + fprintf(output, "\n"); + + /****************************************************************/ + /* u32 gf_svg_get_element_tag(const char *element_name) */ + /****************************************************************/ + fprintf(output, "u32 gf_svg_get_element_tag(const char *element_name)\n{\n\tif (!element_name) return TAG_UndefinedNode;\n"); + for (i=0; isvg_name, elt->implementation_name); + } + fprintf(output, "\treturn TAG_UndefinedNode;\n}\n\n"); + + /***************************************************/ + /* const char *gf_svg_get_element_name(u32 tag) */ + /***************************************************/ + fprintf(output, "const char *gf_svg_get_element_name(u32 tag)\n{\n\tswitch(tag) {\n"); + for (i=0; iimplementation_name, elt->svg_name); + } + fprintf(output, "\tdefault: return \"TAG_SVG_UndefinedNode\";\n\t}\n}\n\n"); + + fprintf(output, "#endif /*GPAC_DISABLE_SVG*/\n\n"); + EndFile(output, 1); + + + generate_laser_tables_da(all_atts); + + gf_list_del(all_atts); + +} + diff --git a/applications/generators/X3D/Makefile b/applications/generators/X3D/Makefile new file mode 100644 index 0000000..c62f2ba --- /dev/null +++ b/applications/generators/X3D/Makefile @@ -0,0 +1,52 @@ +include ../../../config.mak + +vpath %.c $(SRC_PATH)/applications/generators/X3D + +CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= main.o + +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=X3DGen$(EXE) +else +EXT= +PROG=X3DGen +endif + +SRCS := $(OBJS:.o=.c) + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o $@ $(OBJS) $(EXTRALIBS) -L../../../bin/gcc -L../../../extra_lib/lib/gcc -lgpac $(LDFLAGS) + + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + + +clean: + rm -f $(OBJS) $(PROG) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/generators/X3D/X3DGen.dsp b/applications/generators/X3D/X3DGen.dsp new file mode 100644 index 0000000..7afd243 --- /dev/null +++ b/applications/generators/X3D/X3DGen.dsp @@ -0,0 +1,98 @@ +# Microsoft Developer Studio Project File - Name="X3DGen" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=X3DGen - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "X3DGen.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "X3DGen.mak" CFG="X3DGen - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "X3DGen - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "X3DGen - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "X3DGen - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../../../include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "X3DGen - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Obj/W32Deb" +# PROP Intermediate_Dir "Obj/W32Deb" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../../../include" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "X3DGen - Win32 Release" +# Name "X3DGen - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\src\utils\list.c +# End Source File +# Begin Source File + +SOURCE=.\main.c +# End Source File +# End Group +# End Target +# End Project diff --git a/applications/generators/X3D/X3DGen.dsw b/applications/generators/X3D/X3DGen.dsw new file mode 100644 index 0000000..2aca071 --- /dev/null +++ b/applications/generators/X3D/X3DGen.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "X3DGen"=.\X3DGen.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/applications/generators/X3D/main.c b/applications/generators/X3D/main.c new file mode 100644 index 0000000..5abda6c --- /dev/null +++ b/applications/generators/X3D/main.c @@ -0,0 +1,1275 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / X3D Scene Graph Generator 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 +#include +#include + +#include +#include + +#define COPYRIGHT "/*\n * GPAC - Multimedia Framework C SDK\n *\n * Authors: Jean Le Feuvre\n * Copyright (c) Telecom ParisTech 2000-2012\n * All rights reserved\n *\n * This file is part of GPAC / X3D Scene Graph sub-project\n *\n * GPAC is free software; you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation; either version 2, or (at your option)\n * any later version.\n *\n * GPAC is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details. \n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; see the file COPYING. If not, write to\n * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.\n *\n */\n" + +static char *CurrentLine; + +void PrintUsage() +{ + printf("X3DGen [skip_file]\n" + "\nGPAC X3D Scene Graph generator\n" + "\n" + "\nskip_file: txt file with list of nodes to leave unimplemented" + "Generated Files are directly updated in the GPAC distribution - do NOT try to change this\n\n" + "Written by Jean Le Feuvre - (c) 2000-2005\n" + ); +} + +//a node field +typedef struct +{ + char type[50]; + //SFxxx, MFxxx + char familly[50]; + //name + char name[1000]; + //default value + char def[100]; + //bounds + u32 hasBounds; + char b_min[20]; + char b_max[20]; +} X3DField; + +//NDTs + +//a BIFS node +typedef struct +{ + char name[1000]; + //NDT info. NDT are created in alphabetical order + GF_List *NDT; + GF_List *Fields; + u8 hasDefault; + char Child_NDT_Name[1000]; + u8 skip_impl; + +} X3DNode; + + +void skip_sep(char *sep) +{ + //skip separaors + while (*CurrentLine && strchr(sep, *CurrentLine)) { + CurrentLine = CurrentLine + 1; + //end of line - no token + if (*CurrentLine == '\n') return; + } +} + +//note that we increment the line no matter what +u32 GetNextToken(char *token, char *sep) +{ + u32 i , j = 0; + + strcpy(token, ""); + + //skip separaors + while (*CurrentLine && strchr(sep, *CurrentLine)) { + CurrentLine = CurrentLine + 1; + j ++; + //end of line - no token + if (*CurrentLine == '\n') return 0; + } + + //copy token until next blank + i=0; + while (1) { + //bad line + if (! *CurrentLine) { + token[i] = 0; + return 0; + } + //end of token or end of line + if (strchr(sep, *CurrentLine) || (*CurrentLine == '\n') ) { + token[i] = 0; + CurrentLine = CurrentLine + 1; + return i; + } else { + token[i] = *CurrentLine; + } + CurrentLine = CurrentLine + 1; + i++; + j++; + } + return 1; +} + +X3DField *BlankField() +{ + X3DField *n = gf_malloc(sizeof(X3DField)); + memset(n, 0, sizeof(X3DField)); + return n; +} + + +X3DNode *BlankNode() +{ + X3DNode *n = gf_malloc(sizeof(X3DNode)); + memset(n, 0, sizeof(X3DNode)); + n->NDT = gf_list_new(); + n->Fields = gf_list_new(); + return n; +} + +u8 IsNDT(GF_List *NDTs, char *famName) +{ + u32 i; + for (i=0; i\n\n"); + fprintf(f, "#ifndef GPAC_DISABLE_X3D\n\n"); + + //write all tags + fprintf(f, "\n\nenum {\n"); + + for (i=0; iname); + else + fprintf(f, "\tTAG_X3D_%s = GF_NODE_RANGE_FIRST_X3D", n->name); + } + fprintf(f, ",\n\tTAG_LastImplementedX3D\n};\n\n"); + + for (i=0; iskip_impl) continue; + fprintf(f, "typedef struct _tagX3D%s\n{\n", n->name); + fprintf(f, "\tBASE_NODE\n"); + + /*write children field*/ + for (j=0; jFields); j++) { + bf = gf_list_get(n->Fields, j); + if (!stricmp(bf->name, "addChildren") || !strcmp(bf->name, "removeChildren")) continue; + if (strcmp(bf->type, "eventOut") && !strcmp(bf->name, "children")) { + fprintf(f, "\tVRML_CHILDREN\n"); + break; + } + } + for (j=0; jFields); j++) { + bf = gf_list_get(n->Fields, j); + + if (!strcmp(bf->name, "addChildren") || !strcmp(bf->name, "removeChildren")) continue; + if (strcmp(bf->type, "eventOut") && !strcmp(bf->name, "children")) continue; + + if (strstr(bf->familly, "Node")) { + //this is a POINTER to a node + if (strstr(bf->familly, "SF")) { + fprintf(f, "\tGF_Node *%s;\t/*%s*/\n", bf->name, bf->type); + } else { + //this is a POINTER to a chain + fprintf(f, "\tGF_ChildNodeItem *%s;\t/*%s*/\n", bf->name, bf->type); + } + } else { + fprintf(f, "\t%s %s;\t/*%s*/\n", bf->familly, bf->name, bf->type); + } + if (!strcmp(bf->type, "eventIn")) + fprintf(f, "\tvoid (*on_%s)(GF_Node *pThis, struct _route *route);\t/*eventInHandler*/\n", bf->name); + } + fprintf(f, "} X_%s;\n\n\n", n->name); + } + + fprintf(f, "#endif /*GPAC_DISABLE_X3D*/\n\n"); + EndFile(f, 0); + +} + +void WriteNodeFields(FILE *f, X3DNode *n) +{ + u32 i; + X3DField *bf; + + fprintf(f, "\nstatic u32 %s_get_field_count(GF_Node *node, u8 dummy)\n{\n\treturn %d;\n}\n\n", n->name, gf_list_count(n->Fields)); + fprintf(f, "static GF_Err %s_get_field(GF_Node *node, GF_FieldInfo *info)\n{\n\tswitch (info->fieldIndex) {\n", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + + fprintf(f, "\tcase %d:\n", i); + + fprintf(f, "\t\tinfo->name = \"%s\";\n", bf->name); + + //skip all eventIn + if (!strcmp(bf->type, "eventIn")) { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_IN;\n"); + fprintf(f, "\t\tinfo->on_event_in = ((X_%s *)node)->on_%s;\n", n->name, bf->name); + } + else if (!strcmp(bf->type, "eventOut")) { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_OUT;\n"); + } + else if (!strcmp(bf->type, "field")) { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_FIELD;\n"); + } + else { + fprintf(f, "\t\tinfo->eventType = GF_SG_EVENT_EXPOSED_FIELD;\n"); + } + + if (strstr(bf->familly, "Node")) { + if (strstr(bf->familly, "MF")) { + fprintf(f, "\t\tinfo->fieldType = GF_SG_VRML_MFNODE;\n"); + } else { + fprintf(f, "\t\tinfo->fieldType = GF_SG_VRML_SFNODE;\n"); + } + //always remove the SF or MF, as all NDTs are SFXXX + fprintf(f, "\t\tinfo->NDTtype = NDT_SF%s;\n", bf->familly+2); + fprintf(f, "\t\tinfo->far_ptr = & ((X_%s *)node)->%s;\n", n->name, bf->name); + } else { + char szName[20]; + strcpy(szName, bf->familly); + strupr(szName); + //no ext type + fprintf(f, "\t\tinfo->fieldType = GF_SG_VRML_%s;\n", szName); + fprintf(f, "\t\tinfo->far_ptr = & ((X_%s *) node)->%s;\n", n->name, bf->name); + } + fprintf(f, "\t\treturn GF_OK;\n"); + } + fprintf(f, "\tdefault:\n\t\treturn GF_BAD_PARAM;\n\t}\n}\n\n"); + + fprintf(f, "\nstatic s32 %s_get_field_index_by_name(char *name)\n{\n", n->name); + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + fprintf(f, "\tif (!strcmp(\"%s\", name)) return %d;\n", bf->name, i); + } + fprintf(f, "\treturn -1;\n\t}\n"); +} + +void WriteNodeCode(GF_List *BNodes, FILE *vrml_code) +{ + char token[20], tok[20]; + char *store; + u32 i, j, k, go; + X3DField *bf; + X3DNode *n; + + fprintf(vrml_code, "\n#include \n"); + fprintf(vrml_code, "\n#include \n"); + fprintf(vrml_code, "\n/*for NDT tag definitions*/\n#include \n"); + fprintf(vrml_code, "#ifndef GPAC_DISABLE_X3D\n\n"); + + for (k=0; kskip_impl) continue; + fprintf(vrml_code, "\n/*\n\t%s Node deletion\n*/\n\n", n->name); + fprintf(vrml_code, "static void %s_Del(GF_Node *node)\n{\n\tX_%s *p = (X_%s *) node;\n", n->name, n->name, n->name); + + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + //nothing on child events + if (!strcmp(bf->name, "addChildren")) continue; + if (!strcmp(bf->name, "removeChildren")) continue; + + //delete all children node + if (strcmp(bf->type, "eventOut") && !strcmp(bf->name, "children")) { + is_parent = 1; + continue; + } + + //delete ALL fields that must be deleted: this includes eventIn and out since + //all fields are defined in the node + if (!strcmp(bf->familly, "MFInt") + || !strcmp(bf->familly, "MFFloat") + || !strcmp(bf->familly, "MFDouble") + || !strcmp(bf->familly, "MFBool") + || !strcmp(bf->familly, "MFInt32") + || !strcmp(bf->familly, "MFColor") + || !strcmp(bf->familly, "MFRotation") + || !strcmp(bf->familly, "MFString") + || !strcmp(bf->familly, "MFTime") + || !strcmp(bf->familly, "MFVec2f") + || !strcmp(bf->familly, "MFVec3f") + || !strcmp(bf->familly, "MFVec4f") + || !strcmp(bf->familly, "MFVec2d") + || !strcmp(bf->familly, "MFVec3d") + || !strcmp(bf->familly, "MFURL") + || !strcmp(bf->familly, "MFScript") + || !strcmp(bf->familly, "SFString") + || !strcmp(bf->familly, "SFURL") + || !strcmp(bf->familly, "SFImage") + || !strcmp(bf->familly, "MFColorRGBA") + + ) { + char szName[500]; + strcpy(szName, bf->familly); + strlwr(szName); + fprintf(vrml_code, "\tgf_sg_%s_del(p->%s);\n", szName, bf->name); + } else if (strstr(bf->familly, "Node")) { + //this is a POINTER to a node + if (strstr(bf->familly, "SF")) { + fprintf(vrml_code, "\tgf_node_unregister((GF_Node *) p->%s, node);\t\n", bf->name); + } else { + //this is a POINTER to a chain + fprintf(vrml_code, "\tgf_node_unregister_children(node, p->%s);\t\n", bf->name); + } + } + } + if (is_parent) + fprintf(vrml_code, "\tgf_sg_vrml_parent_destroy(node);\t\n"); + /*avoids gcc warnings in case no field to delete*/ + fprintf(vrml_code, "\tgf_node_free((GF_Node *)p);\n}\n\n"); + + //node fields + WriteNodeFields(vrml_code, n); + + // + // Constructor + // + + fprintf(vrml_code, "\n\nstatic GF_Node *%s_Create()\n{\n\tX_%s *p;\n\tGF_SAFEALLOC(p, X_%s);\n", n->name, n->name, n->name); + fprintf(vrml_code, "\tif(!p) return NULL;\n"); + fprintf(vrml_code, "\tgf_node_setup((GF_Node *)p, TAG_X3D_%s);\n", n->name); + + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + //setup all children node + if (strcmp(bf->type, "eventOut") && !strcmp(bf->name, "children")) { + fprintf(vrml_code, "\tgf_sg_vrml_parent_setup((GF_Node *) p);\n"); + break; + } + else if ( strstr(bf->familly, "Node") && strncmp(bf->type, "event", 5) ) { + //this is a POINTER to a node + if (strstr(bf->familly, "MF")) { + //this is a POINTER to a chain + //fprintf(vrml_code, "\tp->%s = gf_list_new();\t\n", bf->name); + } + } + /*special case for SFCommandBuffer: we also create a command list*/ + if (!stricmp(bf->familly, "SFCommandBuffer")) { + fprintf(vrml_code, "\tp->%s.commandList = gf_list_new();\t\n", bf->name); + } + } + + fprintf(vrml_code, "\n\t/*default field values*/\n"); + + for (i=0; iFields); i++) { + bf = gf_list_get(n->Fields, i); + + //nothing on eventIn or Out + if (!strcmp(bf->type, "eventIn")) continue; + if (!strcmp(bf->type, "eventOut")) continue; + + if (!strcmp(bf->def, "")) continue; + + //no default on nodes + if (strstr(bf->familly, "Node")) continue; + //extract default falue + + // + // SF Fields + // + + //SFBool + if (!strcmp(bf->familly, "SFBool")) { + if (!strcmp(bf->def, "1") || !strcmp(bf->def, "TRUE")) + fprintf(vrml_code, "\tp->%s = 1;\n", bf->name); + } + //SFFloat + else if (!strcmp(bf->familly, "SFFloat")) { + fprintf(vrml_code, "\tp->%s = FLT2FIX(%s);\n", bf->name, bf->def); + } + //SFDouble + else if (!strcmp(bf->familly, "SFDouble")) { + fprintf(vrml_code, "\tp->%s = (SFDouble) %s;\n", bf->name, bf->def); + } + //SFTime + else if (!strcmp(bf->familly, "SFTime")) { + fprintf(vrml_code, "\tp->%s = %s;\n", bf->name, bf->def); + } + //SFInt32 + else if (!strcmp(bf->familly, "SFInt32")) { + fprintf(vrml_code, "\tp->%s = %s;\n", bf->name, bf->def); + } + //SFColor + else if (!strcmp(bf->familly, "SFColor")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + + fprintf(vrml_code, "\tp->%s.red = FLT2FIX(%s);\n", bf->name, token); + GetNextToken(token, " "); + fprintf(vrml_code, "\tp->%s.green = FLT2FIX(%s);\n", bf->name, token); + GetNextToken(token, " "); + fprintf(vrml_code, "\tp->%s.blue = FLT2FIX(%s);\n", bf->name, token); + } + //SFVec2f + else if (!strcmp(bf->familly, "SFVec2f")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.x = FLT2FIX(%s);\n", bf->name, token); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.y = FLT2FIX(%s);\n", bf->name, token); + } + //SFVec2d + else if (!strcmp(bf->familly, "SFVec2d")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.x = (SFDouble) %s;\n", bf->name, token); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.y = (SFDouble) %s;\n", bf->name, token); + } + //SFVec3f + else if (!strcmp(bf->familly, "SFVec3f")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.x = FLT2FIX(%s);\n", bf->name, token); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.y = FLT2FIX(%s);\n", bf->name, token); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.z = FLT2FIX(%s);\n", bf->name, token); + } + //SFVec3d + else if (!strcmp(bf->familly, "SFVec3d")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.x = (SFDouble) %s;\n", bf->name, token); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.y = (SFDouble) %s;\n", bf->name, token); + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.z = (SFDouble) %s;\n", bf->name, token); + } + //SFVec4f & SFRotation + else if (!strcmp(bf->familly, "SFVec4f") || !strcmp(bf->familly, "SFRotation")) { + CurrentLine = bf->def; + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.x = FLT2FIX(%s);\n", bf->name, token); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.y = FLT2FIX(%s);\n", bf->name, token); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.z = FLT2FIX(%s);\n", bf->name, token); + + GetNextToken(token, " "); + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.q = FLT2FIX(%s);\n", bf->name, token); + } + //SFString + else if (!strcmp(bf->familly, "SFString")) { + fprintf(vrml_code, "\tp->%s.buffer = (char*) gf_malloc(sizeof(char) * %u);\n", bf->name, (u32) strlen(bf->def)+1); + fprintf(vrml_code, "\tstrcpy(p->%s.buffer, \"%s\");\n", bf->name, bf->def); + } + + // + // MF Fields + // + //MFFloat + else if (!strcmp(bf->familly, "MFFloat")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, " ,")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFFloat *)gf_malloc(sizeof(SFFloat)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, " ,")) go = 0; + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.vals[%d] = FLT2FIX(%s);\n", bf->name, j, token); + j+=1; + } + } + //MFDouble + else if (!strcmp(bf->familly, "MFDouble")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, " ,")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFFloat*)gf_malloc(sizeof(SFFloat)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, " ,")) go = 0; + TranslateToken(token); + fprintf(vrml_code, "\tp->%s.vals[%d] = (SFDouble) %s;\n", bf->name, j, token); + j+=1; + } + } + //MFVec2f + else if (!strcmp(bf->familly, "MFVec2f")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFVec2f*) gf_malloc(sizeof(SFVec2f)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].x = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].y = FLT2FIX(%s);\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFVec2d + else if (!strcmp(bf->familly, "MFVec2d")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFVec2f*)gf_malloc(sizeof(SFVec2f)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].x = (SFDouble) %s;\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].y = (SFDouble) %s;\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFVec3f + else if (!strcmp(bf->familly, "MFVec3f")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFVec3f*)gf_malloc(sizeof(SFVec3f)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].x = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].y = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].z = FLT2FIX(%s);\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFVec3d + else if (!strcmp(bf->familly, "MFVec3d")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFVec2f*)gf_malloc(sizeof(SFVec3f)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].x = (SFDouble) %s;\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].y = (SFDouble) %s;\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].z = (SFDouble) %s;\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFVec4f & MFRotation + else if (!strcmp(bf->familly, "MFVec4f") || !strcmp(bf->familly, "MFRotation")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (GF_Vec4*)gf_malloc(sizeof(GF_Vec4)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].x = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].y = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].z = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d].q = FLT2FIX(%s);\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFInt32 + else if (!strcmp(bf->familly, "MFInt32")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFInt32*)gf_malloc(sizeof(SFInt32)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + fprintf(vrml_code, "\tp->%s.vals[%d] = %s;\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFColor + else if (!strcmp(bf->familly, "MFColor")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFColor*)gf_malloc(sizeof(SFColor)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " "); + fprintf(vrml_code, "\tp->%s.vals[%d].red = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + fprintf(vrml_code, "\tp->%s.vals[%d].green = FLT2FIX(%s);\n", bf->name, j, tok); + GetNextToken(tok, " "); + fprintf(vrml_code, "\tp->%s.vals[%d].blue = FLT2FIX(%s);\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFString + else if (!strcmp(bf->familly, "MFString")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (char**)gf_malloc(sizeof(SFString)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " \""); + fprintf(vrml_code, "\tp->%s.vals[%d] = (char*)gf_malloc(sizeof(char) * %u);\n", bf->name, j, (u32) strlen(tok)+1); + fprintf(vrml_code, "\tstrcpy(p->%s.vals[%d], \"%s\");\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + //MFTime + else if (!strcmp(bf->familly, "MFTime")) { + j = 0; + CurrentLine = bf->def; + while (GetNextToken(token, ",")) j++; + j+=1; + fprintf(vrml_code, "\tp->%s.vals = (SFTime*)gf_malloc(sizeof(SFTime)*%d);\n", bf->name, j); + fprintf(vrml_code, "\tp->%s.count = %d;\n", bf->name, j); + j = 0; + go = 1; + CurrentLine = bf->def; + while (go) { + if (!GetNextToken(token, ",")) go = 0; + store = CurrentLine; + CurrentLine = token; + GetNextToken(tok, " \""); + TranslateToken(tok); + fprintf(vrml_code, "\tp->%s.vals[%d] = %s;\n", bf->name, j, tok); + j+=1; + CurrentLine = store; + } + } + + //other nodes + else if (!strcmp(bf->familly, "SFImage")) { + //we currently only have SFImage, with NO texture so do nothing + } + //unknown init (for debug) + else { + fprintf(vrml_code, "UNKNOWN FIELD (%s);\n", bf->familly); + + } + } + fprintf(vrml_code, "\treturn (GF_Node *)p;\n}\n\n"); + + } + + fprintf(vrml_code, "\n\n\n"); + + //creator function + fprintf(vrml_code, "GF_Node *gf_sg_x3d_node_new(u32 NodeTag)\n{\n\tswitch (NodeTag) {\n"); + for (i=0; iskip_impl) fprintf(vrml_code, "\tcase TAG_X3D_%s:\n\t\treturn %s_Create();\n", n->name, n->name); + } + fprintf(vrml_code, "\tdefault:\n\t\treturn NULL;\n\t}\n}\n\n"); + + fprintf(vrml_code, "const char *gf_sg_x3d_node_get_class_name(u32 NodeTag)\n{\n\tswitch (NodeTag) {\n"); + for (i=0; iskip_impl) fprintf(vrml_code, "\tcase TAG_X3D_%s:\n\t\treturn \"%s\";\n", n->name, n->name); + } + fprintf(vrml_code, "\tdefault:\n\t\treturn \"Unknown Node\";\n\t}\n}\n\n"); + + fprintf(vrml_code, "void gf_sg_x3d_node_del(GF_Node *node)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) fprintf(vrml_code, "\tcase TAG_X3D_%s:\n\t\t%s_Del(node); return;\n", n->name, n->name); + } + fprintf(vrml_code, "\tdefault:\n\t\treturn;\n\t}\n}\n\n"); + + fprintf(vrml_code, "u32 gf_sg_x3d_node_get_field_count(GF_Node *node)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) fprintf(vrml_code, "\tcase TAG_X3D_%s:return %s_get_field_count(node, 0);\n", n->name, n->name); + } + fprintf(vrml_code, "\tdefault:\n\t\treturn 0;\n\t}\n}\n\n"); + + fprintf(vrml_code, "GF_Err gf_sg_x3d_node_get_field(GF_Node *node, GF_FieldInfo *field)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) fprintf(vrml_code, "\tcase TAG_X3D_%s: return %s_get_field(node, field);\n", n->name, n->name); + } + fprintf(vrml_code, "\tdefault:\n\t\treturn GF_BAD_PARAM;\n\t}\n}\n\n"); + + fprintf(vrml_code, "\nu32 gf_node_x3d_type_by_class_name(const char *node_name)\n{\n\tif(!node_name) return 0;\n"); + for (i=0; iskip_impl) fprintf(vrml_code, "\tif (!strcmp(node_name, \"%s\")) return TAG_X3D_%s;\n", n->name, n->name); + } + fprintf(vrml_code, "\treturn 0;\n}\n\n"); + + fprintf(vrml_code, "s32 gf_sg_x3d_node_get_field_index_by_name(GF_Node *node, char *name)\n{\n\tswitch (node->sgprivate->tag) {\n"); + for (i=0; iskip_impl) { + fprintf(vrml_code, "\tcase TAG_X3D_%s: return %s_get_field_index_by_name(name);\n", n->name, n->name); + } + } + fprintf(vrml_code, "\tdefault:\n\t\treturn -1;\n\t}\n}\n\n"); + +} + + +static u32 IsNodeInTable(X3DNode *node, char *NDTName) +{ + u32 i; + + for (i=0; iNDT); i++) { + char *ndt = gf_list_get(node->NDT, i); + if (!strcmp(ndt, NDTName)) return 1; + } + return 0; +} + +static u32 GetNDTCount(char *NDTName, GF_List *XNodes) +{ + u32 i, nodeCount = 0; + for (i=0; iname); + first = 0; + } else { + fprintf(f, ", TAG_X3D_%s", n->name); + } + } + fprintf(f, "\n};\n\n"); + } + + //NodeTag complete translation + fprintf(f, "\n\n\nBool gf_x3d_get_node_type(u32 NDT_Tag, u32 NodeTag)\n{\n\tconst u32 *types;\n\tu32 count, i;\n\tif (!NodeTag) return 0;\n\ttypes = NULL; count = 0;\n"); + + fprintf(f, "\tswitch (NDT_Tag) {\n"); + for (i=0; iname, " \t["); + + //extract the NDTs + GetNextToken(token, "\t[ %#="); + if (strcmp(token, "NDT")) { + printf("Corrupted template file\n"); + return; + } + while (1) { + GetNextToken(token, "=, \t"); + //done with NDTs + if (!token[0]) break; + + //update the NDT list + CheckInTable(token, NDTs); + p = gf_malloc(strlen(token)+1); + strcpy(p, token); + gf_list_add(n->NDT, p); + } + } + //this is NOT a field + else if (token[0] == ']' || token[0] == '{' || token[0] == '}' ) { + break; + } + //parse a field + else { + if (!n) { + printf("Corrupted template file\n"); + return; + } + f = BlankField(); + gf_list_add(n->Fields, f); + + //get the field type + strcpy(f->type, token); + GetNextToken(f->familly, " \t"); + GetNextToken(f->name, " \t"); + //fix for our own code :( + if (!strcmp(f->name, "tag")) strcpy(f->name, "_tag"); + + //has default + skip_sep(" \t"); + if (GetNextToken(token, "#\t")) { + j=0; + while (token[j] == ' ') j+=1; + if (token[j] == '[') j+=1; + if (token[j] == '"') j+=1; + + if (token[j] != '"' && token[j] != ']') { + strcpy(f->def, token+j); + j=1; + while (j) { + switch (f->def[strlen(f->def)-1]) { + case ' ': + case '"': + case ']': + f->def[strlen(f->def)-1] = 0; + break; + default: + j=0; + break; + } + } + } else { + strcpy(f->def, ""); + } + if (!strcmp(f->familly, "SFFloat")) { + if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { + strcpy(f->def, "GF_MAX_FLOAT"); + } else if (!strcmp(f->def, "-I")) { + strcpy(f->def, "GF_MIN_FLOAT"); + } + } else if (!strcmp(f->familly, "SFTime")) { + if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { + strcpy(f->def, "GF_MAX_FLOAT"); + } else if (!strcmp(f->def, "-I")) { + strcpy(f->def, "GF_MIN_FLOAT"); + } + } else if (!strcmp(f->familly, "SFInt32")) { + if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { + strcpy(f->def, "2 << 31"); + } else if (!strcmp(f->def, "-I")) { + strcpy(f->def, "- (2 << 31)"); + } + } + } + //has other + while (GetNextToken(token, " \t#%=")) { + switch (token[0]) { + //bounds + case 'b': + case 'q': + case 'a': + printf("Corrupted X3D template file (quantization/animation not allowed)\n"); + gf_list_del_item(n->Fields, f); + gf_free(f); + return; + default: + break; + } + } + /*we ignore these*/ + if (!stricmp(f->name, "bboxCenter") || !stricmp(f->name, "bboxSize")) { + gf_list_del_item(n->Fields, f); + gf_free(f); + } + } + } + } + + + for (k=0; kFields); i++) { + f = gf_list_get(n->Fields, i); + //nothing on events + if (!strcmp(f->type, "eventIn")) continue; + if (!strcmp(f->type, "eventOut")) continue; + if (!strcmp(f->def, "")) continue; + if (strstr(f->familly, "Node")) continue; + n->hasDefault = 1; + } + } +} + + +void WriteNodeDump(FILE *f, X3DNode *n) +{ + u32 i; + + fprintf(f, "static const char *%s_FieldName[] = {\n", n->name); + for (i=0; iFields); i++) { + X3DField *bf = gf_list_get(n->Fields, i); + if (!i) { + fprintf(f, " \"%s\"", bf->name); + } else { + fprintf(f, ", \"%s\"", bf->name); + } + } + fprintf(f, "\n};\n\n"); +} + + +void parse_profile(GF_List *nodes, FILE *prof) +{ + char sLine[2000]; + X3DNode *n; + Bool found; + u32 i; + + while (!feof(prof)) { + fgets(sLine, 2000, prof); + //skip comment and empty lines + if (sLine[0] == '#') continue; + if (sLine[0] == '\n') continue; + if (strstr(sLine, "Proximity")) + found = 0; + found = 1; + while (found) { + switch (sLine[strlen(sLine)-1]) { + case '\n': + case '\r': + case ' ': + sLine[strlen(sLine)-1] = 0; + break; + default: + found = 0; + break; + } + } + + if (0) { + printf("Warning: cannot disable node %s (required in all BIFS profiles)\n", sLine); + } else { + found = 0; + for (i=0; iname, sLine)) { + n->skip_impl = 1; + found = 1; + break; + } + } + if (!found) printf("cannot disable %s: node not found\n", sLine); + } + } +} + +int main (int argc, char **argv) +{ + FILE *nodes; + GF_List *XNodes, *NDTs; + X3DField *bf; + u32 nb_nodes, nb_imp; + + nodes = gf_fopen("templates_X3D.txt", "rt"); + if (!nodes) { + fprintf(stdout, "cannot open \"templates_X3D.txt\" - aborting\n"); + return 0; + } + + XNodes = gf_list_new(); + NDTs = gf_list_new(); + //all nodes are in the same list but we keep version info + ParseTemplateFile(nodes, XNodes, NDTs); + gf_fclose(nodes); + + if (argc>1) { + FILE *pf; + pf = gf_fopen(argv[1], "rt"); + if (!pf) fprintf(stdout, "Cannot open profile file %s\n", argv[1]); + else { + parse_profile(XNodes, pf); + gf_fclose(pf); + } + } + + //write the nodes def + WriteNodesFile(XNodes, NDTs); + + nodes = BeginFile(1); + + //write all nodes init stuff + WriteNodeCode(XNodes, nodes); + + WriteNDT(nodes, XNodes, NDTs); + fprintf(nodes, "#endif /*GPAC_DISABLE_X3D*/\n\n"); + + EndFile(nodes, 1); + + //free NDTs + while (gf_list_count(NDTs)) { + char *tmp = gf_list_get(NDTs, 0); + gf_free(tmp); + gf_list_rem(NDTs, 0); + } + gf_list_del(NDTs); + + nb_nodes = gf_list_count(XNodes); + nb_imp = 0; + //free nodes + while (gf_list_count(XNodes)) { + X3DNode *n = gf_list_get(XNodes, 0); + if (!n->skip_impl) nb_imp++; + gf_list_rem(XNodes, 0); + while (gf_list_count(n->NDT)) { + char *tmp = gf_list_get(n->NDT, 0); + gf_free(tmp); + gf_list_rem(n->NDT, 0); + } + gf_list_del(n->NDT); + while (gf_list_count(n->Fields)) { + bf = gf_list_get(n->Fields, 0); + gf_free(bf); + gf_list_rem(n->Fields, 0); + } + gf_list_del(n->Fields); + gf_free(n); + } + gf_list_del(XNodes); + + fprintf(stdout, "Generation done: %d nodes implemented (%d nodes total)\n", nb_imp, nb_nodes); + return 0; +} + diff --git a/applications/generators/X3D/skip.txt b/applications/generators/X3D/skip.txt new file mode 100644 index 0000000..62cae25 --- /dev/null +++ b/applications/generators/X3D/skip.txt @@ -0,0 +1,130 @@ +#X3D defined nodes + +#Anchor +#Appearance +#Arc2D +#ArcClose2D +#AudioClip +#Background +#Billboard +#BooleanFilter +#BooleanSequencer +#BooleanToggle +#BooleanTrigger +#Box +#Circle2D +#Collision +#Color +#ColorInterpolator +#ColorRGBA +#Cone +#Contour2D +#ContourPolyline2D +#Coordinate +#CoordinateDouble +#Coordinate2D +#CoordinateInterpolator +#CoordinateInterpolator2D +#Cylinder +#CylinderSensor +#DirectionalLight +#Disk2D +#ElevationGrid +EspduTransform +#Extrusion +#FillProperties +#Fog +#FontStyle +GeoCoordinate +GeoElevationGrid +GeoLocation +GeoLOD +GeoMetadata +GeoOrigin +GeoPositionInterpolator +GeoTouchSensor +GeoViewpoint +#Group +HAnimDisplacer +HAnimHumanoid +HAnimJoint +HAnimSegment +HAnimSite +#ImageTexture +#IndexedFaceSet +#IndexedLineSet +#IndexedTriangleFanSet +#IndexedTriangleSet +#IndexedTriangleStripSet +#Inline +#IntegerSequencer +#IntegerTrigger +#KeySensor +#LineProperties +#LineSet +LoadSensor +#LOD +#Material +#MetadataDouble +#MetadataFloat +#MetadataInteger +#MetadataSet +#MetadataString +#MovieTexture +#MultiTexture +#MultiTextureCoordinate +#MultiTextureTransform +#NavigationInfo +#Normal +#NormalInterpolator +NurbsCurve +NurbsCurve2D +NurbsOrientationInterpolator +NurbsPatchSurface +NurbsPositionInterpolator +NurbsSet +NurbsSurfaceInterpolator +NurbsSweptSurface +NurbsSwungSurface +NurbsTextureCoordinate +NurbsTrimmedSurface +#OrientationInterpolator +#PixelTexture +#PlaneSensor +#PointLight +#PointSet +#Polyline2D +#Polypoint2D +#PositionInterpolator +#PositionInterpolator2D +#ProximitySensor +ReceiverPdu +#Rectangle2D +#ScalarInterpolator +#Script +#Shape +SignalPdu +#Sound +#Sphere +#SphereSensor +#SpotLight +#StaticGroup +#StringSensor +#Switch +#Text +#TextureBackground +#TextureCoordinate +#TextureCoordinateGenerator +#TextureTransform +#TimeSensor +#TimeTrigger +#TouchSensor +#Transform +TransmitterPdu +#TriangleFanSet +#TriangleSet +#TriangleSet2D +#TriangleStripSet +#Viewpoint +#VisibilitySensor +#WorldInfo diff --git a/applications/generators/X3D/templates_X3D.txt b/applications/generators/X3D/templates_X3D.txt new file mode 100644 index 0000000..5f235da --- /dev/null +++ b/applications/generators/X3D/templates_X3D.txt @@ -0,0 +1,1644 @@ +# GPAC X3D template file +# X3D nodes in GPAC are designed so that they match their MPEG-4 counterparts in order to perform type casting, whenever possible ('#X3D extensions' signaled). +# ommented fields are fields where X3D spec, DTD and XSD disagree +# + +#NOT AN MPEG4 extensions because of activate eventIn in mpeg4 +PROTO Anchor [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFString description "" +exposedField MFString parameter [] +exposedField MFURL url [] +exposedField SFMetadataNode metadata NULL +]{ +} + +PROTO Appearance [ #%NDT=SFWorldNode,SFAppearanceNode +exposedField SFMaterialNode material NULL +exposedField SFTextureNode texture NULL +exposedField SFTextureTransformNode textureTransform NULL +#X3D extensions +exposedField SFFillPropertiesNode fillProperties NULL +exposedField SFX3DLinePropertiesNode lineProperties NULL +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Arc2D [ #%NDT=SFWorldNode,SFGeometryNode +field SFFloat endAngle 1.5707963 +field SFFloat radius 1 +field SFFloat startAngle 0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO ArcClose2D [ #%NDT=SFWorldNode,SFGeometryNode +field SFString closureType "PIE" +field SFFloat endAngle 1.5707963 +field SFFloat radius 1 +field SFFloat startAngle 0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO AudioClip [ #%NDT=SFWorldNode,SFAudioNode,SFStreamingNode +exposedField SFString description "" +exposedField SFBool loop FALSE +exposedField SFFloat pitch 1.0 +exposedField SFTime startTime 0 +exposedField SFTime stopTime 0 +exposedField MFURL url [] +eventOut SFTime duration_changed +eventOut SFBool isActive +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFTime pauseTime 0 +exposedField SFTime resumeTime 0 +eventOut SFTime elapsedTime +eventOut SFBool isPaused +] { +} + +PROTO Background [ #%NDT=SFWorldNode,SF3DNode,SFBackground3DNode +eventIn SFBool set_bind +exposedField MFFloat groundAngle [] +exposedField MFColor groundColor [] +exposedField MFURL backUrl [] +exposedField MFURL bottomUrl [] +exposedField MFURL frontUrl [] +exposedField MFURL leftUrl [] +exposedField MFURL rightUrl [] +exposedField MFURL topUrl [] +exposedField MFFloat skyAngle [] +exposedField MFColor skyColor [ 0 0 0 ] +eventOut SFBool isBound +#X3D extensions +exposedField SFMetadataNode metadata NULL +eventOut SFTime bindTime +] { +} + +PROTO Billboard [ #%NDT=SFWorldNode,SF3DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFVec3f axisOfRotation 0 1 0 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO BooleanFilter [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFBool set_boolean +eventOut SFBool inputFalse +eventOut SFBool inputNegate +eventOut SFBool inputTrue +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO BooleanSequencer [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFBool next +eventIn SFBool previous +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFBool keyValue [] +eventOut SFBool value_changed +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO BooleanToggle [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFBool set_boolean +exposedField SFBool toggle FALSE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO BooleanTrigger [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFTime set_triggerTime +eventOut SFBool triggerTrue +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Box [ #%NDT=SFWorldNode,SFGeometryNode +field SFVec3f size 2 2 2 +#X3D extensions +exposedField SFMetadataNode metadata NULL +#field SFBool solid TRUE +] { +} + +PROTO Circle2D [ #%NDT=SFWorldNode,SFGeometryNode +exposedField SFFloat radius 1 +exposedField SFMetadataNode metadata NULL +] { +} + +#note the MPEG-4 version uses "collide" instead of 'enabled" +PROTO Collision [ #%NDT=SFWorldNode,SF3DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFBool enabled TRUE +field SF3DNode proxy NULL +eventOut SFTime collideTime +eventOut SFBool isActive +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Color [ #%NDT=SFWorldNode,SFColorNode +exposedField MFColor color [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO ColorInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFColor keyValue [] +eventOut SFColor value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO ColorRGBA [ #%NDT=SFWorldNode,SFColorNode +exposedField MFColorRGBA color [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Cone [ #%NDT=SFWorldNode,SFGeometryNode +field SFFloat bottomRadius 1 +field SFFloat height 2 +field SFBool side TRUE +field SFBool bottom TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +#field SFBool solid TRUE +] { +} + +PROTO Contour2D [#%NDT=SFWorldNode,SFNurbsControlCurveNode +eventIn MFNurbsControlCurveNode addChildren +eventIn MFNurbsControlCurveNode removeChildren +exposedField MFNurbsControlCurveNode children [] +exposedField SFMetadataNode metadata NULL +]{ +} + +PROTO ContourPolyline2D #%NDT=SFWorldNode,SFNurbsControlCurveNode + exposedField MFVec2f point [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Coordinate [ #%NDT=SFWorldNode,SFCoordinateNode +exposedField MFVec3f point [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} +PROTO CoordinateDouble [ #%NDT=SFWorldNode,SFCoordinateNode +exposedField MFVec3d point [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Coordinate2D [ #%NDT=SFWorldNode,SFCoordinate2DNode +exposedField MFVec2f point [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO CoordinateInterpolator [ #%NDT=SFWorldNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFVec3f keyValue [] +eventOut MFVec3f value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO CoordinateInterpolator2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFVec2f keyValue [] +eventOut MFVec2f value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Cylinder [ #%NDT=SFWorldNode,SFGeometryNode +field SFBool bottom TRUE +field SFFloat height 2 +field SFFloat radius 1 +field SFBool side TRUE +field SFBool top TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +#field SFBool solid TRUE +] { +} + +PROTO CylinderSensor [ #%NDT=SFWorldNode,SF3DNode +exposedField SFBool autoOffset TRUE +exposedField SFFloat diskAngle 0.2617 +exposedField SFBool enabled TRUE +exposedField SFFloat maxAngle -1 +exposedField SFFloat minAngle 0 +exposedField SFFloat offset 0 +eventOut SFBool isActive +eventOut SFRotation rotation_changed +eventOut SFVec3f trackPoint_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFString description "" +eventOut SFBool isOver +] { +} + +PROTO DirectionalLight [ #%NDT=SFWorldNode,SF3DNode +exposedField SFFloat ambientIntensity 0 +exposedField SFColor color 1 1 1 +exposedField SFVec3f direction 0 0 -1 +exposedField SFFloat intensity 1 +exposedField SFBool on TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Disk2D [#%NDT=SFWorldNode,SFGeometryNode +field SFFloat innerRadius 0 +field SFFloat outerRadius 1 +#field SFBool solid FALSE +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO ElevationGrid [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFFloat set_height +exposedField SFColorNode color NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field MFFloat height [] +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFFloat creaseAngle 0.0 +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field SFInt32 xDimension 0 +field SFFloat xSpacing 1.0 +field SFInt32 zDimension 0 +field SFFloat zSpacing 1.0 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO EspduTransform [#%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +eventIn SFFloat set_articulationParameterValue0 +eventIn SFFloat set_articulationParameterValue1 +eventIn SFFloat set_articulationParameterValue2 +eventIn SFFloat set_articulationParameterValue3 +eventIn SFFloat set_articulationParameterValue4 +eventIn SFFloat set_articulationParameterValue5 +eventIn SFFloat set_articulationParameterValue6 +eventIn SFFloat set_articulationParameterValue7 +exposedField SFString address "localhost" +exposedField SFInt32 applicationID 1 +exposedField SFInt32 articulationParameterCount 0 +exposedField MFInt32 articulationParameterDesignatorArray [] +exposedField MFInt32 articulationParameterChangeIndicatorArray [] +exposedField MFInt32 articulationParameterIdPartAttachedToArray [] +exposedField MFInt32 articulationParameterTypeArray [] +exposedField MFFloat articulationParameterArray [] +exposedField SFVec3f center 0 0 0 +exposedField MF3DNode children [] +exposedField SFInt32 collisionType 0 +exposedField SFInt32 deadReckoning 0 +exposedField SFVec3f detonationLocation 0 0 0 +exposedField SFVec3f detonationRelativeLocation 0 0 0 +exposedField SFInt32 detonationResult 0 +exposedField SFInt32 entityCategory 0 +exposedField SFInt32 entityCountry 0 +exposedField SFInt32 entityDomain 0 +exposedField SFInt32 entityExtra 0 +exposedField SFInt32 entityID 0 +exposedField SFInt32 entityKind 0 +exposedField SFInt32 entitySpecific 0 +exposedField SFInt32 entitySubCategory 0 +exposedField SFInt32 eventApplicationID 1 +exposedField SFInt32 eventEntityID 0 +exposedField SFInt32 eventNumber 0 +exposedField SFInt32 eventSiteID 0 +exposedField SFBool fired1 FALSE +exposedField SFBool fired2 FALSE +exposedField SFInt32 fireMissionIndex 0 +exposedField SFFloat firingRange 0.0 +exposedField SFInt32 firingRate 0 +exposedField SFInt32 forceID 0 +exposedField SFInt32 fuse 0 +exposedField SFVec3f linearVelocity 0 0 0 +exposedField SFVec3f linearAcceleration 0 0 0 +exposedField SFString marking "" +exposedField SFString multicastRelayHost "" +exposedField SFInt32 multicastRelayPort 0 +exposedField SFInt32 munitionApplicationID 1 +exposedField SFVec3f munitionEndPoint 0 0 0 +exposedField SFInt32 munitionEntityID 0 +exposedField SFInt32 munitionQuantity 0 +exposedField SFInt32 munitionSiteID 0 +exposedField SFVec3f munitionStartPoint 0 0 0 +exposedField SFString networkMode "standAlone" +exposedField SFInt32 port 0 +exposedField SFTime readInterval 0.1 +exposedField SFRotation rotation 0 0 1 0 +exposedField SFVec3f scale 1 1 1 +exposedField SFRotation scaleOrientation 0 0 1 0 +exposedField SFInt32 siteID 0 +exposedField SFVec3f translation 0 0 0 +exposedField SFInt32 warhead 0 +exposedField SFTime writeInterval 1.0 +field SFBool rtpHeaderExpected FALSE +eventOut SFFloat articulationParameterValue0_changed 0.0 +eventOut SFFloat articulationParameterValue1_changed 0.0 +eventOut SFFloat articulationParameterValue2_changed 0.0 +eventOut SFFloat articulationParameterValue3_changed 0.0 +eventOut SFFloat articulationParameterValue4_changed 0.0 +eventOut SFFloat articulationParameterValue5_changed 0.0 +eventOut SFFloat articulationParameterValue6_changed 0.0 +eventOut SFFloat articulationParameterValue7_changed 0.0 +eventOut SFTime collideTime 0 +eventOut SFTime detonateTime 0 +eventOut SFTime firedTime 0 +eventOut SFBool isActive FALSE +eventOut SFBool isCollided FALSE +eventOut SFBool isDetonated FALSE +eventOut SFBool isNetworkReader FALSE +eventOut SFBool isNetworkWriter FALSE +eventOut SFBool isRtpHeaderHeard FALSE +eventOut SFBool isStandAlone FALSE +eventOut SFTime timestamp 0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Extrusion [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFVec2f set_crossSection +eventIn MFRotation set_orientation +eventIn MFVec2f set_scale +eventIn MFVec3f set_spine +field SFBool beginCap TRUE +field SFBool ccw TRUE +field SFBool convex TRUE +field SFFloat creaseAngle 0.0 +field MFVec2f crossSection [ 1 1, 1 -1, -1 -1, -1 1, 1 1 ] +field SFBool endCap TRUE +field MFRotation orientation [0 0 1 0] +field MFVec2f scale [1 1] +field SFBool solid TRUE +field MFVec3f spine [ 0 0 0, 0 1 0 ] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO FillProperties [ #%NDT=SFWorldNode,SFFillPropertiesNode +exposedField SFBool filled TRUE +exposedField SFColor hatchColor 1 1 1 +exposedField SFBool hatched TRUE +exposedField SFInt32 hatchStyle 1 +] { +} + +PROTO Fog [ #%NDT=SFWorldNode,SF3DNode,SFFogNode +exposedField SFColor color 1 1 1 +exposedField SFString fogType "LINEAR" +exposedField SFFloat visibilityRange 0 +eventIn SFBool set_bind +eventOut SFBool isBound +#X3D extensions +exposedField SFMetadataNode metadata NULL +eventOut SFTime bindTime +] { +} + + +PROTO FontStyle [ #%NDT=SFWorldNode,SFFontStyleNode +exposedField MFString family ["SERIF"] +exposedField SFBool horizontal TRUE +exposedField MFString justify ["BEGIN"] +exposedField SFString language "" +exposedField SFBool leftToRight TRUE +exposedField SFFloat size 1.0 +exposedField SFFloat spacing 1.0 +exposedField SFString style "PLAIN" +exposedField SFBool topToBottom TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO GeoCoordinate [#%NDT=SFWorldNode,SFCoordinateNode +exposedField MFVec3d point [] +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD", "WE"] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO GeoElevationGrid [#%NDT=SFWorldNode,SFGeometryNode +eventIn MFDouble set_height +exposedField SFColorNode color NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +exposedField SFFloat yScale 1.0 +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFFloat creaseAngle 0.0 +field SFString geoGridOrigin "0 0 0" +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD", "WE"] +field MFDouble height [] +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field SFInt32 xDimension 0 +field SFDouble xSpacing 1.0 +field SFInt32 zDimension 0 +field SFDouble zSpacing 1.0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO GeoLocation [#%NDT=SFWorldNode,SF3DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFVec3d geoCoords 0 0 0 +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD", "WE"] +exposedField SFMetadataNode metadata NULL +]{ +} + +#addChildren and removeChildren are commented, it looks like a bug in X3D spec +PROTO GeoLOD [#%NDT=SFWorldNode,SF3DNode +# eventIn MF3DNode addChildren +# eventIn MF3DNode removeChildren +field SFVec3d center 0 0 0 +field MFURL child1Url [] +field MFURL child2Url [] +field MFURL child3Url [] +field MFURL child4Url [] +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD","WE"] +field SFFloat range 10 +field MFURL rootUrl [] +field MF3DNode rootNode [] +eventOut MF3DNode children +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO GeoMetadata [#%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField MF3DNode data [] +exposedField MFString summary [] +exposedField MFURL url [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO GeoOrigin [%#NDT=SFGeoOriginNode +exposedField SFVec3d geoCoords 0 0 0 +exposedField MFString geoSystem ["GD","WE"] +field SFBool rotateYUp FALSE +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO GeoPositionInterpolator [ #%NDT=SFWorldNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFVec3d keyValue [] +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD","WE"] +eventOut SFVec3d geovalue_changed +eventOut SFVec3f value_changed +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO GeoTouchSensor [ #%NDT=SFWorldNode,SF2DNode,SF3DNode +exposedField SFBool enabled TRUE +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD","WE"] +eventOut SFVec3f hitNormal_changed +eventOut SFVec3f hitPoint_changed +eventOut SFVec2f hitTexCoord_changed +eventOut SFVec3d hitGeoCoord_changed +eventOut SFBool isActive +eventOut SFBool isOver +eventOut SFTime touchTime +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO GeoViewpoint [#%NDT=SFWorldNode,SF3DNode,SFViewpointNode +eventIn SFBool set_bind +eventIn SFString set_orientation +eventIn SFString set_position +exposedField SFString description "" +exposedField SFFloat fieldOfView 0.785398 +exposedField SFBool headlight TRUE +exposedField SFBool jump TRUE +exposedField MFString navType ["EXAMINE","ANY"] +eventOut SFTime bindTime +eventOut SFBool isBound +field SFGeoOriginNode geoOrigin NULL +field MFString geoSystem ["GD","WE"] +field SFRotation orientation 0 0 1 0 +field SFVec3d position 0 0 100000 +field SFFloat speedFactor 1.0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Group [ #%NDT=SFWorldNode,SFTopNode,SF3DNode,SF2DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO HAnimDisplacer [#%NDT=SFWorldNode,SFHAnimDisplacerNode +exposedField MFInt32 coordIndex [] +exposedField MFVec3f displacements [] +exposedField SFString name "" +exposedField SFFloat weight 0.0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO HAnimHumanoid [#%NDT=SFWorldNode,SF3DNode +exposedField SFVec3f center 0 0 0 +exposedField MFString info [] +exposedField MFHAnimNode joints [] +exposedField SFString name "" +exposedField SFRotation rotation 0 0 1 0 +exposedField SFVec3f scale 1 1 1 +exposedField SFRotation scaleOrientation 0 0 1 0 +exposedField MFHAnimNode segments [] +exposedField MFHAnimNode sites [] +exposedField MFHAnimNode skeleton [] +exposedField MF3DNode skin [] +exposedField SFCoordinateNode skinCoord NULL +exposedField SFNormalNode skinNormal NULL +exposedField SFVec3f translation 0 0 0 +exposedField SFString version "" +exposedField MFViewpointNode viewpoints [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO HAnimJoint [#%NDT=SFWorldNode,SFHAnimNode +eventIn MFHAnimNode addChildren +eventIn MFHAnimNode removeChildren +exposedField MFHAnimNode children [] +exposedField SFVec3f center 0 0 0 +exposedField MFHAnimDisplacerNode displacers [] +exposedField SFRotation limitOrientation 0 0 1 0 +exposedField MFFloat llimit [] +exposedField SFString name "" +exposedField SFRotation rotation 0 0 1 0 +exposedField SFVec3f scale 1 1 1 +exposedField SFRotation scaleOrientation 0 0 1 0 +exposedField MFInt32 skinCoordIndex [] +exposedField MFFloat skinCoordWeight [] +exposedField MFFloat stiffness [0 0 0] +exposedField SFVec3f translation 0 0 0 +exposedField MFFloat ulimit [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO HAnimSegment [#%NDT=SFWorldNode,SFHAnimNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFVec3f centerOfMass 0 0 0 +exposedField SFCoordinateNode coord NULL +exposedField MFHAnimDisplacerNode displacers [] +exposedField SFFloat mass 0 +exposedField MFFloat momentsOfInertia [0 0 0 0 0 0 0 0 0] +exposedField SFString name "" +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO HAnimSite [#%NDT=SFWorldNode,SFHAnimNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFVec3f center 0 0 0 +exposedField SFString name "" +exposedField SFRotation rotation 0 0 1 0 +exposedField SFVec3f scale 1 1 1 +exposedField SFRotation scaleOrientation 0 0 1 0 +exposedField SFVec3f translation 0 0 0 +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO ImageTexture [ #%NDT=SFWorldNode,SFTextureNode +exposedField MFURL url [] +field SFBool repeatS TRUE +field SFBool repeatT TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO IndexedFaceSet [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +eventIn MFInt32 set_normalIndex +eventIn MFInt32 set_texCoordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field MFInt32 colorIndex [] +field SFBool colorPerVertex TRUE +field SFBool convex TRUE +field MFInt32 coordIndex [] +field SFFloat creaseAngle 0.0 +field MFInt32 normalIndex [] +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field MFInt32 texCoordIndex [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO IndexedLineSet [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFInt32 set_colorIndex +eventIn MFInt32 set_coordIndex +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +field MFInt32 colorIndex [] +field SFBool colorPerVertex TRUE +field MFInt32 coordIndex [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO IndexedTriangleFanSet [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFInt32 set_index +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field MFInt32 index [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO IndexedTriangleSet [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFInt32 set_index +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field MFInt32 index [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO IndexedTriangleStripSet [ #%NDT=SFWorldNode,SFGeometryNode +eventIn MFInt32 set_index +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFFloat creaseAngle 0 +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +field MFInt32 index [] +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO Inline [ #%NDT=SFWorldNode,SF3DNode,SFStreamingNode,SF2DNode +exposedField MFURL url [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFBool load TRUE +] { +} + + +PROTO IntegerSequencer [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFBool next +eventIn SFBool previous +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFInt32 keyValue [] +eventOut SFInt32 value_changed +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO IntegerTrigger [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFBool set_boolean +exposedField SFInt32 integerKey -1 +eventOut SFInt32 triggerValue +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO KeySensor [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField SFBool enabled TRUE +eventOut SFInt32 actionKeyPress +eventOut SFInt32 actionKeyRelease +eventOut SFBool altKey +eventOut SFBool controlKey +eventOut SFBool isActive +eventOut SFString keyPress +eventOut SFString keyRelease +eventOut SFBool shiftKey +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO LineProperties [ #%NDT=SFWorldNode,SFX3DLinePropertiesNode +exposedField SFBool applied TRUE +exposedField SFInt32 linetype 1 +exposedField SFFloat linewidthScaleFactor 0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO LineSet [ #%NDT=SFWorldNode,SFGeometryNode +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField MFInt32 vertexCount [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO LoadSensor [ #%NDT=SFWorldNode,SFStreamingNode +exposedField SFBool enabled TRUE +exposedField SFTime timeOut 0 +exposedField MFStreamingNode watchList [] +eventOut SFBool isActive +eventOut SFBool isLoaded +eventOut SFTime loadTime +eventOut SFFloat progress +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO LOD [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +field SFVec3f center 0 0 0 +field MFFloat range [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Material [ #%NDT=SFWorldNode,SFMaterialNode +exposedField SFFloat ambientIntensity 0.2 +exposedField SFColor diffuseColor 0.8 0.8 0.8 +exposedField SFColor emissiveColor 0 0 0 +exposedField SFFloat shininess 0.2 +exposedField SFColor specularColor 0 0 0 +exposedField SFFloat transparency 0 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO MetadataDouble [ #%NDT=SFWorldNode,SFMetadataNode +exposedField SFString name "" +exposedField SFString reference "" +exposedField MFDouble value [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MetadataFloat[ #%NDT=SFWorldNode,SFMetadataNode +exposedField SFString name "" +exposedField SFString reference "" +exposedField MFFloat value [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MetadataInteger [ #%NDT=SFWorldNode,SFMetadataNode +exposedField SFString name "" +exposedField SFString reference "" +exposedField MFInt32 value [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MetadataSet [ #%NDT=SFWorldNode,SFMetadataNode +exposedField SFString name "" +exposedField SFString reference "" +exposedField MFMetadataNode value [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MetadataString [ #%NDT=SFWorldNode,SFMetadataNode +exposedField SFString name "" +exposedField SFString reference "" +exposedField MFString value [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MovieTexture [ #%NDT=SFWorldNode,SFTextureNode,SFStreamingNode +exposedField SFBool loop FALSE +exposedField SFFloat speed 1.0 +exposedField SFTime startTime 0 +exposedField SFTime stopTime 0 +exposedField MFURL url [] +field SFBool repeatS TRUE +field SFBool repeatT TRUE +eventOut SFTime duration_changed +eventOut SFBool isActive +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFTime resumeTime 0 +exposedField SFTime pauseTime 0 +eventOut SFTime elapsedTime +eventOut SFBool isPaused +] { +} + +PROTO MultiTexture [ #%NDT=SFWorldNode,SFTextureNode +exposedField SFFloat alpha 1 +exposedField SFColor color 1 1 1 +exposedField MFString function [] +exposedField MFString mode [] +exposedField MFString source [] +exposedField MFTextureNode texture [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MultiTextureCoordinate [ #%NDT=SFWorldNode,SFTextureCoordinateNode +MultiTextureCoordinate MFTextureCoordinateNode texCoord NULL +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO MultiTextureTransform [ #%NDT=SFWorldNode,SFTextureTransformNode +exposedField MFTextureTransformNode textureTransform [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NavigationInfo [ #%NDT=SFWorldNode,SF3DNode,SFNavigationInfoNode +eventIn SFBool set_bind +exposedField MFFloat avatarSize [0.25, 1.6, 0.75] +exposedField SFBool headlight TRUE +exposedField SFFloat speed 1.0 +exposedField MFString type ["WALK", "ANY"] +exposedField SFFloat visibilityLimit 0.0 +eventOut SFBool isBound +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField MFString transitionType ["WALK", "ANY"] +eventOut SFTime bindTime +]{ +} + +PROTO Normal [ #%NDT=SFWorldNode,SFNormalNode +exposedField MFVec3f vector [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NormalInterpolator [ #%NDT=SFWorldNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFVec3f keyValue [] +eventOut MFVec3f value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO NurbsCurve [ #%NDT=SFWorldNode,SFGeometryNode,SFNurbsCurveNode +exposedField MFVec3f controlPoint [] +exposedField SFInt32 tessellation 0 +exposedField MFDouble weight [] +field SFBool closed FALSE +field MFFloat knot [] +field SFInt32 order 3 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsCurve2D [ #%NDT=SFWorldNode,SFNurbsControlCurveNode +exposedField MFVec2f controlPoint [] +exposedField SFInt32 tessellation 0 +exposedField MFFloat weight [] +field MFFloat knot [] +field SFInt32 order 3 +field SFBool closed FALSE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsOrientationInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFFloat set_fraction +exposedField SFCoordinateNode controlPoints NULL +exposedField MFDouble knot [] +exposedField SFInt32 order 3 +exposedField MFDouble weight [] +eventOut SFRotation value_changed +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsPatchSurface [ #%NDT=SFWorldNode,SFGeometryNode,SFNurbsSurfaceNode +exposedField SFCoordinateNode controlPoint NULL +exposedField SFTextureCoordinateNode texCoord NULL +exposedField SFInt32 uTessellation 0 +exposedField SFInt32 vTessellation 0 +exposedField MFDouble weight [] +field SFBool solid TRUE +field SFBool uClosed FALSE +field SFInt32 uDimension 0 +field MFDouble uKnot [] +field SFInt32 uOrder 3 +field SFBool vClosed FALSE +field SFInt32 vDimension 0 +field MFDouble vKnot [] +field SFInt32 vOrder 3 +exposedField SFMetadataNode metadata NULL +] { + } + +PROTO NurbsPositionInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFFloat set_fraction +exposedField SFCoordinateNode controlPoints NULL +exposedField MFDouble knot [] +exposedField SFInt32 order 3 +exposedField MFDouble weight [] +eventOut SFVec3f value_changed +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsSet [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn MFNurbsSurfaceNode addGeometry +eventIn MFNurbsSurfaceNode removeGeometry +exposedField MFNurbsSurfaceNode geometry [] +exposedField SFFloat tessellationScale 1.0 +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO NurbsSurfaceInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFVec2f set_fraction +exposedField SFCoordinateNode controlPoints NULL +exposedField MFDouble weight [] +eventOut SFVec3f position_changed +eventOut SFVec3f normal_changed +field SFInt32 uDimension 0 +field MFDouble uKnot [] +field SFInt32 uOrder 3 +field SFInt32 vDimension 0 +field MFDouble vKnot [] +field SFInt32 vOrder 3 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsSweptSurface [ #%NDT=SFWorldNode,SFGeometryNode,SFNurbsSurfaceNode +exposedField SFNurbsControlCurveNode crossSectionCurve NULL +exposedField SFNurbsCurveNode trajectoryCurve NULL +field SFBool ccw TRUE +field SFBool solid TRUE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsSwungSurface [ #%NDT=SFWorldNode,SFGeometryNode,SFNurbsSurfaceNode +exposedField SFNurbsControlCurveNode profileCurve NULL +exposedField SFNurbsControlCurveNode trajectoryCurve NULL +field SFBool ccw TRUE +field SFBool solid TRUE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsTextureCoordinate [ #%NDT=SFWorldNode,SFTextureCoordinateNode +exposedField MFVec2f controlPoint [] +exposedField MFFloat weight [] +field SFInt32 uDimension 0 +field MFDouble uKnot [] +field SFInt32 uOrder 3 +field SFInt32 vDimension 0 +field MFDouble vKnot [] +field SFInt32 vOrder 3 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO NurbsTrimmedSurface [ #%NDT=SFWorldNode,SFGeometryNode,SFNurbsSurfaceNode +eventIn MFNurbsControlCurveNode addTrimmingContour +eventIn MFNurbsControlCurveNode removeTrimmingContour +exposedField MFNurbsControlCurveNode trimmingContour [] +exposedField SFCoordinateNode controlPoint NULL +exposedField SFTextureCoordinateNode texCoord NULL +exposedField SFInt32 uTessellation 0 +exposedField SFInt32 vTessellation 0 +exposedField MFDouble weight [] +field SFBool solid TRUE +field SFBool uClosed FALSE +field SFInt32 uDimension 0 +field MFDouble uKnot [] +field SFInt32 uOrder 3 +field SFBool vClosed FALSE +field SFInt32 vDimension 0 +field MFDouble vKnot [] +field SFInt32 vOrder 3 +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO OrientationInterpolator [ #%NDT=SFWorldNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFRotation keyValue [] +eventOut SFRotation value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO PixelTexture [ #%NDT=SFWorldNode,SFTextureNode +exposedField SFImage image 0 0 0 +field SFBool repeatS TRUE +field SFBool repeatT TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO PlaneSensor [ #%NDT=SFWorldNode,SF3DNode +exposedField SFBool autoOffset TRUE +exposedField SFBool enabled TRUE +exposedField SFVec2f maxPosition -1 -1 +exposedField SFVec2f minPosition 0 0 +exposedField SFVec3f offset 0 0 0 +eventOut SFBool isActive +eventOut SFVec3f trackPoint_changed +eventOut SFVec3f translation_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFString description "" +eventOut SFBool isOver +] { +} + +PROTO PointLight [ #%NDT=SFWorldNode,SF3DNode +exposedField SFFloat ambientIntensity 0 +exposedField SFVec3f attenuation 1 0 0 +exposedField SFColor color 1 1 1 +exposedField SFFloat intensity 1 +exposedField SFVec3f location 0 0 0 +exposedField SFBool on TRUE +exposedField SFFloat radius 100 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO PointSet [ #%NDT=SFWorldNode,SFGeometryNode +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Polyline2D [#%NDT=SFWorldNode,SFGeometryNode +exposedField MFVec2f lineSegments [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Polypoint2D [#%NDT=SFWorldNode,SFGeometryNode +exposedField MFVec2f point [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO PositionInterpolator [ #%NDT=SFWorldNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFVec3f keyValue [] +eventOut SFVec3f value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO PositionInterpolator2D [ #%NDT=SFWorldNode,SF2DNode,SF3DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFVec2f keyValue [] +eventOut SFVec2f value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO ProximitySensor [ #%NDT=SFWorldNode,SF3DNode +exposedField SFVec3f center 0 0 0 +exposedField SFVec3f size 0 0 0 +exposedField SFBool enabled TRUE +eventOut SFBool isActive +eventOut SFVec3f position_changed +eventOut SFRotation orientation_changed +eventOut SFTime enterTime +eventOut SFTime exitTime +#X3D extensions +exposedField SFMetadataNode metadata NULL +eventOut SFVec3f centerOfRotation_changed +] { +} + +PROTO ReceiverPdu [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField SFString address "localhost" +exposedField SFInt32 applicationID 1 +exposedField SFInt32 entityID 0 +exposedField SFString multicastRelayHost "" +exposedField SFInt32 multicastRelayPort 0 +exposedField SFString networkMode "standAlone" +exposedField SFInt32 port 0 +exposedField SFInt32 radioID 0 +exposedField SFFloat readInterval 0.1 +exposedField SFFloat receivedPower 0.0 +exposedField SFInt32 receiverState 0 +exposedField SFBool rtpHeaderExpected +exposedField SFInt32 siteID 0 +exposedField SFInt32 transmitterApplicationID 1 +exposedField SFInt32 transmitterEntityID 0 +exposedField SFInt32 transmitterRadioID 0 +exposedField SFInt32 transmitterSiteID 0 +exposedField SFInt32 whichGeometry 1 +exposedField SFFloat writeInterval 1.0 +eventOut SFBool isActive FALSE +eventOut SFBool isNetworkReader FALSE +eventOut SFBool isNetworkWriter FALSE +eventOut SFBool isRtpHeaderHeard FALSE +eventOut SFBool isStandAlone FALSE +eventOut SFTime timestamp 0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Rectangle2D [ #%NDT=SFWorldNode,SFGeometryNode +field SFVec2f size 2 2 +#X3D extensions +exposedField SFMetadataNode metadata NULL +#exposedField SFBool filled TRUE +]{ +} + +PROTO ScalarInterpolator [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFFloat set_fraction +exposedField MFFloat key [] +exposedField MFFloat keyValue [] +eventOut SFFloat value_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +]{} + +PROTO Script [#%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField MFScript url [] +field SFBool directOutput FALSE +field SFBool mustEvaluate FALSE +#X3D extensions +exposedField SFMetadataNode metadata NULL +]{ +} + +PROTO Shape [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField SFAppearanceNode appearance NULL +exposedField SFGeometryNode geometry NULL +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO SignalPdu [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField SFString address "localhost" +exposedField SFInt32 applicationID 1 +exposedField MFInt32 data [] +exposedField SFInt32 dataLength 0 +exposedField SFInt32 encodingScheme 0 +exposedField SFInt32 entityID 0 +exposedField SFString multicastRelayHost "" +exposedField SFInt32 multicastRelayPort 0 +exposedField SFString networkMode "standAlone" +exposedField SFInt32 port 0 +exposedField SFInt32 radioID 0 +exposedField SFFloat readInterval 0.1 +exposedField SFBool rtpHeaderExpected FALSE +exposedField SFInt32 sampleRate 0 +exposedField SFInt32 samples 0 +exposedField SFInt32 siteID 0 +exposedField SFInt32 tdlType 0 +exposedField SFInt32 whichGeometry 1 +exposedField SFFloat writeInterval 1.0 +eventOut SFBool isActive +eventOut SFBool isNetworkReader +eventOut SFBool isNetworkWriter +eventOut SFBool isRtpHeaderHeard +eventOut SFBool isStandAlone +eventOut SFTime timestamp +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO Sound [ #%NDT=SFWorldNode,SF3DNode +exposedField SFVec3f direction 0 0 1 +exposedField SFFloat intensity 1 +exposedField SFVec3f location 0 0 0 +exposedField SFFloat maxBack 10 +exposedField SFFloat maxFront 10 +exposedField SFFloat minBack 1 +exposedField SFFloat minFront 1 +exposedField SFFloat priority 0 +exposedField SFAudioNode source NULL +field SFBool spatialize TRUE +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO Sphere [ #%NDT=SFWorldNode,SFGeometryNode +field SFFloat radius 1 +#X3D extensions +exposedField SFMetadataNode metadata NULL +#field SFBool solid TRUE +] { +} + + +PROTO SphereSensor [ #%NDT=SFWorldNode,SF3DNode +exposedField SFBool autoOffset TRUE +exposedField SFBool enabled TRUE +exposedField SFRotation offset 0 1 0 0 +eventOut SFBool isActive +eventOut SFRotation rotation_changed +eventOut SFVec3f trackPoint_changed +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFString description "" +eventOut SFBool isOver +]{ +} + +PROTO SpotLight [ #%NDT=SFWorldNode,SF3DNode +exposedField SFFloat ambientIntensity 0 +exposedField SFVec3f attenuation 1 0 0 +exposedField SFFloat beamWidth 1.570796 +exposedField SFColor color 1 1 1 +exposedField SFFloat cutOffAngle 0.785398 +exposedField SFVec3f direction 0 0 -1 +exposedField SFFloat intensity 1 +exposedField SFVec3f location 0 0 0 +exposedField SFBool on TRUE +exposedField SFFloat radius 100 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO StaticGroup [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +field MF3DNode children [] +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO StringSensor [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField SFBool deletionAllowed TRUE +exposedField SFBool enabled TRUE +eventOut SFString enteredText +eventOut SFString finalText +eventOut SFBool isActive +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO Switch [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField MF3DNode children [] +exposedField SFInt32 whichChoice -1 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Text [ #%NDT=SFWorldNode,SFGeometryNode +exposedField MFString string [] +exposedField MFFloat length [] +exposedField SFFontStyleNode fontStyle NULL +exposedField SFFloat maxExtent 0.0 +#X3D extensions +exposedField SFMetadataNode metadata NULL +#field SFBool solid FALSE +] { +} + +PROTO TextureBackground [ #%NDT=SFWorldNode,SF3DNode,SFBackground3DNode +eventIn SFBool set_bind +exposedField MFFloat groundAngle [] +exposedField MFColor groundColor [] +exposedField SFTextureNode backTexture NULL +exposedField SFTextureNode bottomTexture NULL +exposedField SFTextureNode frontTexture NULL +exposedField SFTextureNode leftTexture NULL +exposedField SFTextureNode rightTexture NULL +exposedField SFTextureNode topTexture NULL +exposedField MFFloat skyAngle [] +exposedField MFColor skyColor 0 0 0 +exposedField MFFloat transparency 0 +exposedField SFTime bindTime +exposedField SFBool isBound +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO TextureCoordinate [ #%NDT=SFWorldNode,SFTextureCoordinateNode +exposedField MFVec2f point [] +#X3D extensions +exposedField SFMetadataNode metadata NULL +]{ +} + +PROTO TextureCoordinateGenerator [ #%NDT=SFWorldNode,SFTextureCoordinateNode +exposedField SFString mode "SPHERE" +TextureCoordinateGenerator MFFloat parameter [] +exposedField SFMetadataNode metadata NULL +] { +} + + + +PROTO TextureTransform [ #%NDT=SFWorldNode,SFTextureTransformNode +exposedField SFVec2f center 0 0 +exposedField SFFloat rotation 0 +exposedField SFVec2f scale 1 1 +exposedField SFVec2f translation 0 0 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO TimeSensor [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +exposedField SFTime cycleInterval 1 +exposedField SFBool enabled TRUE +exposedField SFBool loop FALSE +exposedField SFTime startTime 0 +exposedField SFTime stopTime 0 +eventOut SFTime cycleTime +eventOut SFFloat fraction_changed +eventOut SFBool isActive +eventOut SFTime time +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFTime pauseTime 0 +exposedField SFTime resumeTime 0 +eventOut SFTime elapsedTime +eventOut SFBool isPaused +] { +} + +PROTO TimeTrigger [ #%NDT=SFWorldNode,SF3DNode,SF2DNode +eventIn SFBool set_boolean +eventOut SFTime triggerTime +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO TouchSensor [ #%NDT=SFWorldNode,SF2DNode,SF3DNode +exposedField SFBool enabled TRUE +eventOut SFVec3f hitNormal_changed +eventOut SFVec3f hitPoint_changed +eventOut SFVec2f hitTexCoord_changed +eventOut SFBool isActive +eventOut SFBool isOver +eventOut SFTime touchTime +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFString description "" +] {} + +PROTO Transform [ #%NDT=SFWorldNode,SF3DNode +eventIn MF3DNode addChildren +eventIn MF3DNode removeChildren +exposedField SFVec3f center 0 0 0 +exposedField MF3DNode children [] +exposedField SFRotation rotation 0 0 1 0 +exposedField SFVec3f scale 1 1 1 +exposedField SFRotation scaleOrientation 0 0 1 0 +exposedField SFVec3f translation 0 0 0 +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO TransmitterPdu [ #%NDT=SFWorldNode,SF2DNode,SF3DNode +exposedField SFString address "localhost" +exposedField SFVec3f antennaLocation 0 0 0 +exposedField SFInt32 antennaPatternLength 0 +exposedField SFInt32 antennaPatternType 0 +exposedField SFInt32 applicationID 1 +exposedField SFInt32 cryptoKeyID 0 +exposedField SFInt32 cryptoSystem 0 +exposedField SFInt32 entityID 0 +exposedField SFInt32 frequency 0 +exposedField SFInt32 inputSource 0 +exposedField SFInt32 lengthOfModulationParameters 0 +exposedField SFInt32 modulationTypeDetail 0 +exposedField SFInt32 modulationTypeMajor 0 +exposedField SFInt32 modulationTypeSpreadSpectrum 0 +exposedField SFInt32 modulationTypeSystem 0 +exposedField SFString multicastRelayHost "" +exposedField SFInt32 multicastRelayPort 0 +exposedField SFString networkMode "standAlone" +exposedField SFInt32 port 0 +exposedField SFFloat power 0.0 +exposedField SFInt32 radioEntityTypeCategory 0 +exposedField SFInt32 radioEntityTypeCountry 0 +exposedField SFInt32 radioEntityTypeDomain 0 +exposedField SFInt32 radioEntityTypeKind 0 +exposedField SFInt32 radioEntityTypeNomenclature 0 +exposedField SFInt32 radioEntityTypeNomenclatureVersion 0 +exposedField SFInt32 radioID 0 +exposedField SFFloat readInterval 0.1 +exposedField SFVec3f relativeAntennaLocation 0 0 0 +exposedField SFBool rtpHeaderExpected FALSE +exposedField SFInt32 siteID 0 +exposedField SFFloat transmitFrequencyBandwidth 0.0 +exposedField SFInt32 transmitState 0 +exposedField SFInt32 whichGeometry 1 +exposedField SFFloat writeInterval 1.0 +eventOut SFBool isActive FALSE +eventOut SFBool isNetworkReader FALSE +eventOut SFBool isNetworkWriter FALSE +eventOut SFBool isRtpHeaderHeard FALSE +eventOut SFBool isStandAlone FALSE +eventOut SFTime timestamp 0 +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO TriangleFanSet [ #%NDT=SFWorldNode,SFGeometryNode +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField MFInt32 fanCount [] +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +exposedField SFMetadataNode metadata NULL +] { +} + + +PROTO TriangleSet [ #%NDT=SFWorldNode,SFGeometryNode +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFNormalNode normal NULL +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO TriangleSet2D [ #%NDT=SFWorldNode,SFGeometryNode +exposedField MFVec2f vertices [] +#field SFBool solid FALSE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO TriangleStripSet [ #%NDT=SFWorldNode,SFGeometryNode +exposedField SFColorNode color NULL +exposedField SFCoordinateNode coord NULL +exposedField SFNormalNode normal NULL +exposedField MFInt32 stripCount [] +exposedField SFTextureCoordinateNode texCoord NULL +field SFBool ccw TRUE +field SFBool colorPerVertex TRUE +field SFBool normalPerVertex TRUE +field SFBool solid TRUE +exposedField SFMetadataNode metadata NULL +] { +} + +PROTO Viewpoint [ #%NDT=SFWorldNode,SF3DNode,SFViewpointNode +eventIn SFBool set_bind +exposedField SFFloat fieldOfView 0.785398 +exposedField SFBool jump TRUE +exposedField SFRotation orientation 0 0 1 0 +exposedField SFVec3f position 0 0 10 +field SFString description "" +eventOut SFTime bindTime +eventOut SFBool isBound +#X3D extensions +exposedField SFMetadataNode metadata NULL +exposedField SFVec3f centerOfRotation 0 0 0 +] { +} + + +PROTO VisibilitySensor [ #%NDT=SFWorldNode,SF3DNode +exposedField SFVec3f center 0 0 0 +exposedField SFBool enabled TRUE +exposedField SFVec3f size 0 0 0 +eventOut SFTime enterTime +eventOut SFTime exitTime +eventOut SFBool isActive +#X3D extensions +exposedField SFMetadataNode metadata NULL +]{ +} + +PROTO WorldInfo [ #%NDT=SFWorldNode,SF2DNode,SF3DNode +field MFString info [] +field SFString title "" +#X3D extensions +exposedField SFMetadataNode metadata NULL +] { +} diff --git a/applications/gpac/Info.plist b/applications/gpac/Info.plist new file mode 100644 index 0000000..45b0ed1 --- /dev/null +++ b/applications/gpac/Info.plist @@ -0,0 +1,16 @@ + + + + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleShortVersionString + 0.9.0 + CFBundleVersion + 1 + NSCameraUsageDescription + GPAC needs camera access to handle this URL + NSMicrophoneUsageDescription + GPAC needs microphone access to handle this URL + + diff --git a/applications/gpac/Makefile b/applications/gpac/Makefile new file mode 100644 index 0000000..2e60896 --- /dev/null +++ b/applications/gpac/Makefile @@ -0,0 +1,70 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/applications/gpac + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +LINKFLAGS=-L../../bin/gcc +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=gpac$(EXE) +LINKFLAGS+=$(UNICODEFLAGS) +else +EXT= +PROG=gpac +endif + +ifeq ($(STATICBUILD),yes) +##include static modules and other deps for libgpac +include ../../static.mak +LINKFLAGS+=$(shell pkg-config ../../gpac.pc --libs --static | sed 's/-lgpac //' ) +LINKFLAGS+= $(GPAC_SH_FLAGS) +LINKFLAGS+=$(EXTRALIBS) +else +LINKFLAGS+=-lgpac +ifeq ($(CONFIG_DARWIN),yes) +#LINKFLAGS+= -Wl,-rpath,'@loader_path' +else +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +endif +endif + + +#common objs - insert after ../static if any to overwrite list of objects +OBJS= main.o + +SRCS := $(OBJS:.o=.c) + +ifeq ($(CONFIG_WIN32),yes) +OBJS+=$(SRC_PATH)/manifest.o +endif + + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(PROG) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/gpac/main.c b/applications/gpac/main.c new file mode 100644 index 0000000..d9eb0e0 --- /dev/null +++ b/applications/gpac/main.c @@ -0,0 +1,4847 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2022 + * All rights reserved + * + * This file is part of GPAC / gpac application + * + * 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 +#include +#include + +static GF_SystemRTInfo rti; +static GF_FilterSession *session=NULL; +static u32 list_filters = 0; +static Bool dump_stats = GF_FALSE; +static Bool dump_graph = GF_FALSE; +static u32 print_filter_info = 0; +static Bool print_meta_filters = GF_FALSE; +static Bool load_test_filters = GF_FALSE; +static s32 nb_loops = 0; +static s32 runfor = 0; +static Bool runfor_exit = GF_FALSE; +static u32 exit_mode = 0; +static Bool enable_prompt = GF_FALSE; +static u32 enable_reports = 0; +static char *report_filter = NULL; +static Bool do_unit_tests = GF_FALSE; +static Bool use_step_mode = GF_FALSE; +static int alias_argc = 0; +static char **alias_argv = NULL; +static GF_List *args_used = NULL; +static GF_List *args_alloc = NULL; +static u32 gen_doc = 0; +static u32 help_flags = 0; +static u32 loops_done = 0; + +//coverage for FileIO +static const char *make_fileio(const char *inargs, const char **out_arg, Bool is_input, GF_Err *e); +static void cleanup_file_io(); + +//coverage for custom filters +static GF_Filter *load_custom_filter(GF_FilterSession *sess, char *opts, GF_Err *e); + +static FILE *sidebar_md=NULL; +static FILE *helpout = NULL; + +static const char *auto_gen_md_warning = "\n"; + +//uncomment to check argument description matches our conventions - see filter.h +#define CHECK_DOC + +//the default set of separators +static char separator_set[7] = GF_FS_DEFAULT_SEPS; +#define SEP_LINK 5 +#define SEP_FRAG 2 +#define SEP_LIST 3 + +static Bool print_filters(int argc, char **argv, GF_FilterSession *session, GF_SysArgMode argmode); +static void dump_all_props(char *pname); +static void dump_all_colors(void); +static void dump_all_audio_cicp(void); +static void dump_all_codec(GF_FilterSession *session); +static void write_filters_options(GF_FilterSession *fsess); +static void write_core_options(); +static void write_file_extensions(); +static int gpac_make_lang(char *filename); +static Bool gpac_expand_alias(int argc, char **argv); +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); + + + +#ifndef GPAC_DISABLE_DOC +const char *gpac_doc = +"# General\n" +"Filters are configurable processing units consuming and producing data packets. These packets are carried " +"between filters through a data channel called __PID__. A PID is in charge of allocating/tracking data packets, " +"and passing the packets to the destination filter(s). A filter output PID may be connected to zero or more filters. " +"This fan-out is handled internally by GPAC (no such thing as a tee filter in GPAC).\n" +"Note: When a PID cannot be connected to any filter, a warning is thrown and all packets dispatched on " +"this PID will be destroyed. The session may however still run, unless [-full-link](CORE) is set.\n" +" \nEach output PID carries a set of properties describing the data it delivers (e.g. __width__, __height__, __codec__, ...). Properties " +"can be built-in (see [gpac -h props](filters_properties) ), or user-defined. Each PID tracks " +"its properties changes and triggers filter reconfiguration during packet processing. This allows the filter chain to be " +"reconfigured at run time, potentially reloading part of the chain (e.g. unload a video decoder when switching from compressed " +"to uncompressed sources).\n" +" \nEach filter exposes a set of argument to configure itself, using property types and values described as strings formatted with " +"separators. This help is given with default separator sets `:=#,@` to specify filters, properties and options. Use [-seps](GPAC) to change them.\n" +"# Property and filter option format\n" +"- boolean: formatted as `yes`,`true`,`1` or `no`,`false`,`0`\n" +"- enumeration (for filter arguments only): must use the syntax given in the argument description, otherwise value `0` (first in enum) is assumed.\n" +"- 1-dimension (numbers, floats, ints...): formatted as `value[unit]`, where `unit` can be `k`,`K` (x 1000) or `m`,`M` (x 1000000) or `g`,`G` (x 1000000000) or `sec` (x 1000) or `min` (x 60000). `+I` means max float/int/uint value, `-I` min float/int/uint value.\n" +"- fraction: formatted as `num/den` or `num-den` or `num`, in which case the denominator is 1 if `num` is an integer, or 1000000 if `num` is a floating-point value.\n" +"- unsigned 32 bit integer: formatted as number or hexadecimal using the format `0xAABBCCDD`.\n" +"- N-dimension (vectors): formatted as `DIM1xDIM2[xDIM3[xDIM4]]` values, without unit multiplier.\n" +"- 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" +"- 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" +" - `b64@DATA`: load data from base-64 encoded `DATA`.\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" +"Note: The special characters in property formats (0x,/,-,+I,-I,x) cannot be configured.\n" +"# Filter declaration [__FILTER__]\n" +"## Generic declaration\n" +"Each filter is declared by its name, with optional filter arguments appended as a list of colon-separated `name=value` pairs. Additional syntax is provided for:\n" +"- boolean: `value` can be omitted, defaulting to `true` (e.g. `:noedit`). Using `!` before the name negates the result (e.g. `:!moof_first`)\n" +"- enumerations: name can be omitted, e.g. `:disp=pbo` is equivalent to `:pbo`.\n" +"\n \n" +"When string parameters are used (e.g. URLs), it is recommended to escape the string using the keyword `gpac`. \n" +"EX filter:ARG=http://foo/bar?yes:gpac:opt=VAL\n" +"This will properly extract the URL.\n" +"EX filter:ARG=http://foo/bar?yes:opt=VAL\n" +"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" +"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" +"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" +"## Source and Sink filters\n" +"Source and sink filters do not need to be addressed by the filter name, specifying `src=` or `dst=` instead is enough. " +"You can also use the syntax `-src URL` or `-i URL` for sources and `-dst URL` or `-o URL` for destination, this allows prompt completion in shells.\n" +"EX \"src=file.mp4\" or \"-src file.mp4\" or \"-i file.mp4\"\n" +"This will find a filter (for example `fin`) able to load `file.mp4`. The same result can be achieved by using `fin:src=file.mp4`.\n" +"EX \"dst=dump.yuv\" or \"-dst dump.yuv\" or \"-o dump.yuv\"\n" +"This will dump the video content in `dump.yuv`. The same result can be achieved by using `fout:dst=dump.yuv`.\n" +"\n" +"Specific source or sink filters may also be specified using `filterName:src=URL` or `filterName:dst=URL`.\n" +"\n" +"The `src=` and `dst=` syntaxes can also be used in alias for dynamic argument cloning (see `gpac -hx alias`).\n" +"\n" +"## Forcing specific filters\n" +"There is a special option called `gfreg` which allows specifying preferred filters to use when handling URLs.\n" +"EX src=file.mp4:gfreg=ffdmx,ffdec\n" +"This will use __ffdmx__ to read `file.mp4` and __ffdec__ to decode it.\n" +"This can be used to test a specific filter when alternate filter chains are possible.\n" +"## Specifying encoders and decoders\n" +"By default filters chain will be resolved without any decoding/encoding if the destination accepts the desired format. " +"Otherwise, decoders/encoders will be dynamically loaded to perform the conversion, unless dynamic resolution is disabled. " +"There is a special shortcut filter name for encoders `enc` allowing to match a filter providing the desired encoding. " +"The parameters for `enc` are:\n" +"- c=NAME: identifies the desired codec. `NAME` can be the GPAC codec name or the encoder instance for ffmpeg/others\n" +"- b=UINT, rate=UINT, bitrate=UINT: indicates the bitrate in bits per second\n" +"- g=UINT, gop=UINT: indicates the GOP size in frames\n" +"- pfmt=NAME: indicates the target pixel format name (see [properties (-h props)](filters_properties) ) of the source, if supported by codec\n" +"- all_intra=BOOL: indicates all frames should be intra frames, if supported by codec\n" +"\n" +"Other options will be passed to the filter if it accepts generic argument parsing (as is the case for ffmpeg).\n" +"The shortcut syntax `c=TYPE` (e.g. `c=aac:opts`) is also supported.\n" +"\n" +"EX gpac -i dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true -o raw.264\n" +"This creates a 25 fps AVC at 175kbps with a gop duration of 2 seconds, using closed gop and fast encoding settings for ffmpeg.\n" +"\n" +"The inverse operation (forcing a decode to happen) is possible using the __reframer__ filter.\n" +"EX gpac -i file.mp4 reframer:raw=av -o null\n" +"This will force decoding media from `file.mp4` and trash (send to `null`) the result (doing a decoder benchmark for example).\n" +"\n" +"## Escaping option separators\n" +"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" +"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" +"\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" +"\n" +"The filter graph resolver uses this information together with the PID properties to link the different filters.\n" +"\n" +"Link directives, when provided, specify which source a filter can accept connections from.\n" +"__They do not specify which destination a filter can connect to.__\n" +"\n" +"## Default filter linking\n" +"When no link instructions are given (see below), the default linking strategy used is either __implicit mode__ (default in `gpac`) or __complete mode__ (if [-cl](GPAC) is set).\n" +"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 (__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" +" - otherwise (`DST` is a sink) and no previous connections to a non-sink filter, allow connection.\n" +"\n" +"EX gpac -i file.mp4 c=avc -o output\n" +"With this setup in __implicit mode__:\n" +"- if the file has a video PID, it will connect to `enc` but not to `output`. The output PID of `enc` will connect to `output`.\n" +"- if the file has other PIDs than video, they will connect to `output`, since this `enc` filter accepts only video.\n" +"\n" +"EX gpac -cl -i file.mp4 c=avc -o output\n" +"With this setup in __complete mode__:\n" +"- if the file has a video PID, it will connect both to `enc` and to `output`, and the output PID of `enc` will connect to `output`.\n" +"- if the file has other PIDs than video, they will connect to `output`.\n" +"\n" +"Furthermore in __implicit mode__, filter connections are restricted to filters defined between the last source and the sink(s).\n" +"EX gpac -i video1 reframer:saps=1 -i video2 ffsws:osize=128x72 -o output\n" +"This will connect:\n" +"- `video1` to `reframer` then `reframer` to `output` but will prevent `reframer` to `ffsws` connection.\n" +"- `video2` to `ffsws` then `ffsws` to `output` but will prevent `video2` to `reframer` connection.\n" +"\n" +"EX gpac -i video1 -i video2 reframer:saps=1 ffsws:osize=128x72 -o output\n" +"This will connect `video1` AND `video2` to `reframer->ffsws->output`\n" +"\n" +"The __implicit mode__ allows specifying linear processing chains (no PID fan-out except for final output(s)) without link directives, simplifying command lines for common cases.\n" +"Warning: Argument order really matters in implicit mode!\n" +"\n" +"EX gpac -i file.mp4 c=avc c=aac -o output\n" +"If the file has a video PID, it will connect to `c=avc` but not to `output`. The output PID of `c=avc` will connect to `output`.\n" +"If the file has an audio PID, it will connect to `c=aac` but not to `output`. The output PID of `c=aac` will connect to `output`.\n" +"If the file has other PIDs than audio or video, they will connect to `output`.\n" +"\n" +"EX gpac -i file.mp4 ffswf=osize:128x72 c=avc resample=osr=48k c=aac -o output\n" +"This will force:\n" +"- `SRC(video)->ffsws->enc(video)->output` and prevent `SRC(video)->output`, `SRC(video)->enc(video)` and `ffsws->output` connections which would happen in __complete mode__.\n" +"- `SRC(audio)->resample->enc(audio)->output` and prevent `SRC(audio)->output`, `SRC(audio)->enc(audio)` and `resample->output` connections which would happen in __complete mode__.\n" +"\n" +"## Quick links\n" +"Link between filters may be manually specified. The syntax is an `@` character optionally followed by an integer (0 if omitted).\n" +"This indicates that the following filter specified at prompt should be linked only to a previous listed filter.\n" +"The optional integer is a 0-based index to the previous filter declarations, 0 indicating the previous filter declaration, 1 the one before the previous declaration, ...).\n" +"If `@@` is used instead of `@`, the optional integer gives the filter index starting from the first filter (index 0) specified in command line.\n" +"Several link directives can be given for a filter.\n" +"EX fA fB @1 fC\n" +"This indicates that `fC` only accepts inputs from `fA`.\n" +"EX fA fB fC @1 @0 fD\n" +"This indicates that `fD` only accepts inputs from `fB` and `fC`.\n" +"EX fA fB fC ... @@1 fZ\n" +"This indicates that `fZ` only accepts inputs from `fB`.\n" +"\n" +"## Complex links\n" +"The `@` link directive is just a quick shortcut to set the following filter arguments:\n" +"- FID=name: assigns an identifier to the filter\n" +"- SID=name1[,name2...]: sets a list of filter identifiers, or __sourceIDs__, restricting the list of possible inputs for a filter.\n" +"\n" +"EX fA fB @1 fC\n" +"This is equivalent to `fA:FID=1 fB fC:SID=1`.\n" +"EX fA:FID=1 fB fC:SID=1\n" +"This indicates that `fC` only accepts input from `fA`, but `fB` might accept inputs from `fA`.\n" +"EX fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2\n" +"This indicates that `fD` only accepts input from `fA` and `fB` and `fC` only from `fA`\n" +"Note: A filter with sourceID set cannot get input from filters with no IDs.\n" +"\n" +"A sourceID name can be further extended using fragment identifier (`#` by default):\n" +"- name#PIDNAME: accepts only PID(s) with name `PIDNAME`\n" +"- name#TYPE: accepts only PIDs of matching media type. TYPE can be `audio`, `video`, `scene`, `text`, `font`, `meta`\n" +"- name#TYPEN: accepts only `N` (1-based index) PID of matching type from source (e.g. `video2` to only accept second video PID)\n" +"- name#TAG=VAL: accepts the PID if its parent filter has no tag or a tag matching `VAL`\n" +"- name#P4CC=VAL: accepts only PIDs with builtin property of type `P4CC` and value `VAL`.\n" +"- name#PName=VAL: same as above, using the builtin name corresponding to the property.\n" +"- name#AnyName=VAL: same as above, using the name of a non built-in property.\n" +"- name#Name=OtherPropName: compares the value with the value of another property of the PID. The matching will fail if the value to compare to is not present or different from the value to check. The property to compare with shall be a built-in property.\n" +"If the property is not defined on the PID, the property is matched. Otherwise, its value is checked against the given value.\n" +"\n" +"The following modifiers for comparisons are allowed (for any fragment format using `=`):\n" +"- name#P4CC=!VAL: accepts only PIDs with property NOT matching `VAL`.\n" +"- name#P4CC-VAL: accepts only PIDs with property strictly less than `VAL` (only for 1-dimension number properties).\n" +"- name#P4CC+VAL: accepts only PIDs with property strictly greater than `VAL` (only for 1-dimension number properties).\n" +"\n" +"A sourceID name can also use wildcard or be empty to match a property regardless of the source filter.\n" +"EX fA fB:SID=*#ServiceID=2\n" +"EX fA fB:SID=#ServiceID=2\n" +"This indicates to match connection between `fA` and `fB` only for PIDs with a `ServiceID` property of `2`.\n" +"These extensions also work with the __LINK__ `@` shortcut.\n" +"EX fA fB @1#video fC\n" +"This indicates that `fC` only accepts inputs from `fA`, and of type video.\n" +"EX gpac -i img.heif @#ItemID=200 vout\n" +"This indicates to connect to `vout` only PIDs with `ItemID` property equal to `200`.\n" +"EX gpac -i vid.mp4 @#PID=1 vout\n" +"This indicates to connect to `vout` only PIDs with `ID` property equal to `1`.\n" +"EX gpac -i vid.mp4 @#Width=640 vout\n" +"This indicates to connect to `vout` only PIDs with `Width` property equal to `640`.\n" +"EX gpac -i vid.mp4 @#Width-640 vout\n" +"This indicates to connect to `vout` only PIDs with `Width` property less than `640`\n" +"EX gpac -i vid.mp4 @#ID=ItemID#ItemNumber=1 vout\n" +"This will connect to `vout` only PID with an ID property equal to ItemID property (keep items, discard tracks) and an Item number of 1 (first item).\n" +"\n" +"Multiple fragment can be specified to check for multiple PID properties.\n" +"EX gpac -i vid.mp4 @#Width=640#Height+380 vout\n" +"This indicates to connect to `vout` only PIDs with `Width` property equal to `640` and `Height` greater than `380`.\n" +"\n" +"Warning: If a PID directly connects to one or more explicitly loaded filters, no further dynamic link resolution will " +"be done to connect it to other filters with no sourceID set. Link directives should be carefully setup.\n" +"EX fA @ reframer fB\n" +"If `fB` accepts inputs provided by `fA` but `reframer` does not, this will link `fA` PID to `fB` filter since `fB` has no sourceID.\n" +"Since the PID is connected, the filter engine will not try to solve a link between `fA` and `reframer`.\n" +"\n" +"An exception is made for local files: by default, a local file destination will force a remultiplex of input PIDs from a local file.\n" +"EX gpac -i file.mp4 -o dump.mp4\n" +"This will prevent direct connection of PID of type `file` to dst `file.mp4`, remultiplexing the file.\n" +"\n" +"The special option `nomux` is used to allow direct connections (ignored for non-sink filters).\n" +"EX gpac -i file.mp4 -o dump.mp4:nomux\n" +"This will result in a direct file copy.\n" +"\n" +"This only applies to local files destination. For pipes, sockets or other file outputs (HTTP, ROUTE):\n" +"- direct copy is enabled by default\n" +"- `nomux=0` can be used to force remultiplex\n" +"\n" +"## Sub-session tagging\n" +"Filters may be assigned to a sub-session using `:FS=N`, with `N` a positive integer.\n" +"Filters belonging to different sub-sessions may only link to each-other:\n" +"- if explicitly allowed through sourceID directives (`@` or `SID`)\n" +"- or if they have the same sub-session identifier\n" +"\n" +"This is mostly used for __implicit mode__ in `gpac`: each first source filter specified after a sink filter will trigger a new sub-session.\n" +"EX gpac -i in1.mp4 -i in2.mp4 -o out1.mp4 -o out2.mp4\n" +"This will result in both inputs multiplexed in both outputs.\n" +"EX gpac -i in1.mp4 -o out1.mp4 -i in2.mp4 -o out2.mp4\n" +"This will result in in1 mixed to out1 and in2 mixed to out2, these last two filters belonging to a different sub-session.\n" +"\n" +"# Arguments inheriting\n" +"Unless explicitly disabled (see [-max-chain](CORE)), the filter engine will resolve implicit or explicit (__LINK__) connections " +"between filters and will allocate any filter chain required to connect the filters. " +"In doing so, it loads new filters with arguments inherited from both the source and the destination.\n" +"EX gpac -i file.mp4:OPT -o file.aac -o file.264\n" +"This will pass the `:OPT` to all filters loaded between the source and the two destinations.\n" +"EX gpac -i file.mp4 -o file.aac:OPT -o file.264\n" +"This will pass the `:OPT` to all filters loaded between the source and the file.aac destination.\n" +"Note: the destination arguments inherited are the arguments placed **AFTER** the `dst=` option.\n" +"EX gpac -i file.mp4 fout:OPTFOO:dst=file.aac:OPTBAR\n" +"This will pass the `:OPTBAR` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTFOO`.\n" +"Arguments inheriting can be stopped by using the keyword `gfloc`: arguments after the keyword will not be inherited.\n" +"EX gpac -i file.mp4 -o file.aac:OPTFOO:gfloc:OPTBAR -o file.264\n" +"This will pass `:OPTFOO` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTBAR`\n" +"Arguments are by default tracked to check if they were used by the filter chain, and a warning is thrown if this is not the case.\n" +"It may be useful to specify arguments which may not be consumed depending on the graph resolution; the specific keyword `gfopt` indicates that arguments after the keyword will not be tracked.\n" +"EX gpac -i file.mp4 -o file.aac:OPTFOO:gfopt:OPTBAR -o file.264\n" +"This will warn if `OPTFOO` is not consumed, but will not track `OPTBAR`.\n" +" \n" +"A filter may be assigned a name (for inspection purposes, not inherited) using `:N=name` option. This name is not used in link resolution and may be changed at runtime by the filter instance.\n" +" \n" +"A filter may be assigned a tag (any string) using `:TAG=name` option. This tag does not need to be unique, and can be used to exclude filter in link resolution. Tags are not inherited, therefore dynamically loaded filters never have a tag.\n" +" \n" +"# URL templating\n" +"Destination URLs can be dynamically constructed using templates. Pattern `$KEYWORD$` is replaced in the template with the " +"resolved value and `$KEYWORD%%0Nd$` is replaced in the template with the resolved integer, padded with up to N zeros if needed.\n" +"`KEYWORD` is **case sensitive**, and may be present multiple times in the string. Supported `KEYWORD`:\n" +"- num: replaced by file number if defined, 0 otherwise\n" +"- PID: ID of the source PID\n" +"- URL: URL of source file\n" +"- File: path on disk for source file; if not found, use URL if set, or PID name otherwise\n" +"- Type: name of stream type of PID (`video`, `audio` ...)\n" +"- p4cc=ABCD: uses PID property with 4CC value `ABCD`\n" +"- pname=VAL: uses PID property with name `VAL`\n" +"- OTHER: locates property 4CC for the given name, or property name if no 4CC matches.\n" +" \n" +"`$$` is an escape for $\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:clone\n" +"This will create a cropped version of the source, encoded in AVC at 1M, and a full version of the content in AVC at 750k. " +"Outputs will be `dump_0x0x320x180.264` for the cropped version and `dump_0x0x640x360.264` for the non-cropped one.\n" +"# Cloning filters\n" +"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:\n" +"EX gpac -i img.heif -o dump_$ItemID$.jpg\n" +"In this case, only one item (likely the first declared in the file) will connect to the destination.\n" +"Other items will not be connected since the destination only accepts one input PID.\n" +"There is a special option `clone` allowing filters to be cloned with the same arguments. The cloned filters have the same ID as the original one.\n" +"EX gpac -i img.heif -o dump_$ItemID$.jpg:clone\n" +"In this case, the destination will be cloned for each item, and all will be exported to different JPEGs thanks to URL templating.\n" +"EX gpac -i vid.mpd c=avc:FID=1:clone -o transcode.mpd:SID=1\n" +"In this case, the encoder will be cloned for each video PIDs in the source, and the destination will only use PIDs coming from the encoders.\n" +"\n" +"When implicit linking is enabled, all filters are by default clonable. This allows duplicating the processing for each PIDs of the same type.\n" +"EX gpac -i dual_audio resample:osr=48k c=aac -o dst\n" +"The `resampler` filter will be cloned for each audio PID, and the encoder will be cloned for each resampler output.\n" +"You can explicitly deactivate the cloning instructions:\n" +"EX gpac -i dual_audio resample:osr=48k:clone=0 c=aac -o dst\n" +"The first audio will connect to the `resample` filter, the second to the `enc` filter and the `resample` output will connect to a clone of the `enc` filter.\n" +"\n" +"# Templating filter chains\n" +"There can be cases where the number of desired outputs depends on the source content, for example dumping a multiplex of N services into N files. When the destination involves multiplexing the input PIDs, the `:clone` option is not enough since the multiplexer will always accept the input PIDs.\n" +"To handle this, it is possible to use a PID property name in the sourceID of a filter with the value `*` or an empty value. In this case, whenever a new PID with a new value for the property is found, the filter with such sourceID will be dynamically cloned.\n" +"Warning: This feature should only be called with a single property set to `*` (or empty) per source ID, results are undefined otherwise.\n" +"EX gpac -i source.ts -o file_$ServiceID$.mp4:SID=*#ServiceID=*\n" +"EX gpac -i source.ts -o file_$ServiceID$.mp4:SID=#ServiceID=\n" +"In this case, each new `ServiceID` value found when connecting PIDs to the destination will create a new destination file.\n" +"\n" +"Cloning in implicit linking mode applies to output as well:\n" +"EX gpac -i dual_audio -o dst_$PID$.aac\n" +"Each audio track will be dumped to aac (potentially reencoding if needed).\n" +"\n" +"# Assigning PID properties\n" +"It is possible to define properties on output PIDs that will be declared by a filter. This allows tagging parts of the " +"graph with different properties than other parts (for example `ServiceID`). " +"The syntax is the same as filter option, and uses the fragment separator to identify properties, e.g. `#Name=Value`.\n" +"This sets output PIDs property (4cc, built-in name or any name) to the given value. Value can be omitted for boolean " +"(defaults to true, e.g. `:#Alpha`).\n" +"Non built-in properties are parsed as follows:\n" +"- `file@FOO` will be declared as string with a value set to the content of `FOO`.\n" +"- `bxml@FOO` will be declared as data with a value set to the binarized content of `FOO`.\n" +"- `FOO` will be declared as string with a value set to `FOO`.\n" +"- `TYPE@FOO` will be parsed according to `TYPE`. If the type is not recognized, the entire value is copied as string. See `gpac -h props` for defined types.\n" +"\n" +"User-assigned PID properties on filter `fA` will be inherited by all filters dynamically loaded to solve `fA -> fB` connection.\n" +"If `fB` also has user-assigned PID properties, these only apply starting from `fB` in the chain and are not inherited by filters between `fA` and `fB`.\n" +"\n" +"Warning: Properties are not filtered and override the properties of the filter's output PIDs, be careful not to break " +"the session by overriding core properties such as width/height/samplerate/... !\n" +"EX gpac -i v1.mp4:#ServiceID=4 -i v2.mp4:#ServiceID=2 -o dump.ts\n" +"This will multiplex the streams in `dump.ts`, using `ServiceID` 4 for PIDs from `v1.mp4` and `ServiceID` 2 for PIDs from `v2.mp4`.\n" +"\n" +"PID properties may be conditionally assigned by checking other PID properties. The syntax uses parenthesis (not configurable) after the property assignment sign:\n" +"`#Prop=(CP=CV)VAL`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV`.\n" +"`#Prop=(CP=CV)VAL,(CP2=CV2)VAL2`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV`, and to `VAL2` for PIDs with property `CP2` equal to `CV2`.\n" +"`#Prop=(CP=CV)(CP2=CV2)VAL`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV` and property `CP2` equal to `CV2`.\n" +"`#Prop=(CP=CV)VAL,()DEFAULT`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV`, or to `DEFAULT` for other PIDs.\n" +"The condition syntax is the same as source ID fragment syntax.\n" +"Note: When set, the default value (empty condition) always matches the PID, therefore it should be placed last in the list of conditions.\n" +"EX gpac -i source.mp4:#MyProp=(audio)\"Super Audio\",(video)\"Super Video\"\n" +"This will assign property `MyProp` to `Super Audio` for audio PIDs and to `Super Video` for video PIDs.\n" +"EX gpac -i source.mp4:#MyProp=(audio1)\"Super Audio\"\n" +"This will assign property `MyProp` to `Super Audio` for first audio PID declared.\n" +"EX gpac -i source.mp4:#MyProp=(Width+1280)HD\n" +"This will assign property `MyProp` to `HD` for PIDs with property `Width` greater than 1280.\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" +"An option file is a simple text file containing one or more options or PID properties on one or more lines.\n" +"A line beginning with \"//\" is a comment and is ignored.\n" +"Options in an option file may point to other option files, with a maximum redirection level of 5.\n" +"An option file declaration (`filter:myopts.txt`) follows the same inheritance rules as regular options.\n" +"EX gpac -i source.mp4:myopts.txt:foo=bar -o dst\n" +"Any filter loaded between `source.mp4` and `dst` will inherit both `myopts.txt` and `foo` options and will resolve options and PID properties given in `myopts.txt`.\n" +"# Specific filter options\n" +"Some specific keywords are replaced when processing filter options.\n" +"Warning: These keywords do not apply to PID properties. Multiple keywords cannot be defined for a single option.\n" +"Defined keywords:\n" +"- $GSHARE: replaced by system path to GPAC shared directory (e.g. /usr/share/gpac)\n" +"- $GJS: replaced by the first path from global share directory and paths set through [-js-dirs](CORE) that contains the file name following the macro, e.g. $GJS/source.js\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" +"\n" +"The `$GINC` construct can be used to dynamically assign numbers in filter chains:\n" +"EX gpac -i source.ts tssplit @#ServiceID= -o dump_$GINC(10,2).ts\n" +"This will dump first service in dump_10.ts, second service in dump_12.ts, etc...\n" +"\n" +"As seen previously, the following options may be set on any filter, but are not visible in individual filter help:\n" +"- FID: filter identifier\n" +"- SID: filter source(s)\n" +"- N: filter name\n" +"- FS: sub-session identifier\n" +"- TAG: filter tag\n" +"- clone: filter cloning flag\n" +"- nomux: enable/disable direct file copy\n" +"- gfreg: preferred filter registry names for link solving\n" +"- gfloc: following options are local to filter declaration (not inherited)\n" +"- gfopt: following options are not tracked\n" +"- gpac: argument separator for URLs\n" +"\n" +"# External filters\n" +"GPAC comes with a set of built-in filters in libgpac. It may also load external filters in dynamic libraries, located in " +"default module folder or folders listed in [-mod-dirs](CORE) option. The files shall be named `gf_*` and shall export" +" a single function `RegisterFilter` returning a filter register - see [libgpac documentation](https://doxygen.gpac.io/) for more details.\n" +"\n"; +#endif + +static void gpac_filter_help(void) +{ + gf_sys_format_help(helpout, help_flags, +"Usage: gpac [options] FILTER [LINK] FILTER [...] \n" +#ifndef GPAC_DISABLE_DOC + "%s", gf_sys_localized("gpac", "doc", gpac_doc) +#else + "GPAC compiled without built-in doc.\n" +#endif + ); + +} + +#include +#include +#include +#include +#include + +static void gpac_modules_help(void) +{ + u32 i; + gf_sys_format_help(helpout, help_flags, "Available modules:\n"); + for (i=0; i= GF_ARGMODE_EXPERT) { + +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "%s", gf_sys_localized("gpac", "alias", gpac_alias) ); +#else + gf_sys_format_help(helpout, help_flags, "%s", "GPAC compiled without built-in doc.\n"); +#endif + if (argmode == GF_ARGMODE_EXPERT) { + return; + } + } + + count = gf_opts_get_key_count("gpac.alias"); + if (count) { + if (argmode < GF_ARGMODE_EXPERT) { + gf_sys_format_help(helpout, help_flags, "Available aliases (use 'gpac -hx alias' for more info on aliases):\n"); + } else { + gf_sys_format_help(helpout, help_flags, "Available aliases:\n"); + } + for (i=0; i=GF_ARGMODE_ADVANCED) + gf_sys_format_help(helpout, help_flags, " (%s)", alias_value); + + if (alias_doc) + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_OPT_DESC, ": %s", gf_sys_localized("gpac", "aliasdoc", alias_doc)); + else if (argmodedst cap bundle detail)\n" + "- links FNAME: print sources and sinks for filter `FNAME` (either builtin or JS filter)\n" + "- 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" + " - By default only basic filter options and description are shown. Use `-ha` to show advanced options capabilities, `-hx` for expert options, `-hh` for all options and filter capabilities including on filters disabled in this build\n" + "- FNAME.OPT: print option `OPT` in filter `FNAME`\n" + "- OPT: look in filter names and options for `OPT` and suggest possible matches if none found. Use `-hx` to look for keyword in all option descriptions\n" + , NULL, NULL, GF_ARG_STRING, 0), + + GF_DEF_ARG("p", NULL, "use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. Reserved name `0` means a new profile, not stored to disk. Appending `:reload` to the profile name will force recreating a new configuration file", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + 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("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), + GF_DEF_ARG("wf", NULL, "write all filter options in the config file unless already set", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("wfx", NULL, "write all filter options and all meta filter arguments in the config file unless already set (__large config file !__)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("unit-tests", NULL, "enable unit tests of some functions otherwise not covered by gpac test suite", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_HIDE), + 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), + {0} +}; + + +static void gpac_usage(GF_SysArgMode argmode) +{ + u32 i=0; + if ((argmode != GF_ARGMODE_ADVANCED) && (argmode != GF_ARGMODE_EXPERT) ) { + if (gen_doc!=2) + gf_sys_format_help(helpout, help_flags, "Usage: gpac [options] FILTER [LINK] FILTER [...] \n"); + + gf_sys_format_help(helpout, help_flags, "gpac is GPAC's command line tool for setting up and running filter chains.\n\n" + "__FILTER__: a single filter declaration (e.g., `-i file`, `-o dump`, `inspect`, ...), see %s.\n" + "__[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" + " \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" + "Boolean values do not need any value specified. Other types shall be formatted as `opt=val`, except [-i](), `-src`, [-o](), `-dst` and [-h]() options.\n\n" + "\n" + "The session can be interrupted at any time using `ctrl+c`, which can also be used to toggle global reporting.\n" + "\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 -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`", + (gen_doc==1) ? "[gpac -h doc](core_config#global-filter-options)" : "`gpac -h doc`" + ); + } + + while (gpac_args[i].name) { + GF_GPACArg *arg = &gpac_args[i]; + i++; + + if (argmode!=GF_ARGMODE_ALL) { + if ((argmode==GF_ARGMODE_EXPERT) && !(arg->flags & GF_ARG_HINT_EXPERT)) continue; + if ((argmode==GF_ARGMODE_ADVANCED) && !(arg->flags & GF_ARG_HINT_ADVANCED)) continue; + else if ((argmode==GF_ARGMODE_BASE) && (arg->flags & (GF_ARG_HINT_ADVANCED| GF_ARG_HINT_EXPERT)) ) continue; + } + + gf_sys_print_arg(helpout, help_flags, arg, "gpac"); + } + + if (argmode>=GF_ARGMODE_ADVANCED) { + gf_sys_format_help(helpout, help_flags, "\n \nThe following libgpac core options allow customizing the filter session:\n \n" ); + gf_sys_print_core_help(helpout, help_flags, argmode, GF_ARG_SUBSYS_FILTERS); + } + + if (argmode==GF_ARGMODE_BASE) { + if ( gf_opts_get_key_count("gpac.aliasdoc")) { + gf_sys_format_help(helpout, help_flags, "\n"); + gpac_alias_help(GF_ARGMODE_BASE); + } + + gf_sys_format_help(helpout, help_flags, "\ngpac - GPAC command line filter engine - version %s\n%s\n", gf_gpac_version(), gf_gpac_copyright_cite() ); + } +} + +#ifndef GPAC_DISABLE_DOC +static const char *gpac_config = +{ +"# Configuration file\n" +"GPAC uses a configuration file to modify default options of libgpac and filters. This configuration file is located in `$HOME/.gpac/GPAC.cfg`.\n" +"Applications in GPAC can also specify a different configuration file through the [-p](GPAC) option to indicate a profile. " +"This allows different configurations for different usages and simplifies command line typing.\n" +"EX gpac -p=foo []\n" +"This will load configuration from $HOME/.gpac/foo/GPAC.cfg, creating it if needed.\n" +"The reserved name `0` is used to disable configuration file writing.\n" +"By default the configuration file only holds a few system specific options and directories. It is possible to serialize " +"the entire set of options to the configuration file, using [-wc](GPAC) [-wf](GPAC). This should be avoided as the resulting " +"configuration file size will be quite large, hence larger memory usage for the applications.\n" +"The options specified in the configuration file may be overridden by the values in __restrict.cfg__ file located in GPAC share system directory (e.g. /usr/share/gpac), " +"if present; this allows enforcing system-wide configuration values.\n" +"Note: The methods describe in this section apply to any application in GPAC transferring their arguments " +"to libgpac. This is the case for __gpac__, __MP4Box__, __MP4Client/Osmo4__.\n" +"# Core options\n" +"The options from libgpac core can also be assigned though the config file from section __core__ using " +"option name **without initial dash** as key name.\n" +"EX [core]threads=2\n" +"Setting this in the config file is equivalent to using `-threads=2`.\n" +"The options specified at prompt overrides the value of the config file.\n" +"# Filter options in configuration\n" +"It is possible to alter the default value of a filter option by modifying the configuration file. Filter __foo__ options are stored in section " +"`[filter@foo]`, using option name and value as key-value pair. Options specified through the configuration file do not take precedence over " +"options specified at prompt or through alias.\n" +"EX [filter@rtpin]interleave=yes\n" +"This will force the rtp input filter to always request RTP over RTSP by default.\n" +"To generate a configuration file with all filters options serialized, use [-wf](GPAC).\n" +"# Global filter options\n" +"It is possible to specify options global to multiple filters using `--OPTNAME=VAL`. Global options do not override filter options but " +"take precedence over options loaded from configuration file.\n" +"This will set option `OPTNAME`, when present, to `VAL` in any loaded filter.\n" +"EX --buffer=100 -i file vout aout\n" +"This is equivalent to specifying `vout:buffer=100 aout:buffer=100`.\n" +"EX --buffer=100 -i file vout aout:buffer=10\n" +"This is equivalent to specifying `vout:buffer=100 aout:buffer=10`.\n" +"Warning: This syntax only applies to regular filter options. It cannot be used with builtin shortcuts (gfreg, enc, ...).\n" +"Meta-filter options can be set in the same way using the syntax `--OPT_NAME=VAL`.\n" +"EX --profile=Baseline -i file.cmp -o dump.264\n" +"This is equivalent to specifying `-o dump.264:profile=Baseline`.\n" +" \n" +"For both syntax, it is possible to specify the filter registry name of the option, using `--FNAME:OPTNAME=VAL` or `--FNAME@OPTNAME=VAL`.\n" +"In this case the option will only be set for filters which are instances of registry FNAME. This is used when several registries use same option names.\n" +"EX --flist@timescale=100 -i plist1 -i plist2 -o live.mpd\n" +"This will set the timescale option on the playlists filters but not on the dasher filter.\n" +}; +#endif + +static void gpac_config_help() +{ + +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "%s", gf_sys_localized("gpac", "config", gpac_config) ); +#else + gf_sys_format_help(helpout, help_flags, "%s", "GPAC compiled without built-in doc.\n"); +#endif +} + + +static Bool logs_to_file=GF_FALSE; + +#define DEF_LOG_ENTRIES 10 + +struct _logentry +{ + u32 tool, level; + u32 nb_repeat; + u64 clock; + char *szMsg; +} *static_logs; +static u32 nb_log_entries = DEF_LOG_ENTRIES; + +static u32 log_write=0; + +static char *log_buf = NULL; +static u32 log_buf_size=0; +static void gpac_on_logs(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist) +{ + va_list vlist_tmp; + va_copy(vlist_tmp, vlist); + u32 len = vsnprintf(NULL, 0, fmt, vlist_tmp); + va_end(vlist_tmp); + if (log_buf_size < len+2) { + log_buf_size = len+2; + log_buf = gf_realloc(log_buf, log_buf_size); + } + vsprintf(log_buf, fmt, vlist); + + if (log_write && static_logs[log_write-1].szMsg) { + if (!strcmp(static_logs[log_write-1].szMsg, log_buf)) { + static_logs[log_write-1].nb_repeat++; + return; + } + } + + static_logs[log_write].level = log_level; + static_logs[log_write].tool = log_tool; + if (static_logs[log_write].szMsg) gf_free(static_logs[log_write].szMsg); + static_logs[log_write].szMsg = gf_strdup(log_buf); + static_logs[log_write].clock = gf_net_get_utc(); + + log_write++; + if (log_write==nb_log_entries) { + log_write = nb_log_entries - 1; + gf_free(static_logs[0].szMsg); + memmove(&static_logs[0], &static_logs[1], sizeof (struct _logentry) * (nb_log_entries-1) ); + memset(&static_logs[log_write], 0, sizeof(struct _logentry)); + } +} + +static u64 last_report_clock_us = 0; +static void print_date(u64 time) +{ + time_t gtime; + struct tm *t; + u32 sec; + u32 ms; + gtime = time / 1000; + sec = (u32)(time / 1000); + ms = (u32)(time - ((u64)sec) * 1000); + t = gf_gmtime(>ime); +// fprintf(stderr, "[%d-%02d-%02dT%02d:%02d:%02d.%03dZ", 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, ms); + fprintf(stderr, "[%02d:%02d:%02d.%03dZ] ", t->tm_hour, t->tm_min, t->tm_sec, ms); +} + +static Bool in_sig_handler = GF_FALSE; +static void gpac_print_report(GF_FilterSession *fsess, Bool is_init, Bool is_final) +{ + u32 i, count, nb_active; + u64 now; + + if (in_sig_handler) return;; + + if (is_init) { + if (enable_reports==2) + gf_sys_set_console_code(stderr, GF_CONSOLE_SAVE); + + logs_to_file = gf_log_use_file(); + if (!logs_to_file && (enable_reports==2) ) { + if (!nb_log_entries) nb_log_entries = 1; + static_logs = gf_malloc(sizeof(struct _logentry) * nb_log_entries); + memset(static_logs, 0, sizeof(struct _logentry) * nb_log_entries); + gf_log_set_callback(fsess, gpac_on_logs); + } + last_report_clock_us = gf_sys_clock_high_res(); + return; + } + + now = gf_sys_clock_high_res(); + if ( (now - last_report_clock_us < 200000) && !is_final) + return; + + last_report_clock_us = now; + if (!is_final) + gf_sys_set_console_code(stderr, GF_CONSOLE_CLEAR); + + gf_sys_get_rti(100, &rti, 0); + gf_sys_set_console_code(stderr, GF_CONSOLE_CYAN); + print_date(gf_net_get_utc()); + fprintf(stderr, "GPAC Session Status: "); + gf_sys_set_console_code(stderr, GF_CONSOLE_RESET); + fprintf(stderr, "mem % 10"LLD_SUF" kb CPU % 2d", rti.gpac_memory/1000, rti.process_cpu_time); + fprintf(stderr, "\n"); + + gf_fs_lock_filters(fsess, GF_TRUE); + nb_active = count = gf_fs_get_filters_count(fsess); + for (i=0; itype==GF_EVENT_PROGRESS) { + if (event->progress.progress_type==3) { + gpac_print_report(fsess, GF_FALSE, GF_FALSE); + } + } + else if (event->type==GF_EVENT_QUIT) { + gf_fs_abort(fsess, GF_FS_FLUSH_ALL); + } + return GF_FALSE; +} + + +typedef enum +{ + GPAC_COM_UNDEF = 0, + GPAC_QUIT, + GPAC_EXIT, + GPAC_PRINT_STATS, + GPAC_PRINT_GRAPH, + GPAC_SEND_UPDATE, + GPAC_LIST_FILTERS, + GPAC_INSERT_FILTER, + GPAC_REMOVE_FILTER, + GPAC_PRINT_HELP +} GPAC_Command; + +static struct _gpac_key +{ + u8 char_code; + GPAC_Command cmd_type; + const char *cmd_help; + u32 flags; +} GPAC_Keys[] = { + {'q', GPAC_QUIT, "flush all streams and exit", 0}, + {'x', GPAC_EXIT, "exit with no flush (may break output files)", 0}, + {'s', GPAC_PRINT_STATS, "print statistics", 0}, + {'g', GPAC_PRINT_GRAPH, "print filter graph", 0}, + {'l', GPAC_LIST_FILTERS, "list filters", 0}, + {'u', GPAC_SEND_UPDATE, "update argument of filter", 0}, + {'i', GPAC_INSERT_FILTER, "insert a filter in the chain", 0}, + {'r', GPAC_REMOVE_FILTER, "remove a filter from the chain", 0}, + {'h', GPAC_PRINT_HELP, "print this help", 0}, + {0} +}; + +static GPAC_Command get_cmd(u8 char_code) +{ + u32 i=0; + while (GPAC_Keys[i].char_code) { + if (GPAC_Keys[i].char_code == char_code) + return GPAC_Keys[i].cmd_type; + i++; + } + return GPAC_COM_UNDEF; +} + +static void gpac_fsess_task_help() +{ + gf_sys_format_help(helpout, help_flags, "Available runtime options/keys:\n"); + + u32 i=0; + while (GPAC_Keys[i].char_code) { + gf_sys_format_help(helpout, help_flags, "- %c: %s\n", GPAC_Keys[i].char_code, GPAC_Keys[i].cmd_help); + i++; + } +} + +static char szFilter[100]; +static char szCom[2048]; +static u64 run_start_time = 0; +static Bool gpac_fsess_task(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms) +{ + if (enable_prompt && gf_prompt_has_input()) { + u32 i, count; + GF_Filter *filter; + const GF_Filter *link_from=NULL; + GF_Err e; + char *link_args = NULL; + GPAC_Command c = get_cmd(gf_prompt_get_char()); + switch (c) { + case GPAC_QUIT: + gf_fs_abort(fsess, GF_FS_FLUSH_ALL); + nb_loops = 0; + return GF_FALSE; + case GPAC_EXIT: + gf_fs_abort(fsess, GF_FS_FLUSH_NONE); + nb_loops = 0; + return GF_FALSE; + case GPAC_PRINT_STATS: + gf_fs_print_stats(fsess); + break; + case GPAC_PRINT_GRAPH: + gf_fs_print_connections(fsess); + break; + case GPAC_PRINT_HELP: + gpac_fsess_task_help(); + break; + case GPAC_SEND_UPDATE: + fprintf(stderr, "Sending filter update - enter the target filter ID, name, registry name or #N with N the 0-based index in loaded filter list:\n"); + + if (1 > scanf("%99s", szFilter)) { + fprintf(stderr, "Cannot read the filter ID, aborting.\n"); + break; + } + e = GF_OK; + if (szFilter[0]=='#') { + GF_FilterStats stats; + u32 idx = atoi(szFilter+1); + gf_fs_lock_filters(fsess, GF_TRUE); + e = gf_fs_get_filter_stats(fsess, idx, &stats); + gf_fs_lock_filters(fsess, GF_FALSE); + if (e==GF_OK) + strncpy(szFilter, stats.filter_id ? stats.filter_id : stats.name, 99); + } + if (e) { + fprintf(stderr, "Cannot get filter for ID %s, aborting.\n", szFilter); + break; + } + fprintf(stderr, "Enter the command to send\n"); + if (1 > scanf("%2047s", szCom)) { + fprintf(stderr, "Cannot read the command, aborting.\n"); + break; + } + gf_fs_send_update(fsess, szFilter, NULL, szCom, NULL, 0); + break; + case GPAC_LIST_FILTERS: + gf_fs_lock_filters(fsess, GF_TRUE); + count = gf_fs_get_filters_count(fsess); + fprintf(stderr, "Loaded filters:\n"); + for (i=0; i scanf("%99s", szFilter)) { + fprintf(stderr, "Cannot read the filter ID or index, aborting.\n"); + break; + } + link_args = strchr(szFilter, separator_set[SEP_LINK]); + if (link_args) { + link_args[0] = 0; + link_args++; + } + if (szFilter[0]=='#') { + GF_FilterStats stats; + u32 idx = atoi(szFilter+1); + gf_fs_lock_filters(fsess, GF_TRUE); + gf_fs_get_filter_stats(fsess, idx, &stats); + gf_fs_lock_filters(fsess, GF_FALSE); + link_from = stats.filter; + } else { + const GF_Filter *link_from_by_reg=NULL; + gf_fs_lock_filters(fsess, GF_TRUE); + count = gf_fs_get_filters_count(fsess); + for (i=0; i scanf("%2047s", szCom)) { + fprintf(stderr, "Cannot read the filter to insert, aborting.\n"); + break; + } + + if (!strncmp(szCom, "src=", 4)) { + filter = gf_fs_load_source(fsess, szCom+4, NULL, NULL, &e); + } else if (!strncmp(szCom, "dst=", 4)) { + filter = gf_fs_load_destination(fsess, szCom+4, NULL, NULL, &e); + } else { + filter = gf_fs_load_filter(fsess, szCom, &e); + } + + if (!filter) { + fprintf(stderr, "Cannot load filter %s: %s\n", szCom, gf_error_to_string(e)); + break; + } + gf_filter_set_source(filter, (GF_Filter *) link_from, link_args); + //reconnect outputs of source + gf_filter_reconnect_output((GF_Filter *) link_from); + break; + default: + break; + } + } + if (runfor>0) { + u64 now = gf_sys_clock_high_res(); + if (!run_start_time) { + run_start_time = now; + } else if (now - run_start_time > runfor) { + //segfault requested + if (exit_mode==1) { + exit(127); + } + //deadlock requested + else if (exit_mode==2) { + while(1) { + gf_sleep(1); + } + } + if (nb_loops || loops_done) { + gf_fs_abort(fsess, runfor_exit ? GF_FS_FLUSH_NONE : GF_FS_FLUSH_ALL); + run_start_time = 0; + } else { + if (runfor_exit) + exit(0); + gf_fs_abort(fsess, GF_FS_FLUSH_ALL); + } + return GF_FALSE; + } + } + + if (gf_fs_is_last_task(fsess)) + return GF_FALSE; + //check every 50 ms + *reschedule_ms = 50; + return GF_TRUE; +} + +static Bool prev_was_cmd=GF_FALSE; +static Bool signal_catched=GF_FALSE; +static Bool signal_processed=GF_FALSE; +#ifdef WIN32 +#include +static BOOL WINAPI gpac_sig_handler(DWORD sig) +{ + if (sig == CTRL_C_EVENT) { + Bool is_inter = GF_TRUE; +#else +#include +static void gpac_sig_handler(int sig) +{ + if ((sig == SIGINT) || (sig == SIGTERM) || (sig == SIGABRT)) { + Bool is_inter = (sig == SIGINT) ? GF_TRUE : GF_FALSE; +#endif + nb_loops = 0; + if (session) { + char input=0; + int res; + if (signal_catched) { + if (signal_processed) { + fprintf(stderr, "catched SIGINT|SIGTERM twice and session not responding, forcing exit.\n"); + } + exit(1); + } + + signal_catched = GF_TRUE; + if (is_inter) { + in_sig_handler = GF_TRUE; + fprintf(stderr, "\nToggle reports (r) or flush session before exit (Y/f/n) ? \n"); +rescan: + res = scanf("%c", &input); + if (res!=1) input=0; + switch (input) { + case 'Y': + case 'y': + signal_processed = GF_TRUE; + gf_fs_abort(session, GF_FS_FLUSH_FAST); + break; + case 'F': + case 'f': + signal_processed = GF_TRUE; + gf_fs_abort(session, GF_FS_FLUSH_ALL); + break; + case 0: + break; + case '\n': + //prev was a command, flush \n + if (prev_was_cmd) { + prev_was_cmd = GF_FALSE; + goto rescan; + } + break; + + case 'R': + case 'r': + prev_was_cmd = GF_TRUE; + if (!enable_reports) { + enable_reports = 2; + report_filter = NULL; + gf_fs_set_ui_callback(session, gpac_event_proc, session); + gf_fs_enable_reporting(session, GF_TRUE); + in_sig_handler = GF_FALSE; + gpac_print_report(session, GF_TRUE, GF_FALSE); + } else { + enable_reports = 0; + gf_fs_enable_reporting(session, GF_FALSE); + gf_sys_set_console_code(stderr, GF_CONSOLE_CLEAR); + gf_sys_set_console_code(stderr, GF_CONSOLE_RESTORE); + } + signal_catched = GF_FALSE; + signal_processed = GF_FALSE; + break; + default: + signal_processed = GF_TRUE; + gf_fs_abort(session, GF_FS_FLUSH_NONE); + break; + } + in_sig_handler = GF_FALSE; + } else { + signal_processed = GF_TRUE; + gf_fs_abort(session, GF_FS_FLUSH_NONE); + } + } + } +#ifdef WIN32 + return TRUE; +#endif +} + +static void parse_sep_set(const char *arg, Bool *override_seps) +{ + if (!arg) return; + u32 len = (u32) strlen(arg); + if (!len) return; + *override_seps = GF_TRUE; + if (len>=1) separator_set[0] = arg[0]; + if (len>=2) separator_set[1] = arg[1]; + if (len>=3) separator_set[2] = arg[2]; + if (len>=4) separator_set[3] = arg[3]; + if (len>=5) separator_set[4] = arg[4]; + if (len>=6) separator_set[5] = arg[5]; +} + + +static int gpac_exit_fun(int code, char **alias_argv, int alias_argc) +{ + u32 i; + if (code>=0) { + for (i=1; i +static void gpac_load_suggested_filter_args() +{ + u32 i, count, k; + GF_Config *opts = NULL; + GF_FilterSession *fsess; + const char *version, *cfg_path; + char *all_opts; + + if (gf_opts_get_bool("core", "no-argchk")) + return; + + cfg_path = gf_opts_get_filename(); + all_opts = gf_url_concatenate(cfg_path, "all_opts.txt"); + + opts = gf_cfg_force_new("probe", all_opts); + gf_free(all_opts); + + version = gf_cfg_get_key(opts, "version", "version"); + if (version && !strcmp(version, gf_gpac_version()) ) { + gf_cfg_del(opts); + return; + } + gf_cfg_del_section(opts, "allopts"); + gf_cfg_set_key(opts, "version", "version", gf_gpac_version()); + + gf_sys_format_help(stderr, 0, "__Refreshing all options registry, this may take some time ... "); + + fsess = gf_fs_new(0, GF_FS_SCHEDULER_LOCK_FREE, GF_FS_FLAG_LOAD_META, NULL); + if (!fsess) { + gf_sys_format_help(stderr, 0, "\nWarning: Error creating session\n"); + gf_cfg_del(opts); + return; + } + + count = gf_fs_filters_registers_count(fsess); + for (i=0; iargs) { + const char *old_val; + char *argn=NULL; + const GF_FilterArgs *arg = &freg->args[k]; + if (!arg || !arg->arg_name) { + break; + } + k++; + if (arg->flags & GF_FS_ARG_HINT_HIDE) continue; + + if (arg->flags & GF_FS_ARG_META) { + gf_dynstrcat(&argn, "-+", NULL); + } else { + gf_dynstrcat(&argn, "--", NULL); + } + gf_dynstrcat(&argn, arg->arg_name, NULL); + if ((arg->arg_type==GF_PROP_UINT) && arg->min_max_enum && strchr(arg->min_max_enum, '|')) { + gf_dynstrcat(&argn, arg->min_max_enum, "@"); + } + + old_val = gf_cfg_get_key(opts, "allopts", argn); + if (old_val) { + char *newval = gf_strdup(freg->name); + gf_dynstrcat(&newval, old_val, " "); + gf_cfg_set_key(opts, "allopts", argn, newval); + gf_free(newval); + } else { + gf_cfg_set_key(opts, "allopts", argn, freg->name); + } + gf_free(argn); + } + } + gf_fs_del(fsess); + gf_cfg_del(opts); + gf_sys_format_help(stderr, 0, "done\n"); +} + + +static void gpac_suggest_arg(char *aname) +{ + u32 k; + const GF_GPACArg *args; + if (!aname) return; + Bool found=GF_FALSE; + for (k=0; k<2; k++) { + u32 i=0; + if (k==0) args = gpac_args; + else args = gf_sys_get_options(); + + while (args[i].name) { + const GF_GPACArg *arg = &args[i]; + i++; + if (gf_sys_word_match(aname, arg->name)) { + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Unrecognized option \"%s\", did you mean:\n", aname)); + found = GF_TRUE; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("\t-%s (see gpac -hx%s)\n", arg->name, k ? " core" : "")); + } + } + } + //look in alias + u32 nb_alias = gf_opts_get_key_count("gpac.alias"); + for (k=0; kname)) { + if (!first) { + first = GF_TRUE; + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No such filter %s\n", fname)); + found = GF_TRUE; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Closest filter names: \n")); + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("- %s\n", freg->name)); + } + } + } + + if (is_help) { + if (!pass_exact) { + const char *doc_helps[] = { + "log", "core", "modules", "doc", "alias", "props", "colors", "layouts", "cfg", "prompt", "codecs", "links", "bin", "filters", "filters:*", "filters:@", NULL + }; + first = GF_FALSE; + i=0; + while (doc_helps[i]) { + if (gf_sys_word_match(fname, doc_helps[i])) { + if (!first) { + first = GF_TRUE; + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No such filter %s\n", fname)); + found = GF_TRUE; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Closest help commands: \n")); + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("-h %s\n", doc_helps[i])); + } + i++; + } + } + + if (!filter_only) { + u32 nb_alias = gf_opts_get_key_count("gpac.alias"); + first = GF_FALSE; + for (i=0; iname)) { + if (!first) { + first = GF_TRUE; + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No such filter %s\n", fname)); + found = GF_TRUE; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Closest core option: \n")); + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("-%s: %s\n", arg->name, arg->description)); + } + } + + //check filters + first = GF_FALSE; + for (i=0; iargs) { + const GF_FilterArgs *arg = ®->args[j]; + if (!arg || !arg->arg_name) break; + j++; + + if (pass_exact) { + if (!strcmp(fname, arg->arg_name)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s %s\n", reg->name, arg->arg_name, arg->arg_desc); + found = GF_TRUE; + } + continue; + } + if (!gf_sys_word_match(fname, arg->arg_name)) continue; + + if (!first) { + first = GF_TRUE; + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No such filter %s\n", fname)); + found = GF_TRUE; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Closest matching filter options:\n", fname)); + } + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s \n", reg->name, arg->arg_name); + } + } + } + } + if (!found) { + if (pass_exact) { + pass_exact = GF_FALSE; + goto redo_pass; + } + + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No filter%swith similar name found, see gpac -h filters%s\n", + is_help ? ", option or help command " : " ", + is_help ? ", gpac -h or search all help using gpac -hx" : "" + )); + } +} + +static void gpac_suggest_filter_arg(GF_Config *opts, char *argname, u32 atype) +{ + char *keyname; + const char *keyval; + u32 i, count, len, nb_filters, j; + Bool f_found = GF_FALSE; + char szSep[2]; + + if (gf_opts_get_bool("core", "no-argchk")) + return; + + szSep[0] = separator_set[0]; + szSep[1] = 0; + + len = (u32) strlen(argname); + keyname = gf_malloc(sizeof(char)*(len+3)); + sprintf(keyname, "-%c%s", (atype==2) ? '+' : '-', argname); + keyval = gf_cfg_get_key(opts, "allopts", keyname); + gf_free(keyname); + if (keyval) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Argument \"%s%s\" was set but not used by any filter\n", + (atype==2) ? "-+" : (atype ? "--" : szSep), argname)); + return; + } + + nb_filters = gf_fs_get_filters_count(session); + count = gf_cfg_get_key_count(opts, "allopts"); + for (i=0; i2*len) { + if (sep) sep[0] = '@'; + continue; + } + + for (j=0; j=2) || print_meta_filters || dump_codecs || print_filter_info) sflags |= GF_FS_FLAG_LOAD_META; + + if (view_filter_conn || list_filters || (print_filter_info && (argmode == GF_ARGMODE_ALL)) ) + gf_opts_set_key("temp", "gendoc", "yes"); + + if (list_filters || print_filter_info) + gf_opts_set_key("temp", "helponly", "yes"); + +restart: + prev_filter_is_sink = 0; + current_subsession_id = 0; + prev_filter_is_not_source = 0; + current_source_id = 0; + + if (view_conn_for_filter && argmode>=GF_ARGMODE_EXPERT) + sflags |= GF_FS_FLAG_PRINT_CONNECTIONS; + + session = gf_fs_new_defaults(sflags); + + if (!session) { + gpac_exit(1); + } + if (override_seps) gf_fs_set_separators(session, separator_set); + if (load_test_filters) gf_fs_register_test_filters(session); + + if (gf_fs_get_max_resolution_chain_length(session) <= 1 ) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\nDynamic resolution of filter connections disabled\n\n")); + } + + if (list_filters || print_filter_info) { + if (print_filters(argc, argv, session, argmode)==GF_FALSE) + e = GF_FILTER_NOT_FOUND; + goto exit; + } + if (view_filter_conn) { + gf_fs_print_all_connections(session, view_conn_for_filter, gf_sys_format_help); + goto exit; + } + if (dump_codecs) { + dump_all_codec(session); + goto exit; + } + if (write_profile || write_extensions || write_core_opts) { + if (write_core_opts) + write_core_options(); + if (write_extensions) + write_file_extensions(); + if (write_profile) + write_filters_options(session); + goto exit; + } + + 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) )); + goto exit; + } + } + + //all good to go, load filters + has_xopt = GF_FALSE; + links_directive = gf_list_new(); + loaded_filters = gf_list_new(); + for (i=1; i1) { + if (link[1] == separator_set[SEP_LINK] ) { + reverse_order = GF_TRUE; + link++; + } + link_filter_idx = atoi(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)); + e = GF_BAD_PARAM; + goto exit; + } + } else { + link_filter_idx = 0; + } + if (ext) ext[0] = separator_set[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)); + e = GF_BAD_PARAM; + goto exit; + } + gf_filter_set_source(filter, link_from, link_prev_filter_ext); + } + + gf_list_add(loaded_filters, filter); + + //implicit mode, check changes of source and sinks + if (!(sflags & 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); + } + } + } + if (!gf_list_count(loaded_filters) && !session_js) { + if (nothing_to_do && !gen_doc) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Nothing to do, check usage \"gpac -h\"\ngpac - GPAC command line filter engine - version %s\n%s\n", gf_gpac_version(), gf_gpac_copyright_cite())); + e = GF_BAD_PARAM; + } else { + e = GF_EOS; + } + goto exit; + } + if (enable_reports) { + if (enable_reports==2) + gf_fs_set_ui_callback(session, gpac_event_proc, session); + + gf_fs_enable_reporting(session, GF_TRUE); + } + if (gf_list_count(links_directive)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Link separators specified but no following filter, ignoring links ")); + while (gf_list_count(links_directive)) { + const char *ld = gf_list_pop_front(links_directive); + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("\"%s\"", ld)); + } + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("\n")); + } + + if (enable_prompt || (runfor>0)) { + if (enable_prompt && !loops_done) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Running session, press 'h' for help\n")); + } + gf_fs_post_user_task(session, gpac_fsess_task, NULL, "gpac_fsess_task"); + } + if (!enable_prompt) { +#ifdef WIN32 + SetConsoleCtrlHandler((PHANDLER_ROUTINE)gpac_sig_handler, TRUE); +#else + signal(SIGINT, gpac_sig_handler); + signal(SIGTERM, gpac_sig_handler); +#endif + } + + if (enable_reports) { + gpac_print_report(session, GF_TRUE, GF_FALSE); + } + + if (!session_js) { + u32 j; + Bool has_vout = GF_FALSE; + Bool has_compositor = GF_FALSE; + for (j=0; j0) e = GF_OK; + } + + if (e) { + fprintf(stderr, "session error %s\n", gf_error_to_string(e) ); + } else { + e = gf_fs_get_last_connect_error(session); + if (e<0) fprintf(stderr, "session last connect error %s\n", gf_error_to_string(e) ); + + if (!e) { + e = gf_fs_get_last_process_error(session); + if (e<0) fprintf(stderr, "session last process error %s\n", gf_error_to_string(e) ); + } + gpac_check_session_args(); + } + + if (enable_reports) { + if (enable_reports==2) { + gf_sys_set_console_code(stderr, GF_CONSOLE_RESTORE); + } + gpac_print_report(session, GF_FALSE, GF_TRUE); + } + gf_fs_print_non_connected_ex(session, alias_is_play); + +exit: + if (enable_reports==2) { + gf_log_set_callback(session, NULL); + } + + if (e && nb_filters) { + gf_fs_run(session); + } + if (dump_stats) + gf_fs_print_stats(session); + if (dump_graph) + gf_fs_print_connections(session); + + + tmp_sess = session; + session = NULL; + gf_fs_del(tmp_sess); + if (loaded_filters) gf_list_del(loaded_filters); + if (links_directive) gf_list_del(links_directive); + + cleanup_file_io(); + + if (!e && nb_loops) { + if (nb_loops>0) nb_loops--; + loops_done++; + fprintf(stderr, "session done, restarting (loop %d)\n", loops_done); + fflush(stderr); + gf_log_reset_file(); + goto restart; + } + +#ifdef GPAC_HAS_QJS + if (loops_done) { + void gf_js_delete_runtime(); + gf_js_delete_runtime(); + } +#endif + + gpac_exit(e<0 ? 1 : 0); +} + +GF_MAIN_FUNC(gpac_main) + + +/********************************************************* + Filter and property info/dump functions +*********************************************************/ + +static void dump_caps(u32 nb_caps, const GF_FilterCapability *caps) +{ + u32 i; + for (i=0;iflags & GF_CAPFLAG_IN_BUNDLE) && i+1==nb_caps) 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"); + continue; + } + + szName = cap->name ? cap->name : gf_props_4cc_get_name(cap->code); + if (!szName) szName = gf_4cc_to_str(cap->code); + gf_sys_format_help(helpout, help_flags, "\t Flags:"); + if (cap->flags & GF_CAPFLAG_INPUT) gf_sys_format_help(helpout, help_flags, " Input"); + if (cap->flags & GF_CAPFLAG_OUTPUT) gf_sys_format_help(helpout, help_flags, " Output"); + if (cap->flags & GF_CAPFLAG_EXCLUDED) gf_sys_format_help(helpout, help_flags, " Exclude"); + if (cap->flags & GF_CAPFLAG_LOADED_FILTER) gf_sys_format_help(helpout, help_flags, " LoadedFilterOnly"); + + //dump some interesting predefined ones which are not mapped to types + if (cap->code==GF_PROP_PID_STREAM_TYPE) szVal = gf_stream_type_name(cap->val.value.uint); + else if (cap->code==GF_PROP_PID_CODECID) szVal = (const char *) gf_codecid_name(cap->val.value.uint); + else szVal = gf_props_dump_val(&cap->val, szDump, GF_PROP_DUMP_DATA_NONE, NULL); + + gf_sys_format_help(helpout, help_flags, " %s=\"%s\"", szName, szVal); + if (cap->priority) gf_sys_format_help(helpout, help_flags, ", priority=%d", cap->priority); + gf_sys_format_help(helpout, help_flags, "\n"); + } +} + +static void print_filter_arg(const GF_FilterArgs *a, u32 gen_doc) +{ + Bool is_enum = GF_FALSE; + + if ((a->arg_type==GF_PROP_UINT) && a->min_max_enum && strchr(a->min_max_enum, '|')) { + is_enum = GF_TRUE; + } + + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "
", 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, " (%s", is_enum ? "enum" : gf_props_get_type_name(a->arg_type)); + } else { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s (%s", a->arg_name, is_enum ? "enum" : gf_props_get_type_name(a->arg_type)); + } + if (a->arg_default_val) { + if (!strcmp(a->arg_default_val, "2147483647")) + gf_sys_format_help(helpout, help_flags, ", default: __+I__"); + else if (!strcmp(a->arg_default_val, "-2147483648")) + gf_sys_format_help(helpout, help_flags, ", default: __-I__"); + else + gf_sys_format_help(helpout, help_flags, ", default: __%s__", a->arg_default_val); + } else { +// gf_sys_format_help(helpout, help_flags, ", no default"); + } + if (a->min_max_enum && !is_enum) { + if (!strcmp(a->min_max_enum, "-2147483648-I")) + gf_sys_format_help(helpout, help_flags, ", %s: -I-I", /*strchr(a->min_max_enum, '|') ? "Enum" : */"minmax"); + else + gf_sys_format_help(helpout, help_flags, ", %s: %s", /*strchr(a->min_max_enum, '|') ? "Enum" : */"minmax", a->min_max_enum); + } + if (a->flags & GF_FS_ARG_UPDATE) gf_sys_format_help(helpout, help_flags, ", updatable"); +// if (a->flags & GF_FS_ARG_META) gf_sys_format_help(helpout, help_flags, ", meta"); + + if (is_enum && a->arg_desc && !strchr(a->arg_desc, '\n')) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_OPT_DESC, "): %s (%s)\n", a->arg_desc, a->min_max_enum); + } else { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_OPT_DESC, "): %s\n", a->arg_desc); + } + + //check syntax + if (gen_doc) { + GF_GPACArg _a; + memset(&_a, 0, sizeof(GF_GPACArg)); + _a.name = a->arg_name; + _a.description = a->arg_desc; + _a.flags = GF_ARG_HINT_HIDE; + gf_sys_print_arg(NULL, 0, &_a, ""); + } + + if (a->min_max_enum && strchr(a->min_max_enum, '|')) + gf_sys_format_help(helpout, help_flags, "\n"); + else if (!a->min_max_enum && a->arg_desc && strstr(a->arg_desc, "\n- ")) + gf_sys_format_help(helpout, help_flags, "\n"); +} + +static void print_filter_single_opt(const GF_FilterRegister *reg, char *optname, GF_Filter *filter_inst) +{ + u32 idx=0; + Bool found = GF_FALSE; + const GF_FilterArgs *args = NULL; + if (filter_inst) + args = gf_filter_get_args(filter_inst); + else if (reg) + args = reg->args; + + if (!args) return; + + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + if (strcmp(a->arg_name, optname)) continue; + + print_filter_arg(a, 0); + found = GF_TRUE; + break; + } + if (found) return; + + idx = 0; + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + if (gf_sys_word_match(optname, a->arg_name) + || (a->arg_desc && strstr(a->arg_desc, optname)) + ) { + if (!found) { + found = GF_TRUE; + fprintf(stderr, "No such option %s for filter %s - did you mean:\n", optname, filter_inst ? gf_filter_get_name(filter_inst) : reg->name); + } + fprintf(stderr, " - %s: %s\n", a->arg_name, a->arg_desc); + } + } + if (found) return; + + fprintf(stderr, "No such option %s for filter %s\n", optname, filter_inst ? gf_filter_get_name(filter_inst) : reg->name); +} + +static void print_filter(const GF_FilterRegister *reg, GF_SysArgMode argmode, GF_Filter *filter_inst, char *inst_name) +{ + u32 idx=0; + const GF_FilterArgs *args = NULL; + const char *reg_name, *reg_desc; +#ifndef GPAC_DISABLE_DOC + const char *reg_help; +#endif + Bool jsmod_help = (argmode>GF_ARGMODE_ALL) ? GF_TRUE : GF_FALSE; + + if (filter_inst) { + reg_name = inst_name; + reg_desc = gf_filter_get_description(filter_inst); +#ifndef GPAC_DISABLE_DOC + reg_help = gf_filter_get_help(filter_inst); +#endif + } else if (reg) { + reg_name = reg->name; + reg_desc = reg->description; +#ifndef GPAC_DISABLE_DOC + reg_help = reg->help; +#endif + } else { + return; + } + + //happens on some meta filter or JS filters + if (!reg_desc) { + reg_desc = "No description available"; + } + + if (gen_doc==1) { + char szName[1024]; + sprintf(szName, "%s.md", reg_name); + if (gen_doc==1) { + gf_fclose(helpout); + helpout = gf_fopen(szName, "w"); + fprintf(helpout, "[**HOME**](Home) » [**Filters**](Filters) » %s\n", reg_desc); + fprintf(helpout, "%s", auto_gen_md_warning); + + if (!sidebar_md) { + char *sbbuf = NULL; + if (gf_file_exists("_Sidebar.md")) { + char szLine[1024]; + 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); + sbbuf[end_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); + } + } + fprintf(sidebar_md, "[[%s (%s)|%s]] \n", reg_desc, reg_name, reg_name); +#ifndef GPAC_DISABLE_DOC + + if (!reg_help) { + fprintf(stderr, "filter %s without help, forbidden\n", reg_name); + exit(1); + } +#endif + } + +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "# %s\n", reg_desc); +#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"); + } 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"); + } else { + gf_sys_format_help(helpout, help_flags, "This filter may be automatically loaded during graph resolution.\n"); + } + if (reg->flags & GF_FS_REG_REQUIRES_RESOLVER) { + gf_sys_format_help(helpout, help_flags, "This filter requires the graph resolver to be activated.\n"); + } + if (reg->flags & GF_FS_REG_ALLOW_CYCLIC) { + gf_sys_format_help(helpout, help_flags, "Filters of this class can connect to each-other.\n"); + } + } + } else if (!jsmod_help) { + gf_sys_format_help(helpout, help_flags, "# %s\n", reg_name); + gf_sys_format_help(helpout, help_flags, "Description: %s\n", reg_desc ); + + if (filter_inst) { + const char *version = gf_filter_get_version(filter_inst); + if (version) + gf_sys_format_help(helpout, help_flags, "Version: %s\n", version ); + } else { + if (reg->version) { + if (!strncmp(reg->version, "! ", 2)) { + if (!gen_doc) + gf_sys_format_help(helpout, help_flags, "%s\n", reg->version+2); + } else { + gf_sys_format_help(helpout, help_flags, "Version: %s\n", reg->version); + } + } + } + } + + if (filter_inst) { + const char *str; + if (!jsmod_help) { + str = gf_filter_get_author(filter_inst); + if (str) + gf_sys_format_help(helpout, help_flags, "%s: %s\n", (str[0]=='-') ? "Configuration" : "Author", str ); + } + str = gf_filter_get_help(filter_inst); + if (str) + gf_sys_format_help(helpout, help_flags, "\n%s\n\n", str); + } else if (reg) { +#ifndef GPAC_DISABLE_DOC + if (reg->author) { + if (reg->author[0]=='-') { + if (! (help_flags & (GF_PRINTARG_MD|GF_PRINTARG_MAN))) { + gf_sys_format_help(helpout, help_flags, "Configuration: %s\n", reg->author); + } + } else { + gf_sys_format_help(helpout, help_flags, "Author: %s\n", reg->author); + } + } + if (reg->help) { + u32 hf = help_flags; + if (gen_doc==1) hf |= GF_PRINTARG_ESCAPE_XML; + gf_sys_format_help(helpout, hf, "\n%s\n\n", reg->help); + } +#else + gf_sys_format_help(helpout, help_flags, "GPAC compiled without built-in doc\n"); +#endif + } + + if (reg && (argmode==GF_ARGMODE_EXPERT)) { + if (reg->max_extra_pids==(u32) -1) gf_sys_format_help(helpout, help_flags, "Max Input PIDs: any\n"); + else gf_sys_format_help(helpout, help_flags, "Max Input PIDs: %d\n", 1 + reg->max_extra_pids); + + gf_sys_format_help(helpout, help_flags, "Flags:"); + if (reg->flags & GF_FS_REG_EXPLICIT_ONLY) gf_sys_format_help(helpout, help_flags, " ExplicitOnly"); + if (reg->flags & GF_FS_REG_MAIN_THREAD) gf_sys_format_help(helpout, help_flags, " MainThread"); + if (reg->flags & GF_FS_REG_CONFIGURE_MAIN_THREAD) gf_sys_format_help(helpout, help_flags, " ConfigureMainThread"); + if (reg->flags & GF_FS_REG_HIDE_WEIGHT) gf_sys_format_help(helpout, help_flags, " HideWeight"); + if (reg->flags & GF_FS_REG_REQUIRES_RESOLVER) gf_sys_format_help(helpout, help_flags, " RequireResolver"); + if (reg->flags & GF_FS_REG_ALLOW_CYCLIC) gf_sys_format_help(helpout, help_flags, " CyclicAllowed"); + if (reg->probe_url) gf_sys_format_help(helpout, help_flags, " URLMimeProber"); + if (reg->probe_data) gf_sys_format_help(helpout, help_flags, " DataProber"); + if (reg->reconfigure_output) gf_sys_format_help(helpout, help_flags, " ReconfigurableOutput"); + + gf_sys_format_help(helpout, help_flags, "\nPriority %d", reg->priority); + + gf_sys_format_help(helpout, help_flags, "\n"); + } + + if (filter_inst) args = gf_filter_get_args(filter_inst); + else args = reg->args; + + u32 nb_opts=0; + idx=0; + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + if (a->flags & GF_FS_ARG_HINT_HIDE) continue; + nb_opts++; + } + if (!nb_opts) + args = NULL; + + if (args && !jsmod_help) { + idx=0; + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "# Options \n"); + } else { + switch (argmode) { + case GF_ARGMODE_ALL: + case GF_ARGMODE_EXPERT: + gf_sys_format_help(helpout, help_flags, "# Options (expert):\n"); + break; + case GF_ARGMODE_ADVANCED: + gf_sys_format_help(helpout, help_flags, "# Options (advanced):\n"); + break; + case GF_ARGMODE_BASE: + gf_sys_format_help(helpout, help_flags, "# Options (basic):\n"); + break; + } + } + + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + + if (a->flags & GF_FS_ARG_HINT_HIDE) continue; + + switch (argmode) { + case GF_ARGMODE_ALL: + case GF_ARGMODE_EXPERT: + break; + case GF_ARGMODE_ADVANCED: + if (a->flags & GF_FS_ARG_HINT_EXPERT) continue; + break; + case GF_ARGMODE_BASE: + if (a->flags & (GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_HINT_ADVANCED)) continue; + break; + } + + print_filter_arg(a, gen_doc); + + if (!gen_doc) continue; + +#ifdef CHECK_DOC + if (a->flags & GF_FS_ARG_META) continue; + + u8 achar; + u32 j=0; + char szArg[100]; + sprintf(szArg, " %s ", a->arg_name); + char *quoted = reg_help ? strstr(reg_help, szArg) : NULL; + if (quoted) { + fprintf(stderr, "\nWARNING: filter %s bad help, uses arg %s without link: \"... %s\"\n", reg_name, a->arg_name, quoted); + exit(1); + } + while (1) { + const GF_FilterArgs *anarg = & args[j]; + if (!anarg || !anarg->arg_name) break; + j++; + if (a == anarg) continue; + if (reg_help && strstr(reg_help, szArg)) { + fprintf(stderr, "\nWARNING: filter %s bad description for argument %s, uses arg %s without link\n", reg_name, anarg->arg_name, a->arg_name); + exit(1); + } + } + + if (a->min_max_enum) { + //check format + if ((a->arg_type!=GF_PROP_UINT_LIST) && !(a->flags&GF_FS_ARG_META) && strchr(a->min_max_enum, '|') && (!a->arg_default_val || strcmp(a->arg_default_val, "-1")) ) { + const char *a_val = a->min_max_enum; + while (a_val[0] == '|') a_val++; + if (strstr(a->arg_desc, "see filter ")) + a_val = NULL; + while (a_val) { + char szName[100]; + const char *a_sep = strchr(a_val, '|'); + u32 len = a_sep ? (u32)(a_sep - a_val) : (u32)strlen(a_val); + strcpy(szName, "- "); + strncat(szName, a_val, MIN(sizeof(szName)-3,len)); + szName[2+len]=0; + strcat(szName, ": "); + + if (!strstr(a->arg_desc, szName)) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, missing list bullet \"%s\"\n", reg_name, a->arg_name, szName); + exit(1); + } + if (!a_sep) break; + a_val = a_sep+1; + } + if (a_val && !strstr(a->arg_desc, "- ")) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, missing list bullet \"- \"\n", reg_name, a->arg_name); + exit(1); + } + if (a_val && strstr(a->arg_desc, ":\n")) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should not use \":\\n\"\n", reg_name, a->arg_name); + exit(1); + } + } else if (!(a->flags&GF_FS_ARG_META) && strchr(a->arg_desc, '\n')) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should not contain \"\\n\"\n", reg_name, a->arg_name); + exit(1); + } + } + if (!(a->flags&GF_FS_ARG_META)) { + char *sep; + + achar = a->arg_desc[strlen(a->arg_desc)-1]; + if (achar == '\n') { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should not end with \"\\n\"\n", reg_name, a->arg_name); + exit(1); + } + + achar = a->arg_desc[0]; + if ((achar >= 'A') && (achar <= 'Z')) { + achar = a->arg_desc[1]; + if ((achar < 'A') || (achar > 'Z')) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should start with lowercase\n", reg_name, a->arg_name); + exit(1); + } + } + if (a->arg_desc[0] == ' ') { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, first character should not be space\n", reg_name, a->arg_name); + exit(1); + } + sep = strchr(a->arg_desc+1, ' '); + if (sep) sep--; + if (sep && (sep[0] == 's') && (sep[-1] != 's')) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, first word should be infinitive\n", reg_name, a->arg_name); + exit(1); + } + } +#endif + } + } else if (!args) { + gf_sys_format_help(helpout, help_flags, "No options\n"); + } + + if (!gen_doc && (argmode==GF_ARGMODE_ALL)) { + if (filter_inst) { + u32 nb_caps = 0; + const GF_FilterCapability *caps = gf_filter_get_caps(filter_inst, &nb_caps); + dump_caps(nb_caps, caps); + } else if (reg->nb_caps) { + dump_caps(reg->nb_caps, reg->caps); + } + } + gf_sys_format_help(helpout, help_flags, "\n"); +} + +static Bool strstr_nocase(const char *text, const char *subtext, u32 subtext_len) +{ + if (!*text || !subtext || !subtext_len) + return GF_FALSE; + + while (*text) { + if (tolower(*text) == *subtext) { + if (!strnicmp(text, subtext, subtext_len)) + return GF_TRUE; + + } + text++; + } + return GF_FALSE; +} + +struct __jsenum_info +{ + GF_FilterSession *session; + GF_SysArgMode argmode; + Bool print_filter_info; + const char *path; + char *js_dir; +}; + +static Bool jsinfo_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + struct __jsenum_info *jsi = (struct __jsenum_info *)cbck; + GF_Filter *f; + if (jsi->js_dir && strcmp(item_name, "init.js")) { + return GF_FALSE; + } + f = gf_fs_load_filter(jsi->session, item_path, NULL); + if (f) { + char szPath[GF_MAX_PATH]; + char *ext; + if (jsi->js_dir) { + strcpy(szPath, jsi->js_dir); + } else { + strcpy(szPath, item_name); + } + ext = gf_file_ext_start(szPath); + if (ext) ext[0] = 0; + if (jsi->print_filter_info || gen_doc) + print_filter(NULL, jsi->argmode, f, szPath); + else + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s: %s\n", szPath, gf_filter_get_description(f)); + } + return GF_FALSE; +} +static Bool jsinfo_dir_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + char szPath[GF_MAX_PATH]; + struct __jsenum_info *jsi = (struct __jsenum_info *)cbck; + jsi->js_dir = item_name; + + strcpy(szPath, jsi->path); + strcat(szPath, item_name); + strcat(szPath, "/"); + gf_enum_directory(szPath, GF_FALSE, jsinfo_enum, jsi, ".js"); + jsi->js_dir = NULL; + return GF_FALSE; +} + +static Bool print_filters(int argc, char **argv, GF_FilterSession *session, GF_SysArgMode argmode) +{ + Bool found = GF_FALSE; + Bool print_all = GF_FALSE; + char *fname = NULL; + char *l_fname = NULL; + u32 lf_len = 0; + u32 i, count = gf_fs_filters_registers_count(session); + + if (!gen_doc && list_filters) gf_sys_format_help(helpout, help_flags, "Listing %d supported filters%s:\n", count, (list_filters==2) ? " including meta-filters" : ""); + + if (print_filter_info != 1) { + for (i=0; iname, reg->description); +#else + gf_sys_format_help(helpout, help_flags, "%s (compiled without built-in doc)\n", reg->name); +#endif + } + } + found = GF_TRUE; + } + //print a specific filter info, browse args + else { + u32 k; + //all good to go, load filters + for (k=1; k<(u32) argc; k++) { + char *arg = argv[k]; + char *sepe; + Bool found_freg = GF_FALSE; + char *optname = NULL; + if (arg[0]=='-') continue; + + sepe = gf_file_basename(arg); + if (sepe) sepe = strchr(sepe, '.'); + if (sepe) { + if (!strncmp(sepe, ".js.", 4)) sepe = strchr(sepe+1, '.'); + else if (!strcmp(sepe, ".js")) sepe = NULL; + if (sepe) { + sepe[0] = 0; + optname = sepe+1; + } + } + fname = arg; + for (i=0; iname) ) { + if (optname) + print_filter_single_opt(reg, optname, NULL); + else + print_filter(reg, argmode, NULL, NULL); + found_freg = GF_TRUE; + } + //search for name:*, also accept *:* + else { + char *sepo = strchr(arg, ':'); + + if (!strcmp(arg, "*:*") || !strcmp(arg, "@:@") + || (!sepo && (!strcmp(arg, "*") || !strcmp(arg, "@")) ) + || (sepo && (!strcmp(sepo, ":*") || !strcmp(sepo, ":@")) && !strncmp(reg->name, arg, 1+sepo - arg) ) + ) { + if (optname) + print_filter_single_opt(reg, optname, NULL); + else + print_filter(reg, argmode, NULL, NULL); + found_freg = GF_TRUE; + if (!strcmp(arg, "*")) print_all = GF_TRUE; + } + } + } + if (found_freg) { + found = GF_TRUE; + } else /*if (!strchr(arg, ':')) */ { + GF_SysArgMode _argmode = argmode; + char *js_opt = strchr(arg, ':'); + if (js_opt) { + js_opt[0] = 0; + gf_opts_set_key("temp", "gpac-js-help", js_opt+1); + _argmode = GF_ARGMODE_ALL+1; + } + //try to load the filter (JS) + GF_Filter *f = gf_fs_load_filter(session, arg, NULL); + const GF_FilterRegister *reg = f ? gf_filter_get_register(f) : NULL; + if (!reg || !(reg->flags & GF_FS_REG_SCRIPT)) + f = NULL; + + if (f) { + char *ext; + char szPath[GF_MAX_PATH]; + strcpy(szPath, gf_file_basename(arg) ); + ext = gf_file_ext_start(szPath); + if (ext) ext[0] = 0; + if (optname) { + print_filter_single_opt(NULL, optname, f); + } else { + print_filter(NULL, _argmode, f, szPath); + } + found = GF_TRUE; + } + if (js_opt) { + js_opt[0] = ':'; + gf_opts_set_key("temp", "gpac-js-help", NULL); + } + } + if (sepe) sepe[0] = '.'; + } + } + + if (print_all || !print_filter_info || gen_doc) { + const char *js_dirs = gf_opts_get_key("core", "js-dirs"); + char szPath[GF_MAX_PATH]; + struct __jsenum_info jsi; + jsi.argmode = argmode; + jsi.session = session; + jsi.print_filter_info = print_filter_info; + + gf_log_set_tools_levels("console@error", GF_FALSE); + + if (gf_opts_default_shared_directory(szPath)) { + strcat(szPath, "/scripts/jsf/"); + jsi.path = szPath; + gf_enum_directory(szPath, GF_FALSE, jsinfo_enum, &jsi, ".js"); + gf_enum_directory(szPath, GF_TRUE, jsinfo_dir_enum, &jsi, NULL); + } + while (js_dirs && js_dirs[0]) { + char *sep = strchr(js_dirs, ','); + if (sep) { + u32 cplen = (u32) (sep-js_dirs); + if (cplen>=GF_MAX_PATH) cplen = GF_MAX_PATH-1; + strncpy(szPath, js_dirs, cplen); + szPath[cplen]=0; + js_dirs = sep+1; + } else { + strcpy(szPath, js_dirs); + } + //pre 1.1, $GJS was inserted by default + if (strcmp(szPath, "$GJS")) { + u32 len = (u32) strlen(szPath); + if (len && (szPath[len-1]!='/') && (szPath[len-1]!='\\')) + strcat(szPath, "/"); + gf_enum_directory(szPath, GF_FALSE, jsinfo_enum, &jsi, ".js"); + } + if (!sep) break; + } + return GF_TRUE; + } + + + if (found) return GF_TRUE; + if (!fname) return GF_FALSE; + if (!print_filter_info) return GF_FALSE; + if (gen_doc) return GF_FALSE; + + if (argmode==GF_ARGMODE_EXPERT) { + l_fname = gf_strdup(fname); + strlwr(l_fname); + lf_len = (u32) strlen(l_fname); + } + + for (i=0; iargs) { + const GF_FilterArgs *arg = ®->args[j]; + if (!arg || !arg->arg_name) break; + j++; + if (argmode==GF_ARGMODE_EXPERT) { + if (!arg->arg_desc || !strstr_nocase(arg->arg_desc, l_fname, lf_len)) continue; + if (!found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("\"%s\" is mentioned in the following filters options:\n", fname)); + found = GF_TRUE; + } + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s \n", reg->name, arg->arg_name); +#if 0 + + } else { + if (strcmp(arg->arg_name, fname)) continue; + if (!found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("No such filter \"%s\" but found filters with matching options:\n", fname)); + found = GF_TRUE; + } + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s: %s\n", reg->name, arg->arg_name, arg->arg_desc); +#endif + } + } + } + if (l_fname) gf_free(l_fname); + + + if (found) return GF_TRUE; + + gpac_suggest_filter(fname, GF_TRUE, GF_FALSE); + return GF_FALSE; +} + +static void dump_all_props(char *pname) +{ + u32 i=0, pname_len = pname ? (u32) strlen(pname) : 0; + const GF_BuiltInProperty *prop_info; + + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "## Built-in property types\n" + " \n"); + + gf_sys_format_help(helpout, help_flags, "Name | Description \n"); + gf_sys_format_help(helpout, help_flags, "--- | --- \n"); + for (i=GF_PROP_FORBIDEN+1; i=GF_PROP_LAST_NON_ENUM) && (i=GF_PROP_LAST_NON_ENUM) && (iname) continue; + + if (gen_doc==1) { + strcpy(szFlags, ""); + if (prop_info->flags & GF_PROP_FLAG_GSF_REM) strcat(szFlags, "D"); + if (prop_info->flags & GF_PROP_FLAG_PCK) strcat(szFlags, "P"); + + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s | %s | %s | %s \n", prop_info->name, gf_props_get_type_name(prop_info->data_type), + szFlags, + prop_info->description, + gf_4cc_to_str(prop_info->type) + ); + } else if (gen_doc==2) { + gf_sys_format_help(helpout, help_flags, ".TP\n.B %s (%s,%s,%s%s)\n%s\n", prop_info->name, gf_4cc_to_str(prop_info->type), gf_props_get_type_name(prop_info->data_type), + (prop_info->flags & GF_PROP_FLAG_GSF_REM) ? "D" : " ", + (prop_info->flags & GF_PROP_FLAG_PCK) ? "P" : " ", + prop_info->description); + } else { + u32 len; + const char *ptype; + + if (pname) { + if (gf_sys_word_match(prop_info->name, pname)) {} + else if (gf_strnistr(prop_info->description, pname, pname_len)) {} + else continue; + } + szFlags[0]=0; + if (prop_info->flags & GF_PROP_FLAG_GSF_REM) strcat(szFlags, "D"); + if (prop_info->flags & GF_PROP_FLAG_PCK) strcat(szFlags, "P"); + + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s", prop_info->name); + len = (u32) strlen(prop_info->name); + while (len<16) { + gf_sys_format_help(helpout, help_flags, " "); + len++; + } + ptype = gf_props_get_type_name(prop_info->data_type); + gf_sys_format_help(helpout, help_flags, " (%s %s %s):", gf_4cc_to_str(prop_info->type), ptype, szFlags); + len += (u32) strlen(ptype) + (u32) strlen(szFlags); + while (len<24) { + gf_sys_format_help(helpout, help_flags, " "); + len++; + } + + gf_sys_format_help(helpout, help_flags, "%s", prop_info->description); + + if (prop_info->data_type==GF_PROP_PIXFMT) { + gf_sys_format_help(helpout, help_flags, "\n\tNames: %s\n\tFile extensions: %s", gf_pixel_fmt_all_names(), gf_pixel_fmt_all_shortnames() ); + } else if (prop_info->data_type==GF_PROP_PCMFMT) { + gf_sys_format_help(helpout, help_flags, "\n\tNames: %s\n\tFile extensions: %s", gf_audio_fmt_all_names(), gf_audio_fmt_all_shortnames() ); + } else if (gf_props_type_is_enum(prop_info->data_type)) { + gf_sys_format_help(helpout, help_flags, "\n\tNames: %s\n\t", gf_props_enum_all_names(prop_info->data_type) ); + } + gf_sys_format_help(helpout, help_flags, "\n"); + } + } + if (gen_doc==1) { + u32 idx=0; + u32 cicp; + u64 layout; + GF_PixelFormat pfmt; + const char *name, *fileext, *desc; + gf_sys_format_help(helpout, help_flags, "# Pixel formats\n"); + gf_sys_format_help(helpout, help_flags, "Name | File extensions | QT 4CC | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- | --- | --- \n"); + while ( (pfmt = gf_pixel_fmt_enum(&idx, &name, &fileext, &desc) )) { + const char *qtname = ""; + u32 qt_code = gf_pixel_fmt_to_qt_type(pfmt); + if (qt_code) qtname = gf_4cc_to_str(qt_code); + + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s | %s | %s \n", name, fileext, qtname, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Audio formats\n"); + gf_sys_format_help(helpout, help_flags, " Name | File extensions | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- | --- \n"); + while ( gf_audio_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s | %s \n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Stream types\n"); + gf_sys_format_help(helpout, help_flags, " Name | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- \n"); + while ( gf_stream_types_enum(&idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s \n", name, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Codecs\n"); + gf_sys_format_help(helpout, help_flags, "The codec name identifies a codec within GPAC. There can be several names for a given codec. The first name is used as a default file extension when dumping a raw media stream.\n" + " \n"); + + gf_sys_format_help(helpout, help_flags, " Name | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- \n"); + while ( gf_codecid_enum(idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR | GF_PRINTARG_ESCAPE_PIPE, "%s | %s \n", name, desc); + idx++; + } + + + idx=0; + gf_sys_format_help(helpout, help_flags, "# CICP code points for audio channel layout\n"); + gf_sys_format_help(helpout, help_flags, " Name | Integer value | ChannelMask \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- | --- \n"); + while ( (cicp = gf_audio_fmt_cicp_enum(idx, &name, &layout)) ) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %d | 0x%016"LLX_SUF" \n", name, cicp, layout); + idx++; + } + + } else if (gen_doc==2) { + u32 idx=0, cicp; + u64 layout; + const char *name, *fileext, *desc; + gf_sys_format_help(helpout, help_flags, "# Pixel formats\n"); + while ( gf_pixel_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s (ext *.%s)\n%s\n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Audio formats\n"); + while ( gf_audio_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s (ext *.%s)\n%s\n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Stream types\n"); + while ( gf_stream_types_enum(&idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s\n%s\n", name, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Codecs\n"); + while ( gf_codecid_enum(idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s\n%s\n", name, desc); + idx++; + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Stream types\n"); + while ( (cicp = gf_audio_fmt_cicp_enum(idx, &name, &layout)) ) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s (int %d)\nLayout 0x%016"LLX_SUF"\n", name, cicp, layout); + idx++; + } + } +} +#include +static void dump_all_colors(void) +{ + u32 i=0; + GF_Color color; + const char *name; + while (gf_color_enum(&i, &color, &name)) { + gf_sys_format_help(helpout, help_flags|GF_PRINTARG_HIGHLIGHT_FIRST, "%s: 0x%08X\n", name, color); + } +} + +static void dump_all_audio_cicp(void) +{ + u32 i=0, cicp; + const char *name; + u64 layout; + + while ((cicp = gf_audio_fmt_cicp_enum(i, &name, &layout)) ) { + gf_sys_format_help(helpout, help_flags|GF_PRINTARG_HIGHLIGHT_FIRST, "%s (%d): 0x%016"LLX_SUF"\n", name, cicp, layout); + i++; + } +} + +static void dump_all_codec(GF_FilterSession *session) +{ + GF_PropertyValue rawp, filep, stp; + GF_PropertyValue cp, acp; + char szFlags[10], szCap[2]; + u32 cidx=0; + u32 count = gf_fs_filters_registers_count(session); + + gf_sys_format_help(helpout, help_flags, "Codec support in filters, listed as `built_in_name[|variant] [FLAGS]: full_name (mime)` with possible FLAGS values:\n"); + gf_sys_format_help(helpout, help_flags, " - I: Raw format input (demultiplexer) support\n"); + gf_sys_format_help(helpout, help_flags, " - O: Raw format output (multiplexer) support\n"); + gf_sys_format_help(helpout, help_flags, " - D: Decoder support\n"); + gf_sys_format_help(helpout, help_flags, " - E: Encoder support\n"); + gf_sys_format_help(helpout, help_flags, "\nNote: Raw output may still be possible even when no output serializer is given\n\n"); + + rawp.type = cp.type = GF_PROP_UINT; + rawp.value.uint = GF_CODECID_RAW; + filep.type = cp.type = GF_PROP_UINT; + filep.value.uint = GF_STREAM_FILE; + stp.type = GF_PROP_UINT; + cp.type = GF_PROP_UINT; + acp.type = GF_PROP_UINT; + szCap[1] = 0; + + while (1) { + u32 i; + const char *lname; + const char *sname; + const char *mime; + u32 rfc4cc; + Bool enc_found = GF_FALSE; + Bool dec_found = GF_FALSE; + Bool dmx_found = GF_FALSE; + Bool mx_found = GF_FALSE; + cp.value.uint = gf_codecid_enum(cidx, &sname, &lname); + cidx++; + if (cp.value.uint == GF_CODECID_NONE) break; +// if (cp.value.uint == GF_CODECID_RAW) continue; + if (!sname) break; + + stp.value.uint = gf_codecid_type(cp.value.uint); + acp.value.uint = gf_codecid_alt(cp.value.uint); + + for (i=0; iname); + meta_sep = strchr(szSecName + 7, ':'); + if (meta_sep) meta_sep[0] = 0; + + while (freg->args) { + const GF_FilterArgs *arg = &freg->args[j]; + if (!arg || !arg->arg_name) break; + j++; + + if (arg->arg_default_val && !gf_opts_get_key(szSecName, arg->arg_name)) { + gf_opts_set_key(szSecName, arg->arg_name, arg->arg_default_val); + } + } + } +} + +/********************************************************* + Language file creation / update functions +*********************************************************/ + +static Bool lang_updated = GF_FALSE; +static void gpac_lang_set_key(GF_Config *cfg, const char *sec_name, const char *key_name, const char *key_val) +{ + char szKeyCRC[1024]; + const char *opt = gf_cfg_get_key(cfg, sec_name, key_name); + u32 crc_key = 0; + if (opt) { + sprintf(szKeyCRC, "%s_crc", key_name); + const char *crc_opt = gf_cfg_get_key(cfg, sec_name, szKeyCRC); + if (crc_opt) { + u32 old_crc = atoi(crc_opt); + crc_key = gf_crc_32((u8*)key_val, (u32) strlen(key_val)); + if (old_crc != crc_key) { + gf_sys_format_help(helpout, help_flags, "Warning: description has changed for %s:%s (crc %d - crc in file %d) - please check translation\n", sec_name, key_name, crc_key, old_crc); + } + return; + } + } + if (!opt) { + char szKeyCRCVal[100]; + if (!crc_key) { + sprintf(szKeyCRC, "%s_crc", key_name); + crc_key = gf_crc_32((u8*)key_val, (u32) strlen(key_val)); + } + sprintf(szKeyCRCVal, "%u", crc_key); + gf_cfg_set_key(cfg, sec_name, key_name, key_val); + gf_cfg_set_key(cfg, sec_name, szKeyCRC, szKeyCRCVal); + lang_updated = GF_TRUE; + } +} +static int gpac_make_lang(char *filename) +{ + GF_Config *cfg; + u32 i; + gf_sys_init(GF_MemTrackerNone, NULL); + + session = gf_fs_new_defaults(0); + if (!session) { + gf_sys_format_help(helpout, help_flags, "failed to load session, cannot create language file\n"); + return 1; + } + if (!gf_file_exists(filename)) { + FILE *f = gf_fopen(filename, "wt"); + if (!f) { + gf_sys_format_help(helpout, help_flags, "failed to open %s in write mode\n", filename); + gf_fs_del(session); + return 1; + } + gf_fclose(f); + } + + cfg = gf_cfg_new(NULL, filename); + + //print gpac help + i = 0; + while (gpac_args[i].name) { + gpac_lang_set_key(cfg, "gpac", gpac_args[i].name, gpac_args[i].description); + i++; + } + + //print gpac doc + gpac_lang_set_key(cfg, "gpac", "doc", gpac_doc); + + //print gpac alias doc + gpac_lang_set_key(cfg, "gpac", "alias", gpac_alias); + + //print libgpac core help + const GF_GPACArg *args = gf_sys_get_options(); + i = 0; + + while (args[i].name) { + gpac_lang_set_key(cfg, "core", args[i].name, args[i].description); + i++; + } + + //print properties + i=0; + const GF_BuiltInProperty *prop_info; + while ((prop_info = gf_props_get_description(i))) { + i++; + if (! prop_info->name || !prop_info->description) continue; + gpac_lang_set_key(cfg, "properties", prop_info->name, prop_info->description); + } + + //print filters + u32 count = gf_fs_filters_registers_count(session); + for (i=0; idescription) { + gpac_lang_set_key(cfg, reg->name, "desc", reg->description); + } + if (reg->help) { + gpac_lang_set_key(cfg, reg->name, "help", reg->help); + } + while (reg->args && reg->args[j].arg_name) { + gpac_lang_set_key(cfg, reg->name, reg->args[j].arg_name, reg->args[j].arg_desc); + j++; + } + } + + if (!lang_updated) { + gf_cfg_discard_changes(cfg); + fprintf(stderr, "lang file %s has not been modified\n", filename); + } else { + fprintf(stderr, "lang file generated in %s\n", filename); + } + gf_cfg_del(cfg); + + gf_fs_del(session); + gf_sys_close(); + return 0; +} + + +/********************************************************* + Alias functions +*********************************************************/ + +static GFINLINE void push_arg(char *_arg, Bool _dup) +{ + alias_argv = gf_realloc(alias_argv, sizeof(char**) * (alias_argc+1));\ + alias_argv[alias_argc] = _dup ? gf_strdup(_arg) : _arg; \ + if (_dup) { + if (!args_alloc) args_alloc = gf_list_new(); + gf_list_add(args_alloc, alias_argv[alias_argc]); + } + alias_argc++; +} + +static Bool gpac_expand_alias_arg(char *param, char *prefix, char *suffix, int arg_idx, int argc, char **argv); + +static Bool check_param_extension(char *szArg, int arg_idx, int argc, char **argv) +{ + char *par_start = strstr(szArg, "@{"); + if (par_start) { + char szPar[100]; + Bool ok; + char *par_end = strchr(par_start, '}'); + if (!par_end) { + fprintf(stderr, "Bad format %s for alias parameter, expecting @{N}\n", szArg); + return GF_FALSE; + } + par_end[0] = 0; + strcpy(szPar, par_start+2); + par_start[0] = 0; + + ok = gpac_expand_alias_arg(szPar, szArg, par_end+1, arg_idx, argc, argv); + if (!ok) return GF_FALSE; + par_start[0] = '@'; + par_end[0] = '}'; + return GF_TRUE; + } + //done, push arg + push_arg(szArg, 1); + return GF_TRUE; +} + +static Bool gpac_expand_alias_arg(char *param, char *prefix, char *suffix, int arg_idx, int argc, char **argv) +{ + char *alias_arg=NULL; + char szSepList[2]; + char *oparam = param; + szSepList[0] = separator_set[SEP_LIST]; + szSepList[1] = 0; + + Bool is_list = param[0]=='-'; + Bool is_expand = param[0]=='+'; + + if (is_list || is_expand) param++; + + gf_dynstrcat(&alias_arg, prefix, NULL); + while (param) { + u32 idx=0; + u32 last_idx=0; + char *sep = strchr(param, ','); + if (sep) sep[0]=0; + + if ((param[0]=='n') || (param[0]=='N')) { + idx = argc - arg_idx - 1; + if (param[1]=='-') { + u32 diff = atoi(param+2); + if (diff>=idx) { + if (sep) sep[0]=','; + fprintf(stderr, "Bad usage for alias parameter %s: not enough parameters\n", oparam); + gf_free(alias_arg); + return GF_FALSE; + } + idx -= diff; + } + } else { + char *lsep = strchr(param, ':'); + if (lsep) { + lsep[0] = 0; + if (strlen(param)) + idx = atoi(param); + else + idx=1; + + lsep[0] = ':'; + if ((lsep[1]=='n') || (lsep[1]=='N')) { + + last_idx = argc - arg_idx - 1; + if (lsep[2]=='-') { + u32 diff = atoi(lsep+3); + if (diff>=last_idx) { + fprintf(stderr, "Bad usage for alias parameter %s: not enough parameters\n", oparam); + gf_free(alias_arg); + return GF_FALSE; + } + last_idx -= diff; + } + } else { + last_idx = atoi(lsep+1); + } + } else { + idx = atoi(param); + } + } + if (!idx) { + if (sep) sep[0]=','; + fprintf(stderr, "Bad format for alias parameter %s: cannot extract argument index\n", oparam); + gf_free(alias_arg); + return GF_FALSE; + } + if ((int) idx + arg_idx >= argc) { + if (sep) sep[0]=','; + fprintf(stderr, "Bad format for alias parameter %s: argument out of bounds (not enough paramteters?)\n", oparam); + gf_free(alias_arg); + return GF_FALSE; + } + + if (!last_idx) last_idx=idx; + for (; idx<=last_idx;idx++) { + char *an_arg = argv[idx+arg_idx]; + + if (!args_used) args_used = gf_list_new(); + gf_list_add(args_used, an_arg); + + if (is_expand) { + gf_free(alias_arg); + alias_arg = NULL; + gf_dynstrcat(&alias_arg, prefix, NULL); + gf_dynstrcat(&alias_arg, an_arg, NULL); + gf_dynstrcat(&alias_arg, suffix, NULL); + + Bool ok = check_param_extension(alias_arg, arg_idx, argc, argv); + if (!ok) { + if (sep) sep[0]=','; + gf_free(alias_arg); + return GF_FALSE; + } + } else { + gf_dynstrcat(&alias_arg, an_arg, NULL); + if (is_list && (idx +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif +static u32 gpac_unit_tests(GF_MemTrackerType mem_track) +{ +#ifdef GPAC_ENABLE_COVERAGE + u32 ucs4_buf[4]; + u32 i; + u8 utf8_buf[7]; + + void *mem = gf_calloc(4, sizeof(u32)); + gf_free(mem); + + if (mem_track == GF_MemTrackerNone) return 0; + + gpac_fsess_task_help(); //for coverage + gf_dm_sess_last_error(NULL); + gf_log_use_color(); + gf_4cc_parse("abcd"); + gf_gpac_abi_micro(); + gf_audio_fmt_get_layout_from_name("3/2.1"); + gf_audio_fmt_get_dolby_chanmap(4); + gf_itags_get_id3tag(1); + i=0; + gf_itags_enum_tags(&i, NULL, NULL, NULL); + + GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[CoreUnitTests] performing tests\n")); + + utf8_buf[0] = 'a'; + utf8_buf[1] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 1, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for single char\n")); + return 1; + } + utf8_buf[0] = 0xc2; + utf8_buf[1] = 0xa3; + utf8_buf[2] = 'a'; + utf8_buf[3] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 3, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 2-byte + 1-byte char\n")); + return 1; + } + utf8_buf[0] = 0xe0; + utf8_buf[1] = 0xa4; + utf8_buf[2] = 0xb9; + utf8_buf[3] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 3, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 3-byte char\n")); + return 1; + } + utf8_buf[0] = 0xf0; + utf8_buf[1] = 0x90; + utf8_buf[2] = 0x8d; + utf8_buf[3] = 0x88; + utf8_buf[4] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 4, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 4-byte char\n")); + return 1; + } + + utf8_buf[0] = 0xf8; + utf8_buf[1] = 0x80; + utf8_buf[2] = 0x80; + utf8_buf[3] = 0x80; + utf8_buf[4] = 0xaf; + utf8_buf[5] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 5, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 5-byte char\n")); + return 1; + } + utf8_buf[0] = 0xfc; + utf8_buf[1] = 0x80; + utf8_buf[2] = 0x80; + utf8_buf[3] = 0x80; + utf8_buf[4] = 0x80; + utf8_buf[5] = 0xaf; + utf8_buf[6] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 6, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 6-byte char\n")); + return 1; + } + //test error case + utf8_buf[0] = 0xf8; + utf8_to_ucs4 (ucs4_buf, 6, (unsigned char *) utf8_buf); + + char buf[5], obuf[3]; + obuf[0] = 1; + obuf[1] = 2; + u32 res = gf_base16_encode(obuf, 2, buf, 5); + if (res != 4) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] base16 encode fail\n")); + return 1; + } + u32 res2 = gf_base16_decode(buf, res, obuf, 3); + if (res2 != 2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] base16 decode fail\n")); + return 1; + } + + u8 *zbuf; + u32 osize; + GF_Err e; + u8 *ozbuf; + +#ifndef GPAC_DISABLE_ZLIB + zbuf = gf_strdup("123451234512345123451234512345"); + osize=0; + e = gf_gz_compress_payload(&zbuf, 1 + (u32) strlen(zbuf), &osize); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] zlib compress fail\n")); + gf_free(zbuf); + return 1; + } + ozbuf=NULL; + res=0; + e = gf_gz_decompress_payload(zbuf, osize, &ozbuf, &res); + gf_free(zbuf); + if (ozbuf) gf_free(ozbuf); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] zlib decompress fail\n")); + return 1; + } +#endif + + zbuf = gf_strdup("123451234512345123451234512345"); + osize=0; + e = gf_lz_compress_payload(&zbuf, 1+(u32) strlen(zbuf), &osize); + if (e && (e!= GF_NOT_SUPPORTED)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] lzma compress fail\n")); + gf_free(zbuf); + return 1; + } + ozbuf=NULL; + res=0; + e = gf_lz_decompress_payload(zbuf, osize, &ozbuf, &res); + gf_free(zbuf); + if (ozbuf) gf_free(ozbuf); + if (e && (e!= GF_NOT_SUPPORTED)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] lzma decompress fail\n")); + return 1; + } + + gf_htonl(0xAABBCCDD); + gf_ntohl(0xAABBCCDD); + gf_htons(0xAABB); + gf_ntohs(0xAABB); + gf_errno_str(-1); + + /* these two lock the bash shell in test mode + gf_prompt_set_echo_off(GF_TRUE); + gf_prompt_set_echo_off(GF_FALSE); + */ + + gf_net_set_ntp_shift(-1000); + gf_net_get_ntp_diff_ms(gf_net_get_ntp_ts() ); + gf_net_get_timezone(); + gf_net_get_utc_ts(70, 1, 0, 0, 0, 0); + gf_net_ntp_diff_ms(1000000, 1000000); + gf_lang_get_count(); + gf_lang_get_2cc(2); + GF_Blob b; + memset(&b, 0, sizeof(GF_Blob)); + b.data = (u8 *) "test"; + b.size = 5; + char url[100]; + u8 *data; + u32 size; + sprintf(url, "gmem://%p", &b); + + gf_sys_profiler_set_callback(NULL, NULL); + + gf_blob_get(url, &data, &size, NULL); + if (!data || strcmp((char *)data, "test")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] blob url parsing fail\n")); + return 1; + } + gf_sys_get_battery_state(NULL, NULL, NULL, NULL, NULL); + gf_sys_get_process_id(); + data = (u8 *) gf_log_get_tools_levels(); + if (data) gf_free(data); + + gf_sys_is_quiet(); + gf_sys_get_argv(); + gf_mx_get_num_locks(NULL); + signal_catched = GF_TRUE; + +#ifdef WIN32 + gpac_sig_handler(CTRL_C_EVENT); +#else + gpac_sig_handler(SIGINT); + gpac_sig_handler(SIGTERM); +#endif + + gf_mkdir("testdir"); + gf_mkdir("testdir/somedir"); + strcpy(url, "testdir/somedir/test.bin"); + FILE *f=gf_fopen(url, "wb"); + fprintf(f, "some test\n"); +#ifdef GPAC_MEMORY_TRACKING + gf_memory_print(); +#endif + gf_fclose(f); + gf_file_modification_time(url); + gf_m2ts_probe_file(url); + + gf_dir_cleanup("testdir"); + gf_rmdir("testdir"); + + //math.c not covered yet by our sample files + GF_Matrix2D mx; + gf_mx2d_init(mx); + gf_mx2d_add_skew(&mx, FIX_ONE, FIX_ONE); + gf_mx2d_add_skew_x(&mx, GF_PI/4); + gf_mx2d_add_skew_y(&mx, GF_PI/4); + GF_Point2D scale, translate; + Fixed rotate; + gf_mx2d_decompose(&mx, &scale, &rotate, &translate); + GF_Rect rc1, rc2; + memset(&rc1, 0, sizeof(GF_Rect)); + memset(&rc2, 0, sizeof(GF_Rect)); + gf_rect_equal(&rc1, &rc2); + + GF_Matrix mat; + gf_mx_init(mat); + Fixed yaw, pitch, roll; + gf_mx_get_yaw_pitch_roll(&mat, &yaw, &pitch, &roll); + gf_mx_ortho_reverse_z(&mat, -20, 20, -20, 20, 0.1, 100.0); + gf_mx_perspective_reverse_z(&mat, 0.76, 1.0, 0.1, 100.0); + + GF_Ray ray; + GF_Vec center, outPoint; + memset(&ray, 0, sizeof(GF_Ray)); + ray.dir.z = FIX_ONE; + memset(¢er, 0, sizeof(GF_Vec)); + gf_ray_hit_sphere(&ray, ¢er, FIX_ONE, &outPoint); + + gf_closest_point_to_line(center, ray.dir, center); + + GF_Plane plane; + plane.d = FIX_ONE; + plane.normal = center; + gf_plane_intersect_line(&plane, ¢er, &ray.dir, &outPoint); + + GF_Vec4 rot, quat; + rot.x = rot.y = 0; + rot.z = FIX_ONE; + rot.q = GF_PI/4; + quat = gf_quat_from_rotation(rot); + gf_quat_get_inv(&quat); + gf_quat_rotate(&quat, &ray.dir); + gf_quat_slerp(quat, quat, FIX_ONE/2); + GF_BBox bbox; + memset(&bbox, 0, sizeof(GF_BBox)); + gf_bbox_equal(&bbox, &bbox); + + GF_Vec v; + v.x = v.y = v.z = 0; + gf_vec_scale_p(&v, 2*FIX_ONE); + + //token.c + char container[1024]; + gf_token_get_strip("12 34{ 56 : }", 0, "{:", " ", container, 1024); + + //netwok.c + char name[GF_MAX_IP_NAME_LEN]; + gf_sk_get_host_name(name); + gf_sk_set_usec_wait(NULL, 1000); + u32 fam; + u16 port; + //to remove once we have rtsp server back + gf_sk_get_local_info(NULL, &port, &fam); + gf_sk_receive_wait(NULL, NULL, 0, &fam, 1); + gf_sk_send_wait(NULL, NULL, 0, 1); + + //path2D + GF_Path *path = gf_path_new(); + gf_path_add_move_to(path, 0, 0); + gf_path_add_quadratic_to(path, 5, 5, 10, 0); + gf_path_point_over(path, 4, 0); + gf_path_del(path); + + //xml dom - to update once we find a way to integrate atsc demux in tests + GF_DOMParser *dom = gf_xml_dom_new(); + gf_xml_dom_parse_string(dom, "test"); + gf_xml_dom_get_error(dom); + gf_xml_dom_get_line(dom); + gf_xml_dom_get_root_nodes_count(dom); + gf_xml_dom_del(dom); + + //downloader - to update once we find a way to integrate atsc demux in tests + GF_DownloadManager *dm = gf_dm_new(NULL); + gf_dm_set_auth_callback(dm, NULL, NULL); + + gf_dm_set_data_rate(dm, 0); + gf_dm_get_data_rate(dm); + gf_dm_set_localcache_provider(dm, NULL, NULL); + gf_dm_sess_abort(NULL); + gf_dm_del(dm); + + //constants + gf_stream_type_afx_name(GPAC_AFX_3DMC); + //thread + gf_th_stop(NULL); + gf_list_swap(NULL, NULL); + //bitstream + GF_BitStream *bs = gf_bs_new("test", 4, GF_BITSTREAM_READ); + gf_bs_bits_available(bs); + gf_bs_get_bit_offset(bs); + gf_bs_read_vluimsbf5(bs); + gf_bs_del(bs); + //module + gf_module_load_static(NULL); + + gf_mp3_version_name(0); + char tsbuf[188]; + u8 is_pes=GF_TRUE; + memset(tsbuf, 0, 188); + tsbuf[0] = 0x47; + tsbuf[1] = 0x40; + tsbuf[4]=0x00; + tsbuf[5]=0x00; + tsbuf[6]=0x01; + tsbuf[10] = 0x80; + tsbuf[11] = 0xc0; + tsbuf[13] = 0x2 << 4; + gf_m2ts_restamp(tsbuf, 188, 1000, &is_pes); + + + gf_filter_post_task(NULL,NULL,NULL,NULL); + gf_filter_get_arg_str(NULL, NULL, NULL); + gf_filter_all_sinks_done(NULL); + + gf_opts_discard_changes(); + + gf_rtp_reset_ssrc(NULL); + gf_rtp_enable_nat_keepalive(NULL, 0); + gf_rtp_stop(NULL); + gf_rtp_streamer_get_payload_type(NULL); + gf_rtsp_unregister_interleave(NULL, 0); + gf_rtsp_reset_aggregation(NULL); + + get_cmd('h'); + gpac_suggest_arg("blcksize"); + gpac_suggest_filter("outf", GF_FALSE, GF_FALSE); + //todo: build tests for these two + gf_filter_pid_negociate_property_str(NULL, NULL, NULL); + gf_filter_pid_negociate_property_dyn(NULL, NULL, NULL); + + gf_props_parse_type("uint"); + //this one is just a wrapper around an internal function + gf_filter_pck_new_copy(NULL, NULL, NULL); + gf_filter_get_max_extra_input_pids(NULL); + gf_filter_remove(NULL); + gf_filter_reconnect_output(NULL); + gf_filter_pid_get_udta_flags(NULL); + + gf_audio_fmt_get_cicp_layout(2, 1, 1); + gf_audio_fmt_get_layout_from_cicp(3); + gf_audio_fmt_get_layout_name_from_cicp(3); + gf_audio_fmt_get_cicp_from_layout(GF_AUDIO_CH_FRONT_LEFT|GF_AUDIO_CH_FRONT_RIGHT); + + //old bifs parsing stuff + gf_odf_desc_del(gf_odf_desc_new(GF_ODF_ELEM_MASK_TAG)); + GF_TextConfig *txtc = (GF_TextConfig *)gf_odf_desc_new(GF_ODF_TEXT_CFG_TAG); + gf_odf_get_text_config(NULL, 0, 0, txtc); + gf_odf_dump_txtcfg(txtc, NULL, 0, GF_FALSE); + gf_odf_desc_del((GF_Descriptor *) txtc); + + //stuff only used by vtbdec + gf_hevc_read_pps_bs(NULL, NULL); + gf_hevc_read_sps_bs(NULL, NULL); + gf_hevc_read_vps_bs(NULL, NULL); + gf_mpegv12_get_config(NULL, 0, NULL); + + //hinting stuff + GF_HintPacket *hpck = gf_isom_hint_pck_new(GF_ISOM_BOX_TYPE_RTCP_STSD); + gf_isom_hint_pck_length(hpck); + gf_isom_hint_pck_size(hpck); + GF_BitStream *hbs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_isom_hint_pck_write(hpck, hbs); + u8 *hbuf; + u32 hsize; + gf_bs_get_content(hbs, &hbuf, &hsize); + gf_bs_del(hbs); + hbs = gf_bs_new(hbuf, hsize, GF_BITSTREAM_READ); + gf_isom_hint_pck_read(hpck, hbs); + gf_bs_del(hbs); + gf_free(hbuf); + gf_isom_hint_pck_del(hpck); + + gf_isom_last_error(NULL); + gf_isom_get_media_time(NULL, 0, 0, NULL); + gf_isom_get_sample_description_index(NULL, 0, 0); + + gf_sg_has_scripting(); + gf_node_get_proto_root(NULL); + gf_node_proto_is_grouping(NULL); + gf_sg_proto_get_id(NULL); + gf_sg_proto_instance_set_ised(NULL, 0, NULL, 0); + + gf_audio_fmt_to_isobmf(0); + gf_pixel_fmt_probe(0, NULL); + gf_net_ntp_to_utc(0); + gf_sys_profiler_sampling_enabled(); + + +#endif + return 0; +} + +static Bool revert_cache_file(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + const char *url; + GF_Config *cached; + if (strncmp(item_name, "gpac_cache_", 11)) return GF_FALSE; + cached = gf_cfg_new(NULL, item_path); + url = gf_cfg_get_key(cached, "cache", "url"); + if (url) url = strstr(url, "://"); + if (url) { + u32 i, len, dir_len=0, k=0; + char *dst_name; + char *sep; + + sep = strstr(item_path, "gpac_cache_"); + if (sep) { + sep[0] = 0; + dir_len = (u32) strlen(item_path); + sep[0] = 'g'; + } + url+=3; + len = (u32) strlen(url); + dst_name = gf_malloc(len+dir_len+1); + memset(dst_name, 0, len+dir_len+1); + + strncpy(dst_name, item_path, dir_len); + k=dir_len; + for (i=0; ifilep) return GF_BAD_PARAM; + gf_fseek(ioctx->filep, offset, whence); + return GF_OK; +} +static u32 fio_read(GF_FileIO *fileio, u8 *buffer, u32 bytes) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return 0; + return (u32) gf_fread(buffer, bytes, ioctx->filep); +} +static u32 fio_write(GF_FileIO *fileio, u8 *buffer, u32 bytes) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return 0; + if (!bytes) { + fflush(ioctx->filep); + return 0; + } + return (u32) gf_fwrite(buffer, bytes, ioctx->filep); +} +static s64 fio_tell(GF_FileIO *fileio) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return -1; + return gf_ftell(ioctx->filep); +} +static Bool fio_eof(GF_FileIO *fileio) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return GF_TRUE; + return feof(ioctx->filep); +} +static int fio_printf(GF_FileIO *fileio, const char *format, va_list args) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return -1; + return vfprintf(ioctx->filep, format, args); +} + +static GF_FileIO *fio_open(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_err) +{ + GF_FileIO *gfio; + FileIOCtx *ioctx; + u32 i, count; + u64 file_size; + Bool no_concatenate = GF_FALSE; + FileIOCtx *ioctx_ref = gf_fileio_get_udta(fileio_ref); + + *out_err = GF_OK; + + if (!strcmp(mode, "ref")) { + ioctx_ref->nb_refs++; + return fileio_ref; + } + if (!strcmp(mode, "unref")) { + if (!ioctx_ref->nb_refs) return NULL; + ioctx_ref->nb_refs--; + if (ioctx_ref->nb_refs) + return fileio_ref; + + url = NULL; + } + + if (!strcmp(mode, "url")) { + if (!url) return NULL; + GF_SAFEALLOC(ioctx, FileIOCtx); + if (!ioctx) return NULL; + ioctx->path = gf_url_concatenate(ioctx_ref->path, url); + gfio = gf_fileio_new(ioctx->path, ioctx, fio_open, fio_seek, fio_read, fio_write, fio_tell, fio_eof, fio_printf); + if (!gfio) { + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + return NULL; + } + //remember it but no need to keep a ref on it + gf_list_add(all_gfio_defined, gfio); + return gfio; + } + if (!strcmp(mode, "probe")) { + if (!gf_file_exists(url)) *out_err = GF_URL_ERROR; + return NULL; + } + + if (!url) { + if (ioctx_ref->filep) gf_fclose(ioctx_ref->filep); + ioctx_ref->filep = NULL; + + if (!ioctx_ref->nb_refs) { + gf_list_del_item(all_gfio_defined, fileio_ref); + gf_fileio_del(fileio_ref); + if (ioctx_ref->path) gf_free(ioctx_ref->path); + gf_free(ioctx_ref); + } + return NULL; + } + + //file handle not opened, we can use the current gfio + if (!ioctx_ref->filep && (!strnicmp(url, "gfio://", 7) || !strcmp(url, ioctx_ref->path)) ) { + ioctx_ref->filep = gf_fopen(ioctx_ref->path, mode); + if (!ioctx_ref->filep) { + *out_err = GF_IO_ERR; + return NULL; + } + file_size = gf_fsize(ioctx_ref->filep); + //in test mode we want to use our ftell and fseek wrappers + if (strchr(mode, 'r')) { + gf_fileio_set_stats(fileio_ref, file_size, file_size, GF_TRUE, 0); + } + return fileio_ref; + } + + //file handle already open (file is being opened twice), create a new gfio or check if we have already created one + gfio = NULL; + ioctx = NULL; + count = gf_list_count(all_gfio_defined); + for (i=0; ipath, url)) { + if (ioctx->filep) { + no_concatenate = GF_TRUE; + ioctx = NULL; + } + gfio = a_gfio; + break; + } + ioctx = NULL; + } + if (!ioctx) { + GF_SAFEALLOC(ioctx, FileIOCtx); + if (!ioctx) { + *out_err = GF_OUT_OF_MEM; + return NULL; + } + if (strnicmp(url, "gfio://", 7)) { + if (no_concatenate) + ioctx->path = gf_strdup(url); + else + ioctx->path = gf_url_concatenate(ioctx_ref->path, url); + } else { + ioctx->path = gf_strdup(ioctx_ref->path); + } + gfio = gf_fileio_new(ioctx->path, ioctx, fio_open, fio_seek, fio_read, fio_write, fio_tell, fio_eof, fio_printf); + if (!gfio) { + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + *out_err = GF_OUT_OF_MEM; + } + } + + if (strnicmp(url, "gfio://", 7)) { + ioctx->filep = gf_fopen(ioctx->path, mode); + } else { + ioctx->filep = gf_fopen(ioctx_ref->path, mode); + } + + if (!ioctx->filep) { + *out_err = GF_IO_ERR; + gf_list_del_item(all_gfio_defined, gfio); + gf_fileio_del(gfio); + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + return NULL; + } + + file_size = gf_fsize(ioctx->filep); + if (strchr(mode, 'r')) + gf_fileio_set_stats(gfio, file_size,file_size, GF_TRUE, 0); + return gfio; +} + + +static const char *make_fileio(const char *inargs, const char **out_arg, Bool is_input, GF_Err *e) +{ + FileIOCtx *ioctx; + GF_FileIO *fio; + char *sep = (char *) gf_url_colon_suffix(inargs, separator_set[1]); + *out_arg = NULL; + if (sep) sep[0] = 0; + + GF_SAFEALLOC(ioctx, FileIOCtx); + if (!ioctx) return NULL; + ioctx->path = gf_strdup(inargs); + if (!ioctx->path) { + gf_free(ioctx); + return NULL; + } + if (sep) { + sep[0] = ':'; + *out_arg = sep+1; + } + fio = gf_fileio_new(ioctx->path, ioctx, fio_open, fio_seek, fio_read, fio_write, fio_tell, fio_eof, fio_printf); + if (!fio) { + gf_free(ioctx->path); + gf_free(ioctx); + *e = GF_OUT_OF_MEM; + return NULL; + } + if (!all_gfio_defined) { + all_gfio_defined = gf_list_new(); + if (!all_gfio_defined) return NULL; + } + gf_list_add(all_gfio_defined, fio); + //keep alive until end + ioctx->nb_refs = 1; + return gf_fileio_url(fio); +} + +static void cleanup_file_io() +{ + if (!all_gfio_defined) return; + while (gf_list_count(all_gfio_defined)) { + GF_FileIO *gfio = gf_list_pop_back(all_gfio_defined); + FileIOCtx *ioctx = gf_fileio_get_udta(gfio); + gf_fileio_del(gfio); + + if (ioctx->filep) { + fprintf(stderr, "Warning: file IO for %s still opened!\n", ioctx->path); + gf_fclose(ioctx->filep); + } + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + } + gf_list_del(all_gfio_defined); + all_gfio_defined = NULL; +} + +static GF_Err cust_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_FilterEvent evt; + if (is_remove) return GF_OK; + + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} +static GF_Err cust_process(GF_Filter *filter) +{ + u32 i; + for (i=0; i.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/mp4box/filedump.c b/applications/mp4box/filedump.c new file mode 100644 index 0000000..8fddd74 --- /dev/null +++ b/applications/mp4box/filedump.c @@ -0,0 +1,4492 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2021 + * All rights reserved + * + * This file is part of GPAC / mp4box application + * + * 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 "mp4box.h" + +#if defined(GPAC_DISABLE_ISOM) || defined(GPAC_DISABLE_ISOM_WRITE) + +#error "Cannot compile MP4Box if GPAC is not built with ISO File Format support" + +#else + +#ifndef GPAC_DISABLE_X3D +#include +#endif +#ifndef GPAC_DISABLE_BIFS +#include +#endif +#ifndef GPAC_DISABLE_VRML +#include +#endif +#include +#include +#include +/*ISO 639 languages*/ +#include +#include + +#ifndef GPAC_DISABLE_SMGR +#include +#endif +#include +#include +/*for built-in box printing*/ +#include + +#include +#include + +extern u32 swf_flags; +extern Float swf_flatten_angle; +extern GF_FileType get_file_type_by_ext(char *inName); +extern u32 fs_dump_flags; +extern Bool dump_check_xml; + +void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist); + + +#ifdef GPAC_DISABLE_LOG +void mp4box_log(const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + vfprintf(stderr, fmt, vlist); + fflush(stderr); + va_end(vl); +} +#endif + + +u32 PrintLanguages(char *val, u32 opt) +{ + u32 i=0, count = gf_lang_get_count(); + fprintf(stderr, "Supported ISO 639 languages and codes:\n\n"); + for (i=0; i=0) return gf_lang_get_name(idx); + return lcode; +} + +GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name) +{ + const u8 *tag; + FILE *t; + u32 tag_len; + GF_Err e = gf_isom_apple_get_tag(file, GF_ISOM_ITUNE_COVER_ART, &tag, &tag_len); + if (e!=GF_OK) { + if (e==GF_URL_ERROR) { + M4_LOG(GF_LOG_WARNING, ("No cover art found\n")); + return GF_OK; + } + return e; + } + + if (inName) { + char szName[1024]; + if (is_final_name) { + strcpy(szName, inName); + } else { + sprintf(szName, "%s.%s", inName, (tag_len>>31) ? "png" : "jpg"); + } + t = gf_fopen(szName, "wb"); + if (!t) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szName)); + return GF_IO_ERR; + } + } else { + t = stdout; + } + gf_fwrite(tag, tag_len & 0x7FFFFFFF, t); + + if (inName) gf_fclose(t); + return GF_OK; +} + +#ifndef GPAC_DISABLE_SCENE_DUMP + +GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDumpFormat dump_mode, Bool do_log, Bool no_odf_conv) +{ + GF_Err e; + GF_SceneManager *ctx; + GF_SceneGraph *sg; + GF_SceneLoader load; + GF_FileType ftype; + gf_log_cbk prev_logs = NULL; + FILE *logs = NULL; + + sg = gf_sg_new(); + ctx = gf_sm_new(sg); + memset(&load, 0, sizeof(GF_SceneLoader)); + load.fileName = file; + load.ctx = ctx; + load.swf_import_flags = swf_flags; + if (dump_mode == GF_SM_DUMP_SVG) { + load.swf_import_flags |= GF_SM_SWF_USE_SVG; + load.svgOutFile = inName; + } + load.swf_flatten_limit = swf_flatten_angle; + + ftype = get_file_type_by_ext(file); + if (ftype == GF_FILE_TYPE_ISO_MEDIA) { + load.isom = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL); + if (!load.isom) { + e = gf_isom_last_error(NULL); + M4_LOG(GF_LOG_ERROR, ("Error opening file: %s\n", gf_error_to_string(e))); + gf_sm_del(ctx); + gf_sg_del(sg); + return e; + } + if (no_odf_conv) + gf_isom_disable_odf_conversion(load.isom, GF_TRUE); + + } else if (ftype==GF_FILE_TYPE_LSR_SAF) { + load.isom = gf_isom_open("saf_conv", GF_ISOM_WRITE_EDIT, NULL); +#ifndef GPAC_DISABLE_MEDIA_IMPORT + if (load.isom) { + GF_Fraction _frac = {0,0}; + e = import_file(load.isom, file, 0, _frac, 0, NULL, NULL, NULL, 0); + } else +#else + M4_LOG(GF_LOG_WARNING, ("Warning: GPAC was compiled without Media Import support\n")); +#endif + e = gf_isom_last_error(NULL); + + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error importing file: %s\n", gf_error_to_string(e))); + gf_sm_del(ctx); + gf_sg_del(sg); + if (load.isom) gf_isom_delete(load.isom); + return e; + } + } + + if (do_log) { + char szLog[GF_MAX_PATH]; + sprintf(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); + } + e = gf_sm_load_init(&load); + if (!e) e = gf_sm_load_run(&load); + gf_sm_load_done(&load); + if (logs) { + gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_ERROR); + gf_log_set_callback(NULL, prev_logs); + gf_fclose(logs); + } + if (!e && dump_mode != GF_SM_DUMP_SVG) { + u32 count = gf_list_count(ctx->streams); + if (count) + fprintf(stderr, "Scene loaded - dumping %d systems streams\n", count); + else + fprintf(stderr, "Scene loaded - dumping root scene\n"); + + e = gf_sm_dump(ctx, inName, is_final_name, dump_mode); + } + + gf_sm_del(ctx); + gf_sg_del(sg); + if (e) M4_LOG(GF_LOG_ERROR, ("Error loading scene: %s\n", gf_error_to_string(e))); + if (load.isom) gf_isom_delete(load.isom); + return e; +} +#endif + +#ifndef GPAC_DISABLE_SCENE_STATS + +static void dump_stats(FILE *dump, const GF_SceneStatistics *stats) +{ + u32 i; + s32 created, count, draw_created, draw_count, deleted, draw_deleted; + created = count = draw_created = draw_count = deleted = draw_deleted = 0; + + fprintf(dump, "\n"); + fprintf(dump, "\n", gf_list_count(stats->node_stats)); + for (i=0; inode_stats); i++) { + GF_NodeStats *ptr = gf_list_get(stats->node_stats, i); + fprintf(dump, "\n", ptr->name); + + switch (ptr->tag) { +#ifndef GPAC_DISABLE_VRML + case TAG_MPEG4_Bitmap: + case TAG_MPEG4_Background2D: + case TAG_MPEG4_Background: + case TAG_MPEG4_Box: + case TAG_MPEG4_Circle: + case TAG_MPEG4_CompositeTexture2D: + case TAG_MPEG4_CompositeTexture3D: + case TAG_MPEG4_Cylinder: + case TAG_MPEG4_Cone: + case TAG_MPEG4_Curve2D: + case TAG_MPEG4_Extrusion: + case TAG_MPEG4_ElevationGrid: + case TAG_MPEG4_IndexedFaceSet2D: + case TAG_MPEG4_IndexedFaceSet: + case TAG_MPEG4_IndexedLineSet2D: + case TAG_MPEG4_IndexedLineSet: + case TAG_MPEG4_PointSet2D: + case TAG_MPEG4_PointSet: + case TAG_MPEG4_Rectangle: + case TAG_MPEG4_Sphere: + case TAG_MPEG4_Text: + case TAG_MPEG4_Ellipse: + case TAG_MPEG4_XCurve2D: + draw_count += ptr->nb_created + ptr->nb_used - ptr->nb_del; + draw_deleted += ptr->nb_del; + draw_created += ptr->nb_created; + break; +#endif /*GPAC_DISABLE_VRML*/ + } + fprintf(dump, "\n", ptr->nb_created, ptr->nb_used, ptr->nb_del); + count += ptr->nb_created + ptr->nb_used; + deleted += ptr->nb_del; + created += ptr->nb_created; + fprintf(dump, "\n"); + } + if (i) { + fprintf(dump, "\n", count, created, deleted, stats->nb_svg_attributes); + fprintf(dump, "\n", draw_count, draw_created, draw_deleted); + } + fprintf(dump, "\n"); + + created = count = deleted = 0; + if (gf_list_count(stats->proto_stats)) { + fprintf(dump, "\n", gf_list_count(stats->proto_stats)); + for (i=0; iproto_stats); i++) { + GF_NodeStats *ptr = gf_list_get(stats->proto_stats, i); + fprintf(dump, "\n", ptr->name); + fprintf(dump, "\n", ptr->nb_created, ptr->nb_used, ptr->nb_del); + count += ptr->nb_created + ptr->nb_used; + deleted += ptr->nb_del; + created += ptr->nb_created; + fprintf(dump, "\n"); + } + if (i) fprintf(dump, "\n", count, created, deleted); + fprintf(dump, "\n"); + } + fprintf(dump, "\n", FIX2FLT( stats->min_fixed) , FIX2FLT( stats->max_fixed )); + fprintf(dump, "\n", stats->scale_int_res_2d, stats->scale_frac_res_2d, stats->int_res_2d, stats->frac_res_2d); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + fprintf(dump, "\n", stats->count_2d, stats->rem_2d); + if (stats->count_2d) { + fprintf(dump, "\n", FIX2FLT( stats->min_2d.x) , FIX2FLT( stats->min_2d.y ), FIX2FLT( stats->max_2d.x ), FIX2FLT( stats->max_2d.y ) ); + } + fprintf(dump, "\n"); + + fprintf(dump, "\n"); + fprintf(dump, "", stats->count_3d, stats->rem_3d); + if (stats->count_3d) { + fprintf(dump, "\n", FIX2FLT( stats->min_3d.x ), FIX2FLT( stats->min_3d.y ), FIX2FLT( stats->min_3d.z ), FIX2FLT( stats->max_3d.x ), FIX2FLT( stats->max_3d.y ), FIX2FLT( stats->max_3d.z ) ); + } + fprintf(dump, "\n"); + + fprintf(dump, "\n"); + fprintf(dump, "", stats->count_color, stats->rem_color); + fprintf(dump, "\n"); + + fprintf(dump, "\n"); + fprintf(dump, "", stats->count_float, stats->rem_float); + fprintf(dump, "\n"); + + fprintf(dump, "\n"); + fprintf(dump, "", stats->count_2f); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + fprintf(dump, "", stats->count_3f); + fprintf(dump, "\n"); +} + + +static void ReorderAU(GF_List *sample_list, GF_AUContext *au) +{ + u32 i; + for (i=0; itiming_sec > au->timing_sec) + /*set bifs first*/ + || ((ptr->timing_sec == au->timing_sec) && (ptr->owner->streamType < au->owner->streamType)) + ) { + gf_list_insert(sample_list, au, i); + return; + } + } + gf_list_add(sample_list, au); +} + +void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 stat_level) +{ + GF_Err e; + FILE *dump; + Bool close; + u32 i, j, count; + char szBuf[1024]; + GF_SceneManager *ctx; + GF_SceneLoader load; + GF_StatManager *sm; + GF_List *sample_list; + GF_SceneGraph *scene_graph; + + dump = NULL; + sm = NULL; + sample_list = NULL; + + close = 0; + + scene_graph = gf_sg_new(); + ctx = gf_sm_new(scene_graph); + memset(&load, 0, sizeof(GF_SceneLoader)); + load.fileName = file; + load.ctx = ctx; + + if (get_file_type_by_ext(file) == 1) { + 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)))); + gf_sm_del(ctx); + gf_sg_del(scene_graph); + return; + } + } + + e = gf_sm_load_init(&load); + if (!e) e = gf_sm_load_run(&load); + gf_sm_load_done(&load); + if (e<0) goto exit; + + if (inName) { + strcpy(szBuf, inName); + if (!is_final_name) strcat(szBuf, "_stat.xml"); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szBuf)); + return; + } + close = 1; + } else { + dump = stdout; + close = 0; + } + + fprintf(stderr, "Analysing Scene\n"); + + fprintf(dump, "\n"); + fprintf(dump, "\n"); + + fprintf(dump, "\n", gf_file_basename(file), (stat_level==1) ? "full scene" : ((stat_level==2) ? "AccessUnit based" : "SceneGraph after each AU")); + + sm = gf_sm_stats_new(); + + /*stat level 1: complete scene stat*/ + if (stat_level == 1) { + e = gf_sm_stats_for_scene(sm, ctx); + if (!e) dump_stats(dump, gf_sm_stats_get(sm) ); + goto exit; + } + /*re_order all BIFS-AUs*/ + sample_list = gf_list_new(); + /*configure all systems streams we're dumping*/ + for (i=0; istreams); i++) { + GF_StreamContext *sc = gf_list_get(ctx->streams, i); + if (sc->streamType != GF_STREAM_SCENE) continue; + for (j=0; jAUs); j++) { + GF_AUContext *au = gf_list_get(sc->AUs, j); + ReorderAU(sample_list, au); + } + } + + count = gf_list_count(sample_list); + for (i=0; icommands); j++) { + GF_Command *com = gf_list_get(au->commands, j); + /*stat level 2 - get command stats*/ + if (stat_level==2) { + e = gf_sm_stats_for_command(sm, com); + if (e) goto exit; + } + /*stat level 3 - apply command*/ + if (stat_level==3) gf_sg_command_apply(scene_graph, com, 0); + } + /*stat level 3: get graph stat*/ + if (stat_level==3) { + e = gf_sm_stats_for_graph(sm, scene_graph); + if (e) goto exit; + } + if (stat_level==2) { + fprintf(dump, "\n", au->owner->ESID, au->timing); + } else { + fprintf(dump, "\n", au->owner->ESID, au->timing); + } + /*dump stats*/ + dump_stats(dump, gf_sm_stats_get(sm) ); + /*reset stats*/ + gf_sm_stats_reset(sm); + if (stat_level==2) { + fprintf(dump, "\n"); + } else { + fprintf(dump, "\n"); + } + + gf_set_progress("Analysing AU", i+1, count); + } + + +exit: + if (sample_list) gf_list_del(sample_list); + if (sm) gf_sm_stats_del(sm); + gf_sm_del(ctx); + gf_sg_del(scene_graph); + if (load.isom) gf_isom_delete(load.isom); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Stats error: %s\n", gf_error_to_string(e))); + } else { + fprintf(dump, "\n"); + } + if (dump && close) gf_fclose(dump); + fprintf(stderr, "done\n"); +} +#endif /*GPAC_DISABLE_SCENE_STATS*/ + + + +#ifndef GPAC_DISABLE_VRML + +static void PrintFixed(Fixed val, Bool add_space) +{ + if (add_space) fprintf(stderr, " "); + if (val==FIX_MIN) fprintf(stderr, "-I"); + else if (val==FIX_MAX) fprintf(stderr, "+I"); + else fprintf(stderr, "%g", FIX2FLT(val)); +} + +static void PrintNodeSFField(u32 type, void *far_ptr) +{ + if (!far_ptr) return; + switch (type) { + case GF_SG_VRML_SFBOOL: + fprintf(stderr, "%s", (*(SFBool *)far_ptr) ? "TRUE" : "FALSE"); + break; + case GF_SG_VRML_SFINT32: + fprintf(stderr, "%d", (*(SFInt32 *)far_ptr)); + break; + case GF_SG_VRML_SFFLOAT: + PrintFixed((*(SFFloat *)far_ptr), 0); + break; + case GF_SG_VRML_SFTIME: + fprintf(stderr, "%g", (*(SFTime *)far_ptr)); + break; + case GF_SG_VRML_SFVEC2F: + PrintFixed(((SFVec2f *)far_ptr)->x, 0); + PrintFixed(((SFVec2f *)far_ptr)->y, 1); + break; + case GF_SG_VRML_SFVEC3F: + PrintFixed(((SFVec3f *)far_ptr)->x, 0); + PrintFixed(((SFVec3f *)far_ptr)->y, 1); + PrintFixed(((SFVec3f *)far_ptr)->z, 1); + break; + case GF_SG_VRML_SFROTATION: + PrintFixed(((SFRotation *)far_ptr)->x, 0); + PrintFixed(((SFRotation *)far_ptr)->y, 1); + PrintFixed(((SFRotation *)far_ptr)->z, 1); + PrintFixed(((SFRotation *)far_ptr)->q, 1); + break; + case GF_SG_VRML_SFCOLOR: + PrintFixed(((SFColor *)far_ptr)->red, 0); + PrintFixed(((SFColor *)far_ptr)->green, 1); + PrintFixed(((SFColor *)far_ptr)->blue, 1); + break; + case GF_SG_VRML_SFSTRING: + if (((SFString*)far_ptr)->buffer) + fprintf(stderr, "\"%s\"", ((SFString*)far_ptr)->buffer); + else + fprintf(stderr, "NULL"); + break; + } +} +#endif + +#ifndef GPAC_DISABLE_VRML +static void do_print_node(GF_Node *node, GF_SceneGraph *sg, const char *name, u32 graph_type, Bool is_nodefield, Bool do_cov) +{ + u32 nbF, i; + GF_FieldInfo f; +#ifndef GPAC_DISABLE_BIFS + u8 qt, at; + Fixed bmin, bmax; + u32 nbBits; +#endif /*GPAC_DISABLE_BIFS*/ + + nbF = gf_node_get_field_count(node); + + if (is_nodefield) { + char szField[1024]; + u32 tfirst, tlast; + if (gf_node_get_field_by_name(node, szField, &f) != GF_OK) { + M4_LOG(GF_LOG_ERROR, ("Field %s is not a member of node %s\n", szField, name)); + return; + } + fprintf(stderr, "Allowed nodes in %s.%s:\n", name, szField); + if (graph_type==1) { + tfirst = GF_NODE_RANGE_FIRST_X3D; + tlast = GF_NODE_RANGE_LAST_X3D; + } else { + tfirst = GF_NODE_RANGE_FIRST_MPEG4; + tlast = GF_NODE_RANGE_LAST_MPEG4; + } + for (i=tfirst; icount; j++) { + if (j) fprintf(stderr, " "); + gf_sg_vrml_mf_get_item(f.far_ptr, f.fieldType, &ptr, j); + PrintNodeSFField(sftype, ptr); + } + fprintf(stderr, "]"); + } +#ifndef GPAC_DISABLE_BIFS + if (gf_bifs_get_aq_info(node, i, &qt, &at, &bmin, &bmax, &nbBits)) { + if (qt) { + fprintf(stderr, " #QP=%d", qt); + if (qt==13) fprintf(stderr, " NbBits=%d", nbBits); + if (bmin && bmax) { + fprintf(stderr, " Bounds=["); + PrintFixed(bmin, 0); + fprintf(stderr, ","); + PrintFixed(bmax, 0); + fprintf(stderr, "]"); + } + } + } +#endif /*GPAC_DISABLE_BIFS*/ + fprintf(stderr, "\n"); + + if (do_cov) { + gf_node_get_field_by_name(node, (char *) f.name, &f); + } + } + fprintf(stderr, "}\n\n"); + +} +#endif + + +u32 PrintNode(const char *name, u32 graph_type) +{ +#ifdef GPAC_DISABLE_VRML + M4_LOG(GF_LOG_ERROR, ("VRML/MPEG-4/X3D scene graph is disabled in this build of GPAC\n")); + return 2; +#else + const char *std_name; + GF_Node *node; + GF_SceneGraph *sg; + u32 tag; +#ifndef GPAC_DISABLE_BIFS +#endif /*GPAC_DISABLE_BIFS*/ + Bool is_nodefield = 0; + + char *sep = strchr(name, '.'); + if (sep) { + sep[0] = 0; + is_nodefield = 1; + } + + if (graph_type==1) { +#ifndef GPAC_DISABLE_X3D + tag = gf_node_x3d_type_by_class_name(name); + std_name = "X3D"; +#else + M4_LOG(GF_LOG_ERROR, ("X3D node printing is not supported (X3D support disabled)\n")); + return 2; +#endif + } else { + tag = gf_node_mpeg4_type_by_class_name(name); + std_name = "MPEG4"; + } + if (!tag) { + M4_LOG(GF_LOG_ERROR, ("Unknown %s node %s\n", std_name, name)); + return 2; + } + + sg = gf_sg_new(); + node = gf_node_new(sg, tag); + gf_node_register(node, NULL); + name = gf_node_get_class_name(node); + if (!node) { + M4_LOG(GF_LOG_ERROR, ("Node %s not supported in current built\n", name)); + return 2; + } + do_print_node(node, sg, name, graph_type, is_nodefield, GF_FALSE); + + gf_node_unregister(node, NULL); + gf_sg_del(sg); +#endif /*GPAC_DISABLE_VRML*/ + return 1; +} + +u32 PrintBuiltInNodes(char *arg_val, u32 dump_type) +{ +#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_X3D) && !defined(GPAC_DISABLE_SVG) + GF_SceneGraph *sg; + u32 i, nb_in, nb_not_in, start_tag, end_tag; + u32 graph_type; + Bool dump_nodes = ((dump_type==1) || (dump_type==3)) ? 1 : 0; + + if (dump_type==4) graph_type = 2; + else if ((dump_type==2) || (dump_type==3)) graph_type = 1; + else graph_type = 0; + + if (graph_type==1) { +#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_X3D) + start_tag = GF_NODE_RANGE_FIRST_X3D; + end_tag = TAG_LastImplementedX3D; +#else + M4_LOG(GF_LOG_ERROR, ("X3D scene graph disabled in this build of GPAC\n")); + return 2; +#endif + } else if (graph_type==2) { +#ifdef GPAC_DISABLE_SVG + M4_LOG(GF_LOG_ERROR, ("SVG scene graph disabled in this build of GPAC\n")); + return 2; +#else + start_tag = GF_NODE_RANGE_FIRST_SVG; + end_tag = GF_NODE_RANGE_LAST_SVG; +#endif + } else { +#ifdef GPAC_DISABLE_VRML + M4_LOG(GF_LOG_ERROR, ("VRML/MPEG-4 scene graph disabled in this build of GPAC\n")); + return 2; +#else + start_tag = GF_NODE_RANGE_FIRST_MPEG4; + end_tag = TAG_LastImplementedMPEG4; +#endif + } + nb_in = nb_not_in = 0; + sg = gf_sg_new(); + + if (graph_type==1) { + fprintf(stderr, "Available X3D nodes in this build (dumping):\n"); + } else if (graph_type==2) { + fprintf(stderr, "Available SVG nodes in this build (dumping and LASeR coding):\n"); + } else { + fprintf(stderr, "Available MPEG-4 nodes in this build (encoding/decoding/dumping):\n"); + } + for (i=start_tag; i\n"); + //index 0 is our internal unknown box handler + for (i=1; i\n"); + return 1; +} + +#if !defined(GPAC_DISABLE_ISOM_HINTING) && !defined(GPAC_DISABLE_ISOM_DUMP) + +void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name) +{ + u32 i, j, size; + FILE *dump; + const char *sdp; + + if (inName) { + char szBuf[1024]; + strcpy(szBuf, inName); + if (!is_final_name) strcat(szBuf, "_rtp.xml"); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s\n", szBuf)); + return; + } + } else { + dump = stdout; + } + + fprintf(dump, "\n"); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + + for (i=0; i\n", gf_isom_get_track_id(file, i+1)); + gf_isom_sdp_track_get(file, i+1, &sdp, &size); + fprintf(dump, "%s", sdp); + +#ifndef GPAC_DISABLE_ISOM_HINTING + for (j=0; j\n"); + } + fprintf(dump, "\n"); + if (inName) gf_fclose(dump); +} +#endif + + +void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_mode) +{ + u32 i, j, k, count; + Bool has_ctts_error, is_fragmented=GF_FALSE; + FILE *dump; + Bool skip_offset = ((dump_mode==2) || (dump_mode==4)) ? GF_TRUE : GF_FALSE; + Bool check_ts = ((dump_mode==3) || (dump_mode==4)) ? GF_TRUE : GF_FALSE; + struct _ts_info { + u64 dts; + s64 cts; + }; + struct _ts_info *timings = NULL; + u32 nb_timings=0, nb_timings_alloc = 0; + + + if (inName) { + char szBuf[1024]; + strcpy(szBuf, inName); + if (!is_final_name) strcat(szBuf, "_ts.txt"); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s\n", szBuf)); + return; + } + } else { + dump = stdout; + } + if (gf_isom_is_fragmented(file)) + is_fragmented = GF_TRUE; + + has_ctts_error = GF_FALSE; + for (i=0; iDTS; + cts = dts + (s32) samp->CTS_Offset; + fprintf(dump, "Sample %d\tDTS "LLU"\tCTS "LLD"\t%d\t%d", j+1, dts, cts, samp->dataLength, samp->IsRAP); + + if (!skip_offset) + fprintf(dump, "\t"LLU, offset); + + fprintf(dump, "\t%d\t%d\t%d\t%d\t%d\t%d\t%d", isLeading, dependsOn, dependedOn, redundant, is_rap, roll_type, roll_distance); + + if (cts< (s64) dts) { + if (has_cts_offset==2) { + if (cts_dts_shift && (cts+cts_dts_shift < (s64) dts)) { + fprintf(dump, " #NEGATIVE CTS OFFSET!!!"); + has_ctts_error = 1; + } else if (!cts_dts_shift) { + fprintf(dump, " #possible negative CTS offset (no cslg in file)"); + } + } else { + fprintf(dump, " #NEGATIVE CTS OFFSET!!!"); + has_ctts_error = 1; + } + } + if (has_cts_offset && check_ts) { + for (k=0; k\n", trackID, count, timescale); + +#ifndef GPAC_DISABLE_AV_PARSERS + +#define DUMP_ARRAY(arr, name, loc, _is_svc)\ + if (arr) {\ + fprintf(dump, " <%sArray location=\"%s\">\n", name, loc);\ + for (i=0; isize);\ + 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);\ + }\ + fprintf(dump, " \n", name);\ + }\ + +#else + +#define DUMP_ARRAY(arr, name, loc, _is_svc)\ + if (arr) {\ + fprintf(dump, " <%sArray location=\"%s\">\n", name, loc);\ + for (i=0; isize);\ + fprintf(dump, "/>\n");\ + }\ + fprintf(dump, " \n", name);\ + }\ + +#endif + + nalh_size = 0; + + for (j=0; j\n"); + + if (!avccfg && !svccfg && !hevccfg && !lhvccfg && !vvccfg) { + M4_LOG(GF_LOG_ERROR, ("Error: Track #%d is not NALU or OBU based!\n", trackID)); + return GF_BAD_PARAM; + } + + if (avccfg) { + nalh_size = avccfg->nal_unit_size; + + DUMP_ARRAY(avccfg->sequenceParameterSets, "AVCSPS", "avcC", is_svc); + DUMP_ARRAY(avccfg->pictureParameterSets, "AVCPPS", "avcC", is_svc) + DUMP_ARRAY(avccfg->sequenceParameterSetExtensions, "AVCSPSEx", "avcC", is_svc) + } + if (is_svc) { + if (!nalh_size) nalh_size = svccfg->nal_unit_size; + DUMP_ARRAY(svccfg->sequenceParameterSets, "SVCSPS", "svcC", is_svc) + DUMP_ARRAY(svccfg->pictureParameterSets, "SVCPPS", "svcC", is_svc) + } + if (mvccfg) { + if (!nalh_size) nalh_size = mvccfg->nal_unit_size; + DUMP_ARRAY(mvccfg->sequenceParameterSets, "SVCSPS", "mvcC", is_svc) + DUMP_ARRAY(mvccfg->pictureParameterSets, "SVCPPS", "mvcC", is_svc) + } + if (hevccfg) { + u32 idx; + nalh_size = hevccfg->nal_unit_size; + for (idx=0; idxparam_array); idx++) { + GF_NALUFFParamArray *ar = gf_list_get(hevccfg->param_array, idx); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCSPS", "hvcC", 0) + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCPPS", "hvcC", 0) + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCVPS", "hvcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "hvcC", 0) + } + } + } + if (vvccfg) { + u32 idx; + nalh_size = vvccfg->nal_unit_size; + for (idx=0; idxparam_array); idx++) { + GF_NALUFFParamArray *ar = gf_list_get(vvccfg->param_array, idx); + if (ar->type==GF_VVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "VVCSPS", "vvcC", 0) + } else if (ar->type==GF_VVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "VVCPPS", "vvcC", 0) + } else if (ar->type==GF_VVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "VVCVPS", "vvcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "VVCUnknownPS", "vvcC", 0) + } + } + } + if (lhvccfg) { + u32 idx; + nalh_size = lhvccfg->nal_unit_size; + for (idx=0; idxparam_array); idx++) { + GF_NALUFFParamArray *ar = gf_list_get(lhvccfg->param_array, idx); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCSPS", "lhcC", 0) + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCPPS", "lhcC", 0) + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCVPS", "lhcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "lhcC", 0) + } + } + } + fprintf(dump, " \n"); + + if (avccfg) gf_odf_avc_cfg_del(avccfg); + if (svccfg) { + gf_odf_avc_cfg_del(svccfg); + has_svcc = GF_TRUE; + } + if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); + if (vvccfg) gf_odf_vvc_cfg_del(vvccfg); + if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg); + } + + /*fixme: for dumping encrypted track: we don't have neither avccfg nor svccfg*/ + if (!nalh_size) nalh_size = 4; + + /*for testing dependency*/ + countRef = gf_isom_get_reference_count(file, track, GF_ISOM_REF_SCAL); + if (countRef > 0) + { + GF_ISOTrackID refTrackID; + fprintf(dump, " \n"); + for (i = 1; i <= (u32) countRef; i++) + { + gf_isom_get_reference_ID(file, track, GF_ISOM_REF_SCAL, i, &refTrackID); + fprintf(dump, " \n", i, refTrackID); + } + + fprintf(dump, " \n"); + } + + fprintf(dump, " \n"); + gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT); + is_adobe_protected = gf_isom_is_adobe_protection_media(file, track, 1); + is_cenc_protected = gf_isom_is_cenc_media(file, track, 1); + for (i=0; iDTS; + cts = dts + (s32) samp->CTS_Offset; + is_rap = samp->IsRAP; + if (!is_rap) gf_isom_get_sample_rap_roll_info(file, track, i+1, &is_rap, NULL, NULL); + + if (dump_flags&2) { + fprintf(dump, " dataLength, is_rap); + } else { + fprintf(dump, " dataLength, is_rap); + } + if (nb_descs>1) + fprintf(dump, " sample_description=\"%d\"", di); + fprintf(dump, " >\n"); + + if (cts\n"); + + idx = 1; + ptr = samp->data; + size = samp->dataLength; + if (is_adobe_protected) { + u8 encrypted_au = ptr[0]; + if (encrypted_au) { + fprintf(dump, " \n", i+1); + fprintf(dump, " \n\n"); + continue; + } + else { + ptr++; + size--; + } + } + while (size) { + if (size\n", idx, nal_size, size); + break; + } + nal_size = read_nal_size_hdr(ptr, nalh_size); + ptr += nalh_size; + + if (nal_size >= UINT_MAX-nalh_size || nalh_size + nal_size > size) { + fprintf(dump, " \n", idx, nal_size, size); + break; + } else { + fprintf(dump, " \n"); +#endif + } + idx++; + ptr+=nal_size; + size -= nal_size + nalh_size; + } + fprintf(dump, " \n"); + gf_isom_sample_del(&samp); + + fprintf(dump, "\n"); + gf_set_progress("Analysing Track NALUs", i+1, count); + } + fprintf(dump, " \n"); + fprintf(dump, "\n"); + + gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode); + +#ifndef GPAC_DISABLE_AV_PARSERS + if (hevc_state) gf_free(hevc_state); + if (vvc_state) gf_free(vvc_state); + if (avc_state) gf_free(avc_state); +#endif + + return e; +} + +static GF_Err dump_isom_obu(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc); +static GF_Err dump_qt_prores(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc); + +GF_Err dump_isom_nal(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, u32 dump_flags) +{ + char szFileName[GF_MAX_PATH]; + Bool is_av1 = GF_FALSE; + Bool is_prores = GF_FALSE; + + FILE *dump; + if (inName) { + GF_ESD* esd; + + strcpy(szFileName, inName); + + u32 track = gf_isom_get_track_by_id(file, trackID); + esd = gf_isom_get_esd(file, track, 1); + + if (!esd || !esd->decoderConfig) { + switch (gf_isom_get_media_subtype(file, track, 1)) { + case GF_ISOM_SUBTYPE_AV01: + is_av1 = GF_TRUE; + break; + case GF_QT_SUBTYPE_APCH: + case GF_QT_SUBTYPE_APCO: + case GF_QT_SUBTYPE_APCN: + case GF_QT_SUBTYPE_APCS: + case GF_QT_SUBTYPE_AP4X: + case GF_QT_SUBTYPE_AP4H: + is_prores = GF_TRUE; + break; + } + } + else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AV1) { + is_av1 = GF_TRUE; + } + if (esd) gf_odf_desc_del((GF_Descriptor*)esd); + + if (!is_final_name) sprintf(szFileName, "%s_%d_%s.xml", inName, trackID, is_av1 ? "obu" : "nalu"); + dump = gf_fopen(szFileName, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szFileName)); + return GF_URL_ERROR; + } + } else { + dump = stdout; + } + + GF_Err e = GF_OK; + if (is_av1) + e = dump_isom_obu(file, trackID, dump, dump_flags); + else if (is_prores) + e = dump_qt_prores(file, trackID, dump, dump_flags); + else + e = dump_isom_nal_ex(file, trackID, dump, dump_flags); + + if (inName) gf_fclose(dump); + + if (!e && dump_check_xml) { + if (inName) { + GF_DOMParser *xml_parser = gf_xml_dom_new(); + GF_Err e = gf_xml_dom_parse(xml_parser, szFileName, NULL, NULL); + if (e) { + fprintf(stderr, "Failed to parse XML dump %s: line %d: %s\n", szFileName, gf_xml_dom_get_line(xml_parser), gf_xml_dom_get_error(xml_parser) ); + } + gf_xml_dom_del(xml_parser); + } else { + fprintf(stderr, "Cannot check XML for dump on stdout\n"); + } + } + return e; +} + +#ifndef GPAC_DISABLE_AV_PARSERS +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); +#endif + +static GF_Err dump_isom_obu(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc) +{ + GF_Err e = GF_OK; +#ifndef GPAC_DISABLE_AV_PARSERS + u32 i, count, track, timescale; + AV1State av1; + ObuType obu_type = 0; + u64 obu_size = 0; + u32 hdr_size = 0; + GF_BitStream *bs; + u32 idx; + + 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) { + M4_LOG(GF_LOG_ERROR, ("Error: Track #%d is not AV1!\n", trackID)); + return GF_ISOM_INVALID_FILE; + } + + count = gf_isom_get_sample_count(file, track); + timescale = gf_isom_get_media_timescale(file, track); + + fprintf(dump, "\n", trackID, count, timescale); + + fprintf(dump, " \n"); + + for (i=0; iobu_array); i++) { + GF_AV1_OBUArrayEntry *obu = gf_list_get(av1.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); + if (eobu, obu->obu_length, obu_type, obu_size, hdr_size, dump_crc); + gf_bs_del(bs); + } + fprintf(dump, " \n"); + + fprintf(dump, " \n"); + + e = GF_OK; + for (i=0; iDTS; + cts = dts + (s32) samp->CTS_Offset; + + fprintf(dump, " \n", i+1, dts, cts, samp->dataLength, samp->IsRAP); + if (cts\n"); + + 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); + if (e size) { + fprintf(dump, " \n", idx, (u32) obu_size, size); + break; + } + gf_inspect_dump_obu(dump, &av1, ptr, obu_size, obu_type, obu_size, hdr_size, dump_crc); + ptr += obu_size; + size -= (u32)obu_size; + idx++; + } + gf_bs_del(bs); + fprintf(dump, " \n"); + gf_isom_sample_del(&samp); + + fprintf(dump, "\n"); + + if (e\n"); + fprintf(dump, "\n"); + + if (av1.config) gf_odf_av1_cfg_del(av1.config); + gf_av1_reset_state(&av1, GF_TRUE); +#endif + return e; +} + +static GF_Err dump_qt_prores(GF_ISOFile *file, u32 trackID, FILE *dump, Bool dump_crc) +{ + GF_Err e = GF_OK; +#ifndef GPAC_DISABLE_AV_PARSERS + u32 i, count, track, timescale; + + track = gf_isom_get_track_by_id(file, trackID); + + count = gf_isom_get_sample_count(file, track); + timescale = gf_isom_get_media_timescale(file, track); + + fprintf(dump, "\n", trackID, count, timescale); + + for (i=0; iDTS; + cts = dts + (s32) samp->CTS_Offset; + + if (cts!=dts) fprintf(dump, "\n", cts, dts); + if (!samp->IsRAP) fprintf(dump, "\n"); + + fprintf(dump, " \n", i+1, cts, samp->dataLength); + + gf_inspect_dump_prores(dump, samp->data, samp->dataLength, dump_crc); + fprintf(dump, " \n"); + + gf_isom_sample_del(&samp); + + fprintf(dump, "\n"); + gf_set_progress("Analysing ProRes Track", i+1, count); + } + fprintf(dump, "\n"); +#endif + return e; +} + +void dump_isom_saps(GF_ISOFile *file, GF_ISOTrackID trackID, u32 dump_saps_mode, char *inName, Bool is_final_name) +{ + FILE *dump; + u32 i, count; + s64 media_offset=0; + u32 track = gf_isom_get_track_by_id(file, trackID); + if (inName) { + char szBuf[GF_MAX_PATH]; + strcpy(szBuf, inName); + + if (!is_final_name) sprintf(szBuf, "%s_%d_cues.xml", inName, trackID); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szBuf)); + return; + } + } else { + dump = stdout; + } + + fprintf(dump, "\n"); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + + count = gf_isom_get_sample_count(file, track); + for (i=0; iIsRAP; + if (!sap_type) { + Bool is_rap; + GF_ISOSampleRollType roll_type; + s32 roll_dist; + gf_isom_get_sample_rap_roll_info(file, track, i+1, &is_rap, &roll_type, &roll_dist); + if (roll_type) sap_type = SAP_TYPE_4; + else if (is_rap) sap_type = SAP_TYPE_3; + } + + if (!sap_type) { + gf_isom_sample_del(&samp); + continue; + } + + dts = cts = samp->DTS; + cts += samp->CTS_Offset; + fprintf(dump, "\n"); + + gf_isom_sample_del(&samp); + } + fprintf(dump, "\n"); + fprintf(dump, "\n"); + if (inName) gf_fclose(dump); +} + + +typedef struct +{ + u32 track_num; + u32 chunk_num; + u32 first_sample_num; + u32 timescale; + u32 sample_per_chunk; + u64 chunk_offset; + u64 size; +} ChunkInfo; + +static s32 sort_chunk(const void* e1, const void* e2) +{ + const ChunkInfo *p1 = e1; + const ChunkInfo *p2 = e2; + + if (p1->chunk_offset < p2->chunk_offset) return -1; + if (p1->chunk_offset > p2->chunk_offset) return 1; + return 0; +} + +void dump_isom_chunks(GF_ISOFile *file, char *inName, Bool is_final_name) +{ + u32 i, count, nb_chunks_total, cur; + FILE *dump; + Bool dump_stsd = 0; + u32 dump_hm = 0; + u64 prev_time = 0; + u64 unused_space = 0; + u64 prev_chunk_end = 0; + Bool do_prog=GF_TRUE; + ChunkInfo *all_chunks; + + if (inName) { + char szBuf[1024]; + strcpy(szBuf, inName); + if (!is_final_name) strcat(szBuf, "_chunks.txt"); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szBuf)); + return; + } + } else { + dump = stdout; + do_prog = GF_FALSE; + } + + count = gf_isom_get_track_count(file); + nb_chunks_total = 0; + for (i=0; i 3600 * ts) dump_hm = 2; + else if (dur > 60 * ts) dump_hm = 1; + + if (nb_chunks>1) { + csize = gf_isom_get_constant_sample_size(file, i+1); + if (csize) { + gf_isom_enable_raw_pack(file, i+1, 1024); + csize = gf_isom_get_constant_sample_size(file, i+1); + } + } + c1 = c2 = 0; + for (j=0; jtrack_num = i+1; + ci->chunk_num = j+1; + ci->chunk_offset = offset; + ci->first_sample_num = sample_num; + ci->timescale = ts; + ci->sample_per_chunk = spc; + + if (csize) { + ci->size += csize * spc; + } else { + for (k=0; ksize += gf_isom_get_sample_size(file, i+1, sample_num+k); + } + } + + if (di>1) dump_stsd=1; + + if (do_prog) + gf_set_progress("Analysing chunks", cur+1, nb_chunks_total); + cur++; + } + } + + qsort(all_chunks, nb_chunks_total, sizeof(ChunkInfo), sort_chunk); + + fprintf(stderr, "Dumping chunk info - diff_prev_ms is the diff in ms between start time of current and prev chunk\n"); + + if (dump_stsd) + fprintf(dump, "ChunkNum\tOffset\tTkNum\tChkNbInTk\tChkSize\tSamp/Chunk\tFirstSample\tDescIdx\tSAP\tTime\tCTS\tdiff_prev_ms\n"); + else + fprintf(dump, "ChunkNum\tOffset\tTkNum\tChkNbInTk\tChkSize\tSamp/Chunk\tFirstSample\tSAP\tTime\tCTS\tdiff_prev_ms\n"); + + for (i=0; itrack_num, ci->first_sample_num, &di, NULL); + if (samp) { + u32 h, m, s, ms, secs; + u64 time = samp->DTS+samp->CTS_Offset; + s64 diff; + time *= 1000; + time /= ci->timescale; + + secs = (u32) (time/1000); + h = secs / 3600; + m = (secs - 3600 * h) / 60; + s = (secs - 3600 * h - 60 * m); + ms = (u32) (time - secs*1000); + + diff = time; + diff -= prev_time; + prev_time = time; + + fprintf(dump, "%d\t"LLU"\t%d\t%d\t"LLU"\t%d\t%d\t", i+1, ci->chunk_offset, ci->track_num, ci->chunk_num, ci->size, ci->sample_per_chunk, ci->first_sample_num); + if (dump_stsd) fprintf(dump, "%d\t", di); + fprintf(dump, "%d\t", samp->IsRAP); + if (dump_hm==2) + fprintf(dump, "%02d:", h); + if (dump_hm>=1) + fprintf(dump, "%02d:", m); + fprintf(dump, "%02d.%03d\t"LLD"\t"LLD"\n", s, ms, samp->DTS+samp->CTS_Offset, diff); + gf_isom_sample_del(&samp); + } else { + fprintf(dump, "%d\t"LLU"\t%d\t%d\t%d\tN/A\tN/A\tN/A\tN/A\n", i+1, ci->chunk_offset, ci->track_num, ci->chunk_num, ci->first_sample_num); + } + //we assume single mdat ... + if (!prev_chunk_end) { + prev_chunk_end = gf_isom_get_first_mdat_start(file); + } + + if (ci->chunk_offset > prev_chunk_end) + unused_space += ci->chunk_offset - prev_chunk_end; + + prev_chunk_end = ci->chunk_offset + ci->size; + + if (do_prog) + gf_set_progress("Dumping chunks", i+1, nb_chunks_total); + } + gf_free(all_chunks); + if (inName) + gf_fclose(dump); + + prev_chunk_end = gf_isom_get_unused_box_bytes(file); + if (prev_chunk_end) + fprintf(stderr, "Unused bytes in box structure: "LLU"\n", prev_chunk_end); + if (unused_space) + fprintf(stderr, "Unused bytes in mdat: "LLU"\n", unused_space); +} + +#ifndef GPAC_DISABLE_ISOM_DUMP + +void dump_isom_ismacryp(GF_ISOFile *file, char *inName, Bool is_final_name) +{ + u32 i, j; + FILE *dump; + + if (inName) { + char szBuf[1024]; + strcpy(szBuf, inName); + if (!is_final_name) strcat(szBuf, "_ismacryp.xml"); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szBuf)); + return; + } + } else { + dump = stdout; + } + + fprintf(dump, "\n"); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + + + for (i=0; i\n", gf_isom_get_track_id(file, i+1)); + for (j=0; j\n"); + } + fprintf(dump, "\n"); + if (inName) gf_fclose(dump); +} + + +void dump_isom_timed_text(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, Bool is_convert, GF_TextDumpType dump_type) +{ + FILE *dump; + GF_Err e; + u32 track; + + track = gf_isom_get_track_by_id(file, trackID); + if (!track) { + M4_LOG(GF_LOG_ERROR, ("Cannot find track ID %d\n", trackID)); + return; + } + + switch (gf_isom_get_media_type(file, track)) { + case GF_ISOM_MEDIA_TEXT: + case GF_ISOM_MEDIA_SUBT: + break; + default: + M4_LOG(GF_LOG_ERROR, ("Track ID %d is not a 3GPP text track\n", trackID)); + return; + } + + if (inName) { + char szBuf[1024]; + char *ext; + ext = ((dump_type==GF_TEXTDUMPTYPE_SVG) ? "svg" : ((dump_type==GF_TEXTDUMPTYPE_SRT) ? "srt" : "ttxt")); + if (is_final_name) { + strcpy(szBuf, inName) ; + } else if (is_convert) + sprintf(szBuf, "%s.%s", inName, ext) ; + else + sprintf(szBuf, "%s_%d_text.%s", inName, trackID, ext); + + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szBuf)); + return; + } + } else { + dump = stdout; + } + e = gf_isom_text_dump(file, track, dump, dump_type); + if (inName) gf_fclose(dump); + + if (e) { + M4_LOG(GF_LOG_ERROR, ("Conversion failed (%s)\n", gf_error_to_string(e))); + } else { + fprintf(stderr, "Conversion done\n"); + } +} + +#endif /*GPAC_DISABLE_ISOM_DUMP*/ + +#ifndef GPAC_DISABLE_ISOM_HINTING + +void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name) +{ + const char *sdp; + u32 size, i; + FILE *dump; + + if (inName) { + char szBuf[1024]; + strcpy(szBuf, inName); + if (!is_final_name) { + char *ext = strchr(szBuf, '.'); + if (ext) ext[0] = 0; + strcat(szBuf, "_sdp.txt"); + } + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + M4_LOG(GF_LOG_ERROR, ("Failed to open %s for dumping\n", szBuf)); + return; + } + } else { + dump = stdout; + fprintf(dump, "# File SDP content \n\n"); + } + //get the movie SDP + gf_isom_sdp_get(file, &sdp, &size); + if (sdp && size) + fprintf(dump, "%s", sdp); + fprintf(dump, "\r\n"); + + //then tracks + for (i=0; i\n"); + if (do_track_dump) { + fprintf(dump, "\n"); + } + e = gf_isom_dump(file, dump, skip_init, skip_samples); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error dumping ISO structure\n")); + } + + if (do_track_dump) { +#ifndef GPAC_DISABLE_MEDIA_EXPORT + 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); + u32 tcount = gf_isom_get_track_count(the_file); + fprintf(dump, "\n"); + + for (i=0; i\n", name, trackID); + +#ifndef GPAC_DISABLE_ISOM_HINTING + u32 j, scount=gf_isom_get_sample_count(the_file, i+1); + for (j=0; j\n", name); + fmt_handled = GF_TRUE; +#endif /*GPAC_DISABLE_ISOM_HINTING*/ + } + else if (gf_isom_get_avc_svc_type(the_file, i+1, 1) + || gf_isom_get_hevc_lhvc_type(the_file, i+1, 1) + || gf_isom_get_vvc_type(the_file, i+1, 1) + ) { + e = dump_isom_nal_ex(the_file, trackID, dump, GF_FALSE); + fmt_handled = GF_TRUE; + } + else if (msubtype == GF_ISOM_SUBTYPE_AV01) { + e = dump_isom_obu(the_file, trackID, dump, GF_FALSE); + fmt_handled = GF_TRUE; + } + else if (msubtype == GF_ISOM_SUBTYPE_AV01) { + e = dump_isom_obu(the_file, trackID, dump, GF_FALSE); + fmt_handled = GF_TRUE; + } + else if ((msubtype == GF_QT_SUBTYPE_APCH) || (msubtype == GF_QT_SUBTYPE_APCO) + || (msubtype == GF_QT_SUBTYPE_APCN) || (msubtype == GF_QT_SUBTYPE_APCS) + || (msubtype == GF_QT_SUBTYPE_AP4X) || (msubtype == GF_QT_SUBTYPE_AP4H) + ) { + e = dump_qt_prores(the_file, trackID, dump, GF_FALSE); + fmt_handled = GF_TRUE; + } + else if ((mtype==GF_ISOM_MEDIA_TEXT) || (mtype==GF_ISOM_MEDIA_SUBT) ) { + + if (msubtype==GF_ISOM_SUBTYPE_WVTT) { + e = gf_webvtt_dump_iso_track(&dumper, i+1, merge_vtt_cues, GF_TRUE); + fmt_handled = GF_TRUE; + } else if ((msubtype==GF_ISOM_SUBTYPE_TX3G) || (msubtype==GF_ISOM_SUBTYPE_TEXT)) { + e = gf_isom_text_dump(the_file, i+1, dump, GF_TEXTDUMPTYPE_TTXT_BOXES); + fmt_handled = GF_TRUE; + } + } + + if (!fmt_handled) { + dumper.flags = GF_EXPORT_NHML | GF_EXPORT_NHML_FULL; + dumper.print_stats_graph = fs_dump_flags; + e = gf_media_export(&dumper); + } + if (e) break; + } +#else + return GF_NOT_SUPPORTED; +#endif /*GPAC_DISABLE_MEDIA_EXPORT*/ + gf_isom_delete(the_file); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + } + if (do_close) gf_fclose(dump); + + if (!e && dump_check_xml) { + if (do_close) { + GF_DOMParser *xml_parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(xml_parser, szFileName, NULL, NULL); + if (e) { + fprintf(stderr, "Failed to parse XML dump %s: line %d: %s\n", szFileName, gf_xml_dom_get_line(xml_parser), gf_xml_dom_get_error(xml_parser) ); + } + gf_xml_dom_del(xml_parser); + } else { + fprintf(stderr, "Cannot check XML for dump on stdout\n"); + } + } + return e; +} +#endif + + +static 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; +} + +static char *format_date(u64 time, char *szTime) +{ + time_t now; + if (!time) { + strcpy(szTime, "UNKNOWN DATE"); + } else { + time -= 2082844800; + now = (u32) time; + sprintf(szTime, "GMT %s", asctime(gf_gmtime(&now)) ); + } + return szTime; +} + +void print_udta(GF_ISOFile *file, u32 track_number, Bool has_itags) +{ + u32 i, count; + + count = gf_isom_get_udta_count(file, track_number); + if (!count) return; + + if (has_itags) { + for (i=0; i\n"); + fprintf(t, "\n"); + fprintf(t, "\n"); + fprintf(t, "\n"); + fprintf(t, "\n"); + } + + for (i=0; i%s\n" + , format_duration(chapter_time, 1000, szDur), name); + } + } + if (dump_mode==GF_TEXTDUMPTYPE_TTXT_CHAP) { + fprintf(t, "\n"); + } + if (inName) gf_fclose(t); + return GF_OK; +} + + +static void dump_key_info(const u8 *key_info, u32 key_info_size, Bool is_protected) +{ + if (!key_info) return; + u32 j, k, kpos=3; + u32 nb_keys = 1; + if (key_info[0]) { + nb_keys = key_info[1]; + nb_keys <<= 8; + nb_keys |= key_info[2]; + } + for (k=0; k1) + fprintf(stderr, "%d", k+1); + fprintf(stderr, " "); + for (j=0; j<16; j++) fprintf(stderr, "%02X", key_info[kpos+1+j]); + kpos+=17; + if (!iv_size && is_protected) { + constant_iv_size = key_info[1]; + kpos += 1 + constant_iv_size; + } + fprintf(stderr, " - %sIV size %d \n", constant_iv_size ? "const " : "", constant_iv_size ? constant_iv_size : iv_size); + } +} + +static void DumpMetaItem(GF_ISOFile *file, Bool root_meta, u32 tk_num, char *name) +{ + char szInd[2]; + u32 i, count, primary_id; + u32 meta_type = gf_isom_get_meta_type(file, root_meta, tk_num); + if (name[0]=='\t') { + szInd[0] = '\t'; + szInd[1] = 0; + } else { + szInd[0] = 0; + } + + count = gf_isom_get_meta_item_count(file, root_meta, tk_num); + if (!count && !meta_type) return; + + primary_id = gf_isom_get_meta_primary_item_id(file, root_meta, tk_num); + fprintf(stderr, "%s type: \"%s\" - %d resource item(s)\n", name, meta_type ? gf_4cc_to_str(meta_type) : "undefined", (count+(primary_id>0))); + switch (gf_isom_has_meta_xml(file, root_meta, tk_num)) { + case 1: + fprintf(stderr, "%sMeta has XML resource\n", szInd); + break; + case 2: + fprintf(stderr, "%sMeta has BinaryXML resource\n", szInd); + break; + } + if (primary_id) { + fprintf(stderr, "%sPrimary Item - ID %d\n", szInd, primary_id); + } + for (i=0; i1) ? "s" : ""); + for (j=1; jdata, slc->size, hash); + fprintf(stderr, "\t%s#%d hash: ", szName, i+1); + for (j=0; j<20; j++) fprintf(stderr, "%02X", hash[j]); + fprintf(stderr, "\n"); + } +} + +void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + , HEVCState *hevc_state +#endif /*GPAC_DISABLE_AV_PARSERS && defined(GPAC_DISABLE_HEVC)*/ + ) +{ +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + u32 idx; +#endif + u32 k; + Bool non_hevc_base_layer=GF_FALSE; + fprintf(stderr, "\t%s Info:", hevccfg->is_lhvc ? "LHVC" : "HEVC"); + if (!hevccfg->is_lhvc) + fprintf(stderr, " Profile %s @ Level %g - Chroma Format %s\n", gf_hevc_get_profile_name(hevccfg->profile_idc), ((Double)hevccfg->level_idc) / 30.0, gf_avc_hevc_get_chroma_format_name(hevccfg->chromaFormat)); + fprintf(stderr, "\n"); + fprintf(stderr, "\tNAL Unit length bits: %d", 8*hevccfg->nal_unit_size); + if (!hevccfg->is_lhvc) + fprintf(stderr, " - general profile compatibility 0x%08X\n", hevccfg->general_profile_compatibility_flags); + fprintf(stderr, "\n"); + fprintf(stderr, "\tParameter Sets: "); + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *ar=gf_list_get(hevccfg->param_array, k); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + fprintf(stderr, "%d SPS ", gf_list_count(ar->nalus)); + } + if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + fprintf(stderr, "%d PPS ", gf_list_count(ar->nalus)); + } + if (ar->type==GF_HEVC_NALU_VID_PARAM) { + fprintf(stderr, "%d VPS ", gf_list_count(ar->nalus)); + +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + for (idx=0; idxnalus); idx++) { + GF_NALUFFParam *vps = gf_list_get(ar->nalus, idx); + s32 ps_idx=gf_hevc_read_vps(vps->data, vps->size, hevc_state); + if (hevccfg->is_lhvc && (ps_idx>=0)) { + non_hevc_base_layer = ! hevc_state->vps[ps_idx].base_layer_internal_flag; + } + } +#endif + + } + } + + fprintf(stderr, "\n"); +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *ar=gf_list_get(hevccfg->param_array, k); + u32 width, height; + s32 par_n, par_d; + + if (ar->type !=GF_HEVC_NALU_SEQ_PARAM) continue; + for (idx=0; idxnalus); idx++) { + GF_Err e; + GF_NALUFFParam *sps = gf_list_get(ar->nalus, idx); + par_n = par_d = -1; + e = gf_hevc_get_sps_info_with_state(hevc_state, sps->data, sps->size, NULL, &width, &height, &par_n, &par_d); + if (e==GF_OK) { + fprintf(stderr, "\tSPS resolution %dx%d", width, height); + if ((par_n>0) && (par_d>0)) { + u32 tw, th; + gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL); + fprintf(stderr, " - Pixel Aspect Ratio %d:%d - Indicated track size %d x %d", par_n, par_d, tw, th); + } + fprintf(stderr, "\n"); + } else { + M4_LOG(GF_LOG_ERROR, ("Failed to read SPS: %s\n\n", gf_error_to_string(e) )); + } + } + } +#endif + if (!hevccfg->is_lhvc) + fprintf(stderr, "\tBit Depth luma %d - Chroma %d - %d temporal layers\n", hevccfg->luma_bit_depth, hevccfg->chroma_bit_depth, hevccfg->numTemporalLayers); + else + fprintf(stderr, "\t%d temporal layers\n", hevccfg->numTemporalLayers); + if (hevccfg->is_lhvc) { + fprintf(stderr, "\t%sHEVC base layer - Complete representation %d\n", non_hevc_base_layer ? "Non-" : "", hevccfg->complete_representation); + } + + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *ar=gf_list_get(hevccfg->param_array, k); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) print_config_hash(ar->nalus, "SPS"); + else if (ar->type==GF_HEVC_NALU_PIC_PARAM) print_config_hash(ar->nalus, "PPS"); + else if (ar->type==GF_HEVC_NALU_VID_PARAM) print_config_hash(ar->nalus, "VPS"); + } +} + +void dump_vvc_track_info(GF_ISOFile *file, u32 trackNum, GF_VVCConfig *vvccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) + , VVCState *vvc_state +#endif /*GPAC_DISABLE_AV_PARSERS && defined(GPAC_DISABLE_HEVC)*/ + ) +{ +#if !defined(GPAC_DISABLE_AV_PARSERS) + u32 idx; +#endif + u32 k; + fprintf(stderr, "\tNAL Unit length bits: %d", 8*vvccfg->nal_unit_size); + if (vvccfg->ptl_present) { + fprintf(stderr, " Profile %d @ Level %d - Chroma Format %s", vvccfg->general_profile_idc, vvccfg->general_level_idc, gf_avc_hevc_get_chroma_format_name(vvccfg->chroma_format)); + if (vvccfg->general_constraint_info && vvccfg->num_constraint_info && vvccfg->general_constraint_info[0]) { + fprintf(stderr, " - general constraint info 0x"); + for (idx=0; idxnum_constraint_info; idx++) { + fprintf(stderr, "%02X", vvccfg->general_constraint_info[idx]); + } + } + fprintf(stderr, "\n"); + fprintf(stderr, "\tBit Depth %d - %d temporal layers\n", vvccfg->bit_depth, vvccfg->numTemporalLayers); + } + + fprintf(stderr, "\tParameter Sets: "); + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *ar=gf_list_get(vvccfg->param_array, k); + if (ar->type==GF_VVC_NALU_SEQ_PARAM) { + fprintf(stderr, "%d SPS ", gf_list_count(ar->nalus)); + } + if (ar->type==GF_VVC_NALU_PIC_PARAM) { + fprintf(stderr, "%d PPS ", gf_list_count(ar->nalus)); + } + if (ar->type==GF_VVC_NALU_VID_PARAM) { + fprintf(stderr, "%d VPS ", gf_list_count(ar->nalus)); + +#if !defined(GPAC_DISABLE_AV_PARSERS) && 0 //TODO + for (idx=0; idxnalus); idx++) { + GF_NALUFFParam *vps = gf_list_get(ar->nalus, idx); + s32 ps_idx=gf_hevc_read_vps(vps->data, vps->size, hevc_state); + if (hevccfg->is_lhvc && (ps_idx>=0)) { + non_hevc_base_layer = ! hevc_state->vps[ps_idx].base_layer_internal_flag; + } + } +#endif + + } + } + + fprintf(stderr, "\n"); +#if !defined(GPAC_DISABLE_AV_PARSERS) + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *ar=gf_list_get(vvccfg->param_array, k); + u32 width, height; + s32 par_n, par_d; + + if (ar->type !=GF_VVC_NALU_SEQ_PARAM) continue; + for (idx=0; idxnalus); idx++) { + GF_Err e; + GF_NALUFFParam *sps = gf_list_get(ar->nalus, idx); + par_n = par_d = -1; + e = gf_vvc_get_sps_info(sps->data, sps->size, NULL, &width, &height, &par_n, &par_d); + if (e==GF_OK) { + fprintf(stderr, "\tSPS resolution %dx%d", width, height); + if ((par_n>0) && (par_d>0)) { + u32 tw, th; + gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL); + fprintf(stderr, " - Pixel Aspect Ratio %d:%d - Indicated track size %d x %d", par_n, par_d, tw, th); + } + fprintf(stderr, "\n"); + } else { + M4_LOG(GF_LOG_ERROR, ("\nFailed to read SPS: %s\n\n", gf_error_to_string(e) )); + } + } + } +#endif + + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *ar=gf_list_get(vvccfg->param_array, k); + if (ar->type==GF_VVC_NALU_SEQ_PARAM) print_config_hash(ar->nalus, "SPS"); + else if (ar->type==GF_VVC_NALU_PIC_PARAM) print_config_hash(ar->nalus, "PPS"); + else if (ar->type==GF_VVC_NALU_VID_PARAM) print_config_hash(ar->nalus, "VPS"); + } +} + +void gf_inspect_format_timecode(const u8 *data, u32 size, u32 tmcd_flags, u32 tc_num, u32 tc_den, u32 tmcd_fpt, char szFmt[100]); + +void DumpTrackInfo(GF_ISOFile *file, GF_ISOTrackID trackID, Bool full_dump, Bool is_track_num, Bool dump_m4sys) +{ + char szCodec[RFC6381_CODEC_NAME_SIZE_MAX]; + Double scale, max_rate, rate; + Bool is_od_track = 0; + u32 trackNum, i, j, ts, mtype, msub_type, timescale, sr, nb_ch, count, alt_group, nb_groups, nb_edits, cdur, csize, bps, pfmt, codecid; + u64 time_slice, dur, size; + s32 cts_shift; + GF_ESD *esd; + char szDur[50]; + char *lang; + + if (!is_track_num) { + trackNum = gf_isom_get_track_by_id(file, trackID); + } else { + trackNum = trackID; + trackID = gf_isom_get_track_id(file, trackNum); + } + if (!trackNum) { + M4_LOG(GF_LOG_ERROR, ("No track with ID %d found\n", trackID)); + return; + } + + 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"); + + 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); + if (nb_edits) + fprintf(stderr, "Track has %d edits: track duration is %s\n", nb_edits, format_duration(gf_isom_get_track_duration(file, trackNum), gf_isom_get_timescale(file), szDur)); + + cts_shift = gf_isom_get_composition_offset_shift(file, trackNum); + if (cts_shift) + fprintf(stderr, "Track composition offset shift (negative CTS offset): %d\n", cts_shift); + + if (gf_isom_is_track_in_root_od(file, trackNum) ) fprintf(stderr, "Track is present in Root OD\n"); + + u32 tk_flags = gf_isom_get_track_flags(file, trackNum); + fprintf(stderr, "Track flags:"); + if (tk_flags & GF_ISOM_TK_ENABLED) fprintf(stderr, " Enabled"); + else fprintf(stderr, " Disabled"); + if (tk_flags & GF_ISOM_TK_IN_MOVIE) fprintf(stderr, " In Movie"); + if (tk_flags & GF_ISOM_TK_IN_PREVIEW) fprintf(stderr, " In Preview"); + if (tk_flags & GF_ISOM_TK_SIZE_IS_AR) fprintf(stderr, " Size is AspectRatio"); + fprintf(stderr, "\n"); + + gf_isom_get_media_language(file, trackNum, &lang); + fprintf(stderr, "Media Info: Language \"%s (%s)\" - ", GetLanguage(lang), lang ); + gf_free(lang); + mtype = gf_isom_get_media_type(file, trackNum); + fprintf(stderr, "Type \"%s:", gf_4cc_to_str(mtype)); + msub_type = gf_isom_get_mpeg4_subtype(file, trackNum, 1); + if (!msub_type) msub_type = gf_isom_get_media_subtype(file, trackNum, 1); + fprintf(stderr, "%s\" - %d samples\n", gf_4cc_to_str(msub_type), gf_isom_get_sample_count(file, trackNum)); + + pfmt = gf_pixel_fmt_from_qt_type(msub_type); + codecid = gf_codec_id_from_isobmf(msub_type); + + count = gf_isom_get_track_kind_count(file, trackNum); + for (i = 0; i < count; i++) { + char *kind_scheme, *kind_value; + gf_isom_get_track_kind(file, trackNum, i, &kind_scheme, &kind_value); + fprintf(stderr, "Kind: %s - %s\n", kind_scheme ? kind_scheme : "null", kind_value ? kind_value : "null"); + if (kind_scheme) gf_free(kind_scheme); + if (kind_value) gf_free(kind_value); + } + + if (gf_isom_is_track_fragmented(file, trackID) ) { + u32 defaultDuration, defaultSize, defaultDescriptionIndex, defaultRandomAccess; + u8 defaultPadding; + u16 defaultDegradationPriority; + u32 frag_samples; + u64 frag_duration; + 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)); + + gf_isom_get_fragment_defaults(file, trackNum, &defaultDuration, &defaultSize, &defaultDescriptionIndex, &defaultRandomAccess, &defaultPadding, &defaultDegradationPriority); + + fprintf(stderr, "Fragment sample defaults: duration %d size %d stsd %d sync %d padding %d degradation_priority %d\n", + defaultDuration, defaultSize, defaultDescriptionIndex, defaultRandomAccess, + (u32) defaultPadding, (u32) defaultDegradationPriority + ); + } + + if (!gf_isom_is_self_contained(file, trackNum, 1)) { + const char *url, *urn; + gf_isom_get_data_reference(file, trackNum, 1, &url, &urn); + fprintf(stderr, "Media Data Location: %s\n", url ? url : urn); + } + + if (full_dump) { + const char *handler_name; + gf_isom_get_handler_name(file, trackNum, &handler_name); + fprintf(stderr, "Handler name: %s\n", handler_name); + } + + print_udta(file, trackNum, GF_FALSE); + + DumpMetaItem(file, 0, trackNum, "\tTrack Meta"); + + gf_isom_get_track_switch_group_count(file, trackNum, &alt_group, &nb_groups); + if (alt_group) { + fprintf(stderr, "Alternate Group ID %d\n", alt_group); + for (i=0; idv_version_major, dovi->dv_version_minor, dovi->dv_profile, dovi->dv_level, + dovi->rpu_present_flag, dovi->bl_present_flag, dovi->el_present_flag, dovi->dv_bl_signal_compatibility_id); + + gf_odf_dovi_cfg_del(dovi); + } + } + + gf_isom_get_audio_info(file, trackNum, 1, &sr, &nb_ch, &bps); + gf_isom_set_nalu_extract_mode(file, trackNum, GF_ISOM_NALU_EXTRACT_INSPECT); + + msub_type = gf_isom_get_media_subtype(file, trackNum, 1); + if (msub_type==GF_ISOM_SUBTYPE_MPEG4_CRYP) + gf_isom_get_original_format_type(file, trackNum, 1, &msub_type); + + if ((msub_type==GF_ISOM_SUBTYPE_MPEG4) + || (msub_type==GF_ISOM_SUBTYPE_AVC_H264) + || (msub_type==GF_ISOM_SUBTYPE_AVC2_H264) + || (msub_type==GF_ISOM_SUBTYPE_AVC3_H264) + || (msub_type==GF_ISOM_SUBTYPE_AVC4_H264) + || (msub_type==GF_ISOM_SUBTYPE_SVC_H264) + || (msub_type==GF_ISOM_SUBTYPE_MVC_H264) + || (msub_type==GF_ISOM_SUBTYPE_LSR1) + || (msub_type==GF_ISOM_SUBTYPE_HVC1) + || (msub_type==GF_ISOM_SUBTYPE_HEV1) + || (msub_type==GF_ISOM_SUBTYPE_HVC2) + || (msub_type==GF_ISOM_SUBTYPE_HEV2) + || (msub_type==GF_ISOM_SUBTYPE_LHV1) + || (msub_type==GF_ISOM_SUBTYPE_LHE1) + || (msub_type==GF_ISOM_SUBTYPE_HVT1) + ) { + esd = gf_isom_get_esd(file, trackNum, 1); + if (!esd || !esd->decoderConfig) { + M4_LOG(GF_LOG_WARNING, ("WARNING: Broken MPEG-4 Track\n")); + if (esd) gf_odf_desc_del((GF_Descriptor *)esd); + } else { + const char *st = gf_stream_type_name(esd->decoderConfig->streamType); + if (dump_m4sys) { + if (st) { + fprintf(stderr, "MPEG-4 Config%s%s Stream - ObjectTypeIndication 0x%02x\n", + full_dump ? "\n\t" : ": ", st, esd->decoderConfig->objectTypeIndication); + } else { + fprintf(stderr, "MPEG-4 Config%sStream Type 0x%02x - ObjectTypeIndication 0x%02x\n", + full_dump ? "\n\t" : ": ", esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication); + } + } + + if (esd->decoderConfig->streamType==GF_STREAM_OD) + is_od_track=1; + + if (esd->decoderConfig->streamType==GF_STREAM_VISUAL) { + u32 w, h; + u16 rvc_predef; + w = h = 0; + if (esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) { +#ifndef GPAC_DISABLE_AV_PARSERS + if (!esd->decoderConfig->decoderSpecificInfo) { +#else + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + fprintf(stderr, "MPEG-4 Visual Size %d x %d\n", w, h); +#endif + M4_LOG(GF_LOG_WARNING, ("Non-compliant MPEG-4 Visual track: video_object_layer infos not found in sample description\n")); +#ifndef GPAC_DISABLE_AV_PARSERS + } else { + GF_M4VDecSpecInfo dsi; + gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); + if (full_dump) fprintf(stderr, "\t"); + w = dsi.width; + h = dsi.height; + fprintf(stderr, "MPEG-4 Visual Size %d x %d - %s\n", w, h, gf_m4v_get_profile_name(dsi.VideoPL)); + if (dsi.par_den && dsi.par_num) { + u32 tw, th; + gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL); + fprintf(stderr, "Pixel Aspect Ratio %d:%d - Indicated track size %d x %d\n", dsi.par_num, dsi.par_den, tw, th); + } + } +#endif + } else if (gf_isom_get_avc_svc_type(file, trackNum, 1) != GF_ISOM_AVCTYPE_NONE) { + GF_AVCConfig *avccfg, *svccfg, *mvccfg; + + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + if (full_dump) fprintf(stderr, "\t"); + fprintf(stderr, "AVC/H264 Video - Visual Size %d x %d\n", w, h); + + avccfg = gf_isom_avc_config_get(file, trackNum, 1); + svccfg = gf_isom_svc_config_get(file, trackNum, 1); + mvccfg = gf_isom_mvc_config_get(file, trackNum, 1); + if (!avccfg && !svccfg && !mvccfg) { + M4_LOG(GF_LOG_ERROR, ("\tNon-compliant AVC track: SPS/PPS not found in sample description\n")); + } else if (avccfg) { + fprintf(stderr, "\tAVC Info: %d SPS - %d PPS", gf_list_count(avccfg->sequenceParameterSets) , gf_list_count(avccfg->pictureParameterSets) ); + fprintf(stderr, " - Profile %s @ Level %g\n", gf_avc_get_profile_name(avccfg->AVCProfileIndication), ((Double)avccfg->AVCLevelIndication)/10.0 ); + fprintf(stderr, "\tNAL Unit length bits: %d\n", 8*avccfg->nal_unit_size); + +#ifndef GPAC_DISABLE_AV_PARSERS + for (i=0; isequenceParameterSets); i++) { + s32 par_n, par_d; + GF_NALUFFParam *slc = gf_list_get(avccfg->sequenceParameterSets, i); + gf_avc_get_sps_info(slc->data, slc->size, NULL, NULL, NULL, &par_n, &par_d); + if ((par_n>0) && (par_d>0)) { + u32 tw, th; + gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL); + fprintf(stderr, "\tPixel Aspect Ratio %d:%d - Indicated track size %d x %d\n", par_n, par_d, tw, th); + } + if (!full_dump) break; + } +#endif + + if (avccfg->chroma_bit_depth) { + fprintf(stderr, "\tChroma format %s - Luma bit depth %d - chroma bit depth %d\n", gf_avc_hevc_get_chroma_format_name(avccfg->chroma_format), avccfg->luma_bit_depth, avccfg->chroma_bit_depth); + } + + print_config_hash(avccfg->sequenceParameterSets, "SPS"); + print_config_hash(avccfg->pictureParameterSets, "PPS"); + + gf_odf_avc_cfg_del(avccfg); + } + if (svccfg) { + fprintf(stderr, "\n\tSVC Info: %d SPS - %d PPS - Profile %s @ Level %g\n", gf_list_count(svccfg->sequenceParameterSets) , gf_list_count(svccfg->pictureParameterSets), gf_avc_get_profile_name(svccfg->AVCProfileIndication), ((Double)svccfg->AVCLevelIndication)/10.0 ); + fprintf(stderr, "\tSVC NAL Unit length bits: %d\n", 8*svccfg->nal_unit_size); +#ifndef GPAC_DISABLE_AV_PARSERS + for (i=0; isequenceParameterSets); i++) { + GF_NALUFFParam *slc = gf_list_get(svccfg->sequenceParameterSets, i); + if (slc) { + s32 par_n, par_d; + u32 s_w, s_h, sps_id; + gf_avc_get_sps_info(slc->data, slc->size, &sps_id, &s_w, &s_h, &par_n, &par_d); + fprintf(stderr, "\t\tSPS ID %d - Visual Size %d x %d\n", sps_id, s_w, s_h); + if ((par_n>0) && (par_d>0)) { + u32 tw, th; + gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL); + fprintf(stderr, "\tPixel Aspect Ratio %d:%d - Indicated track size %d x %d\n", par_n, par_d, tw, th); + } + } + } +#endif + print_config_hash(svccfg->sequenceParameterSets, "SPS"); + print_config_hash(svccfg->pictureParameterSets, "PPS"); + print_config_hash(svccfg->sequenceParameterSetExtensions, "SPSEx"); + + gf_odf_avc_cfg_del(svccfg); + } + + if (mvccfg) { + fprintf(stderr, "\n\tMVC Info: %d SPS - %d PPS - Profile %s @ Level %g\n", gf_list_count(mvccfg->sequenceParameterSets) , gf_list_count(mvccfg->pictureParameterSets), gf_avc_get_profile_name(mvccfg->AVCProfileIndication), ((Double)mvccfg->AVCLevelIndication)/10.0 ); + fprintf(stderr, "\tMVC NAL Unit length bits: %d\n", 8*mvccfg->nal_unit_size); +#ifndef GPAC_DISABLE_AV_PARSERS + for (i=0; isequenceParameterSets); i++) { + GF_NALUFFParam *slc = gf_list_get(mvccfg->sequenceParameterSets, i); + if (slc) { + u32 s_w, s_h, sps_id; + s32 par_n, par_d; + gf_avc_get_sps_info(slc->data, slc->size, &sps_id, &s_w, &s_h, &par_n, &par_d); + fprintf(stderr, "\t\tSPS ID %d - Visual Size %d x %d\n", sps_id, s_w, s_h); + if ((par_n>0) && (par_d>0)) { + u32 tw, th; + gf_isom_get_track_layout_info(file, trackNum, &tw, &th, NULL, NULL, NULL); + fprintf(stderr, "\tPixel Aspect Ratio %d:%d - Indicated track size %d x %d\n", par_n, par_d, tw, th); + } + } + } +#endif + print_config_hash(mvccfg->sequenceParameterSets, "SPS"); + print_config_hash(mvccfg->pictureParameterSets, "PPS"); + gf_odf_avc_cfg_del(mvccfg); + } + + } else if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_HEVC) + || (esd->decoderConfig->objectTypeIndication==GF_CODECID_LHVC) + ) { + GF_HEVCConfig *hevccfg, *lhvccfg; + GF_OperatingPointsInformation *oinf; +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + HEVCState hevc_state; + memset(&hevc_state, 0, sizeof(HEVCState)); + hevc_state.sps_active_idx = -1; +#endif + + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + if (full_dump) fprintf(stderr, "\t"); + fprintf(stderr, "HEVC Video - Visual Size %d x %d\n", w, h); + hevccfg = gf_isom_hevc_config_get(file, trackNum, 1); + lhvccfg = gf_isom_lhvc_config_get(file, trackNum, 1); + + if (msub_type==GF_ISOM_SUBTYPE_HVT1) { + const u8 *data; + u32 tsize; + u32 is_default, tx,ty,tw,th, id, independent; + Bool full_frame; + if (gf_isom_get_tile_info(file, trackNum, 1, &is_default, &id, &independent, &full_frame, &tx, &ty, &tw, &th)) { + fprintf(stderr, "\tHEVC Tile - ID %d independent %d (x,y,w,h)=%d,%d,%d,%d \n", id, independent, tx, ty, tw, th); + } else if (gf_isom_get_sample_group_info(file, trackNum, 1, GF_ISOM_SAMPLE_GROUP_TRIF, &is_default, &data, &tsize)) { + fprintf(stderr, "\tHEVC Tile track containing a tile set\n"); + } else { + fprintf(stderr, "\tHEVC Tile track without tiling info\n"); + } + } else if (!hevccfg && !lhvccfg) { + M4_LOG(GF_LOG_ERROR, ("\tNon-compliant HEVC track: No hvcC or shcC found in sample description\n")); + } + + if (gf_isom_get_reference_count(file, trackNum, GF_ISOM_REF_SABT)) { + fprintf(stderr, "\tHEVC Tile base track\n"); + } + if (hevccfg) { + dump_hevc_track_info(file, trackNum, hevccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + , &hevc_state +#endif + ); + gf_odf_hevc_cfg_del(hevccfg); + fprintf(stderr, "\n"); + } + if (lhvccfg) { + dump_hevc_track_info(file, trackNum, lhvccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + , &hevc_state +#endif + ); + gf_odf_hevc_cfg_del(lhvccfg); + } + + if (gf_isom_get_oinf_info(file, trackNum, &oinf)) { + fprintf(stderr, "\n\tOperating Points Information -"); + fprintf(stderr, " scalability_mask %d (", oinf->scalability_mask); + switch (oinf->scalability_mask) { + case 2: + fprintf(stderr, "Multiview"); + break; + case 4: + fprintf(stderr, "Spatial scalability"); + break; + case 8: + fprintf(stderr, "Auxiliary"); + break; + default: + fprintf(stderr, "unknown"); + } + //TODO: need to dump more info ? + fprintf(stderr, ") num_profile_tier_level %d ", gf_list_count(oinf->profile_tier_levels) ); + fprintf(stderr, " num_operating_points %d dependency layers %d \n", gf_list_count(oinf->operating_points), gf_list_count(oinf->dependency_layers) ); + } + } + + /*OGG media*/ + else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_THEORA) { + char *szName; + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + if (full_dump) fprintf(stderr, "\t"); + szName = "Unknown"; + if (esd->decoderConfig->decoderSpecificInfo + && (esd->decoderConfig->decoderSpecificInfo->dataLength>=10) + && !strnicmp((char *) &esd->decoderConfig->decoderSpecificInfo->data[3], "theora", 6) + ) + szName = "Theora"; + + fprintf(stderr, "Ogg/%s video / GPAC Mux - Visual Size %d x %d\n", szName, w, h); + } + else { + //check if we know this codec from its OTI + u32 codec_id = gf_codecid_from_oti(GF_STREAM_VISUAL, esd->decoderConfig->objectTypeIndication); + if (codec_id) { + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + fprintf(stderr, "%s - Visual Size %d x %d\n", gf_codecid_name(codec_id), w, h); + } + } + if (!w || !h) { + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + if (full_dump) fprintf(stderr, "\t"); + fprintf(stderr, "Visual Size %d x %d\n", w, h); + } + if (gf_isom_get_rvc_config(file, trackNum, 1, &rvc_predef, NULL, NULL, NULL)==GF_OK) { + fprintf(stderr, "Has RVC signaled - Predefined configuration %d\n", rvc_predef); + } + + } else if (esd->decoderConfig->streamType==GF_STREAM_AUDIO) { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_M4ADecSpecInfo a_cfg; + GF_Err e; + u32 oti; +#endif + u32 codec_id; + Bool is_mp2 = GF_FALSE; + switch (esd->decoderConfig->objectTypeIndication) { + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + is_mp2 = GF_TRUE; + case GF_CODECID_AAC_MPEG4: +#ifndef GPAC_DISABLE_AV_PARSERS + if (!esd->decoderConfig->decoderSpecificInfo) + e = GF_NON_COMPLIANT_BITSTREAM; + else + e = gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &a_cfg); + if (full_dump) fprintf(stderr, "\t"); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Corrupted AAC Config\n")); + } else { + char *signaling = "implicit"; + char *heaac = ""; + if (!is_mp2 && a_cfg.has_sbr) { + if (a_cfg.has_ps) heaac = "(HE-AAC v2) "; + else heaac = "(HE-AAC v1) "; + } + if (a_cfg.base_object_type==2) { + if (a_cfg.has_ps || a_cfg.has_sbr) + signaling = "backward compatible"; + } else { + signaling = "hierarchical"; + } + fprintf(stderr, "%s (AOT=%d %s) %s- %d Channel(s) - SampleRate %d", gf_m4a_object_type_name(a_cfg.base_object_type), a_cfg.base_object_type, signaling, heaac, a_cfg.nb_chan, a_cfg.base_sr); + if (is_mp2) fprintf(stderr, " (MPEG-2 Signaling)"); + if (a_cfg.has_sbr) fprintf(stderr, " - SBR: SampleRate %d Type %s", a_cfg.sbr_sr, gf_m4a_object_type_name(a_cfg.sbr_object_type)); + if (a_cfg.has_ps) fprintf(stderr, " - PS"); + fprintf(stderr, "\n"); + } +#else + fprintf(stderr, "MPEG-2/4 Audio - %d Channels - SampleRate %d\n", nb_ch, sr); +#endif + break; + case GF_CODECID_MPEG2_PART3: + case GF_CODECID_MPEG_AUDIO: + case GF_CODECID_MPEG_AUDIO_L1: + if (msub_type == GF_ISOM_SUBTYPE_MPEG4_CRYP) { + fprintf(stderr, "MPEG-1/2 Audio - %d Channels - SampleRate %d\n", nb_ch, sr); + } else { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_ISOSample *samp = gf_isom_get_sample(file, trackNum, 1, &oti); + if (samp) { + if (samp->data && (samp->dataLength>4)) { + u32 mhdr = GF_4CC((u8)samp->data[0], (u8)samp->data[1], (u8)samp->data[2], (u8)samp->data[3]); + if (full_dump) fprintf(stderr, "\t"); + fprintf(stderr, "%s Audio - %d Channel(s) - SampleRate %d - Layer %d\n", + gf_mp3_version_name(mhdr), + gf_mp3_num_channels(mhdr), + gf_mp3_sampling_rate(mhdr), + gf_mp3_layer(mhdr) + ); + } + gf_isom_sample_del(&samp); + } else { + M4_LOG(GF_LOG_ERROR, ("Error fetching sample: %s\n", gf_error_to_string(gf_isom_last_error(file)) )); + } +#else + fprintf(stderr, "MPEG-1/2 Audio - %d Channels - SampleRate %d\n", nb_ch, sr); +#endif + } + break; + case GF_CODECID_EVRC: + fprintf(stderr, "EVRC Audio - Sample Rate 8000 - 1 channel\n"); + break; + case GF_CODECID_SMV: + fprintf(stderr, "SMV Audio - Sample Rate 8000 - 1 channel\n"); + break; + case GF_CODECID_QCELP: + fprintf(stderr, "QCELP Audio - Sample Rate 8000 - 1 channel\n"); + break; + /*packetVideo hack for EVRC...*/ + case GF_CODECID_EVRC_PV: + if (esd->decoderConfig->decoderSpecificInfo && (esd->decoderConfig->decoderSpecificInfo->dataLength==8) + && !strnicmp((char *)esd->decoderConfig->decoderSpecificInfo->data, "pvmm", 4)) { + if (full_dump) fprintf(stderr, "\t"); + fprintf(stderr, "EVRC Audio (PacketVideo Mux) - Sample Rate 8000 - 1 channel\n"); + } + break; + default: + codec_id = gf_codecid_from_oti(GF_STREAM_AUDIO, esd->decoderConfig->objectTypeIndication); + if (codec_id) { + fprintf(stderr, "%s - Sample Rate %d - %d channel(s)\n", gf_codecid_name(codec_id), sr, nb_ch); + } + break; + } + } + else if (esd->decoderConfig->streamType==GF_STREAM_SCENE) { + if (esd->decoderConfig->objectTypeIndication<=4) { + GF_BIFSConfig *b_cfg = gf_odf_get_bifs_config(esd->decoderConfig->decoderSpecificInfo, esd->decoderConfig->objectTypeIndication); + if (b_cfg) { + fprintf(stderr, "BIFS Scene description - %s stream\n", b_cfg->elementaryMasks ? "Animation" : "Command"); + if (full_dump && !b_cfg->elementaryMasks) { + fprintf(stderr, "\tWidth %d Height %d Pixel Metrics %s\n", b_cfg->pixelWidth, b_cfg->pixelHeight, b_cfg->pixelMetrics ? "yes" : "no"); + } + gf_odf_desc_del((GF_Descriptor *)b_cfg); + } else { + fprintf(stderr, "! Invalid BIFS configuration !\n"); + } + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_AFX) { + u8 tag = (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) ? esd->decoderConfig->decoderSpecificInfo->data[0] : 0xFF; + const char *afxtype = gf_stream_type_afx_name(tag); + fprintf(stderr, "AFX Stream - type %s (%d)\n", afxtype, tag); + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_FONT) { + fprintf(stderr, "Font Data stream\n"); + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_LASER) { + GF_LASERConfig l_cfg; + gf_odf_get_laser_config(esd->decoderConfig->decoderSpecificInfo, &l_cfg); + fprintf(stderr, "LASER Stream - %s\n", l_cfg.newSceneIndicator ? "Full Scene" : "Scene Segment"); + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_TEXT_MPEG4) { + fprintf(stderr, "MPEG-4 Streaming Text stream\n"); + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_SYNTHESIZED_TEXTURE) { + fprintf(stderr, "Synthetized Texture stream stream\n"); + } else { + M4_LOG(GF_LOG_WARNING, ("Unknown Systems stream OTI %d\n", esd->decoderConfig->objectTypeIndication)); + } + } + + /*sync is only valid if we open all tracks to take care of default MP4 sync..*/ + if (!full_dump) { + if (dump_m4sys) { + if (!esd->OCRESID || (esd->OCRESID == esd->ESID)) + fprintf(stderr, "Self-synchronized\n"); + else + fprintf(stderr, "Synchronized on stream %d\n", esd->OCRESID); + } + } else { + fprintf(stderr, "\tDecoding Buffer size %d - Bitrate: avg %d - max %d kbps\n", esd->decoderConfig->bufferSizeDB, esd->decoderConfig->avgBitrate/1000, esd->decoderConfig->maxBitrate/1000); + if (esd->dependsOnESID) + fprintf(stderr, "\tDepends on stream %d for decoding\n", esd->dependsOnESID); + else + fprintf(stderr, "\tNo stream dependencies for decoding\n"); + + fprintf(stderr, "\tStreamPriority %d\n", esd->streamPriority); + if (esd->URLString) fprintf(stderr, "\tRemote Data Source %s\n", esd->URLString); + } + gf_odf_desc_del((GF_Descriptor *) esd); + } + } else if (msub_type == GF_ISOM_SUBTYPE_AV01) { + GF_AV1Config *av1c; + u32 w, h; + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + fprintf(stderr, "\tAOM AV1 stream - Resolution %d x %d\n", w, h); + + av1c = gf_isom_av1_config_get(file, trackNum, 1); + if (!av1c) { + fprintf(stderr, "\tCorrupted av1 config\n"); + } else { + fprintf(stderr, "\tversion=%u, profile=%u, level_idx0=%u, tier=%u\n", (u32)av1c->version, (u32)av1c->seq_profile, (u32)av1c->seq_level_idx_0, (u32)av1c->seq_tier_0); + fprintf(stderr, "\thigh_bitdepth=%u, twelve_bit=%u, monochrome=%u\n", (u32)av1c->high_bitdepth, (u32)av1c->twelve_bit, (u32)av1c->monochrome); + fprintf(stderr, "\tchroma: subsampling_x=%u, subsampling_y=%u, sample_position=%u\n", (u32)av1c->chroma_subsampling_x, (u32)av1c->chroma_subsampling_y, (u32)av1c->chroma_sample_position); + + if (av1c->initial_presentation_delay_present) + fprintf(stderr, "\tInitial presentation delay %u\n", (u32) av1c->initial_presentation_delay_minus_one+1); + + count = gf_list_count(av1c->obu_array); + for (i=0; iobu_array, i); + gf_sha1_csum((u8*)obu->obu, (u32)obu->obu_length, hash); + fprintf(stderr, "\tOBU#%d %s hash: ", i+1, gf_av1_get_obu_name(obu->obu_type) ); + for (j=0; j<20; j++) fprintf(stderr, "%02X", hash[j]); + fprintf(stderr, "\n"); + } + gf_odf_av1_cfg_del(av1c); + } + } else if (msub_type == GF_ISOM_SUBTYPE_3GP_H263) { + u32 w, h; + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + fprintf(stderr, "\t3GPP H263 stream - Resolution %d x %d\n", w, h); + } else if (msub_type == GF_ISOM_SUBTYPE_MJP2) { + u32 w, h; + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + fprintf(stderr, "\tMotionJPEG2000 stream - Resolution %d x %d\n", w, h); + } else if ((msub_type == GF_ISOM_SUBTYPE_3GP_AMR) || (msub_type == GF_ISOM_SUBTYPE_3GP_AMR_WB)) { + fprintf(stderr, "\t3GPP AMR%s stream - Sample Rate %d - %d channel(s) %d bps\n", (msub_type == GF_ISOM_SUBTYPE_3GP_AMR_WB) ? " Wide Band" : "", sr, nb_ch, (u32) bps); + } else if (msub_type == GF_ISOM_SUBTYPE_3GP_EVRC) { + fprintf(stderr, "\t3GPP EVRC stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); + } else if (msub_type == GF_ISOM_SUBTYPE_3GP_QCELP) { + fprintf(stderr, "\t3GPP QCELP stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); + } else if (msub_type == GF_ISOM_SUBTYPE_MP3) { + fprintf(stderr, "\tMPEG 1/2 Audio stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); + } else if ((msub_type == GF_ISOM_SUBTYPE_AC3) || (msub_type == GF_ISOM_SUBTYPE_EC3)) { + u32 br = 0; + const char *lfe = ""; + Bool is_ec3 = (msub_type == GF_ISOM_SUBTYPE_EC3) ? GF_TRUE : GF_FALSE; +#ifndef GPAC_DISABLE_AV_PARSERS + GF_AC3Config *ac3 = gf_isom_ac3_config_get(file, trackNum, 1); + if (ac3) { + nb_ch = gf_ac3_get_channels(ac3->streams[0].acmod); + for (i=0; istreams[0].nb_dep_sub; ++i) { + assert(ac3->streams[0].nb_dep_sub == 1); + nb_ch += gf_ac3_get_channels(ac3->streams[0].chan_loc); + } + if (ac3->streams[0].lfon) lfe = ".1"; + br = ac3->is_ec3 ? ac3->brcode : gf_ac3_get_bitrate(ac3->brcode); + is_ec3 = ac3->is_ec3; + gf_free(ac3); + } +#endif + fprintf(stderr, "\t%s stream - Sample Rate %d - %d%s channel(s) - bitrate %d\n", is_ec3 ? "EC-3" : "AC-3", sr, nb_ch, lfe, br); + } 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) { + u32 w, h; + GF_DIMSDescription dims; + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + + gf_isom_get_dims_description(file, trackNum, 1, &dims); + fprintf(stderr, "\t3GPP DIMS stream - size %d x %d - Profile %d - Level %d\n", w, h, dims.profile, dims.level); + fprintf(stderr, "\tpathComponents: %d - useFullRequestHost: %s\n", dims.pathComponents, dims.fullRequestHost ? "yes" : "no"); + fprintf(stderr, "\tstream type: %s - redundant: %s\n", dims.streamType ? "primary" : "secondary", (dims.containsRedundant==1) ? "main" : ((dims.containsRedundant==2) ? "redundant" : "main+redundant") ); + if (dims.textEncoding[0]) fprintf(stderr, "\ttext encoding %s\n", dims.textEncoding); + if (dims.contentEncoding[0]) fprintf(stderr, "\tcontent encoding %s\n", dims.contentEncoding); + if (dims.content_script_types) fprintf(stderr, "\tscript languages %s\n", dims.content_script_types); + } else if (mtype==GF_ISOM_MEDIA_HINT) { + u32 refTrack; + s32 refCount = gf_isom_get_reference_count(file, trackNum, GF_ISOM_REF_HINT); + if (refCount>0) { + fprintf(stderr, "Streaming Hint Track for track%s ", (refCount>1) ? "s" :""); + for (i=0; i<(u32) refCount; i++) { + gf_isom_get_reference(file, trackNum, GF_ISOM_REF_HINT, i+1, &refTrack); + if (i) fprintf(stderr, " - "); + fprintf(stderr, "ID %d", gf_isom_get_track_id(file, refTrack)); + } + fprintf(stderr, "\n"); + } else { + fprintf(stderr, "Streaming Hint Track (no refs)\n"); + } +#ifndef GPAC_DISABLE_ISOM_HINTING + refCount = gf_isom_get_payt_count(file, trackNum); + if (refCount>0) { + for (i=0; i<(u32) refCount; i++) { + const char *name = gf_isom_get_payt_info(file, trackNum, i+1, &refTrack); + fprintf(stderr, "\tPayload ID %d: type %s\n", refTrack, name); + } + } +#endif + } else if (mtype==GF_ISOM_MEDIA_FLASH) { + fprintf(stderr, "Macromedia Flash Movie\n"); + } else if ((mtype==GF_ISOM_MEDIA_TEXT) || (mtype==GF_ISOM_MEDIA_SUBT) || (mtype==GF_ISOM_MEDIA_MPEG_SUBT)) { + u32 w, h; + s16 l; + s32 tx, ty; + const char *content_encoding = NULL; + const char *mime = NULL; + const char *config = NULL; + const char *_namespace = NULL; + const char *schema_loc = NULL; + const char *auxiliary_mimes = NULL; + gf_isom_get_track_layout_info(file, trackNum, &w, &h, &tx, &ty, &l); + if (msub_type == GF_ISOM_SUBTYPE_SBTT) { + gf_isom_stxt_get_description(file, trackNum, 1, &mime, &content_encoding, &config); + fprintf(stderr, "Textual Subtitle Stream "); + fprintf(stderr, "- mime %s", mime); + if (content_encoding != NULL) { + fprintf(stderr, " - encoding %s", content_encoding); + } + if (config != NULL) { + fprintf(stderr, " - %d bytes config", (u32) strlen(config)); + } + } else if (msub_type == GF_ISOM_SUBTYPE_STXT) { + gf_isom_stxt_get_description(file, trackNum, 1, &mime, &content_encoding, &config); + fprintf(stderr, "Simple Timed Text Stream "); + fprintf(stderr, "- mime %s", mime); + if (content_encoding != NULL) { + fprintf(stderr, " - encoding %s", content_encoding); + } + if (config != NULL) { + fprintf(stderr, " - %d bytes config", (u32) strlen(config)); + } + } else if (msub_type == GF_ISOM_SUBTYPE_STPP) { + gf_isom_xml_subtitle_get_description(file, trackNum, 1, &_namespace, &schema_loc, &auxiliary_mimes); + fprintf(stderr, "XML Subtitle Stream "); + fprintf(stderr, "- namespace %s", _namespace); + if (schema_loc != NULL) { + fprintf(stderr, " - schema-location %s", schema_loc); + } + if (auxiliary_mimes != NULL) { + fprintf(stderr, " - auxiliary-mime-types %s", auxiliary_mimes); + } + } else if (mtype == GF_ISOM_MEDIA_SUBT) { + fprintf(stderr, "QT/3GPP subtitle"); + } else { + fprintf(stderr, "Unknown Text Stream"); + } + fprintf(stderr, "\n\tSize %d x %d - Translation X=%d Y=%d - Layer %d\n", w, h, tx, ty, l); + } else if (mtype == GF_ISOM_MEDIA_META) { + const char *content_encoding = NULL; + if (msub_type == GF_ISOM_SUBTYPE_METT) { + const char *mime = NULL; + const char *config = NULL; + gf_isom_stxt_get_description(file, trackNum, 1, &mime, &content_encoding, &config); + fprintf(stderr, "Textual Metadata Stream - mime %s", mime); + if (content_encoding != NULL) { + fprintf(stderr, " - encoding %s", content_encoding); + } + if (config != NULL) { + fprintf(stderr, " - %d bytes config", (u32) strlen(config)); + } + fprintf(stderr, "\n"); + } else if (msub_type == GF_ISOM_SUBTYPE_METX) { + const char *_namespace = NULL; + const char *schema_loc = NULL; + gf_isom_get_xml_metadata_description(file, trackNum, 1, &_namespace, &schema_loc, &content_encoding); + fprintf(stderr, "XML Metadata Stream - namespace %s", _namespace); + if (content_encoding != NULL) { + fprintf(stderr, " - encoding %s", content_encoding); + } + if (schema_loc != NULL) { + fprintf(stderr, " - schema-location %s", schema_loc); + } + fprintf(stderr, "\n"); + } else { + fprintf(stderr, "Unknown Metadata Stream\n"); + } + } else if ((msub_type==GF_ISOM_SUBTYPE_VVC1) || (msub_type==GF_ISOM_SUBTYPE_VVI1)) { + GF_VVCConfig *vvccfg; + u32 w, h; +#if !defined(GPAC_DISABLE_AV_PARSERS) + VVCState *vvc_state; + GF_SAFEALLOC(vvc_state, VVCState); + if (vvc_state) vvc_state->sps_active_idx = -1; +#endif + + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + if (full_dump) fprintf(stderr, "\t"); + fprintf(stderr, "VVC Video - Visual Size %d x %d\n", w, h); + vvccfg = gf_isom_vvc_config_get(file, trackNum, 1); + + if (!vvccfg) { + M4_LOG(GF_LOG_ERROR, ("Non-compliant VVC track: No vvcC found in sample description\n")); + } else { + dump_vvc_track_info(file, trackNum, vvccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) + , vvc_state +#endif + ); + gf_odf_vvc_cfg_del(vvccfg); + fprintf(stderr, "\n"); + } +#if !defined(GPAC_DISABLE_AV_PARSERS) + if (vvc_state) gf_free(vvc_state); +#endif + } else if ((msub_type == GF_ISOM_SUBTYPE_MH3D_MHA1) || (msub_type == GF_ISOM_SUBTYPE_MH3D_MHA2) + || (msub_type == GF_ISOM_SUBTYPE_MH3D_MHM1) || (msub_type == GF_ISOM_SUBTYPE_MH3D_MHM2) + ) { + const u8 *compat_profiles; + u32 nb_compat_profiles; + Bool valid = GF_FALSE; + Bool allow_inband = GF_FALSE; + if ( (msub_type == GF_ISOM_SUBTYPE_MH3D_MHM1) || (msub_type == GF_ISOM_SUBTYPE_MH3D_MHM2)) + allow_inband = GF_TRUE; + + fprintf(stderr, "\tMPEG-H Audio stream - Sample Rate %d\n", sr); + + esd = gf_media_map_esd(file, trackNum, 1); + if (!esd || !esd->decoderConfig || !esd->decoderConfig->decoderSpecificInfo + || !esd->decoderConfig->decoderSpecificInfo->data + ) { + if (allow_inband) { + GF_ISOSample *samp = gf_isom_get_sample(file, trackNum, 1, NULL); + if (samp) { + u64 ch_layout=0; + s32 PL = gf_mpegh_get_mhas_pl(samp->data, samp->dataLength, &ch_layout); + if (PL>=0) { + fprintf(stderr, "\tProfileLevelIndication: 0x%02X", PL); + if (ch_layout) + fprintf(stderr, " - Reference Channel Layout %s", gf_audio_fmt_get_layout_name(ch_layout) ); + fprintf(stderr, "\n"); + } + gf_isom_sample_del(&samp); + } + valid = GF_TRUE; + } + } else if (esd->decoderConfig->decoderSpecificInfo->dataLength>=5) { + fprintf(stderr, "\tProfileLevelIndication: 0x%02X - Reference Channel Layout %s\n", esd->decoderConfig->decoderSpecificInfo->data[1] + , gf_audio_fmt_get_layout_name_from_cicp(esd->decoderConfig->decoderSpecificInfo->data[2]) + ); + valid = GF_TRUE; + } + if (!valid) { + M4_LOG(GF_LOG_ERROR, ("Invalid MPEG-H audio config\n")); + } + if (esd) gf_odf_desc_del((GF_Descriptor *)esd); + compat_profiles = gf_isom_get_mpegh_compatible_profiles(file, trackNum, 1, &nb_compat_profiles); + for (i=0; idata) { + char szTimecode[100]; + gf_inspect_format_timecode(sample->data, sample->dataLength, tmcd_flags, tmcd_num, tmcd_den, tmcd_fpt, szTimecode); + fprintf(stderr, "\tFirst timecode: %s\n", szTimecode); + } + gf_isom_sample_del(&sample); + } + } else if (msub_type==GF_ISOM_SUBTYPE_OPUS) { + fprintf(stderr, "\tOpus Audio - Sample Rate %d ch %d\n", sr, nb_ch); + } else { + GF_GenericSampleDescription *udesc; + + udesc = gf_isom_get_generic_sample_description(file, trackNum, 1); + if (udesc) { + if (gf_isom_is_video_handler_type(mtype) ) { + fprintf(stderr, "%s - Compressor \"%s\" - Resolution %d x %d\n", + ( (mtype == GF_ISOM_MEDIA_VISUAL ? "Visual" : "Auxiliary Video") ), + udesc->compressor_name, udesc->width, udesc->height); + } else if (mtype==GF_ISOM_MEDIA_AUDIO) { + fprintf(stderr, "Audio - Sample Rate %d - %d channel(s)\n", udesc->samplerate, udesc->nb_channels); + } else { + fprintf(stderr, "Unknown media type\n"); + } + if (udesc->vendor_code) + fprintf(stderr, "\tVendor code \"%s\" - Version %d - revision %d\n", gf_4cc_to_str(udesc->vendor_code), udesc->version, udesc->revision); + + if (udesc->extension_buf) { + fprintf(stderr, "\tCodec configuration data size: %d bytes\n", udesc->extension_buf_size); + gf_free(udesc->extension_buf); + } + gf_free(udesc); + } else { + fprintf(stderr, "Unknown track type\n"); + } + } + + + /*Crypto info*/ + if (gf_isom_is_track_encrypted(file, trackNum)) { + const char *scheme_URI, *KMS_URI; + u32 scheme_type, version; + u32 IV_size; + Bool use_sel_enc; + + if (gf_isom_is_ismacryp_media(file, trackNum, 1)) { + gf_isom_get_ismacryp_info(file, trackNum, 1, NULL, &scheme_type, &version, &scheme_URI, &KMS_URI, &use_sel_enc, &IV_size, NULL); + fprintf(stderr, "\n\tProtected by ISMA E&A scheme %s (version %d)\n", gf_4cc_to_str(scheme_type), version); + if (scheme_URI) fprintf(stderr, "scheme location: %s\n", scheme_URI); + if (KMS_URI) { + if (!strnicmp(KMS_URI, "(key)", 5)) fprintf(stderr, "\tKMS location: key in file\n"); + else fprintf(stderr, "\tKMS location: %s\n", KMS_URI); + } + fprintf(stderr, "\tSelective Encryption: %s\n", use_sel_enc ? "Yes" : "No"); + if (IV_size) fprintf(stderr, "\tInitialization Vector size: %d bits\n", IV_size*8); + } else if (gf_isom_is_omadrm_media(file, trackNum, 1)) { + const char *textHdrs; + u32 enc_type, hdr_len; + u64 orig_len; + gf_isom_get_omadrm_info(file, trackNum, 1, NULL, &scheme_type, &version, &scheme_URI, &KMS_URI, &textHdrs, &hdr_len, &orig_len, &enc_type, &use_sel_enc, &IV_size, NULL); + fprintf(stderr, "\n\tProtected by OMA DRM scheme %s (version %d)\n", gf_4cc_to_str(scheme_type), version); + fprintf(stderr, "\tRights Issuer: %s\n", KMS_URI); + fprintf(stderr, "\tContent ID: %s\n", scheme_URI); + if (textHdrs) { + u32 offset; + const char *start = textHdrs; + fprintf(stderr, "\tOMA Textual Headers:\n"); + i=0; + offset=0; + while (iDTS+samp->CTS_Offset; + size += samp->dataLength; + rate += samp->dataLength; + if ((samp->DTS - time_slice > ts) || (j+1==count) ) { + Double max_tmp = rate * ts / (samp->DTS - time_slice); + if (max_rate < max_tmp ) + max_rate = max_tmp; + + rate = 0; + time_slice = samp->DTS; + } + gf_isom_sample_del(&samp); + } + } + fprintf(stderr, "\nComputed info from media:\n"); + if (csize && cdur) { + fprintf(stderr, "\tConstant sample size %d bytes and dur %d / %d\n", csize, cdur, ts); + } + scale = 1000.0 / ts; + dur = (u64) (scale * dur); + fprintf(stderr, "\tTotal size "LLU" bytes - Total samples duration "LLU" ms\n", size, dur); + if (!dur) { + fprintf(stderr, "\n"); + return; + } + /*rate in byte, dur is in ms*/ + rate = 8000.0 * size / dur; + + if (!max_rate) + max_rate = rate; + else + max_rate *= 8.0; + + if (rate >= 1500) { + fprintf(stderr, "\tAverage rate %.2f kbps - Max Rate %.2f kbps\n", rate/1000, max_rate/1000); + } else { + fprintf(stderr, "\tAverage rate %.2f bps - Max Rate %.2f bps\n", rate, max_rate); + } + + { + u32 dmin, dmax, davg, smin, smax, savg; + gf_isom_get_chunks_infos(file, trackNum, &dmin, &davg, &dmax, &smin, &savg, &smax); + fprintf(stderr, "\tChunk durations: min %d ms - max %d ms - average %d ms\n", (1000*dmin)/ts, (1000*dmax)/ts, (1000*davg)/ts); + fprintf(stderr, "\tChunk sizes (bytes): min %d - max %d - average %d\n", smin, smax, savg); + } + fprintf(stderr, "\n"); + + count = gf_isom_get_chapter_count(file, trackNum); + if (count) { + const char *name; + u64 time; + fprintf(stderr, "\nChapters:\n"); + for (j=0; j1 ? "s" : "", timescale); + + modif = gf_isom_get_duration(file); + create = gf_isom_get_original_duration(file); + fprintf(stderr, "Duration %s", format_duration(create, timescale, szDur)); + if (create!=modif) { + fprintf(stderr, " (recomputed %s)", format_duration(modif, timescale, szDur)); + } + fprintf(stderr, "\n"); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + if (gf_isom_is_fragmented(file)) { + fprintf(stderr, "Fragmented: yes - duration %s\n%d fragments - %d SegmentIndexes\n", format_duration(gf_isom_get_fragmented_duration(file), timescale, szDur), gf_isom_get_fragments_count(file, 0) , gf_isom_get_fragments_count(file, 1) ); + } else { + fprintf(stderr, "Fragmented: no\n"); + } +#endif + + if (gf_isom_moov_first(file)) + fprintf(stderr, "Progressive (moov before mdat)\n"); + + if (gf_isom_get_brand_info(file, &brand, &min, &count) == GF_OK) { + fprintf(stderr, "Major Brand %s - version %d - compatible brands:", gf_4cc_to_str(brand), min); + for (i=0; itag == GF_ODF_IOD_TAG) { + fprintf(stderr, "File has root IOD (%d bytes)\n", desc_size); + fprintf(stderr, "Scene PL 0x%02x - Graphics PL 0x%02x - OD PL 0x%02x\n", iod->scene_profileAndLevel, iod->graphics_profileAndLevel, iod->OD_profileAndLevel); + fprintf(stderr, "Visual PL: %s (0x%02x)\n", gf_m4v_get_profile_name(iod->visual_profileAndLevel), iod->visual_profileAndLevel); + fprintf(stderr, "Audio PL: %s (0x%02x)\n", gf_m4a_get_profile_name(iod->audio_profileAndLevel), iod->audio_profileAndLevel); + //fprintf(stderr, "inline profiles included %s\n", iod->inlineProfileFlag ? "yes" : "no"); + } else { + fprintf(stderr, "File has root OD (%d bytes)\n", desc_size); + } + if (!gf_list_count(iod->ESDescriptors)) + fprintf(stderr, "No streams included in root OD\n"); + else + dump_m4sys = GF_TRUE; + + gf_odf_desc_del((GF_Descriptor *) iod); + } + if (gf_isom_is_JPEG2000(file)) fprintf(stderr, "File is JPEG 2000\n"); + + count = gf_isom_get_copyright_count(file); + if (count) { + const char *lang, *note; + fprintf(stderr, "\nCopyrights:\n"); + for (i=0; iuser; + + switch (evt_type) { + case GF_M2TS_EVT_PAT_FOUND: + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, 0); + } + break; + case GF_M2TS_EVT_PAT_UPDATE: + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, 0); + } + break; + case GF_M2TS_EVT_PAT_REPEAT: + /* WARNING: We detect the pat on a repetition, probably to ensure that we also have seen all the PMT + To be checked */ + dumper->has_seen_pat = 1; + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, 0); + } +// fprintf(stderr, "Repeated PAT found - %d programs\n", gf_list_count(ts->programs) ); + break; + case GF_M2TS_EVT_CAT_FOUND: + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, 0); + } + break; + case GF_M2TS_EVT_CAT_UPDATE: + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, 0); + } + break; + case GF_M2TS_EVT_CAT_REPEAT: + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, 0); + } + break; + case GF_M2TS_EVT_PMT_FOUND: + prog = (GF_M2TS_Program*)par; + if (gf_list_count(ts->programs)>1 && prog->number!=dumper->prog_number) + break; + + count = gf_list_count(prog->streams); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Program number %d found - %d streams:\n", prog->number, count)); + for (i=0; istreams, i); + if (es->pid == prog->pmt_pid) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tPID %d: Program Map Table\n", es->pid)); + } else { + GF_M2TS_PES *pes = (GF_M2TS_PES *)es; + gf_m2ts_set_pes_framing(pes, dumper->pes_out ? GF_M2TS_PES_FRAMING_RAW : GF_M2TS_PES_FRAMING_DEFAULT); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tPID %d: %s ", pes->pid, gf_m2ts_get_stream_name(pes->stream_type) )); + if (pes->mpeg4_es_id) GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, (" - MPEG-4 ES ID %d", pes->mpeg4_es_id)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\n")); + } + } + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, prog->pmt_pid); + } + break; + case GF_M2TS_EVT_PMT_UPDATE: + prog = (GF_M2TS_Program*)par; + if (gf_list_count(ts->programs)>1 && prog->number!=dumper->prog_number) + break; + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, prog->pmt_pid); + } + break; + case GF_M2TS_EVT_PMT_REPEAT: + prog = (GF_M2TS_Program*)par; + if (gf_list_count(ts->programs)>1 && prog->number!=dumper->prog_number) + break; + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\n", ts->pck_number, prog->pmt_pid); + } + break; + case GF_M2TS_EVT_SDT_FOUND: +#ifndef GPAC_DISABLE_LOG + count = gf_list_count(ts->SDTs) ; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Program Description found - %d desc:\n", count)); + for (i=0; iSDTs, i); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tServiceID %d - Provider %s - Name %s\n", sdt->service_id, sdt->provider, sdt->service)); + } +#endif + break; + case GF_M2TS_EVT_SDT_UPDATE: +#ifndef GPAC_DISABLE_LOG + count = gf_list_count(ts->SDTs) ; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Program Description updated - %d desc\n", count)); + for (i=0; iSDTs, i); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tServiceID %d - Provider %s - Name %s\n", sdt->service_id, sdt->provider, sdt->service)); + } +#endif + break; + case GF_M2TS_EVT_SDT_REPEAT: + break; + case GF_M2TS_EVT_PES_TIMING: + pck = par; + if (gf_list_count(ts->programs)>1 && pck->stream->program->number != dumper->prog_number) + break; + + break; + case GF_M2TS_EVT_PES_PCK: + pck = par; + if (gf_list_count(ts->programs)>1 && pck->stream->program->number != dumper->prog_number) + break; + if (dumper->has_seen_pat) { + + /*We need the interpolated PCR for the pcrb, hence moved this calculus out, and saving the calculated value in index_info to put it in the pcrb*/ + GF_M2TS_PES *pes = pck->stream; + /*FIXME : not used GF_M2TS_Program *prog = pes->program; */ + /* Interpolated PCR value for the TS packet containing the PES header start */ + u64 interpolated_pcr_value = 0; + if (pes->last_pcr_value && pes->before_last_pcr_value_pck_number && pes->last_pcr_value > pes->before_last_pcr_value) { + u32 delta_pcr_pck_num = pes->last_pcr_value_pck_number - pes->before_last_pcr_value_pck_number; + u32 delta_pts_pcr_pck_num = pes->pes_start_packet_number - pes->last_pcr_value_pck_number; + u64 delta_pcr_value = pes->last_pcr_value - pes->before_last_pcr_value; + if ((pes->pes_start_packet_number > pes->last_pcr_value_pck_number) + && (pes->last_pcr_value > pes->before_last_pcr_value)) { + + pes->last_pcr_value = pes->before_last_pcr_value; + } + /* we can compute the interpolated pcr value for the packet containing the PES header */ + interpolated_pcr_value = pes->last_pcr_value + (u64)((delta_pcr_value*delta_pts_pcr_pck_num*1.0)/delta_pcr_pck_num); + } + + if (dumper->timestamps_info_file) { + Double diff; + fprintf(dumper->timestamps_info_file, "%u\t%d\t", pck->stream->pes_start_packet_number, pck->stream->pid); + if (interpolated_pcr_value) fprintf(dumper->timestamps_info_file, "%f", interpolated_pcr_value/(300.0 * 90000)); + fprintf(dumper->timestamps_info_file, "\t"); + if (pck->DTS) fprintf(dumper->timestamps_info_file, "%f", (pck->DTS / 90000.0)); + fprintf(dumper->timestamps_info_file, "\t%f\t%d\t%d", pck->PTS / 90000.0, (pck->flags & GF_M2TS_PES_PCK_RAP) ? 1 : 0, (pck->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0); + if (interpolated_pcr_value) { + diff = (pck->DTS ? pck->DTS : pck->PTS) / 90000.0; + diff -= pes->last_pcr_value / (300.0 * 90000); + fprintf(dumper->timestamps_info_file, "\t%f\n", diff); + if (diff<0) { + M4_LOG(GF_LOG_WARNING, ("Warning: detected PTS/DTS value less than current PCR of %g sec\n", diff)); + } + } else { + fprintf(dumper->timestamps_info_file, "\t\n"); + } + } + } + + if (dumper->has_seen_pat && dumper->pes_out && (dumper->dump_pid == pck->stream->pid)) { + gf_fwrite(pck->data, pck->data_len, dumper->pes_out); + } + break; + case GF_M2TS_EVT_PES_PCR: + pck = par; + if (gf_list_count(ts->programs)>1 && pck->stream->program->number != dumper->prog_number) + break; + if (dumper->timestamps_info_file) { + fprintf(dumper->timestamps_info_file, "%u\t%d\t%f\t\t\t\t%d\n", pck->stream->program->last_pcr_value_pck_number, pck->stream->pid, pck->PTS / (300*90000.0), (pck->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0); + } + break; + case GF_M2TS_EVT_SL_PCK: +#if 0 + { + GF_M2TS_SL_PCK *sl_pck = par; + if (dumper->pes_out && (dumper->dump_pid == sl_pck->stream->pid)) { + GF_SLHeader header; + u32 header_len; + if (sl_pck->stream->mpeg4_es_id) { + GF_ESD *esd = ((GF_M2TS_PES*)sl_pck->stream)->esd; + if (!dumper->is_info_dumped) { + if (esd->decoderConfig->decoderSpecificInfo) gf_fwrite(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, dumper->pes_out_info); + dumper->is_info_dumped = 1; + fprintf(dumper->pes_out_nhml, "pes_out_nhml, "timeScale=\"%d\" ", esd->slConfig->timestampResolution); + fprintf(dumper->pes_out_nhml, "streamType=\"%d\" ", esd->decoderConfig->streamType); + fprintf(dumper->pes_out_nhml, "objectTypeIndication=\"%d\" ", esd->decoderConfig->objectTypeIndication); + if (esd->decoderConfig->decoderSpecificInfo) fprintf(dumper->pes_out_nhml, "specificInfoFile=\"%s\" ", dumper->info); + 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_fwrite(sl_pck->data+header_len, sl_pck->data_len-header_len, dumper->pes_out); + fprintf(dumper->pes_out_nhml, "\n", header.decodingTimeStamp, sl_pck->data_len-header_len, (header.randomAccessPointFlag?"yes":"no")); + } + } + } +#endif + break; + } +} + +void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) +{ + u8 data[188]; + GF_M2TS_Dump dumper; + + u32 size; + u64 fsize, fdone; + GF_M2TS_Demuxer *ts; + FILE *src; + + if (!prog_num && !out_name) { + fprintf(stderr, "No program number nor output filename specified. No timestamp file will be generated."); + } + + src = gf_fopen(mpeg2ts_file, "rb"); + if (!src) { + M4_LOG(GF_LOG_ERROR, ("Cannot open %s: no such file\n", mpeg2ts_file)); + return; + } + ts = gf_m2ts_demux_new(); + ts->on_event = on_m2ts_dump_event; + ts->notify_pes_timing = 1; + memset(&dumper, 0, sizeof(GF_M2TS_Dump)); + ts->user = &dumper; + dumper.prog_number = prog_num; + + /*PES dumping*/ + if (out_name) { + char *pid = strrchr(out_name, '#'); + if (pid) { + dumper.dump_pid = atoi(pid+1); + pid[0] = 0; + sprintf(dumper.dump, "%s_%d.raw", out_name, dumper.dump_pid); + dumper.pes_out = gf_fopen(dumper.dump, "wb"); +#if 0 + sprintf(dumper.nhml, "%s_%d.nhml", pes_out_name, dumper.dump_pid); + dumper.pes_out_nhml = gf_fopen(dumper.nhml, "wt"); + sprintf(dumper.info, "%s_%d.info", pes_out_name, dumper.dump_pid); + dumper.pes_out_info = gf_fopen(dumper.info, "wb"); +#endif + pid[0] = '#'; + } + } + + gf_fseek(src, 0, SEEK_END); + fsize = gf_ftell(src); + gf_fseek(src, 0, SEEK_SET); + + /* first loop to process all packets between two PAT, and assume all signaling was found between these 2 PATs */ + while (!feof(src)) { + size = (u32) gf_fread(data, 188, src); + if (size<188) break; + + gf_m2ts_process_data(ts, data, size); + if (dumper.has_seen_pat) break; + } + dumper.has_seen_pat = GF_TRUE; + + if (!prog_num) { + GF_M2TS_Program *p = gf_list_get(ts->programs, 0); + if (p) prog_num = p->number; + fprintf(stderr, "No program number specified, defaulting to first program\n"); + } + + if (!prog_num && !out_name) { + fprintf(stderr, "No program number nor output filename specified. No timestamp file will be generated\n"); + } + + if (prog_num) { + sprintf(dumper.timestamps_info_name, "%s_prog_%d_timestamps.txt", mpeg2ts_file, prog_num/*, mpeg2ts_file*/); + dumper.timestamps_info_file = gf_fopen(dumper.timestamps_info_name, "wt"); + if (!dumper.timestamps_info_file) { + M4_LOG(GF_LOG_ERROR, ("Cannot open file %s\n", dumper.timestamps_info_name)); + return; + } + fprintf(dumper.timestamps_info_file, "PCK#\tPID\tPCR\tDTS\tPTS\tRAP\tDiscontinuity\tDTS-PCR Diff\n"); + } + + gf_m2ts_reset_parsers(ts); + gf_fseek(src, 0, SEEK_SET); + fdone = 0; + + while (!feof(src)) { + size = (u32) gf_fread(data, 188, src); + if (size<188) break; + + gf_m2ts_process_data(ts, data, size); + + fdone += size; + gf_set_progress("MPEG-2 TS Parsing", fdone, fsize); + } + + gf_fclose(src); + gf_m2ts_demux_del(ts); + if (dumper.pes_out) gf_fclose(dumper.pes_out); +#if 0 + if (dumper.pes_out_nhml) { + if (dumper.is_info_dumped) fprintf(dumper.pes_out_nhml, "\n"); + gf_fclose(dumper.pes_out_nhml); + gf_fclose(dumper.pes_out_info); + } +#endif + if (dumper.timestamps_info_file) gf_fclose(dumper.timestamps_info_file); +} + +#endif /*GPAC_DISABLE_MPEG2TS*/ + + +void get_file_callback(void *usr_cbk, GF_NETIO_Parameter *parameter) +{ + if (parameter->msg_type==GF_NETIO_DATA_EXCHANGE) { + u64 tot_size, done, max; + u32 bps; + gf_dm_sess_get_stats(parameter->sess, NULL, NULL, &tot_size, &done, &bps, NULL); + if (tot_size) { + max = done; + max *= 100; + max /= tot_size; + fprintf(stderr, "download %02d %% at %05d kpbs\r", (u32) max, bps*8/1000); + } + } +} + +static GF_DownloadSession *get_file(const char *url, GF_DownloadManager *dm, GF_Err *e) +{ + GF_DownloadSession *sess; + sess = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_NOT_THREADED, get_file_callback, NULL, e); + if (!sess) return NULL; + *e = gf_dm_sess_process(sess); + if (*e) { + gf_dm_sess_del(sess); + return NULL; + } + return sess; +} + +static void revert_cache_file(char *item_path) +{ + char szPATH[GF_MAX_PATH]; + const char *url; + GF_Config *cached; + + if (!strstr(item_path, "gpac_cache_")) { + fprintf(stderr, "%s is not a gpac cache file\n", item_path); + return; + } + if (!strncmp(item_path, "./", 2) || !strncmp(item_path, ".\\", 2)) + item_path += 2; + + strcpy(szPATH, item_path); + strcat(szPATH, ".txt"); + + cached = gf_cfg_new(NULL, szPATH); + url = gf_cfg_get_key(cached, "cache", "url"); + if (url) url = strstr(url, "://"); + if (url) { + u32 i, len, dir_len=0, k=0; + char *sep; + char *dst_name; + sep = strstr(item_path, "gpac_cache_"); + if (sep) { + sep[0] = 0; + dir_len = (u32) strlen(item_path); + sep[0] = 'g'; + } + url+=3; + len = (u32) strlen(url); + dst_name = gf_malloc(len+dir_len+1); + memset(dst_name, 0, len+dir_len+1); + + strncpy(dst_name, item_path, dir_len); + k=dir_len; + for (i=0; itype==GF_MPD_TYPE_DYNAMIC) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("MPD rip is not supported on live sources\n")); + e = GF_NOT_SUPPORTED; + goto err_exit; + } + + i=0; + while ((period = (GF_MPD_Period *) gf_list_enum(mpd->periods, &i))) { + char *initTemplate = NULL; + Bool segment_base = GF_FALSE; + u32 j=0; + + if (period->segment_base) segment_base=GF_TRUE; + + if (period->segment_template && period->segment_template->initialization) { + initTemplate = period->segment_template->initialization; + } + + while ((as = gf_list_enum(period->adaptation_sets, &j))) { + u32 k=0; + if (!initTemplate && as->segment_template && as->segment_template->initialization) { + initTemplate = as->segment_template->initialization; + } + if (as->segment_base) segment_base=GF_TRUE; + + while ((rep = gf_list_enum(as->representations, &k))) { + u64 out_range_start, out_range_end, segment_duration; + Bool is_in_base_url; + char *seg_url; + u32 seg_idx=0; + if (rep->segment_template && rep->segment_template->initialization) { + initTemplate = rep->segment_template->initialization; + } else if (k>1) { + initTemplate = NULL; + } + 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); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error resolving init segment name : %s\n", gf_error_to_string(e))); + continue; + } + //not a byte range, replace URL + if (segment_base) { + + } else if (out_range_start || out_range_end || !seg_url) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("byte range rip not yet implemented\n")); + if (seg_url) gf_free(seg_url); + e = GF_NOT_SUPPORTED; + goto err_exit; + } + + fprintf(stderr, "Downloading %s\n", seg_url); + sess = get_file(seg_url, dm, &e); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error downloading init segment %s from MPD %s : %s\n", seg_url, mpd_src, gf_error_to_string(e))); + goto err_exit; + } + revert_cache_file((char *) gf_dm_sess_get_cache_name(sess) ); + gf_free(seg_url); + gf_dm_sess_del(sess); + + 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); + if (e) { + if (e<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error resolving segment name : %s\n", gf_error_to_string(e))); + } + break; + } + + seg_idx++; + + if (out_range_start || out_range_end || !seg_url) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("byte range rip not yet implemented\n")); + if (seg_url) gf_free(seg_url); + break; + } + fprintf(stderr, "Downloading %s\n", seg_url); + sess = get_file(seg_url, dm, &e); + if (e) { + gf_free(seg_url); + if (e != GF_URL_ERROR) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error downloading segment %s: %s\n", seg_url, gf_error_to_string(e))); + } else { + //todo, properly detect end of dash representation + e = GF_OK; + } + break; + } + revert_cache_file((char *) gf_dm_sess_get_cache_name(sess) ); + gf_free(seg_url); + gf_dm_sess_del(sess); + } + } + } + } + +err_exit: + if (mpd) gf_mpd_del(mpd); + gf_dm_del(dm); + return e; +} diff --git a/applications/mp4box/fileimport.c b/applications/mp4box/fileimport.c new file mode 100644 index 0000000..7054f72 --- /dev/null +++ b/applications/mp4box/fileimport.c @@ -0,0 +1,3852 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2021 + * All rights reserved + * + * This file is part of GPAC / mp4box application + * + * 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 "mp4box.h" + +#include +#include +#include +#include +#include + +#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_X3D) && !defined(GPAC_DISABLE_SVG) +#include +#endif + + +#ifndef GPAC_DISABLE_BIFS +#include +#endif +#ifndef GPAC_DISABLE_VRML +#include +#endif + +#ifndef GPAC_DISABLE_ISOM_WRITE + +#include +#include + +typedef struct +{ + const char *root_file; + const char *dir; + GF_List *imports; +} WGTEnum; + +GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, Bool is_box_array, Bool is_string) +{ + u8 *data = NULL; + GF_Err res = GF_OK; + u32 size; + bin128 uuid; + memset(uuid, 0 , 16); + + if (!udta_type && !is_box_array) return GF_BAD_PARAM; + + if (!src || !strlen(src)) { + GF_Err e = gf_isom_remove_user_data(dest, tracknum, udta_type, uuid); + if (e==GF_EOS) { + e = GF_OK; + M4_LOG(GF_LOG_WARNING, ("No track.udta found, ignoring\n")); + } + return e; + } + +#ifndef GPAC_DISABLE_CORE_TOOLS + if (!strnicmp(src, "base64", 6)) { + src += 7; + size = (u32) strlen(src); + data = gf_malloc(sizeof(char) * size); + size = gf_base64_decode((u8 *)src, size, data, size); + } else +#endif + if (is_string) { + data = (u8 *) src; + size = (u32) strlen(src)+1; + is_box_array = 0; + } else { + GF_Err e = gf_file_load_data(src, (u8 **) &data, &size); + if (e) return e; + } + + if (size && data) { + if (is_box_array) { + res = gf_isom_add_user_data_boxes(dest, tracknum, data, size); + } else { + res = gf_isom_add_user_data(dest, tracknum, udta_type, uuid, data, size); + } + if (!is_string) + gf_free(data); + } + return res; +} + +#ifndef GPAC_DISABLE_MEDIA_IMPORT + +extern u32 swf_flags; +extern Float swf_flatten_angle; +extern Bool keep_sys_tracks; +extern u32 fs_dump_flags; + +void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist); + +void convert_file_info(char *inName, GF_ISOTrackID trackID) +{ + GF_Err e; + u32 i; + Bool found; + GF_MediaImporter import; + memset(&import, 0, sizeof(GF_MediaImporter)); + import.trackID = trackID; + import.in_name = inName; + import.flags = GF_IMPORT_PROBE_ONLY; + import.print_stats_graph = fs_dump_flags; + + e = gf_media_import(&import); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error probing file %s: %s\n", inName, gf_error_to_string(e))); + return; + } + if (trackID) { + fprintf(stderr, "Import probing results for track %s#%d:\n", inName, trackID); + } else { + fprintf(stderr, "Import probing results for %s:\n", inName); + if (!import.nb_tracks) { + M4_LOG(GF_LOG_WARNING, ("File has no selectable tracks\n")); + return; + } + 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)); + } + found = 0; + for (i=0; i> 16, import.tk_info[i].video_info.par & 0xFFFF); + } + + fprintf(stderr, "\n"); + } + else if ((import.tk_info[i].stream_type==GF_STREAM_AUDIO) && import.tk_info[i].audio_info.sample_rate) { + fprintf(stderr, "\tSampleRate %d - %d channels\n", import.tk_info[i].audio_info.sample_rate, import.tk_info[i].audio_info.nb_channels); + } + + if (trackID) { + found = 1; + break; + } + } + fprintf(stderr, "\n"); + M4_LOG(GF_LOG_INFO, ("For more details, use `gpac -i %s inspect[:deep][:analyze=on|bs]`\n", gf_file_basename(inName))); + if (!found && trackID) { + M4_LOG(GF_LOG_ERROR, ("Cannot find track %d in file\n", trackID)); + } +} + +static GF_Err set_chapter_track(GF_ISOFile *file, u32 track, u32 chapter_ref_trak) +{ + u64 ref_duration, chap_duration; + Double scale; + GF_Err e; + + e = gf_isom_set_track_reference(file, chapter_ref_trak, GF_ISOM_REF_CHAP, gf_isom_get_track_id(file, track) ); + if (e) return e; + e = gf_isom_set_track_enabled(file, track, GF_FALSE); + if (e) return e; + + ref_duration = gf_isom_get_media_duration(file, chapter_ref_trak); + chap_duration = gf_isom_get_media_duration(file, track); + scale = (Double) (s64) gf_isom_get_media_timescale(file, track); + scale /= gf_isom_get_media_timescale(file, chapter_ref_trak); + ref_duration = (u64) (ref_duration * scale); + + if (chap_duration < ref_duration) { + chap_duration -= gf_isom_get_sample_duration(file, track, gf_isom_get_sample_count(file, track)); + chap_duration = ref_duration - chap_duration; + e = gf_isom_set_last_sample_duration(file, track, (u32) chap_duration); + if (e) return e; + } +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + u8 NbBits; + u32 switchGroupID, nb_crit, size, reserved; + u8 priority; + Bool discardable; + GF_AudioChannelLayout layout; + gf_isom_get_sample_size(file, track, 1); + gf_isom_get_sample_dts(file, track, 1); + gf_isom_get_sample_from_dts(file, track, 0); + gf_isom_set_sample_padding(file, track, 0); + gf_isom_has_padding_bits(file, track); + gf_isom_get_sample_padding_bits(file, track, 1, &NbBits); + gf_isom_keep_utc_times(file, 1); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + gf_isom_set_single_moof_mode(file, GF_TRUE); + 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); +#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); + + gf_isom_get_track_switch_parameter(file, track, 1, &switchGroupID, &nb_crit); + gf_isom_sample_has_subsamples(file, track, 1, 0); + gf_isom_sample_get_subsample(file, track, 1, 0, 1, &size, &priority, &reserved, &discardable); +#ifndef GPAC_DISABLE_ISOM_HINTING + gf_isom_hint_blank_data(NULL, 0, 0); + gf_isom_hint_sample_description_data(NULL, 0, 0, 1, 0, 0, 0); + gf_isom_get_payt_info(NULL, 0, 0, NULL); +#endif + gf_isom_estimate_size(file); + + } +#endif + return GF_OK; +} + +GF_Err parse_fracs(char *str, GF_Fraction64 *f, GF_Fraction64 *dur) +{ + GF_Err e = GF_OK; + if (dur) { + char *sep = strchr(str, '-'); + if (sep) { + sep[0] = 0; + if (!gf_parse_lfrac(str, f)) e = GF_BAD_PARAM; + if (!gf_parse_lfrac(sep+1, dur)) e = GF_BAD_PARAM; + sep[0] = '-'; + return e; + } + dur->num = 0; + dur->den = 0; + } + if (!gf_parse_lfrac(str, f)) e = GF_BAD_PARAM; + return e; +} + +Bool scan_color(char *val, u32 *clr_prim, u32 *clr_tranf, u32 *clr_mx, Bool *clr_full_range) +{ + char *sep = strchr(val, ','); + if (!sep) return GF_FALSE; + sep[0] = 0; + *clr_prim = gf_cicp_parse_color_primaries(val); + sep[0] = ','; + if (*clr_prim == (u32) -1) return GF_FALSE; + val = sep+1; + + sep = strchr(val, ','); + if (!sep) return GF_FALSE; + sep[0] = 0; + *clr_tranf = gf_cicp_parse_color_transfer(val); + sep[0] = ','; + if (*clr_tranf == (u32) -1) return GF_FALSE; + val = sep+1; + + sep = strchr(val, ','); + if (sep) sep[0] = 0; + *clr_mx = gf_cicp_parse_color_matrix(val); + if (sep) sep[0] = ','; + if (*clr_mx == (u32) -1) return GF_FALSE; + if (!sep) return GF_TRUE; + + val = sep+1; + if (!strcmp(val, "yes") || !strcmp(val, "on")) { + *clr_full_range = GF_TRUE; + return GF_TRUE; + } + if (!strcmp(val, "no") || !strcmp(val, "off")) { + *clr_full_range = GF_FALSE; + return GF_TRUE; + } + + if (sscanf(val, "%d", (s32*) clr_full_range) == 1) + return GF_TRUE; + + return GF_FALSE; +} + +static GF_Err set_dv_profile(GF_ISOFile *dest, u32 track, char *dv_profile_str) +{ + GF_Err e; + Bool remove=GF_FALSE; + Bool force_dv=GF_FALSE; + u32 dv_profile = 0; + u32 dv_compat_id=0; + char *sep = strchr(dv_profile_str, '.'); + if (sep) { + sep[0] = 0; + if (!strcmp(sep+1, "none")) dv_compat_id=0; + else if (!strcmp(sep+1, "hdr10")) dv_compat_id=1; + else if (!strcmp(sep+1, "bt709")) dv_compat_id=2; + else if (!strcmp(sep+1, "hlg709")) dv_compat_id=3; + else if (!strcmp(sep+1, "hlg2100")) dv_compat_id=4; + else if (!strcmp(sep+1, "bt2020")) dv_compat_id=5; + else if (!strcmp(sep+1, "brd")) dv_compat_id=6; + else if ((sep[1]>='0') && (sep[1]<='9')) dv_compat_id=atoi(sep+1); + else { + M4_LOG(GF_LOG_WARNING, ("DV compatibility mode %s not recognized, using none\n", sep+1)); + } + } + if (dv_profile_str[0]=='f') { + force_dv = GF_TRUE; + dv_profile_str++; + } + + if (!strcmp(dv_profile_str, "none")) { + remove = GF_TRUE; + } else { + dv_profile = atoi(dv_profile_str); + if (dv_profile==8) { + if ((dv_compat_id!=1) && (dv_compat_id!=2)) { + M4_LOG(GF_LOG_ERROR, ("DV profile 8 must indicate a compatibility mode `hdr10` or `bt709`\n")); + return GF_BAD_PARAM; + } + } + } + + GF_DOVIDecoderConfigurationRecord *dovi = gf_isom_dovi_config_get(dest, track, 1); + if (dovi) { + dovi->dv_profile = dv_profile; + dovi->dv_bl_signal_compatibility_id = dv_compat_id; + dovi->force_dv = force_dv; + e = gf_isom_set_dolby_vision_profile(dest, track, 1, remove ? NULL : dovi); + gf_odf_dovi_cfg_del(dovi); + return e; + } + if (remove) return GF_OK; + u32 nb_samples = gf_isom_get_sample_count(dest, track); + if (!nb_samples) { + M4_LOG(GF_LOG_ERROR, ("No DV config in file and no samples, cannot guess DV config\n")); + return GF_NOT_SUPPORTED; + } + + GF_DOVIDecoderConfigurationRecord _dovi; + memset(&_dovi, 0, sizeof(GF_DOVIDecoderConfigurationRecord)); + _dovi.dv_version_major = 1; + _dovi.dv_version_minor = 0; + _dovi.dv_profile = dv_profile; + _dovi.dv_bl_signal_compatibility_id = dv_compat_id; + _dovi.force_dv = force_dv; + + u32 w, h; + Bool is_avc = GF_FALSE; + + e = gf_isom_get_visual_info(dest, track, 1, &w, &h); + if (e) return e; + //no DV profile present in file, we need to guess + GF_AVCConfig *avcc = gf_isom_avc_config_get(dest, track, 1); + GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(dest, track, 1); + + if (!avcc && !hvcc) { + M4_LOG(GF_LOG_WARNING, ("DV profile can only be set on AVC or HEVC tracks\n")); + return GF_BAD_PARAM; + } + + u32 nalu_length = avcc ? avcc->nal_unit_size : hvcc->nal_unit_size; + if (avcc) { + is_avc = GF_TRUE; + gf_odf_avc_cfg_del(avcc); + } + if (hvcc) gf_odf_hevc_cfg_del(hvcc); + + //inspect at most first 50 samples + u32 i; + for (i=0; i<50; i++) { + u32 stsd_idx; + GF_BitStream *bs; + + GF_ISOSample *samp = gf_isom_get_sample(dest, track, i+1, &stsd_idx); + if (!samp) break; + + bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ); + + while (gf_bs_available(bs)) { + u32 nal_type; + u32 nal_size = gf_bs_read_int(bs, nalu_length*8); + if (is_avc) { + nal_type = gf_bs_read_u8(bs); + nal_type = nal_type & 0x1F; + nal_size--; + if (nal_type == GF_AVC_NALU_DV_RPU) _dovi.rpu_present_flag = 1; + else if (nal_type == GF_AVC_NALU_DV_EL) _dovi.el_present_flag = 1; + else if (nal_type <= GF_AVC_NALU_IDR_SLICE) _dovi.bl_present_flag = 1; + } else { + gf_bs_read_int(bs, 1); + nal_type = gf_bs_read_int(bs, 6); + gf_bs_read_int(bs, 1); + nal_size--; + if (nal_type == GF_HEVC_NALU_DV_RPU) _dovi.rpu_present_flag = 1; + else if (nal_type == GF_HEVC_NALU_DV_EL) _dovi.el_present_flag = 1; + else if (nal_type <= GF_HEVC_NALU_SLICE_CRA) _dovi.bl_present_flag = 1; + } + gf_bs_skip_bytes(bs, nal_size); + } + gf_bs_del(bs); + gf_isom_sample_del(&samp); + } + + u64 mdur = gf_isom_get_media_duration(dest, track); + mdur /= nb_samples; + u32 timescale = gf_isom_get_media_timescale(dest, track); + + _dovi.dv_level = gf_dolby_vision_level(w, h, timescale, mdur, is_avc ? GF_CODECID_AVC : GF_CODECID_HEVC); + return gf_isom_set_dolby_vision_profile(dest, track, 1, &_dovi); +} + + + +GF_Err apply_edits(GF_ISOFile *dest, u32 track, char *edits) +{ + 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); + + while (edits) { + GF_Err e; + char c=0; + char *sep = strchr(edits+1, 'r'); + if (!sep) sep = strchr(edits+1, 'e'); + if (sep) { + c = sep[0]; + sep[0] = 0; + } + + //remove all edits + if (edits[0] == 'r') { + e = gf_isom_remove_edits(dest, track); + if (e) goto error; + } + else if (edits[0]=='e') { + u64 movie_t, media_t, edur; + u32 rate; + GF_Fraction64 movie_time, media_time, media_rate, edit_dur; + char *mtime_sep; + + edits+=1; + mtime_sep = strchr(edits, ','); + movie_time.den = media_time.den = media_rate.den = 0; + if (!mtime_sep) { + e = parse_fracs(edits, &movie_time, &edit_dur); + if (e) goto error; + } + else { + mtime_sep[0] = 0; + e = parse_fracs(edits, &movie_time, &edit_dur); + if (e) goto error; + mtime_sep[0] = ','; + edits = mtime_sep+1; + mtime_sep = strchr(edits, ','); + if (!mtime_sep) { + e = parse_fracs(edits, &media_time, NULL); + if (e) goto error; + media_rate.num = media_rate.den = 1; + } else { + mtime_sep[0] = 0; + e = parse_fracs(edits, &media_time, NULL); + if (e) goto error; + mtime_sep[0] = ','; + e = parse_fracs(mtime_sep+1, &media_rate, NULL); + if (e) goto error; + } + } + if (!movie_time.den || (movie_time.num<0)) { + e = GF_BAD_PARAM; + fprintf(stderr, "Wrong edit format %s, movie time must be valid and >= 0\n", edits); + goto error; + } + movie_t = movie_time.num * movie_ts / movie_time.den; + if (!edit_dur.den || !edit_dur.num) { + edur = media_dur; + edur *= movie_ts; + edur /= media_ts; + } else { + edur = edit_dur.num; + edur *= movie_ts; + edur /= edit_dur.den; + if (edur>media_dur) + edur = media_dur; + } + if (!media_time.den) { + e = gf_isom_set_edit(dest, track, movie_t, edur, 0, GF_ISOM_EDIT_EMPTY); + } else { + rate = 0; + if (media_rate.den) { + u64 frac; + rate = (u32) ( media_rate.num / media_rate.den ); + frac = media_rate.num - rate*media_rate.den; + frac *= 0xFFFF; + frac /= media_rate.den; + rate = (rate<<16) | (u32) frac; + } + media_t = media_time.num * media_ts / media_time.den; + e = gf_isom_set_edit_with_rate(dest, track, movie_t, edur, media_t, rate); + } + if (e==GF_EOS) { + fprintf(stderr, "Inserted empty edit before edit at start time "LLD"/"LLU"\n", movie_time.num, movie_time.den); + e = GF_OK; + } + if (e) goto error; + } else { + e = GF_BAD_PARAM; + fprintf(stderr, "Wrong edit format %s, should start with 'e' or 'r'\n", edits); + goto error; + } +error: + if (sep) sep[0] = c; + if (e) return e; + + if (!sep) break; + edits = sep; + } + return GF_OK; +} + +static const char *videofmt_names[] = { "component", "pal", "ntsc", "secam", "mac", "undef"}; + + +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) +{ + u32 track_id, i, j, timescale, track, stype, profile, compat, level, new_timescale, rescale_num, rescale_den, svc_mode, txt_flags, split_tile_mode, temporal_mode, nb_tracks; + s32 par_d, par_n, prog_id, force_rate, moov_timescale; + s32 tw, th, tx, ty, tz, txtw, txth, txtx, txty; + Bool do_audio, do_video, do_auxv,do_pict, do_all, track_layout, text_layout, chap_ref, is_chap, is_chap_file, keep_handler, rap_only, refs_only, force_par, rewrite_bs; + u32 group, handler, rvc_predefined, check_track_for_svc, check_track_for_lhvc, check_track_for_hevc, do_disable; + const char *szLan; + GF_Err e = GF_OK; + GF_Fraction delay; + 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_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; + Bool has_clap=GF_FALSE; + Bool use_stz2=GF_FALSE; + Bool has_mx=GF_FALSE; + s32 mx[9]; + u32 bitdepth=0; + char dv_profile[100]; /*Dolby Vision*/ + u32 clr_type=0; + u32 clr_prim; + u32 clr_tranf; + u32 clr_mx; + Bool rescale_override=GF_FALSE; + Bool clr_full_range=GF_FALSE; + Bool fmt_ok = GF_TRUE; + u32 icc_size=0, track_flags=0; + u8 *icc_data = NULL; + u32 tc_fps_num=0, tc_fps_den=0, tc_h=0, tc_m=0, tc_s=0, tc_f=0, tc_frames_per_tick=0; + Bool tc_force_counter=GF_FALSE; + Bool tc_drop_frame = GF_FALSE; + char *ext_start; + u32 xps_inband=0; + u64 source_magic=0; + char *opt_src = NULL; + char *opt_dst = NULL; + char *fchain = NULL; + char *edits = NULL; + const char *fail_msg = NULL; + char *hdr_file=NULL; + Bool set_ccst=GF_FALSE; + Bool has_last_sample_dur=GF_FALSE; + u32 fake_import = 0; + GF_Fraction last_sample_dur = {0,0}; + s32 fullrange, videofmt, colorprim, colortfc, colormx; + clap_wn = clap_wd = clap_hn = clap_hd = clap_hon = clap_hod = clap_von = clap_vod = 0; + GF_ISOMTrackFlagOp track_flags_mode=0; + u32 roll_change=0; + u32 roll = 0; + Bool src_is_isom = GF_FALSE; + + dv_profile[0] = 0; + rvc_predefined = 0; + chapter_name = NULL; + new_timescale = 1; + moov_timescale = 0; + rescale_num = rescale_den = 0; + text_layout = 0; + /*0: merge all + 1: split base and all SVC in two tracks + 2: split all base and SVC layers in dedicated tracks + */ + svc_mode = 0; + + if (import_flags==0xFFFFFFFF) { + import_flags = 0; + fake_import = 1; + } + + memset(&import, 0, sizeof(GF_MediaImporter)); + + final_name = gf_strdup(inName); +#ifdef WIN32 + /*dirty hack for msys&mingw: when we use import options, the ':' separator used prevents msys from translating the path + we do this for regular cases where the path starts with the drive letter. If the path start with anything else (/home , /opt, ...) we're screwed :( */ + if ( (final_name[0]=='/') && (final_name[2]=='/')) { + final_name[0] = final_name[1]; + final_name[1] = ':'; + } +#endif + + is_chap_file = 0; + handler = 0; + do_disable = 0; + chap_ref = 0; + is_chap = 0; + kinds = gf_list_new(); + track_layout = 0; + szLan = NULL; + delay.num = delay.den = 0; + group = 0; + stype = 0; + profile = compat = level = 0; + fullrange = videofmt = colorprim = colortfc = colormx = -1; + split_tile_mode = 0; + temporal_mode = 0; + rap_only = 0; + refs_only = 0; + txt_flags = 0; + max_layer_id_plus_one = max_temporal_id_plus_one = 0; + force_rate = -1; + + tw = th = tx = ty = tz = txtw = txth = txtx = txty = 0; + par_d = par_n = -1; + force_par = rewrite_bs = GF_FALSE; + + ext_start = gf_file_ext_start(final_name); + ext = strrchr(ext_start ? ext_start : final_name, '#'); + if (!ext) ext = gf_url_colon_suffix(final_name, '='); + char c_sep = ext ? ext[0] : 0; + if (ext) ext[0] = 0; + if (!strlen(final_name) || !strcmp(final_name, "self")) { + fake_import = 2; + } + if (gf_isom_probe_file(final_name)) + src_is_isom = GF_TRUE; + + if (ext) ext[0] = c_sep; + + 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; } + + + handler_name = NULL; + rvc_config = NULL; + while (ext) { + char *ext2 = gf_url_colon_suffix(ext+1, '='); + + if (ext2) ext2[0] = 0; + + /*all extensions for track-based importing*/ + if (!strnicmp(ext+1, "dur=", 4)) { + CHECK_FAKEIMPORT("dur") + + if (strchr(ext, '-')) { + import.duration.num = atoi(ext+5); + import.duration.den = 1; + } else { + gf_parse_frac(ext+5, &import.duration); + } + } + else if (!strnicmp(ext+1, "start=", 6)) { + CHECK_FAKEIMPORT("start") + import.start_time = atof(ext+7); + } + else if (!strnicmp(ext+1, "lang=", 5)) { + /* prevent leak if param is set twice */ + if (szLan) + gf_free((char*) szLan); + + szLan = gf_strdup(ext+6); + } + else if (!strnicmp(ext+1, "delay=", 6)) { + if (sscanf(ext+7, "%d/%u", &delay.num, &delay.den)!=2) { + delay.num = atoi(ext+7); + delay.den = 1000; //in ms + } + } + else if (!strnicmp(ext+1, "par=", 4)) { + if (!stricmp(ext + 5, "none")) { + par_n = par_d = 0; + } else if (!stricmp(ext + 5, "auto")) { + force_par = GF_TRUE; + } else if (!stricmp(ext + 5, "force")) { + par_n = par_d = 1; + force_par = GF_TRUE; + } else { + if (ext2) { + ext2[0] = ':'; + ext2 = strchr(ext2+1, ':'); + if (ext2) ext2[0] = 0; + } + if (ext[5]=='w') { + rewrite_bs = GF_TRUE; + if (sscanf(ext+6, "%d:%d", &par_n, &par_d)!=2) { + M4_LOG(GF_LOG_ERROR, ("Unrecognized syntax for par=, expecting N:D got %s\n", ext+5)); + e = GF_BAD_PARAM; + goto exit; + } + } else { + if (sscanf(ext+5, "%d:%d", &par_n, &par_d) != 2) { + M4_LOG(GF_LOG_ERROR, ("Unrecognized syntax for par=, expecting N:D got %s\n", ext+5)); + e = GF_BAD_PARAM; + goto exit; + } + } + } + } + else if (!strnicmp(ext+1, "clap=", 5)) { + if (!stricmp(ext+6, "none")) { + has_clap=GF_TRUE; + } else { + if (sscanf(ext+6, "%d,%d,%d,%d,%d,%d,%d,%d", &clap_wn, &clap_wd, &clap_hn, &clap_hd, &clap_hon, &clap_hod, &clap_von, &clap_vod)==8) { + has_clap=GF_TRUE; + } + } + } + else if (!strnicmp(ext+1, "mx=", 3)) { + if (strstr(ext+4, "0x")) { + if (sscanf(ext+4, "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%d", &mx[0], &mx[1], &mx[2], &mx[3], &mx[4], &mx[5], &mx[6], &mx[7], &mx[8])==9) { + has_mx=GF_TRUE; + } + } else if (sscanf(ext+4, "%d,%d,%d,%d,%d,%d,%d,%d,%d", &mx[0], &mx[1], &mx[2], &mx[3], &mx[4], &mx[5], &mx[6], &mx[7], &mx[8])==9) { + has_mx=GF_TRUE; + } + } + else if (!strnicmp(ext+1, "name=", 5)) { + handler_name = gf_strdup(ext+6); + } + else if (!strnicmp(ext+1, "ext=", 4)) { + CHECK_FAKEIMPORT("ext") + /*extensions begin with '.'*/ + if (*(ext+5) == '.') + import.force_ext = gf_strdup(ext+5); + else { + import.force_ext = gf_calloc(1+strlen(ext+5)+1, 1); + import.force_ext[0] = '.'; + strcat(import.force_ext+1, ext+5); + } + } + else if (!strnicmp(ext+1, "hdlr=", 5)) handler = GF_4CC(ext[6], ext[7], ext[8], ext[9]); + else if (!strnicmp(ext+1, "stype=", 6)) stype = GF_4CC(ext[7], ext[8], ext[9], ext[10]); + else if (!strnicmp(ext+1, "tkhd", 4)) { + char *flags = ext+6; + if (flags[0]=='+') { track_flags_mode = GF_ISOM_TKFLAGS_ADD; flags += 1; } + else if (flags[0]=='-') { track_flags_mode = GF_ISOM_TKFLAGS_REM; flags += 1; } + else track_flags_mode = GF_ISOM_TKFLAGS_SET; + + if (strstr(flags, "enable")) track_flags |= GF_ISOM_TK_ENABLED; + if (strstr(flags, "movie")) track_flags |= GF_ISOM_TK_IN_MOVIE; + if (strstr(flags, "preview")) track_flags |= GF_ISOM_TK_IN_PREVIEW; + if (strstr(flags, "size_ar")) track_flags |= GF_ISOM_TK_SIZE_IS_AR; + if (!track_flags) { + if (!strnicmp(flags, "0x", 2)) flags += 2; + sscanf(flags, "%X", &track_flags); + } + } else if (!strnicmp(ext+1, "disable", 7)) { + do_disable = !stricmp(ext+1, "disable=no") ? 2 : 1; + } + else if (!strnicmp(ext+1, "group=", 6)) { + group = atoi(ext+7); + if (!group) group = gf_isom_get_next_alternate_group_id(dest); + } + else if (!strnicmp(ext+1, "fps=", 4)) { + u32 ticks, dts_inc; + CHECK_FAKEIMPORT("fps") + if (!strcmp(ext+5, "auto")) { + M4_LOG(GF_LOG_ERROR, ("Warning, fps=auto option is deprecated\n")); + } else if ((sscanf(ext+5, "%u-%u", &ticks, &dts_inc) == 2) || (sscanf(ext+5, "%u/%u", &ticks, &dts_inc) == 2)) { + if (!dts_inc) dts_inc=1; + force_fps.num = ticks; + force_fps.den = dts_inc; + } else { + if (gf_sys_old_arch_compat()) { + force_fps.den = 1000; + force_fps.num = (u32) (atof(ext+5) * force_fps.den); + } else { + gf_parse_frac(ext+5, &force_fps); + } + } + } + else if (!stricmp(ext+1, "rap")) rap_only = 1; + else if (!stricmp(ext+1, "refs")) refs_only = 1; + else if (!stricmp(ext+1, "trailing")) { CHECK_FAKEIMPORT("trailing") import_flags |= GF_IMPORT_KEEP_TRAILING; } + else if (!strnicmp(ext+1, "agg=", 4)) { CHECK_FAKEIMPORT("agg") frames_per_sample = atoi(ext+5); } + else if (!stricmp(ext+1, "dref")) { CHECK_FAKEIMPORT("dref") import_flags |= GF_IMPORT_USE_DATAREF; } + else if (!stricmp(ext+1, "keep_refs")) { CHECK_FAKEIMPORT("keep_refs") import_flags |= GF_IMPORT_KEEP_REFS; } + else if (!stricmp(ext+1, "nodrop")) { CHECK_FAKEIMPORT("nodrop") import_flags |= GF_IMPORT_NO_FRAME_DROP; } + else if (!stricmp(ext+1, "packed")) { CHECK_FAKEIMPORT("packed") import_flags |= GF_IMPORT_FORCE_PACKED; } + else if (!stricmp(ext+1, "sbr")) { CHECK_FAKEIMPORT("sbr") import_flags |= GF_IMPORT_SBR_IMPLICIT; } + else if (!stricmp(ext+1, "sbrx")) { CHECK_FAKEIMPORT("sbrx") import_flags |= GF_IMPORT_SBR_EXPLICIT; } + else if (!stricmp(ext+1, "ovsbr")) { CHECK_FAKEIMPORT("ovsbr") import_flags |= GF_IMPORT_OVSBR; } + else if (!stricmp(ext+1, "ps")) { CHECK_FAKEIMPORT("ps") import_flags |= GF_IMPORT_PS_IMPLICIT; } + else if (!stricmp(ext+1, "psx")) { CHECK_FAKEIMPORT("psx") import_flags |= GF_IMPORT_PS_EXPLICIT; } + else if (!stricmp(ext+1, "mpeg4")) { CHECK_FAKEIMPORT("mpeg4") import_flags |= GF_IMPORT_FORCE_MPEG4; } + 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; } + + /*split SVC layers*/ + else if (!strnicmp(ext+1, "svcmode=", 8) || !strnicmp(ext+1, "lhvcmode=", 9)) { + char *mode = ext+9; + CHECK_FAKEIMPORT_2("svcmode/lhvcmode") + if (mode[0]=='=') mode = ext+10; + + if (!stricmp(mode, "splitnox")) + svc_mode = 3; + else if (!stricmp(mode, "splitnoxib")) + svc_mode = 4; + else if (!stricmp(mode, "splitall") || !stricmp(mode, "split")) + svc_mode = 2; + else if (!stricmp(mode, "splitbase")) + svc_mode = 1; + else if (!stricmp(mode, "merged") || !stricmp(mode, "merge")) + svc_mode = 0; + } + /*split SHVC temporal sublayers*/ + else if (!strnicmp(ext+1, "temporal=", 9)) { + char *mode = ext+10; + CHECK_FAKEIMPORT_2("svcmode/lhvcmode") + if (!stricmp(mode, "split")) + temporal_mode = 2; + else if (!stricmp(mode, "splitnox")) + temporal_mode = 3; + else if (!stricmp(mode, "splitbase")) + temporal_mode = 1; + else { + M4_LOG(GF_LOG_ERROR, ("Unrecognized temporal mode %s, ignoring\n", mode)); + temporal_mode = 0; + } + } + 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, "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; } + else if (!stricmp(ext+1, "au_delim")) { CHECK_FAKEIMPORT("au_delim") keep_audelim = GF_TRUE; } + else if (!strnicmp(ext+1, "max_lid=", 8) || !strnicmp(ext+1, "max_tid=", 8)) { + s32 val = atoi(ext+9); + CHECK_FAKEIMPORT_2("max_lid/lhvcmode") + if (val < 0) { + M4_LOG(GF_LOG_ERROR, ("Warning: request max layer/temporal id is negative - ignoring\n")); + } else { + if (!strnicmp(ext+1, "max_lid=", 8)) + max_layer_id_plus_one = 1 + (u8) val; + else + max_temporal_id_plus_one = 1 + (u8) val; + } + } + else if (!stricmp(ext+1, "tiles")) { CHECK_FAKEIMPORT_2("tiles") split_tile_mode = 2; } + else if (!stricmp(ext+1, "tiles_rle")) { CHECK_FAKEIMPORT_2("tiles_rle") split_tile_mode = 3; } + else if (!stricmp(ext+1, "split_tiles")) { CHECK_FAKEIMPORT_2("split_tiles") split_tile_mode = 1; } + + /*force all composition offsets to be positive*/ + else if (!strnicmp(ext+1, "negctts", 7)) { + neg_ctts_mode = !strnicmp(ext+1, "negctts=no", 10) ? 2 : 1; + } + else if (!stricmp(ext+1, "chap")) is_chap = 1; + else if (!strnicmp(ext+1, "chapter=", 8)) { + chapter_name = gf_strdup(ext+9); + } + else if (!strnicmp(ext+1, "chapfile=", 9)) { + chapter_name = gf_strdup(ext+10); + is_chap_file=1; + } + else if (!strnicmp(ext+1, "layout=", 7)) { + track_layout = 1; + if ( sscanf(ext+13, "%dx%dx%dx%dx%d", &tw, &th, &tx, &ty, &tz)==5) { + } else if ( sscanf(ext+13, "%dx%dx%dx%d", &tw, &th, &tx, &ty)==4) { + tz = 0; + } else if ( sscanf(ext+13, "%dx%dx%d", &tw, &th, &tz)==3) { + tx = ty = 0; + } else if ( sscanf(ext+8, "%dx%d", &tw, &th)==2) { + tx = ty = tz = 0; + } + } + + else if (!strnicmp(ext+1, "rescale=", 8)) { + if (sscanf(ext+9, "%u/%u", &rescale_num, &rescale_den) != 2) { + rescale_num = atoi(ext+9); + rescale_den = 0; + } + } + else if (!strnicmp(ext+1, "sampdur=", 8)) { + if (sscanf(ext+9, "%u/%u", &rescale_den, &rescale_num) != 2) { + rescale_den = atoi(ext+9); + rescale_num = 0; + } + rescale_override = GF_TRUE; + } + else if (!strnicmp(ext+1, "timescale=", 10)) { + new_timescale = atoi(ext+11); + } + else if (!strnicmp(ext+1, "moovts=", 7)) { + moov_timescale = atoi(ext+8); + } + + else if (!stricmp(ext+1, "noedit")) { import_flags |= GF_IMPORT_NO_EDIT_LIST; } + + + else if (!strnicmp(ext+1, "rvc=", 4)) { + if (sscanf(ext+5, "%d", &rvc_predefined) != 1) { + rvc_config = 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; + else if (!stricmp(ext+9, "high")) profile = 100; + else if (!stricmp(ext+9, "extended")) profile = 88; + else if (!stricmp(ext+9, "main")) profile = 77; + else if (!stricmp(ext+9, "baseline")) profile = 66; + else profile = atoi(ext+9); + } + else if (!strnicmp(ext+1, "level=", 6)) { + if( atof(ext+7) < 6 ) + level = (int)(10*atof(ext+7)+.5); + else + level = atoi(ext+7); + } + else if (!strnicmp(ext+1, "compat=", 7)) { + compat = atoi(ext+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 = atoi(ext+6); } + else if (!strnicmp(ext+1, "text_layout=", 12)) { + if ( sscanf(ext+13, "%dx%dx%dx%d", &txtw, &txth, &txtx, &txty)==4) { + text_layout = 1; + } else if ( sscanf(ext+8, "%dx%d", &txtw, &txth)==2) { + track_layout = 1; + txtx = txty = 0; + } + } + +#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); } +#endif + + else if (!strnicmp(ext+1, "kind=", 5)) { + char *kind_scheme, *kind_value; + char *kind_data = ext+6; + char *sep = strchr(kind_data, '='); + if (sep) { + *sep = 0; + } + kind_scheme = gf_strdup(kind_data); + if (sep) { + *sep = '='; + kind_value = gf_strdup(sep+1); + } else { + kind_value = NULL; + } + gf_list_add(kinds, kind_scheme); + gf_list_add(kinds, kind_value); + } + else if (!strnicmp(ext+1, "txtflags", 8)) { + if (!strnicmp(ext+1, "txtflags=", 9)) { + sscanf(ext+10, "%x", &txt_flags); + } + else if (!strnicmp(ext+1, "txtflags+=", 10)) { + sscanf(ext+11, "%x", &txt_flags); + txt_mode = GF_ISOM_TEXT_FLAGS_TOGGLE; + } + else if (!strnicmp(ext+1, "txtflags-=", 10)) { + sscanf(ext+11, "%x", &txt_flags); + txt_mode = GF_ISOM_TEXT_FLAGS_UNTOGGLE; + } + } + else if (!strnicmp(ext+1, "rate=", 5)) { + force_rate = atoi(ext+6); + } + else if (!stricmp(ext+1, "stats") || !stricmp(ext+1, "fstat")) + print_stats_graph |= 1; + else if (!stricmp(ext+1, "graph") || !stricmp(ext+1, "graph")) + print_stats_graph |= 2; + else if (!strncmp(ext+1, "sopt", 4) || !strncmp(ext+1, "dopt", 4) || !strncmp(ext+1, "@", 1)) { + if (ext2) ext2[0] = ':'; + opt_src = strstr(ext, ":sopt:"); + opt_dst = strstr(ext, ":dopt:"); + fchain = strstr(ext, ":@"); + if (opt_src) opt_src[0] = 0; + if (opt_dst) opt_dst[0] = 0; + if (fchain) fchain[0] = 0; + + 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 (fchain[2]=='@') { + 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; + } + } + + ext = NULL; + break; + } + + 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; + else if (!stricmp(mode, "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; + else if (!stricmp(mode, "v1-qt")) + import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF; + else + M4_LOG(GF_LOG_ERROR, ("Unrecognized audio sample entry mode %s, ignoring\n", mode)); + } + else if (!strnicmp(ext+1, "audio_roll=", 11)) { roll_change = 3; roll = atoi(ext+12); } + else if (!strnicmp(ext+1, "roll=", 5)) { roll_change = 1; roll = atoi(ext+6); } + else if (!strnicmp(ext+1, "proll=", 6)) { roll_change = 2; roll = atoi(ext+7); } + else if (!strcmp(ext+1, "stz2")) { + use_stz2 = GF_TRUE; + } else if (!strnicmp(ext+1, "bitdepth=", 9)) { + bitdepth=atoi(ext+10); + } + else if (!strnicmp(ext+1, "hdr=", 4)) { + hdr_file = gf_strdup(ext+5); + } + else if (!strnicmp(ext+1, "colr=", 5)) { + char *cval = ext+6; + if (!strcmp(cval, "none")) { + clr_type = (u32) -1; + } else if (strlen(cval)<6) { + fmt_ok = GF_FALSE; + } else { + clr_type = GF_4CC(cval[0],cval[1],cval[2],cval[3]); + cval+=4; + if (cval[0] != ',') { + fmt_ok = GF_FALSE; + } + else if ((clr_type==GF_ISOM_SUBTYPE_NCLX) || (clr_type==GF_ISOM_SUBTYPE_NCLC)) { + fmt_ok = scan_color(cval+1, &clr_prim, &clr_tranf, &clr_mx, &clr_full_range); + } + else if ((clr_type==GF_ISOM_SUBTYPE_RICC) || (clr_type==GF_ISOM_SUBTYPE_PROF)) { + FILE *f = gf_fopen(cval+1, "rb"); + if (!f) { + M4_LOG(GF_LOG_ERROR, ("Failed to open file %s\n", cval+1)); + e = GF_BAD_PARAM; + goto exit; + } else { + gf_fseek(f, 0, SEEK_END); + icc_size = (u32) gf_ftell(f); + icc_data = gf_malloc(sizeof(char)*icc_size); + gf_fseek(f, 0, SEEK_SET); + icc_size = (u32) gf_fread(icc_data, icc_size, f); + gf_fclose(f); + } + } else { + M4_LOG(GF_LOG_ERROR, ("Unrecognized colr profile %s\n", gf_4cc_to_str(clr_type) )); + e = GF_BAD_PARAM; + goto exit; + } + } + if (!fmt_ok) { + e = GF_BAD_PARAM; + GOTO_EXIT("parsing colr option"); + } + } + else if (!strnicmp(ext + 1, "dv-profile=", 11)) { + strncpy(dv_profile, ext + 12, 99); + dv_profile[99]=0; + } + else if (!strnicmp(ext+1, "fullrange=", 10)) { + if (!stricmp(ext+11, "off") || !stricmp(ext+11, "no")) fullrange = 0; + else if (!stricmp(ext+11, "on") || !stricmp(ext+11, "yes")) fullrange = 1; + else { + e = GF_BAD_PARAM; + GOTO_EXIT("invalid format for fullrange") + } + } + else if (!strnicmp(ext+1, "videofmt=", 10)) { + u32 idx, count = GF_ARRAY_LENGTH(videofmt_names); + for (idx=0; idx(u32)th) ? h-th : 0; + 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; + } + + 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 moov timescale is <0 (auto mode) set it at import time + if (moov_timescale<0) { + import.moov_timescale = moov_timescale; + } + //otherwise force it now + else if (moov_timescale>0) { + e = gf_isom_set_timescale(dest, moov_timescale); + GOTO_EXIT("changing timescale") + } + + import.run_in_session = fsess; + import.update_mux_args = NULL; + if (do_all) + import.flags |= GF_IMPORT_KEEP_REFS; + + e = gf_media_import(&import); + if (e) { + 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; + goto exit; + } + } + + nb_tracks = gf_isom_get_track_count(dest); + for (i=0; i>=32; + keep_handler = (tk_source_magic & 1) ? GF_TRUE : GF_FALSE; + } else { + keep_handler = GF_TRUE; + + if (do_audio && (media_type!=GF_ISOM_MEDIA_AUDIO)) continue; + if (do_video && (media_type!=GF_ISOM_MEDIA_VISUAL)) continue; + if (do_auxv && (media_type!=GF_ISOM_MEDIA_AUXV)) continue; + if (do_pict && (media_type!=GF_ISOM_MEDIA_PICT)) continue; + if (track_id && (gf_isom_get_track_id(dest, track) != track_id)) + continue; + } + + timescale = gf_isom_get_timescale(dest); + if (szLan) { + e = gf_isom_set_media_language(dest, track, (char *) szLan); + GOTO_EXIT("changing language") + } + if (do_disable) { + e = gf_isom_set_track_enabled(dest, track, (do_disable==2) ? GF_TRUE : GF_FALSE); + GOTO_EXIT("disabling track") + } + if (track_flags_mode) { + e = gf_isom_set_track_flags(dest, track, track_flags, track_flags_mode); + GOTO_EXIT("disabling track") + } + + if (import_flags & GF_IMPORT_NO_EDIT_LIST) { + e = gf_isom_remove_edits(dest, track); + GOTO_EXIT("removing edits") + } + if (delay.num && delay.den) { + u64 tk_dur; + e = gf_isom_remove_edits(dest, track); + tk_dur = gf_isom_get_track_duration(dest, track); + if (delay.num>0) { + //cast to s64, timescale*delay could be quite large before /1000 + e |= gf_isom_append_edit(dest, track, ((s64) delay.num) * timescale / delay.den, 0, GF_ISOM_EDIT_EMPTY); + e |= gf_isom_append_edit(dest, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); + } else { + //cast to s64, timescale*delay could be quite large before /1000 + u64 to_skip = ((s64) -delay.num) * timescale / delay.den; + if (to_skip=0) && (par_d>=0)) || force_par) { + e = gf_media_change_par(dest, track, par_n, par_d, force_par, rewrite_bs); + GOTO_EXIT("changing PAR") + } + if ((fullrange>=0) || (videofmt>=0) || (colorprim>=0) || (colortfc>=0) || (colormx>=0)) { + e = gf_media_change_color(dest, i+1, fullrange, videofmt, colorprim, colortfc, colormx); + GOTO_EXIT("changing color in bitstream") + } + if (has_clap) { + e = gf_isom_set_clean_aperture(dest, track, 1, clap_wn, clap_wd, clap_hn, clap_hd, clap_hon, clap_hod, clap_von, clap_vod); + GOTO_EXIT("changing clean aperture") + } + if (bitdepth) { + e = gf_isom_set_visual_bit_depth(dest, track, 1, bitdepth); + GOTO_EXIT("changing bit depth") + } + if (clr_type) { + if (clr_type==(u32)-1) + clr_type = 0; + + e = gf_isom_set_visual_color_info(dest, track, 1, clr_type, clr_prim, clr_tranf, clr_mx, clr_full_range, icc_data, icc_size); + GOTO_EXIT("changing color info") + } + if (hdr_file) { + e = parse_high_dynamc_range_xml_desc(dest, track, hdr_file); + GOTO_EXIT("setting HDR info") + } + if (dv_profile[0]) { + e = set_dv_profile(dest, track, dv_profile); + GOTO_EXIT("setting DV profile") + } + + if (set_ccst) { + e = gf_isom_set_image_sequence_coding_constraints(dest, track, 1, GF_FALSE, GF_FALSE, GF_TRUE, 15); + GOTO_EXIT("setting image sequence constraints") + } + } + if (has_mx) { + e = gf_isom_set_track_matrix(dest, track, mx); + GOTO_EXIT("setting track matrix") + } + if (use_stz2) { + e = gf_isom_use_compact_size(dest, track, GF_TRUE); + GOTO_EXIT("setting compact size") + } + + if (gf_isom_get_media_subtype(dest, track, 1) == GF_ISOM_MEDIA_TIMECODE) { + tmcd_track = track; + } + if (rap_only || refs_only) { + e = gf_media_remove_non_rap(dest, track, refs_only); + GOTO_EXIT("removing non RAPs") + } + if (handler_name) { + e = gf_isom_set_handler_name(dest, track, handler_name); + GOTO_EXIT("setting handler name") + } + else if (!keep_handler) { + char szHName[1024]; + const char *fName = gf_url_get_resource_name((const char *)inName); + fName = strchr(fName, '.'); + if (fName) fName += 1; + else fName = "?"; + + sprintf(szHName, "%s@GPAC%s", fName, gf_gpac_version()); + e = gf_isom_set_handler_name(dest, track, szHName); + GOTO_EXIT("setting handler name") + } + if (handler) { + e = gf_isom_set_media_type(dest, track, handler); + GOTO_EXIT("setting media type") + } + if (group) { + e = gf_isom_set_alternate_group_id(dest, track, group); + GOTO_EXIT("setting alternate group") + } + + if (track_layout) { + e = gf_isom_set_track_layout_info(dest, track, tw<<16, th<<16, tx<<16, ty<<16, tz); + GOTO_EXIT("setting track layout") + } + if (stype) { + e = gf_isom_set_media_subtype(dest, track, 1, stype); + GOTO_EXIT("setting media subtype") + } + if (is_chap && chap_ref) { + e = set_chapter_track(dest, track, chap_ref); + GOTO_EXIT("setting chapter track") + } + + for (j = 0; j < gf_list_count(kinds); j+=2) { + char *kind_scheme = (char *)gf_list_get(kinds, j); + char *kind_value = (char *)gf_list_get(kinds, j+1); + e = gf_isom_add_track_kind(dest, i+1, kind_scheme, kind_value); + GOTO_EXIT("setting track kind") + } + + if (profile || compat || level) { + e = gf_media_change_pl(dest, track, profile, compat, level); + GOTO_EXIT("changing video PL") + } + if (gf_isom_get_mpeg4_subtype(dest, track, 1)) + keep_sys_tracks = 1; + + //if moov timescale is <0 (auto mode) set it at import time + if (fake_import) { + if (import_flags & GF_IMPORT_NO_EDIT_LIST) + gf_isom_remove_edits(dest, track); + + if (moov_timescale<0) { + moov_timescale = gf_isom_get_media_timescale(dest, track); + } + if (moov_timescale>0) { + e = gf_isom_set_timescale(dest, moov_timescale); + GOTO_EXIT("changing timescale") + } + + 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); + } + } + + if (roll_change) { + if ((roll_change!=3) || (media_type==GF_ISOM_MEDIA_AUDIO)) { + e = gf_isom_set_sample_roll_group(dest, track, (u32) -1, (roll_change==2) ? GF_ISOM_SAMPLE_PREROLL : GF_ISOM_SAMPLE_ROLL, roll); + GOTO_EXIT("assigning roll") + } + } + + if (new_timescale>1) { + e = gf_isom_set_media_timescale(dest, track, new_timescale, 0, 0); + GOTO_EXIT("setting media timescale") + } + + if (rescale_num > 1) { + switch (gf_isom_get_media_type(dest, track)) { + case GF_ISOM_MEDIA_AUDIO: + if (!rescale_override) { + M4_LOG(GF_LOG_WARNING, ("Cannot force media timescale for audio media types - ignoring\n")); + break; + } + default: + e = gf_isom_set_media_timescale(dest, track, rescale_num, rescale_den, rescale_override ? 2 : 1); + if (e==GF_EOS) { + M4_LOG(GF_LOG_WARNING, ("Rescale ignored, same config in source file\n")); + e = GF_OK; + } + GOTO_EXIT("rescaling media track") + break; + } + } + + if (has_last_sample_dur) { + e = gf_isom_set_last_sample_duration_ex(dest, track, last_sample_dur.num, last_sample_dur.den); + GOTO_EXIT("setting last sample duration") + } + if (rvc_config) { +#ifdef GPAC_DISABLE_ZLIB + M4_LOG(GF_LOG_ERROR, ("Error: no zlib support - RVC not available\n")); + e = GF_NOT_SUPPORTED; + goto exit; +#else + u8 *data; + u32 size; + e = gf_file_load_data(rvc_config, (u8 **) &data, &size); + GOTO_EXIT("loading RVC config file") + + gf_gz_compress_payload(&data, size, &size); + e |= gf_isom_set_rvc_config(dest, track, 1, 0, "application/rvc-config+xml+gz", data, size); + gf_free(data); + GOTO_EXIT("compressing and assigning RVC config") +#endif + } else if (rvc_predefined>0) { + e = gf_isom_set_rvc_config(dest, track, 1, rvc_predefined, NULL, NULL, 0); + GOTO_EXIT("setting RVC predefined config") + } + + if (neg_ctts_mode) { + e = gf_isom_set_composition_offset_mode(dest, track, (neg_ctts_mode==1) ? GF_TRUE : GF_FALSE); + GOTO_EXIT("setting composition offset mode") + } + + if (gf_isom_get_avc_svc_type(dest, track, 1)>=GF_ISOM_AVCTYPE_AVC_SVC) + check_track_for_svc = track; + + switch (gf_isom_get_hevc_lhvc_type(dest, track, 1)) { + case GF_ISOM_HEVCTYPE_HEVC_LHVC: + case GF_ISOM_HEVCTYPE_LHVC_ONLY: + check_track_for_lhvc = i+1; + break; + case GF_ISOM_HEVCTYPE_HEVC_ONLY: + check_track_for_hevc=1; + break; + default: + break; + } + + if (txt_flags) { + e = gf_isom_text_set_display_flags(dest, track, 0, txt_flags, txt_mode); + GOTO_EXIT("setting text track display flags") + } + + if (edits) { + e = apply_edits(dest, track, edits); + GOTO_EXIT("applying edits") + } + + if (force_rate>=0) { + e = gf_isom_update_bitrate(dest, i+1, 1, force_rate, force_rate, 0); + GOTO_EXIT("updating bitrate") + } + + if (split_tile_mode) { + switch (gf_isom_get_media_subtype(dest, track, 1)) { + case GF_ISOM_SUBTYPE_HVC1: + case GF_ISOM_SUBTYPE_HEV1: + case GF_ISOM_SUBTYPE_HVC2: + case GF_ISOM_SUBTYPE_HEV2: + break; + default: + split_tile_mode = 0; + break; + } + } + } + + if (chapter_name) { + if (is_chap_file) { + GF_Fraction a_fps = {0,0}; + e = gf_media_import_chapters(dest, chapter_name, a_fps, GF_FALSE); + } else { + e = gf_isom_add_chapter(dest, 0, 0, chapter_name); + } + GOTO_EXIT("importing chapters") + } + + if (tmcd_track) { + u32 tmcd_id = gf_isom_get_track_id(dest, tmcd_track); + for (i=0; i < gf_isom_get_track_count(dest); i++) { + switch (gf_isom_get_media_type(dest, i+1)) { + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: + break; + default: + continue; + } + e = gf_isom_set_track_reference(dest, i+1, GF_ISOM_REF_TMCD, tmcd_id); + GOTO_EXIT("assigning TMCD track references") + } + } + + /*force to rewrite all dependencies*/ + for (i = 1; i <= gf_isom_get_track_count(dest); i++) + { + e = gf_isom_rewrite_track_dependencies(dest, i); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Warning: track ID %d has references to a track not imported\n", gf_isom_get_track_id(dest, i) )); + e = GF_OK; + } + } + +#ifndef GPAC_DISABLE_AV_PARSERS + if (max_layer_id_plus_one || max_temporal_id_plus_one) { + for (i = 1; i <= gf_isom_get_track_count(dest); i++) + { + e = gf_media_filter_hevc(dest, i, max_temporal_id_plus_one, max_layer_id_plus_one); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Warning: track ID %d: error while filtering LHVC layers\n", gf_isom_get_track_id(dest, i))); + e = GF_OK; + } + } + } +#endif + + if (check_track_for_svc) { + if (svc_mode) { + e = gf_media_split_svc(dest, check_track_for_svc, (svc_mode==2) ? 1 : 0); + GOTO_EXIT("splitting SVC track") + } else { + e = gf_media_merge_svc(dest, check_track_for_svc, 1); + GOTO_EXIT("merging SVC/SHVC track") + } + } +#ifndef GPAC_DISABLE_AV_PARSERS + if (check_track_for_lhvc) { + if (svc_mode) { + GF_LHVCExtractoreMode xmode = GF_LHVC_EXTRACTORS_ON; + if (svc_mode==3) xmode = GF_LHVC_EXTRACTORS_OFF; + else if (svc_mode==4) xmode = GF_LHVC_EXTRACTORS_OFF_FORCE_INBAND; + e = gf_media_split_lhvc(dest, check_track_for_lhvc, GF_FALSE, (svc_mode==1) ? 0 : 1, xmode ); + GOTO_EXIT("splitting L-HEVC track") + } else { + //TODO - merge, temporal sublayers + } + } +#ifndef GPAC_DISABLE_HEVC + if (check_track_for_hevc) { + if (split_tile_mode) { + e = gf_media_split_hevc_tiles(dest, split_tile_mode - 1); + GOTO_EXIT("splitting HEVC tiles") + } + if (temporal_mode) { + GF_LHVCExtractoreMode xmode = (temporal_mode==3) ? GF_LHVC_EXTRACTORS_OFF : GF_LHVC_EXTRACTORS_ON; + e = gf_media_split_lhvc(dest, check_track_for_hevc, GF_TRUE, (temporal_mode==1) ? GF_FALSE : GF_TRUE, xmode ); + GOTO_EXIT("splitting HEVC temporal sublayers") + } + } +#endif + + if (tc_fps_num) { + u32 desc_index=0; + u32 tmcd_tk, tmcd_id; + u32 video_ref = 0; + GF_BitStream *bs; + GF_ISOSample *samp; + for (i=0; iIsRAP = SAP_TYPE_1; + gf_bs_get_content(bs, &samp->data, &samp->dataLength); + gf_bs_del(bs); + e = gf_isom_add_sample(dest, tmcd_tk, desc_index, samp); + gf_isom_sample_del(&samp); + GOTO_EXIT("assigning TMCD sample") + + if (video_ref) { + u64 video_ref_dur = gf_isom_get_media_duration(dest, video_ref); + video_ref_dur *= tc_fps_num; + video_ref_dur /= gf_isom_get_media_timescale(dest, video_ref); + e = gf_isom_set_last_sample_duration(dest, tmcd_tk, (u32) video_ref_dur); + } else { + e = gf_isom_set_last_sample_duration(dest, tmcd_tk, tc_fps_den ? tc_fps_den : 1); + } + GOTO_EXIT("setting TMCD sample dur") + } + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +exit: + while (gf_list_count(kinds)) { + char *kind = (char *)gf_list_get(kinds, 0); + gf_list_rem(kinds, 0); + if (kind) gf_free(kind); + } + if (opt_src) opt_src[0] = ':'; + if (opt_dst) opt_dst[0] = ':'; + if (fchain) fchain[0] = ':'; + if (hdr_file) gf_free(hdr_file); + + 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 (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 (!e) return GF_OK; + if (fail_msg) { + M4_LOG(GF_LOG_ERROR, ("Failure while %s: %s\n", fail_msg, gf_error_to_string(e) )); + } + return e; +} + +typedef struct +{ + Double progress; + u32 file_idx; +} SplitInfo; + +static Bool on_split_event(void *_udta, GF_Event *evt) +{ + Double progress; + SplitInfo *sinfo = (SplitInfo *)_udta; + if (!_udta) return GF_FALSE; + if (evt->type != GF_EVENT_PROGRESS) return GF_FALSE; + if (!evt->progress.total) return GF_FALSE; + + progress = (Double) (100*evt->progress.done) / evt->progress.total; + if (progress <= sinfo->progress) + return GF_FALSE; + + if (evt->progress.done == evt->progress.total) { + if (sinfo->progress <= 0) + return GF_FALSE; +#ifndef GPAC_DISABLE_LOG + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("splitting: file %d done\n", sinfo->file_idx)); +#else + fprintf(stderr, "splitting: file %d done\n", sinfo->file_idx); +#endif + sinfo->file_idx++; + sinfo->progress = -1; + } else { + sinfo->progress = progress; +#ifndef GPAC_DISABLE_LOG + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("splitting: % 2.2f %%\r", progress)); +#else + fprintf(stderr, "splitting: % 2.2f %%\r", progress); +#endif + } + return GF_FALSE; +} + +extern u32 do_flat; +extern Bool do_frag; +extern Double interleaving_time; + +GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double InterleavingTime, Double chunk_start_time, u32 adjust_split_end, char *outName, Bool force_rap_split, const char *split_range_str, u32 fs_dump_flags) +{ + Bool chunk_extraction, rap_split, split_until_end; + GF_Err e; + char *ext, szName[GF_MAX_PATH], szFile[GF_MAX_PATH+100], szArgs[100]; + Double chunk_start = (Double) chunk_start_time; + char *filter_args = NULL; + GF_FilterSession *fs; + GF_Filter *src, *reframe, *dst; + SplitInfo sinfo; + + sinfo.progress = -1; + sinfo.file_idx = 1; + + chunk_extraction = (chunk_start>=0) ? GF_TRUE : GF_FALSE; + if (split_range_str) + chunk_extraction = GF_TRUE; + split_until_end = GF_FALSE; + rap_split = GF_FALSE; + if (split_size_kb == (u64)-1) rap_split = GF_TRUE; + if (split_dur == -1) rap_split = GF_TRUE; + else if (split_dur <= -2) { + split_size_kb = 0; + split_until_end = GF_TRUE; + } + else if (force_rap_split) + rap_split = GF_TRUE; + + //split in same dir as source + strcpy(szName, inName); + ext = strrchr(szName, '.'); + if (ext) ext[0] = 0; + + fs = gf_fs_new_defaults(0); + if (!fs) { + M4_LOG(GF_LOG_ERROR, ("Failed to load filter session, aborting\n")); + return GF_IO_ERR; + } + + //load source with all tracks processing + sprintf(szArgs, "mp4dmx:mov=%p:alltk", mp4); + src = gf_fs_load_filter(fs, szArgs, &e); + + if (!src) { + M4_LOG(GF_LOG_ERROR, ("Failed to load source filter: %s\n", gf_error_to_string(e) )); + gf_fs_del(fs); + return e; + } + //default output name formatting + if (!outName) { + strcpy(szFile, szName); + strcat(szFile, "_$num%03d$"); + } + + gf_dynstrcat(&filter_args, "reframer:splitrange", NULL); + if (rap_split) { + gf_dynstrcat(&filter_args, ":xs=SAP", NULL); + } else if (split_size_kb) { + sprintf(szArgs, ":xs=S"LLU"k", split_size_kb); + gf_dynstrcat(&filter_args, szArgs, NULL); + } else if (chunk_extraction) { + //we adjust end: start at the iframe at or after requested time and use xadjust (move end to next I-frame) + //so that two calls with X:Y and Y:Z have the same Y boundary + if (adjust_split_end==1) { + gf_dynstrcat(&filter_args, ":xadjust:xround=after", NULL); + } else if (adjust_split_end==2) { + gf_dynstrcat(&filter_args, ":xadjust:xround=before", NULL); + } else if (adjust_split_end==3) { + gf_dynstrcat(&filter_args, ":xround=seek", NULL); + } else if (!gf_sys_find_global_arg("xround")) { + gf_dynstrcat(&filter_args, ":xround=closest", NULL); + } + if (!adjust_split_end || (adjust_split_end==3)) { + if (!gf_sys_find_global_arg("probe_ref")) { + gf_dynstrcat(&filter_args, ":probe_ref", NULL); + } + } + + if (split_range_str) { + Bool is_time = GF_FALSE; + char c; + //S-E syntax + char *end = (char *) strchr(split_range_str, '-'); + if (!end) { + //S:E syntax + end = (char *) strchr(split_range_str, ':'); + //if another `:` assume time format + if (end && strchr(end+1, ':')) + is_time = GF_TRUE; + } else if (strchr(split_range_str, ':')) { + is_time = GF_TRUE; + } + if (!end) { + gf_free(filter_args); + M4_LOG(GF_LOG_ERROR, ("Invalid range specifer %s, expecting START-END or START:END\n", split_range_str )); + gf_fs_del(fs); + return GF_BAD_PARAM; + } + + c = end[0]; + end[0] = 0; + if (is_time) { + sprintf(szArgs, ":xs=T%s:xe=T%s", split_range_str, end+1); + } else { + sprintf(szArgs, ":xs=%s:xe=%s", split_range_str, end+1); + } + end[0] = c; + } else if (split_until_end) { + Double end=0; + if (split_dur<-2) { + end = (Double) gf_isom_get_duration(mp4); + end /= gf_isom_get_timescale(mp4); + split_dur = -2 - split_dur; + if (end > split_dur) end-=split_dur; + else end = 0; + } + if (end>0) { + sprintf(szArgs, ":xs=%u/1000:xe=%u/1000", (u32) (chunk_start*1000), (u32) (end * 1000) ); + } else { + sprintf(szArgs, ":xs=%u/1000", (u32) (chunk_start*1000)); + } + } else { + sprintf(szArgs, ":xs=%u/1000:xe=%u/1000", (u32) (chunk_start*1000), (u32) ((chunk_start+split_dur) * 1000) ); + } + gf_dynstrcat(&filter_args, szArgs, NULL); + if (!outName) { + sprintf(szFile, "%s_$FS$", szName); + } + } else if (split_dur) { + sprintf(szArgs, ":xs=D%u", (u32) (split_dur*1000)); + gf_dynstrcat(&filter_args, szArgs, NULL); + if (adjust_split_end) { + gf_dynstrcat(&filter_args, ":xadjust", NULL); + } + } else { + gf_fs_del(fs); + gf_free(filter_args); + M4_LOG(GF_LOG_ERROR, ("Unrecognized split syntax\n")); + return GF_BAD_PARAM; + } + + reframe = gf_fs_load_filter(fs, filter_args, &e); + gf_free(filter_args); + filter_args = NULL; + if (!reframe) { + M4_LOG(GF_LOG_ERROR, ("Failed to load reframer filter: %s\n", gf_error_to_string(e) )); + gf_fs_del(fs); + return e; + } + + if (!outName) { + strcat(szFile, ".mp4"); + } else { + strcpy(szFile, outName); + } + if (gf_dir_exists(szFile)) { + char c = szFile[strlen(szFile)-1]; + if ((c!='/') && (c!='\\')) + strcat(szFile, "/"); + + strcat(szFile, szName); + strcat(szFile, "_$num%03d$.mp4"); + M4_LOG(GF_LOG_WARNING, ("Split output is a directory, will use template %s\n", szFile)); + } + else if (split_size_kb || split_dur) { + if (!strchr(szFile, '$') && (stricmp(szFile, "null") || !strcmp(szFile, "/dev/null")) ) { + char *sep = gf_file_ext_start(szFile); + if (sep) sep[0] = 0; + strcat(szFile, "_$num$.mp4"); + M4_LOG(GF_LOG_WARNING, ("Split by %s but output not a template, using %s as output\n", split_size_kb ? "size" : "duration", szFile)); + } + } + if (do_frag) { + sprintf(szArgs, ":cdur=%g", interleaving_time); + strcat(szFile, ":store=frag"); + strcat(szFile, szArgs); + } + else if (do_flat==1) { + strcat(szFile, ":store=flat"); + } + else if (do_flat || interleaving_time) { + if (do_flat==3) { + strcat(szFile, ":store=fstart"); + } + sprintf(szArgs, ":cdur=%g", interleaving_time); + strcat(szFile, szArgs); + } + + dst = gf_fs_load_destination(fs, szFile, NULL, NULL, &e); + if (!dst) { + M4_LOG(GF_LOG_ERROR, ("Failed to load destination filter: %s\n", gf_error_to_string(e) )); + gf_fs_del(fs); + return e; + } + //link reframer to dst + gf_filter_set_source(dst, reframe, NULL); + + if (!gf_sys_is_test_mode() +#ifndef GPAC_DISABLE_LOG + && (gf_log_get_tool_level(GF_LOG_APP)!=GF_LOG_QUIET) +#endif + && !gf_sys_is_quiet() + ) { + gf_fs_enable_reporting(fs, GF_TRUE); + gf_fs_set_ui_callback(fs, on_split_event, &sinfo); + } +#ifdef GPAC_ENABLE_COVERAGE + else if (gf_sys_is_cov_mode()) { + on_split_event(NULL, NULL); + } +#endif //GPAC_ENABLE_COVERAGE + + + e = gf_fs_run(fs); + if (e>=GF_OK) { + e = gf_fs_get_last_connect_error(fs); + if (e>=GF_OK) + e = gf_fs_get_last_process_error(fs); + } + gf_fs_print_non_connected(fs); + if (fs_dump_flags & 1) gf_fs_print_stats(fs); + if (fs_dump_flags & 2) gf_fs_print_connections(fs); + + gf_fs_del(fs); + + if (esize==slc_dst->size) && !memcmp(slc->data, slc_dst->data, slc->size) ) { + found = 1; + break; + } + } + if (!found) { + return GF_FALSE; + } + } + return GF_TRUE; +} + +static u32 merge_avc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile **o_orig, u32 src_track, Bool force_cat, u32 *orig_nal_len, u32 *dst_nal_len) +{ + GF_AVCConfig *avc_src, *avc_dst; + GF_ISOFile *orig = *o_orig; + u32 dst_tk = gf_isom_get_track_by_id(dest, tk_id); + + 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; + } + + /*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); + if (!force_cat) { + gf_isom_avc_set_inband_config(dest, dst_tk, 1, GF_FALSE); + } else { + M4_LOG(GF_LOG_WARNING, ("WARNING: Concatenating track ID %d even though sample descriptions do not match\n", tk_id)); + } + } + return dst_tk; +} + +#ifndef GPAC_DISABLE_HEVC +static u32 merge_hevc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile **o_orig, u32 src_track, Bool force_cat, u32 *orig_nal_len, u32 *dst_nal_len) +{ + u32 i; + GF_HEVCConfig *hevc_src, *hevc_dst; + GF_ISOFile *orig = *o_orig; + u32 dst_tk = gf_isom_get_track_by_id(dest, tk_id); + + hevc_src = gf_isom_hevc_config_get(orig, src_track, 1); + hevc_dst = gf_isom_hevc_config_get(dest, dst_tk, 1); + + if (hevc_src->profile_idc != hevc_dst->profile_idc) dst_tk = 0; + else if (hevc_src->level_idc != hevc_dst->level_idc) dst_tk = 0; + else if (hevc_src->general_profile_compatibility_flags != hevc_dst->general_profile_compatibility_flags ) dst_tk = 0; + else { + /*rewrite all samples if using different NALU size*/ + if (hevc_src->nal_unit_size > hevc_dst->nal_unit_size) { + gf_media_nal_rewrite_samples(dest, dst_tk, 8*hevc_src->nal_unit_size); + hevc_dst->nal_unit_size = hevc_src->nal_unit_size; + } else if (hevc_src->nal_unit_size < hevc_dst->nal_unit_size) { + *orig_nal_len = hevc_src->nal_unit_size; + *dst_nal_len = hevc_dst->nal_unit_size; + } + + /*merge PS*/ + for (i=0; iparam_array); i++) { + u32 k; + GF_NALUFFParamArray *src_ar = gf_list_get(hevc_src->param_array, i); + for (k=0; kparam_array); k++) { + GF_NALUFFParamArray *dst_ar = gf_list_get(hevc_dst->param_array, k); + if (dst_ar->type==src_ar->type) { + if (!merge_parameter_set(src_ar->nalus, dst_ar->nalus, "SPS")) + dst_tk = 0; + break; + } + } + } + + gf_isom_hevc_config_update(dest, dst_tk, 1, hevc_dst); + } + + gf_odf_hevc_cfg_del(hevc_src); + gf_odf_hevc_cfg_del(hevc_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); + if (!force_cat) { + gf_isom_hevc_set_inband_config(dest, dst_tk, 1, GF_FALSE); + } else { + M4_LOG(GF_LOG_WARNING, ("WARNING: Concatenating track ID %d even though sample descriptions do not match\n", tk_id)); + } + } + return dst_tk; +} +#endif /*GPAC_DISABLE_HEVC */ + +static GF_Err rewrite_nal_size_field(GF_ISOSample *samp, u32 orig_nal_len, u32 dst_nal_len) +{ + GF_Err e = GF_OK; + u32 msize=0, remain = samp->dataLength; + GF_BitStream *oldbs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ); + GF_BitStream *newbs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + u8 *buffer = NULL; + + while (remain) { + u32 size = gf_bs_read_int(oldbs, orig_nal_len*8); + if (size > remain) { + e = GF_ISOM_INVALID_FILE; + goto exit; + } + gf_bs_write_int(newbs, size, dst_nal_len*8); + remain -= orig_nal_len; + if (size > msize) { + msize = size; + buffer = (u8*)gf_realloc(buffer, sizeof(u8)*msize); + if (!buffer) { + e = GF_OUT_OF_MEM; + goto exit; + } + } + gf_bs_read_data(oldbs, buffer, size); + gf_bs_write_data(newbs, buffer, size); + remain -= size; + } + gf_free(samp->data); + samp->data = NULL; + samp->dataLength = 0; + gf_bs_get_content(newbs, &samp->data, &samp->dataLength); + +exit: + gf_bs_del(newbs); + gf_bs_del(oldbs); + if (buffer) gf_free(buffer); + return e; +} + + +GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); + +GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command, Bool is_pl) +{ + u32 i, j, count, nb_tracks, nb_samp, nb_done; + GF_ISOFile *orig; + GF_Err e; + char *opts, *multi_cat; + Double ts_scale, media_ts_scale; + Double dest_orig_dur; + u32 dst_tk, tk_id, mtype; + u64 insert_dts; + Bool is_isom; + GF_ISOSample *samp; + GF_Fraction64 aligned_to_DTS_frac; + + if (is_pl) return cat_playlist(dest, fileName, import_flags, force_fps, frames_per_sample, force_cat, align_timelines, allow_add_in_command); + + if (strchr(fileName, '*') || (strchr(fileName, '@')) ) + return cat_multiple_files(dest, fileName, import_flags, force_fps, frames_per_sample, force_cat, align_timelines, allow_add_in_command); + + multi_cat = allow_add_in_command ? strchr(fileName, '+') : NULL; + if (multi_cat) { + multi_cat[0] = 0; + multi_cat = &multi_cat[1]; + } + + opts = gf_url_colon_suffix(fileName, '='); + e = GF_OK; + + /*if options are specified, reimport the file*/ + if (opts) opts[0] = 0; + is_isom = gf_isom_probe_file(fileName); + + if (!is_isom || multi_cat) { + orig = gf_isom_open("temp", GF_ISOM_WRITE_EDIT, NULL); + if (opts) opts[0] = ':'; + e = import_file(orig, fileName, import_flags, force_fps, frames_per_sample, NULL, NULL, NULL, 0); + if (e) return e; + } else { + //open read+edit mode to allow applying options on file + orig = gf_isom_open(fileName, GF_ISOM_OPEN_READ_EDIT, NULL); + if (opts) opts[0] = ':'; + if (!orig) return gf_isom_last_error(NULL); + + if (opts) { + e = import_file(orig, fileName, 0xFFFFFFFF, force_fps, frames_per_sample, NULL, NULL, NULL, 0); + if (e) return e; + } + } + + while (multi_cat) { + char *sep = strchr(multi_cat, '+'); + if (sep) sep[0] = 0; + + e = import_file(orig, multi_cat, import_flags, force_fps, frames_per_sample, NULL, NULL, NULL, 0); + if (e) { + gf_isom_delete(orig); + return e; + } + if (!sep) break; + sep[0]=':'; + multi_cat = sep+1; + } + + nb_samp = 0; + nb_tracks = gf_isom_get_track_count(orig); + for (i=0; i1)) { + insert_dts = 2*gf_isom_get_sample_dts(dest, dst_tk, count) - gf_isom_get_sample_dts(dest, dst_tk, count-1); + } else { + insert_dts = dest_track_dur_before_cat; + if (!count) insert_dts = 0; + } + + ts_scale = gf_isom_get_media_timescale(dest, dst_tk); + ts_scale /= gf_isom_get_media_timescale(orig, i+1); + media_ts_scale = gf_isom_get_media_timescale(dest, dst_tk); + media_ts_scale /= gf_isom_get_media_timescale(orig, i+1); + + /*if not a new track, see if we can merge the edit list - this is a crude test that only checks + we have the same edit types*/ + if (nb_edits && (nb_edits == gf_isom_get_edits_count(dest, dst_tk)) ) { + u64 editTime, segmentDuration, mediaTime, dst_editTime, dst_segmentDuration, dst_mediaTime; + GF_ISOEditType dst_editMode, editMode; + merge_edits = 1; + for (j=0; jDTS; + samp->DTS = (u64) (ts_scale * samp->DTS + (new_track ? 0 : insert_dts)); + samp->CTS_Offset = (u32) (samp->CTS_Offset * ts_scale); + + if (gf_isom_is_self_contained(orig, i+1, di)) { + if (orig_nal_len && dst_nal_len) { + e = rewrite_nal_size_field(samp, orig_nal_len, dst_nal_len); + if (e) { + gf_isom_sample_del(&samp); + goto err_exit; + } + } + e = gf_isom_add_sample(dest, dst_tk, di + dst_tk_sample_entry, samp); + } else { + u64 offset; + GF_ISOSample *s = gf_isom_get_sample_info(orig, i+1, j+1, &di, &offset); + e = gf_isom_add_sample_reference(dest, dst_tk, di + dst_tk_sample_entry, samp, offset); + gf_isom_sample_del(&s); + } + if (samp->nb_pack) + j+= samp->nb_pack-1; + + gf_isom_sample_del(&samp); + if (e) goto err_exit; + + e = gf_isom_copy_sample_info(dest, dst_tk, orig, i+1, j+1); + if (e) goto err_exit; + + if (j+1==count) { + u32 sdur = gf_isom_get_sample_duration(orig, i+1, j+1); + gf_isom_set_last_sample_duration(dest, dst_tk, (u32) (ts_scale * sdur) ); + } + + gf_set_progress("Appending", nb_done, nb_samp); + nb_done++; + } + /*scene description and text: compute last sample duration based on original media duration*/ + if (!use_ts_dur) { + u64 extend_dur = gf_isom_get_media_duration(orig, i+1) - last_DTS; + //extend the duration of the last sample, but don't insert any edit list entry + gf_isom_set_last_sample_duration(dest, dst_tk, (u32) (ts_scale*extend_dur) ); + } + + if (new_track && insert_dts) { + u64 media_dur = gf_isom_get_media_duration(orig, i+1); + /*convert from media time to track time*/ + Double rescale = (Float) gf_isom_get_timescale(dest); + rescale /= (Float) gf_isom_get_media_timescale(dest, dst_tk); + /*convert from orig to dst time scale*/ + rescale *= ts_scale; + + gf_isom_set_edit(dest, dst_tk, 0, (u64) (s64) (insert_dts*rescale), 0, GF_ISOM_EDIT_EMPTY); + gf_isom_set_edit(dest, dst_tk, (u64) (s64) (insert_dts*rescale), (u64) (s64) (media_dur*rescale), 0, GF_ISOM_EDIT_NORMAL); + } else if (merge_edits) { + /*convert from media time to track time*/ + u32 movts_dst = gf_isom_get_timescale(dest); + u32 trackts_dst = gf_isom_get_media_timescale(dest, dst_tk); + u32 trackts_orig = gf_isom_get_media_timescale(orig, i+1); + + /*get the first edit normal mode and add the new track dur*/ + for (j=nb_edits; j>0; j--) { + u64 editTime, segmentDuration, mediaTime; + GF_ISOEditType editMode; + gf_isom_get_edit(dest, dst_tk, j, &editTime, &segmentDuration, &mediaTime, &editMode); + + if (editMode==GF_ISOM_EDIT_NORMAL) { + Double prev_dur = (Double) (s64) dest_track_dur_before_cat; + Double dur = (Double) (s64) gf_isom_get_media_duration(orig, i+1); + + /*safety test: some files have broken edit lists. If no more than 2 entries, check that the segment duration + is less or equal to the movie duration*/ + if (prev_dur * movts_dst < segmentDuration * trackts_dst) { + M4_LOG(GF_LOG_WARNING, ("Warning: suspicious edit list entry found: duration %g sec but longest track duration before cat is %g - fixing it\n", (Double) (s64) segmentDuration/movts_dst, prev_dur/trackts_dst)); + segmentDuration = (dest_track_dur_before_cat - mediaTime) * movts_dst; + segmentDuration /= trackts_dst; + } + + //express original dur in new timescale + dur /= trackts_orig; + dur *= movts_dst; + + segmentDuration += (u64) (s64) dur; + gf_isom_modify_edit(dest, dst_tk, j, segmentDuration, mediaTime, editMode); + break; + } + } + } else { + u64 editTime, segmentDuration, mediaTime, edit_offset; + Double t; + GF_ISOEditType editMode; + + count = gf_isom_get_edits_count(dest, dst_tk); + if (count) { + e = gf_isom_get_edit(dest, dst_tk, count, &editTime, &segmentDuration, &mediaTime, &editMode); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error: edit segment error on destination track %u could not be retrieved.\n", dst_tk)); + goto err_exit; + } + } else if (gf_isom_get_edits_count(orig, i+1)) { + /*fake empty edit segment*/ + /*convert from media time to track time*/ + Double rescale = (Float) gf_isom_get_timescale(dest); + rescale /= (Float) gf_isom_get_media_timescale(dest, dst_tk); + segmentDuration = (u64) (dest_track_dur_before_cat * rescale); + editTime = 0; + mediaTime = 0; + if (segmentDuration) + gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration, mediaTime, GF_ISOM_EDIT_NORMAL); + } else { + editTime = 0; + segmentDuration = 0; + } + + /*convert to dst time scale*/ + ts_scale = (Float) gf_isom_get_timescale(dest); + ts_scale /= (Float) gf_isom_get_timescale(orig); + + edit_offset = editTime + segmentDuration; + count = gf_isom_get_edits_count(orig, i+1); + for (j=0; j 0)) { + editMode = GF_ISOM_EDIT_NORMAL; + } + gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration, mediaTime, editMode); + } + } + gf_media_update_bitrate(dest, dst_tk); + + } + for (i = 0; i < gf_isom_get_track_count(dest); ++i) { + if (gf_isom_get_media_type(dest, i+1) == GF_ISOM_MEDIA_TIMECODE) { + u32 video_ref = 0; + for (j = 0; j < gf_isom_get_track_count(dest); ++j) { + if (gf_isom_is_video_handler_type(gf_isom_get_media_type(dest, j+1))) { + video_ref = j+1; + break; + } + } + if (video_ref) { + u64 video_ref_dur = gf_isom_get_media_duration(dest, video_ref); + u32 last_sample_dur = gf_isom_get_sample_duration(dest, i+1, gf_isom_get_sample_count(dest, i+1)); + u64 dur = video_ref_dur - gf_isom_get_media_duration(dest, i+1) + last_sample_dur; + gf_isom_set_last_sample_duration(dest, i+1, (u32)dur); + } + } + } + gf_set_progress("Appending", nb_samp, nb_samp); + + /*check brands*/ + gf_isom_get_brand_info(orig, NULL, NULL, &j); + for (i=0; iszRad1); + if (strnicmp(szName, cat_enum->szRad1, len_rad1)) return 0; + if (strlen(cat_enum->szRad2) && !strstr(szName + len_rad1, cat_enum->szRad2) ) return 0; + + strcpy(szFileName, szPath); + strcat(szFileName, cat_enum->szOpt); + + e = cat_isomedia_file(cat_enum->dest, szFileName, cat_enum->import_flags, cat_enum->force_fps, cat_enum->frames_per_sample, cat_enum->force_cat, cat_enum->align_timelines, cat_enum->allow_add_in_command, GF_FALSE); + if (e) return 1; + return 0; +} + +GF_Err cat_multiple_files(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) +{ + CATEnum cat_enum; + char *sep; + + cat_enum.dest = dest; + cat_enum.import_flags = import_flags; + cat_enum.force_fps = force_fps; + cat_enum.frames_per_sample = frames_per_sample; + cat_enum.force_cat = force_cat; + cat_enum.align_timelines = align_timelines; + cat_enum.allow_add_in_command = allow_add_in_command; + + if (strlen(fileName) >= sizeof(cat_enum.szPath)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", fileName)); + return GF_NOT_SUPPORTED; + } + strcpy(cat_enum.szPath, fileName); + sep = strrchr(cat_enum.szPath, GF_PATH_SEPARATOR); + if (!sep) sep = strrchr(cat_enum.szPath, '/'); + if (!sep) { + strcpy(cat_enum.szPath, "."); + if (strlen(fileName) >= sizeof(cat_enum.szRad1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", fileName)); + return GF_NOT_SUPPORTED; + } + strcpy(cat_enum.szRad1, fileName); + } else { + if (strlen(sep + 1) >= sizeof(cat_enum.szRad1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", (sep + 1))); + return GF_NOT_SUPPORTED; + } + strcpy(cat_enum.szRad1, sep+1); + sep[0] = 0; + } + sep = strchr(cat_enum.szRad1, '*'); + if (!sep) sep = strchr(cat_enum.szRad1, '@'); + if (strlen(sep + 1) >= sizeof(cat_enum.szRad2)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", (sep + 1))); + return GF_NOT_SUPPORTED; + } + strcpy(cat_enum.szRad2, sep+1); + sep[0] = 0; + sep = strchr(cat_enum.szRad2, '%'); + if (!sep) sep = strchr(cat_enum.szRad2, '#'); + if (!sep) sep = gf_url_colon_suffix(cat_enum.szRad2, '='); + strcpy(cat_enum.szOpt, ""); + if (sep) { + if (strlen(sep) >= sizeof(cat_enum.szOpt)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Invalid option: %s.\n", sep)); + return GF_NOT_SUPPORTED; + } + strcpy(cat_enum.szOpt, sep); + sep[0] = 0; + } + return gf_enum_directory(cat_enum.szPath, 0, cat_enumerate, &cat_enum, NULL); +} + +GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) +{ + GF_Err e; + FILE *pl = gf_fopen(playlistName, "r"); + if (!pl) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to open playlist file %s\n", playlistName)); + return GF_URL_ERROR; + } + + e = GF_OK; + while (!feof(pl)) { + char szLine[10000]; + char *url; + u32 len; + szLine[0] = 0; + if (gf_fgets(szLine, 10000, pl) == NULL) break; + if (szLine[0]=='#') continue; + len = (u32) strlen(szLine); + while (len && strchr("\r\n \t", szLine[len-1])) { + szLine[len-1] = 0; + len--; + } + if (!len) continue; + + url = gf_url_concatenate(playlistName, szLine); + if (!url) url = gf_strdup(szLine); + + e = cat_isomedia_file(dest, url, import_flags, force_fps, frames_per_sample, force_cat, align_timelines, allow_add_in_command, GF_FALSE); + + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to concatenate file %s\n", url)); + gf_free(url); + break; + } + gf_free(url); + } + gf_fclose(pl); + return e; +} + +#ifndef GPAC_DISABLE_SCENE_ENCODER +/* + MPEG-4 encoding +*/ + +GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *logs) +{ +#ifdef GPAC_DISABLE_SMGR + return GF_NOT_SUPPORTED; +#else + GF_Err e; + GF_SceneLoader load; + GF_SceneManager *ctx; + GF_SceneGraph *sg; +#ifndef GPAC_DISABLE_SCENE_STATS + GF_StatManager *statsman = NULL; +#endif + + sg = gf_sg_new(); + ctx = gf_sm_new(sg); + memset(&load, 0, sizeof(GF_SceneLoader)); + load.fileName = in; + load.ctx = ctx; + load.swf_import_flags = swf_flags; + load.swf_flatten_limit = 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); + M4_LOG(GF_LOG_ERROR, ("Cannot load context %s - %s\n", in, gf_error_to_string(e))); + goto err_exit; + } + e = gf_sm_load_run(&load); + gf_sm_load_done(&load); + +#ifndef GPAC_DISABLE_SCENE_STATS + if (opts->auto_quant) { + fprintf(stderr, "Analysing Scene for Automatic Quantization\n"); + statsman = gf_sm_stats_new(); + e = gf_sm_stats_for_scene(statsman, ctx); + if (!e) { + const GF_SceneStatistics *stats = gf_sm_stats_get(statsman); + /*LASeR*/ + if (opts->auto_quant==1) { + if (opts->resolution > (s32)stats->frac_res_2d) { + fprintf(stderr, " Given resolution %d is (unnecessarily) too high, using %d instead.\n", opts->resolution, stats->frac_res_2d); + opts->resolution = stats->frac_res_2d; + } else if (stats->int_res_2d + opts->resolution <= 0) { + fprintf(stderr, " Given resolution %d is too low, using %d instead.\n", opts->resolution, stats->int_res_2d - 1); + opts->resolution = 1 - stats->int_res_2d; + } + opts->coord_bits = stats->int_res_2d + opts->resolution; + fprintf(stderr, " Coordinates & Lengths encoded using "); + if (opts->resolution < 0) fprintf(stderr, "only the %d most significant bits (of %d).\n", opts->coord_bits, stats->int_res_2d); + else fprintf(stderr, "a %d.%d representation\n", stats->int_res_2d, opts->resolution); + + fprintf(stderr, " Matrix Scale & Skew Coefficients "); + if (opts->coord_bits - 8 < stats->scale_int_res_2d) { + opts->scale_bits = stats->scale_int_res_2d - opts->coord_bits + 8; + fprintf(stderr, "encoded using a %d.8 representation\n", stats->scale_int_res_2d); + } else { + opts->scale_bits = 0; + fprintf(stderr, "encoded using a %d.8 representation\n", opts->coord_bits - 8); + } + } +#ifndef GPAC_DISABLE_VRML + /*BIFS*/ + else if (stats->base_layer) { + GF_AUContext *au; + GF_CommandField *inf; + M_QuantizationParameter *qp; + GF_Command *com = gf_sg_command_new(ctx->scene_graph, GF_SG_GLOBAL_QUANTIZER); + qp = (M_QuantizationParameter *) gf_node_new(ctx->scene_graph, TAG_MPEG4_QuantizationParameter); + + inf = gf_sg_command_field_new(com); + inf->new_node = (GF_Node *)qp; + inf->field_ptr = &inf->new_node; + inf->fieldType = GF_SG_VRML_SFNODE; + gf_node_register(inf->new_node, NULL); + au = gf_list_get(stats->base_layer->AUs, 0); + gf_list_insert(au->commands, com, 0); + qp->useEfficientCoding = 1; + qp->textureCoordinateQuant = 0; + if ((stats->count_2f+stats->count_2d) && opts->resolution) { + qp->position2DMin = stats->min_2d; + qp->position2DMax = stats->max_2d; + qp->position2DNbBits = opts->resolution; + qp->position2DQuant = 1; + } + if ((stats->count_3f+stats->count_3d) && opts->resolution) { + qp->position3DMin = stats->min_3d; + qp->position3DMax = stats->max_3d; + qp->position3DNbBits = opts->resolution; + qp->position3DQuant = 1; + qp->textureCoordinateQuant = 1; + } + //float quantif is disabled since 2008, check if we want to re-enable it +#if 0 + if (stats->count_float && opts->resolution) { + qp->scaleMin = stats->min_fixed; + qp->scaleMax = stats->max_fixed; + qp->scaleNbBits = 2*opts->resolution; + qp->scaleQuant = 1; + } +#endif + } +#endif + } + } +#endif /*GPAC_DISABLE_SCENE_STATS*/ + + if (e<0) { + M4_LOG(GF_LOG_ERROR, ("Error loading file %s\n", gf_error_to_string(e))); + goto err_exit; + } else { + gf_log_cbk prev_logs = NULL; + if (logs) { + gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_DEBUG); + prev_logs = gf_log_set_callback(logs, scene_coding_log); + } + opts->src_url = in; + e = gf_sm_encode_to_file(ctx, mp4, opts); + if (logs) { + gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_ERROR); + gf_log_set_callback(NULL, prev_logs); + } + } + + gf_isom_set_brand_info(mp4, GF_ISOM_BRAND_MP42, 1); + gf_isom_modify_alternate_brand(mp4, GF_ISOM_BRAND_ISOM, GF_TRUE); + +err_exit: +#ifndef GPAC_DISABLE_SCENE_STATS + if (statsman) gf_sm_stats_del(statsman); +#endif + gf_sm_del(ctx); + gf_sg_del(sg); + return e; + +#endif /*GPAC_DISABLE_SMGR*/ +} +#endif /*GPAC_DISABLE_SCENE_ENCODER*/ + + +#ifndef GPAC_DISABLE_BIFS_ENC +/* + MPEG-4 chunk encoding +*/ + +static u32 GetNbBits(u32 MaxVal) +{ + u32 k=0; + while ((s32) MaxVal > ((1<streams starting at AU index 1 (0 is SceneReplace from previous context) */ + bifsenc = gf_bifs_encoder_new(ctx->scene_graph); + e = GF_OK; + + iod = (GF_InitialObjectDescriptor *) ctx->root_od; + /*if no iod check we only have one bifs*/ + if (!iod) { + count = 0; + for (i=0; istreams); i++) { + GF_StreamContext *sc = gf_list_get(ctx->streams, i); + if (sc->streamType == GF_STREAM_OD) count++; + } + if (count>1) return GF_NOT_SUPPORTED; + } + + count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + + if (sc->streamType != GF_STREAM_SCENE) continue; + + if (iod) { + for (j=0; jESDescriptors); j++) { + esd = gf_list_get(iod->ESDescriptors, j); + if (esd->decoderConfig && esd->decoderConfig->streamType == GF_STREAM_SCENE) { + if (!sc->ESID) sc->ESID = esd->ESID; + if (sc->ESID == esd->ESID) { + break; + } + } + /*special BIFS direct import from NHNT*/ + else if (gf_list_count(iod->ESDescriptors)==1) { + sc->ESID = esd->ESID; + break; + } + esd = NULL; + } + } + + if (!esd) { + esd = gf_odf_desc_esd_new(2); + if (!esd) return GF_OUT_OF_MEM; + gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo); + esd->decoderConfig->decoderSpecificInfo = NULL; + esd->ESID = sc->ESID; + esd->decoderConfig->streamType = GF_STREAM_SCENE; + delete_esd = GF_TRUE; + } + if (!esd->decoderConfig) return GF_OUT_OF_MEM; + + /*should NOT happen (means inputctx is not properly setup)*/ + if (!esd->decoderConfig->decoderSpecificInfo) { + bcfg = (GF_BIFSConfig*)gf_odf_desc_new(GF_ODF_BIFS_CFG_TAG); + delete_bcfg = 1; + } + /*regular retrieve from ctx*/ + else if (esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_BIFS_CFG_TAG) { + bcfg = (GF_BIFSConfig *)esd->decoderConfig->decoderSpecificInfo; + delete_bcfg = 0; + } + /*should not happen either (unless loading from MP4 in which case BIFSc is not decoded)*/ + else { + bcfg = gf_odf_get_bifs_config(esd->decoderConfig->decoderSpecificInfo, esd->decoderConfig->objectTypeIndication); + delete_bcfg = 1; + } + /*NO CHANGE TO BIFSC otherwise the generated update will not match the input context*/ + nbb = GetNbBits(ctx->max_node_id); + if (!bcfg->nodeIDbits) bcfg->nodeIDbits=nbb; + if (bcfg->nodeIDbitsmax_route_id); + if (!bcfg->routeIDbits) bcfg->routeIDbits = nbb; + if (bcfg->routeIDbitsmax_proto_id); + if (!bcfg->protoIDbits) bcfg->protoIDbits=nbb; + if (bcfg->protoIDbitsESID, bcfg, encode_names, 0); + if (delete_bcfg) gf_odf_desc_del((GF_Descriptor *)bcfg); + + if (!esd->slConfig) esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG); + if (sc->timeScale) esd->slConfig->timestampResolution = sc->timeScale; + if (!esd->slConfig->timestampResolution) esd->slConfig->timestampResolution = 1000; + esd->ESID = sc->ESID; + gf_bifs_encoder_get_config(bifsenc, sc->ESID, &data, &data_len); + + if (esd->decoderConfig->decoderSpecificInfo) gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo); + esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG); + esd->decoderConfig->decoderSpecificInfo->data = data; + esd->decoderConfig->decoderSpecificInfo->dataLength = data_len; + esd->decoderConfig->objectTypeIndication = gf_bifs_encoder_get_version(bifsenc, sc->ESID); + + for (j=1; jAUs); j++) { + au = gf_list_get(sc->AUs, j); + e = gf_bifs_encode_au(bifsenc, sc->ESID, au->commands, &data, &data_len); + if (data) { + sprintf(szName, "%s-%02d-%02d.bifs", szRad, sc->ESID, j); + f = gf_fopen(szName, "wb"); + gf_fwrite(data, data_len, f); + gf_fclose(f); + gf_free(data); + } + } + if (delete_esd) gf_odf_desc_del((GF_Descriptor*)esd); + } + gf_bifs_encoder_del(bifsenc); + return e; +} +#endif /*GPAC_DISABLE_SMGR*/ + + +#endif /*GPAC_DISABLE_BIFS_ENC*/ + +/** +\param chunkFile BT chunk to be encoded +\param bifs output file name for the BIFS data +\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) +{ +#if defined(GPAC_DISABLE_SMGR) || defined(GPAC_DISABLE_BIFS_ENC) || defined(GPAC_DISABLE_SCENE_ENCODER) || defined (GPAC_DISABLE_SCENE_DUMP) + M4_LOG(GF_LOG_WARNING, ("BIFS encoding is not supported in this build of GPAC\n")); + return GF_NOT_SUPPORTED; +#else + GF_Err e; + GF_SceneGraph *sg; + GF_SceneManager *ctx; + GF_SceneLoader load; + + /*Step 1: create context and load input*/ + sg = gf_sg_new(); + ctx = gf_sm_new(sg); + memset(&load, 0, sizeof(GF_SceneLoader)); + load.fileName = inputContext; + load.ctx = ctx; + /*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) e = gf_sm_load_run(&load); + gf_sm_load_done(&load); + if (e) { + M4_LOG(GF_LOG_WARNING, ("Cannot load context %s: %s\n", inputContext, gf_error_to_string(e))); + goto exit; + } + + /* Step 2: make sure we have only ONE RAP for each stream*/ + e = gf_sm_aggregate(ctx, 0); + if (e) goto exit; + + /*Step 3: loading the chunk into the context*/ + memset(&load, 0, sizeof(GF_SceneLoader)); + load.fileName = chunkFile; + load.ctx = ctx; + load.flags = GF_SM_LOAD_MPEG4_STRICT | GF_SM_LOAD_CONTEXT_READY; + e = gf_sm_load_init(&load); + if (!e) e = gf_sm_load_run(&load); + gf_sm_load_done(&load); + if (e) { + M4_LOG(GF_LOG_WARNING, ("Cannot load scene commands chunk %s: %s\n", chunkFile, gf_error_to_string(e))); + goto exit; + } + M4_LOG(GF_LOG_INFO, ("Context and chunks loaded\n")); + + /* Assumes that the first AU contains only one command a SceneReplace and + that is not part of the current chunk */ + /* Last argument is a callback to pass the encoded AUs: not needed here + Saving is not handled correctly */ + e = EncodeBIFSChunk(ctx, bifs, NULL); + if (e) goto exit; + + + if (outputContext) { + u32 d_mode, do_enc; + char szF[GF_MAX_PATH], *ext; + + /*make random access for storage*/ + e = gf_sm_aggregate(ctx, 0); + if (e) goto exit; + + /*check if we dump to BT, XMT or encode to MP4*/ + strcpy(szF, outputContext); + ext = strrchr(szF, '.'); + d_mode = GF_SM_DUMP_BT; + do_enc = 0; + if (ext) { + if (!stricmp(ext, ".xmt") || !stricmp(ext, ".xmta")) d_mode = GF_SM_DUMP_XMTA; + else if (!stricmp(ext, ".mp4")) do_enc = 1; + ext[0] = 0; + } + + if (do_enc) { + GF_ISOFile *mp4; + strcat(szF, ".mp4"); + mp4 = gf_isom_open(szF, GF_ISOM_WRITE_EDIT, NULL); + e = gf_sm_encode_to_file(ctx, mp4, NULL); + if (e) gf_isom_delete(mp4); + else gf_isom_close(mp4); + } + else e = gf_sm_dump(ctx, szF, GF_FALSE, d_mode); + } + +exit: + if (ctx) { + sg = ctx->scene_graph; + gf_sm_del(ctx); + gf_sg_del(sg); + } + + return e; + +#endif /*defined(GPAC_DISABLE_BIFS_ENC) || defined(GPAC_DISABLE_SCENE_ENCODER) || defined (GPAC_DISABLE_SCENE_DUMP)*/ + +} + +#endif /*GPAC_DISABLE_MEDIA_IMPORT*/ + + +#ifndef GPAC_DISABLE_CORE_TOOLS +void sax_node_start(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes) +{ + char szCheck[100]; + GF_List *imports = sax_cbck; + u32 i=0; + + /*do not process hyperlinks*/ + if (!strcmp(node_name, "a") || !strcmp(node_name, "Anchor")) return; + + for (i=0; iname, "xlink:href") && stricmp(att->name, "url")) continue; + if (att->value[0]=='#') continue; + if (!strnicmp(att->value, "od:", 3)) continue; + sprintf(szCheck, "%d", atoi(att->value)); + if (!strcmp(szCheck, att->value)) continue; + gf_list_add(imports, gf_strdup(att->value) ); + } +} + +static Bool wgt_enum_files(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) +{ + WGTEnum *wgt = (WGTEnum *)cbck; + + if (!strcmp(wgt->root_file, file_path)) return 0; + //remove hidden files + if (file_path[0] == '.') return 0; + gf_list_add(wgt->imports, gf_strdup(file_path) ); + return 0; +} +static Bool wgt_enum_dir(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) +{ + if (!stricmp(file_name, "cvs") || !stricmp(file_name, ".svn") || !stricmp(file_name, ".git")) return 0; + gf_enum_directory(file_path, 0, wgt_enum_files, cbck, NULL); + return gf_enum_directory(file_path, 1, wgt_enum_dir, cbck, NULL); +} + +GF_ISOFile *package_file(char *file_name, char *fcc, Bool make_wgt) +{ + GF_ISOFile *file = NULL; + GF_Err e; + GF_SAXParser *sax; + GF_List *imports; + Bool ascii; + char root_dir[GF_MAX_PATH]; + char *isom_src = NULL; + u32 i, count, mtype, skip_chars; + char *type; + + type = gf_xml_get_root_type(file_name, &e); + if (!type) { + M4_LOG(GF_LOG_ERROR, ("Cannot process XML file %s: %s\n", file_name, gf_error_to_string(e) )); + return NULL; + } + if (make_wgt) { + if (strcmp(type, "widget")) { + M4_LOG(GF_LOG_ERROR, ("XML Root type %s differs from \"widget\" \n", type)); + gf_free(type); + return NULL; + } + gf_free(type); + type = gf_strdup("application/mw-manifest+xml"); + fcc = "mwgt"; + } + imports = gf_list_new(); + + + root_dir[0] = 0; + if (make_wgt) { + WGTEnum wgt; + char *sep = strrchr(file_name, '\\'); + if (!sep) sep = strrchr(file_name, '/'); + if (sep) { + char c = sep[1]; + sep[1]=0; + strcpy(root_dir, file_name); + sep[1] = c; + } else { + strcpy(root_dir, "./"); + } + wgt.dir = root_dir; + wgt.root_file = file_name; + wgt.imports = imports; + gf_enum_directory(wgt.dir, 0, wgt_enum_files, &wgt, NULL); + gf_enum_directory(wgt.dir, 1, wgt_enum_dir, &wgt, NULL); + ascii = 1; + } else { + sax = gf_xml_sax_new(sax_node_start, NULL, NULL, imports); + e = gf_xml_sax_parse_file(sax, file_name, NULL); + ascii = !gf_xml_sax_binary_file(sax); + gf_xml_sax_del(sax); + if (e<0) goto exit; + e = GF_OK; + } + + if (fcc) { + mtype = GF_4CC(fcc[0],fcc[1],fcc[2],fcc[3]); + } else { + mtype = 0; + if (!stricmp(type, "svg")) mtype = ascii ? GF_META_TYPE_SVG : GF_META_TYPE_SVGZ; + else if (!stricmp(type, "smil")) mtype = ascii ? GF_META_TYPE_SMIL : GF_META_TYPE_SMLZ; + else if (!stricmp(type, "x3d")) mtype = ascii ? GF_META_TYPE_X3D : GF_META_TYPE_X3DZ ; + else if (!stricmp(type, "xmt-a")) mtype = ascii ? GF_META_TYPE_XMTA : GF_META_TYPE_XMTZ; + } + if (!mtype) { + M4_LOG(GF_LOG_ERROR, ("Missing 4CC code for meta name - please use ABCD:fileName\n")); + e = GF_BAD_PARAM; + goto exit; + } + + + if (!make_wgt) { + count = gf_list_count(imports); + for (i=0; iname, "HDR")) { + M4_LOG(GF_LOG_ERROR, ("Error parsing HDR XML file: root name is \"%s\", expecting \"HDR\"\n", root->name)); + gf_xml_dom_del(parser); + return GF_NON_COMPLIANT_BITSTREAM; + } + + i = 0; + while ((stream = gf_list_enum(root->content, &i))) { + u32 j; + GF_XMLAttribute* att = NULL; + GF_XMLNode *box = NULL; + + if (stream->type != GF_XML_NODE_TYPE) continue; + if (strcmp(stream->name, "Track")) continue; + + if (!track) { + j = 0; + while ((att = gf_list_enum(stream->attributes, &j))) { + if (!strcmp(att->name, "id")) { + u32 id = atoi(att->value); + track = gf_isom_get_track_by_id(movie, id); + if (!track) { + fprintf(stderr, "HDR XML: no track with ID %d, ignoring attribute\n", id); + } + } + else fprintf(stderr, "HDR XML: ignoring track attribute \"%s\"\n", att->name); + } + } + + j = 0; + while ((box = gf_list_enum(stream->content, &j))) { + u32 k; + + if (box->type != GF_XML_NODE_TYPE) continue; + + if (!strcmp(box->name, "mdcv")) { + k = 0; + while ((att = gf_list_enum(box->attributes, &k))) { + if (!strcmp(att->name, "display_primaries_0_x")) mdcv.display_primaries[0].x = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_0_y")) mdcv.display_primaries[0].y = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_1_x")) mdcv.display_primaries[1].x = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_1_y")) mdcv.display_primaries[1].y = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_2_x")) mdcv.display_primaries[2].x = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_2_y")) mdcv.display_primaries[2].y = atoi(att->value); + else if (!strcmp(att->name, "white_point_x")) mdcv.white_point_x = atoi(att->value); + else if (!strcmp(att->name, "white_point_y")) mdcv.white_point_y = atoi(att->value); + else if (!strcmp(att->name, "max_display_mastering_luminance")) mdcv.max_display_mastering_luminance = atoi(att->value); + else if (!strcmp(att->name, "min_display_mastering_luminance")) mdcv.min_display_mastering_luminance = atoi(att->value); + else fprintf(stderr, "HDR XML: ignoring box \"%s\" attribute \"%s\"\n", box->name, att->name); + } + } else if (!strcmp(box->name, "clli")) { + k = 0; + while ((att = gf_list_enum(box->attributes, &k))) { + if (!strcmp(att->name, "max_content_light_level")) clli.max_content_light_level = atoi(att->value); + else if (!strcmp(att->name, "max_pic_average_light_level")) clli.max_pic_average_light_level = atoi(att->value); + else fprintf(stderr, "HDR XML: ignoring box \"%s\" attribute \"%s\"\n", box->name, att->name); + } + } else { + fprintf(stderr, "HDR XML: ignoring box element \"%s\"\n", box->name); + continue; + } + } + + if (!track) { + for (i=0; i +#endif +#ifndef GPAC_DISABLE_STREAMING +#include +#endif + +#include + +#if defined(GPAC_DISABLE_ISOM) || defined(GPAC_DISABLE_ISOM_WRITE) + +#error "Cannot compile MP4Box if GPAC is not built with ISO File Format support" + +#else + +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + + + + +typedef struct +{ + GF_RTPStreamer *rtp; + Bool manual_rtcp; + u16 ESID; + + u8 *carousel_data; + u32 carousel_size, carousel_alloc; + u32 last_carousel_time; + u64 carousel_ts, time_at_carousel_store; + + u32 timescale, init_time; + u32 carousel_period, ts_delta; + u16 aggregate_on_stream; + Bool adjust_carousel_time, discard, aggregate, rap, m2ts_vers_inc; + u32 critical; +} RTPChannel; + +typedef struct +{ + GF_SceneEngine *seng; + Bool force_carousel, carousel_generation; + GF_List *streams; + u32 start_time; + Bool critical; +} LiveSession; + + +RTPChannel *next_carousel(LiveSession *sess, u32 *timeout) +{ + RTPChannel *to_send = NULL; + u32 i, time, count, now; + + if (!sess->start_time) sess->start_time = gf_sys_clock(); + now = gf_sys_clock() - sess->start_time; + + time = (u32) -1; + count = gf_list_count(sess->streams); + for (i=0; istreams, i); + if (!ch->carousel_period) continue; + if (!ch->carousel_size) continue; + + if (!ch->last_carousel_time) ch->last_carousel_time = now; + + if (ch->last_carousel_time + ch->carousel_period < time) { + to_send = ch; + time = ch->last_carousel_time + ch->carousel_period; + } + } + if (!to_send) { + if (timeout) *timeout = 0; + return NULL; + } + if (timeout) { + if (time>now) time-=now; + else time=0; + *timeout = time; + } + return to_send; +} + + +static void live_session_callback(void *calling_object, u16 ESID, u8 *data, u32 size, u64 ts) +{ + LiveSession *livesess = (LiveSession *) calling_object; + RTPChannel *rtpch; + u32 i=0; + + while ( (rtpch = (RTPChannel*)gf_list_enum(livesess->streams, &i))) { + if (rtpch->ESID == ESID) { + + /*store carousel data*/ + if (livesess->carousel_generation && rtpch->carousel_period) { + if (rtpch->carousel_alloc < size) { + rtpch->carousel_data = gf_realloc(rtpch->carousel_data, size); + rtpch->carousel_alloc = size; + } + memcpy(rtpch->carousel_data, data, size); + rtpch->carousel_size = size; + rtpch->carousel_ts = ts; + rtpch->time_at_carousel_store = gf_sys_clock(); + fprintf(stderr, "\nStream %d: Storing new carousel TS "LLD", %d bytes\n", ESID, ts, size); + } + /*send data*/ + else { + u32 critical = 0; + Bool rap = rtpch->rap; + if (livesess->carousel_generation) rap = 1; + ts += rtpch->timescale*((u64)gf_sys_clock()-rtpch->init_time + rtpch->ts_delta)/1000; + if (rtpch->critical) critical = rtpch->critical; + else if (livesess->critical) critical = 1; + + gf_rtp_streamer_send_au_with_sn(rtpch->rtp, data, size, ts, ts, rap, critical); + + fprintf(stderr, "Stream %d: Sending update at TS "LLD", %d bytes - RAP %d - critical %d\n", ESID, ts, size, rap, critical); + rtpch->rap = rtpch->critical = 0; + + if (rtpch->manual_rtcp) gf_rtp_streamer_send_rtcp(rtpch->rtp, 0, 0, 0, 0, 0); + } + return; + } + } +} + +static void live_session_send_carousel(LiveSession *livesess, RTPChannel *ch) +{ + u32 now = gf_sys_clock(); + u64 ts; + if (ch) { + if (ch->carousel_size) { + ts = ch->carousel_ts + ch->timescale * ( (ch->adjust_carousel_time ? (u64)gf_sys_clock() : ch->time_at_carousel_store) - ch->init_time + ch->ts_delta)/1000; + + gf_rtp_streamer_send_au_with_sn(ch->rtp, ch->carousel_data, ch->carousel_size, ts, ts, 1, 0); + ch->last_carousel_time = now - livesess->start_time; + fprintf(stderr, "Stream %d: Sending carousel at TS "LLD", %d bytes\n", ch->ESID, ts, ch->carousel_size); + + if (ch->manual_rtcp) { + ts = ch->carousel_ts + ch->timescale * ( gf_sys_clock() - ch->init_time + ch->ts_delta)/1000; + gf_rtp_streamer_send_rtcp(ch->rtp, 1, (u32) ts, 0, 0, 0); + } + } + } else { + u32 i=0; + while (NULL != (ch = gf_list_enum(livesess->streams, &i))) { + if (ch->carousel_size) { + if (ch->adjust_carousel_time) { + ts = ch->carousel_ts + ch->timescale*(gf_sys_clock()-ch->init_time + ch->ts_delta)/1000; + } else { + ts = ch->carousel_ts; + } + gf_rtp_streamer_send_au_with_sn(ch->rtp, ch->carousel_data, ch->carousel_size, ts, ts, 1, 0); + ch->last_carousel_time = now - livesess->start_time; + fprintf(stderr, "Stream %d: Sending carousel at TS "LLD" , %d bytes\n", ch->ESID, ts, ch->carousel_size); + + if (ch->manual_rtcp) { + ts = ch->carousel_ts + ch->timescale*(gf_sys_clock()-ch->init_time + ch->ts_delta)/1000; + gf_rtp_streamer_send_rtcp(ch->rtp, 1, (u32) ts, 0, 0, 0); + } + } + } + } +} + +static Bool live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 path_mtu, u32 ttl, char *ifce_addr, char *sdp_name) +{ + RTPChannel *rtpch; + u32 count = gf_seng_get_stream_count(livesess->seng); + u32 i; + char *iod64 = gf_seng_get_base64_iod(livesess->seng); + char *sdp = gf_rtp_streamer_format_sdp_header("GPACSceneStreamer", ip, NULL, iod64); + if (iod64) gf_free(iod64); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + GF_Descriptor *desc = gf_seng_get_iod(livesess->seng); + if (desc) gf_odf_desc_del(desc); + } +#endif + for (i=0; iseng, i, &ESID, &config, &config_len, &st, &oti, &ts); + + GF_SAFEALLOC(rtpch, RTPChannel); + if (!rtpch) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Cannot allocate rtp input handler\n")); + continue; + } + rtpch->timescale = ts; + rtpch->init_time = gf_sys_clock(); + + switch (st) { + case GF_STREAM_OD: + case GF_STREAM_SCENE: + rtpch->rtp = gf_rtp_streamer_new(st, oti, ts, ip, port, path_mtu, ttl, ifce_addr, + GP_RTP_PCK_SYSTEMS_CAROUSEL, config, config_len, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, GF_FALSE); + + if (rtpch->rtp) { + gf_rtp_streamer_disable_auto_rtcp(rtpch->rtp); + rtpch->manual_rtcp = 1; + } + break; + default: + rtpch->rtp = gf_rtp_streamer_new(st, oti, ts, ip, port, path_mtu, ttl, ifce_addr, GP_RTP_PCK_SIGNAL_RAP, config, config_len, 96, 0, 0, GF_FALSE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GF_FALSE); + break; + } + rtpch->ESID = ESID; + rtpch->adjust_carousel_time = 1; + gf_list_add(livesess->streams, rtpch); + + if (!rtpch->rtp) + return GF_FALSE; + + gf_rtp_streamer_append_sdp(rtpch->rtp, ESID, config, config_len, NULL, &sdp); + + /*fetch initial config of the broadcast*/ + gf_seng_get_stream_carousel_info(livesess->seng, ESID, &rtpch->carousel_period, &rtpch->aggregate_on_stream); + port += 2; + } + if (sdp) { + FILE *out = gf_fopen(sdp_name, "wt"); + fprintf(out, "%s", sdp); + gf_fclose(out); + gf_free(sdp); + } + return GF_TRUE; +} + +void live_session_shutdown(LiveSession *livesess) +{ + gf_seng_terminate(livesess->seng); + + if (livesess->streams) { + while (gf_list_count(livesess->streams)) { + RTPChannel *rtpch = gf_list_get(livesess->streams, 0); + gf_list_rem(livesess->streams, 0); + gf_rtp_streamer_del(rtpch->rtp); + if (rtpch->carousel_data) gf_free(rtpch->carousel_data); + gf_free(rtpch); + } + gf_list_del(livesess->streams); + } +} + + +static RTPChannel *set_broadcast_params(LiveSession *livesess, u16 esid, u32 period, u32 ts_delta, u16 aggregate_on_stream, Bool adjust_carousel_time, Bool force_rap, Bool aggregate_au, Bool discard_pending, Bool signal_rap, u32 signal_critical, Bool version_inc) +{ + RTPChannel *rtpch = NULL; + + /*locate our stream*/ + if (esid) { + u32 i=0; + while ( (rtpch = gf_list_enum(livesess->streams, &i))) { + if (rtpch->ESID == esid) break; + } + } else { + rtpch = gf_list_get(livesess->streams, 0); + } + + /*TODO - set/reset the ESID for the parsers*/ + if (!rtpch) return NULL; + + /*TODO - if discard is set, abort current carousel*/ + if (discard_pending) { + } + + /*remember RAP flag*/ + rtpch->rap = signal_rap; + rtpch->critical = signal_critical; + rtpch->m2ts_vers_inc = version_inc; + + rtpch->ts_delta = ts_delta; + rtpch->aggregate = aggregate_au; + rtpch->adjust_carousel_time = adjust_carousel_time; + + /*change stream aggregation mode*/ + if ((aggregate_on_stream != (u16)-1) && (rtpch->aggregate_on_stream != aggregate_on_stream)) { + gf_seng_enable_aggregation(livesess->seng, esid, aggregate_on_stream); + rtpch->aggregate_on_stream = aggregate_on_stream; + } + /*change stream aggregation mode*/ + if ((period!=(u32)-1) && (rtpch->carousel_period != period)) { + rtpch->carousel_period = period; + rtpch->last_carousel_time = 0; + } + + if (force_rap) { + livesess->force_carousel = 1; + } + return rtpch; +} + +int live_session(int argc, char **argv) +{ + GF_Err e; + u32 i; + char *filename = NULL; + char *dst = NULL; + const char *ifce_addr = NULL; + char *sdp_name = "session.sdp"; + u16 dst_port = 7000; + u32 load_type=0; + u32 check; + u32 ttl = 1; + u32 path_mtu = 1450; + s32 next_time; + u64 last_src_modif, mod_time, runfor=0, start_time; + char *src_name = NULL; + Bool run, has_carousel, no_rap; + Bool udp = 0; + u16 sk_port=0; + GF_Socket *sk = NULL; + LiveSession livesess; + RTPChannel *ch; + char *update_buffer = NULL; + u32 update_buffer_size = 0; + u16 aggregate_on_stream; + Bool adjust_carousel_time, force_rap, aggregate_au, discard_pending, signal_rap, version_inc; + Bool update_context; + u32 period, ts_delta, signal_critical; + u16 es_id; + e = GF_OK; + aggregate_au = 1; + es_id = 0; + no_rap = 0; + gf_sys_init(GF_MemTrackerNone, NULL); + + memset(&livesess, 0, sizeof(LiveSession)); + + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_INFO); + + gf_sys_set_args(argc, (const char **) argv); + + + for (i=1; i<(u32) argc; i++) { + char *arg = argv[i]; + if (arg[0] != '-') filename = arg; + else if (!strnicmp(arg, "-dst=", 5)) dst = arg+5; + else if (!strnicmp(arg, "-port=", 6)) dst_port = atoi(arg+6); + else if (!strnicmp(arg, "-sdp=", 5)) sdp_name = arg+5; + else if (!strnicmp(arg, "-mtu=", 5)) path_mtu = atoi(arg+5); + else if (!strnicmp(arg, "-ttl=", 5)) ttl = atoi(arg+5); + else if (!strnicmp(arg, "-no-rap", 7)) no_rap = 1; + else if (!strnicmp(arg, "-dims", 5)) load_type = GF_SM_LOAD_DIMS; + else if (!strnicmp(arg, "-src=", 5)) src_name = arg+5; + else if (!strnicmp(arg, "-udp=", 5)) { + sk_port = atoi(arg+5); + udp = 1; + } + else if (!strnicmp(arg, "-tcp=", 5)) { + sk_port = atoi(arg+5); + udp = 0; + } + else if (!stricmp(arg, "-run-for")) { + runfor = 1 + 1000 * atoi(argv[i+1]); + i++; + } + } + if (!filename) { + M4_LOG(GF_LOG_ERROR, ("Missing filename\n")); + PrintLiveUsage(); + return 1; + } + ifce_addr = gf_opts_get_key("core", "ifce"); + + if (dst_port && dst) livesess.streams = gf_list_new(); + + livesess.seng = gf_seng_init(&livesess, filename, load_type, NULL, (load_type == GF_SM_LOAD_DIMS) ? 1 : 0); + if (!livesess.seng) { + M4_LOG(GF_LOG_ERROR, ("Cannot create scene engine\n")); + return 1; + } + if (livesess.streams) { + Bool res = live_session_setup(&livesess, dst, dst_port, path_mtu, ttl, (char *) ifce_addr, sdp_name); + if (!res) { + live_session_shutdown(&livesess); + if (update_buffer) gf_free(update_buffer); + if (sk) gf_sk_del(sk); + gf_sys_close(); + return e ? 1 : 0; + } + } + + has_carousel = 0; + last_src_modif = src_name ? gf_file_modification_time(src_name) : 0; + + if (sk_port) { + sk = gf_sk_new(udp ? GF_SOCK_TYPE_UDP : GF_SOCK_TYPE_TCP); + if (udp) { + e = gf_sk_bind(sk, NULL, sk_port, NULL, 0, 0); + if (e != GF_OK) { + if (sk) gf_sk_del(sk); + sk = NULL; + } + } else { + } + } + + + for (i=0; i<(u32) argc; i++) { + char *arg = argv[i]; + if (!strnicmp(arg, "-rap=", 5)) { + u32 id, j; + period = id = 0; + if (strchr(arg, ':')) { + sscanf(arg, "-rap=ESID=%u:%u", &id, &period); + e = gf_seng_enable_aggregation(livesess.seng, id, 1); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Cannot enable aggregation on stream %u: %s\n", id, gf_error_to_string(e))); + goto exit; + } + } else { + sscanf(arg, "-rap=%u", &period); + } + + j=0; + while (NULL != (ch = gf_list_enum(livesess.streams, &j))) { + if (!id || (ch->ESID==id)) + ch->carousel_period = period; + } + has_carousel = 1; + } + } + + i=0; + while (NULL != (ch = gf_list_enum(livesess.streams, &i))) { + if (ch->carousel_period) { + has_carousel = 1; + break; + } + } + + update_context = 0; + + if (has_carousel || !no_rap) { + livesess.carousel_generation = 1; + gf_seng_encode_context(livesess.seng, live_session_callback); + livesess.carousel_generation = 0; + } + + live_session_send_carousel(&livesess, NULL); + + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + aggregate_on_stream = (u16) -1; + adjust_carousel_time = force_rap = discard_pending = signal_rap = signal_critical = 0; + aggregate_au = version_inc = 1; + period = -1; + ts_delta = 0; + es_id = 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); + } +#endif + + start_time = gf_sys_clock_high_res(); + check = 10; + run = 1; + while (run) { + check--; + if (!check) { + check = 10; + if (gf_prompt_has_input()) { + char c = gf_prompt_get_char(); + switch (c) { + case 'q': + run=0; + break; + case 'U': + case 'u': + { + char szCom[8192]; + fprintf(stderr, "Enter command to send:\n"); + szCom[0] = 0; + if (1 > scanf("%8191[^\t\n]", szCom)) { + 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); + 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)); + livesess.critical = (c=='U') ? 1 : 0; + update_context = 1; + } + break; + case 'E': + case 'e': + { + char szCom[8192]; + fprintf(stderr, "Enter command to send:\n"); + szCom[0] = 0; + if (1 > scanf("%8191[^\t\n]", szCom)) { + 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); + 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); + if (e) fprintf(stderr, "Aggregating context failed: %s\n", gf_error_to_string(e)); + + } + break; + + case 'p': + { + char rad[GF_MAX_PATH]; + fprintf(stderr, "Enter output file name - \"std\" for stderr: "); + if (1 > scanf("%1023s", rad)) { + fprintf(stderr, "No output file name entered, aborting.\n"); + break; + } + e = gf_seng_save_context(livesess.seng, !strcmp(rad, "std") ? NULL : rad); + fprintf(stderr, "Dump done (%s)\n", gf_error_to_string(e)); + } + break; + case 'F': + update_context = 1; + case 'f': + livesess.force_carousel = 1; + break; + } + e = GF_OK; + } + } + + /*process updates from file source*/ + if (src_name) { + mod_time = gf_file_modification_time(src_name); + if (mod_time != last_src_modif) { + FILE *srcf; + char flag_buf[201], *flag; + fprintf(stderr, "Update file modified - processing\n"); + last_src_modif = mod_time; + + srcf = gf_fopen(src_name, "rt"); + if (!srcf) continue; + + /*checks if we have a broadcast config*/ + if (!gf_fgets(flag_buf, 200, srcf)) + flag_buf[0] = '\0'; + gf_fclose(srcf); + + aggregate_on_stream = (u16) -1; + adjust_carousel_time = force_rap = discard_pending = signal_rap = signal_critical = 0; + aggregate_au = version_inc = 1; + period = -1; + ts_delta = 0; + es_id = 0; + + /*find our keyword*/ + flag = strstr(flag_buf, "gpac_broadcast_config "); + if (flag) { + flag += strlen("gpac_broadcast_config "); + /*move to next word*/ + while (flag[0]==' ') flag++; + + while (1) { + char *sep = strchr(flag, ' '); + if (sep) sep[0] = 0; + if (!strnicmp(flag, "esid=", 5)) es_id = atoi(flag+5); + else if (!strnicmp(flag, "period=", 7)) period = atoi(flag+7); + else if (!strnicmp(flag, "ts=", 3)) ts_delta = atoi(flag+3); + else if (!strnicmp(flag, "carousel=", 9)) aggregate_on_stream = atoi(flag+9); + else if (!strnicmp(flag, "restamp=", 8)) adjust_carousel_time = atoi(flag+8); + + else if (!strnicmp(flag, "discard=", 8)) discard_pending = atoi(flag+8); + else if (!strnicmp(flag, "aggregate=", 10)) aggregate_au = atoi(flag+10); + else if (!strnicmp(flag, "force_rap=", 10)) force_rap = atoi(flag+10); + else if (!strnicmp(flag, "rap=", 4)) signal_rap = atoi(flag+4); + else if (!strnicmp(flag, "critical=", 9)) signal_critical = atoi(flag+9); + else if (!strnicmp(flag, "vers_inc=", 9)) version_inc = atoi(flag+9); + if (sep) { + sep[0] = ' '; + flag = sep+1; + } else { + break; + } + } + + 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); + } + + e = gf_seng_encode_from_file(livesess.seng, es_id, aggregate_au ? 0 : 1, src_name, 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); + + update_context = no_rap ? 0 : 1; + } + } + + /*process updates from socket source*/ + if (sk) { + u8 buffer[2049]; + u32 bytes_read; + u32 update_length; + u32 bytes_received; + + + e = gf_sk_receive(sk, buffer, 2048, &bytes_read); + if (e == GF_OK) { + u32 hdr_length = 0; + u8 cmd_type = buffer[0]; + bytes_received = 0; + switch (cmd_type) { + case 0: + { + GF_BitStream *bs = gf_bs_new(buffer, bytes_read, GF_BITSTREAM_READ); + gf_bs_read_u8(bs); + es_id = gf_bs_read_u16(bs); + aggregate_on_stream = gf_bs_read_u16(bs); + if (aggregate_on_stream==0xFFFF) aggregate_on_stream = -1; + adjust_carousel_time = gf_bs_read_int(bs, 1); + force_rap = gf_bs_read_int(bs, 1); + aggregate_au = gf_bs_read_int(bs, 1); + discard_pending = gf_bs_read_int(bs, 1); + signal_rap = gf_bs_read_int(bs, 1); + signal_critical = gf_bs_read_int(bs, 1); + version_inc = gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 1); + period = gf_bs_read_u16(bs); + if (period==0xFFFF) period = -1; + ts_delta = gf_bs_read_u16(bs); + update_length = gf_bs_read_u32(bs); + hdr_length = 12; + gf_bs_del(bs); + } + + 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); + break; + default: + update_length = 0; + break; + } + + if (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; + } + while (bytes_received start_time+runfor)) + break; + + + if (!has_carousel) { + gf_sleep(10); + continue; + } + ch = next_carousel(&livesess, (u32 *) &next_time); + if ((ch==NULL) || (next_time > 20)) { + gf_sleep(20); + continue; + } + if (next_time) gf_sleep(next_time); + live_session_send_carousel(&livesess, ch); + } + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { +/* gf_seng_save_context(livesess.seng, NULL); + gf_seng_aggregate_context + gf_seng_encode_from_string + gf_seng_encode_from_file +*/ + } +#endif + +exit: + live_session_shutdown(&livesess); + if (update_buffer) gf_free(update_buffer); + if (sk) gf_sk_del(sk); + gf_sys_close(); + return e ? 1 : 0; +} + + +#endif /*!defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG)*/ + +#endif /*defined(GPAC_DISABLE_ISOM) || defined(GPAC_DISABLE_ISOM_WRITE)*/ diff --git a/applications/mp4box/main.c b/applications/mp4box/main.c new file mode 100644 index 0000000..1b3fb4f --- /dev/null +++ b/applications/mp4box/main.c @@ -0,0 +1,6588 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / mp4box application + * + * 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 "mp4box.h" + +#ifdef GPAC_DISABLE_ISOM + +#error "Cannot compile MP4Box if GPAC is not built with ISO File Format support" + +#else + +#if defined(WIN32) && !defined(_WIN32_WCE) +#include +#include +#endif + +#include +#include + +/*RTP packetizer flags*/ +#ifndef GPAC_DISABLE_STREAMING +#include +#endif + +#ifndef GPAC_DISABLE_CRYPTO +#include +#endif + +#include +#include + +#include + +#define BUFFSIZE 8192 +#define DEFAULT_INTERLEAVING_IN_SEC 0.5 + +//undefine to check validity of defined args' syntax +//#define TEST_ARGS + + +int mp4boxTerminal(int argc, char **argv); + +static u32 mp4box_cleanup(u32 ret_code); + +/* + * START OF ARGUMENT VALUES DECLARATION + */ + + + +typedef struct +{ + GF_ISOTrackID trackID; + char *line; +} SDPLine; + +typedef enum { + META_ACTION_SET_TYPE = 0, + META_ACTION_ADD_ITEM = 1, + META_ACTION_REM_ITEM = 2, + META_ACTION_SET_PRIMARY_ITEM = 3, + META_ACTION_SET_XML = 4, + META_ACTION_SET_BINARY_XML = 5, + META_ACTION_REM_XML = 6, + META_ACTION_DUMP_ITEM = 7, + META_ACTION_DUMP_XML = 8, + META_ACTION_ADD_IMAGE_ITEM = 9, + META_ACTION_ADD_IMAGE_DERIVED = 10, +} MetaActionType; + +typedef struct { + u32 ref_item_id; + u32 ref_type; +} MetaRef; + +typedef struct +{ + MetaActionType act_type; + Bool root_meta, use_dref; + GF_ISOTrackID trackID; + u32 meta_4cc; + char *szPath, *szName, *mime_type, *enc_type, *keep_props; + u32 item_id; + Bool primary; + Bool replace; + u32 item_type; + u32 ref_item_id; + GF_List *item_refs; + u32 group_id; + u32 group_type; + GF_ImageItemProperties *image_props; +} MetaAction; + + +typedef enum { + 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_MEDIA_TIME, +} TrackActionType; + +typedef struct +{ + TrackActionType act_type; + GF_ISOTrackID trackID; + char lang[10]; + GF_Fraction delay; + const char *kms; + const char *hdl_name; + s32 par_num, par_den; + u8 force_par, rewrite_bs; + u32 dump_type, sample_num; + char *out_name; + char *src_name; + char *string; + u32 udta_type; + char *kind_scheme, *kind_value; + u32 newTrackID; + s32 clap_wnum, clap_wden, clap_hnum, clap_hden, clap_honum, clap_hoden, clap_vonum, clap_voden; + s32 mx[9]; + u64 time; + u8 dump_track_type; +} TrackAction; + +enum +{ + GF_ISOM_CONV_TYPE_ISMA = 1, + GF_ISOM_CONV_TYPE_ISMA_EX, + GF_ISOM_CONV_TYPE_3GPP, + GF_ISOM_CONV_TYPE_IPOD, + GF_ISOM_CONV_TYPE_PSP, + GF_ISOM_CONV_TYPE_MOV +}; + +typedef enum { + TSEL_ACTION_SET_PARAM = 0, + TSEL_ACTION_REMOVE_TSEL = 1, + TSEL_ACTION_REMOVE_ALL_TSEL_IN_GROUP = 2, +} TSELActionType; + +typedef struct +{ + TSELActionType act_type; + GF_ISOTrackID trackID; + + GF_ISOTrackID refTrackID; + u32 criteria[30]; + u32 nb_criteria; + Bool is_switchGroup; + u32 switchGroupID; +} TSELAction; + +GF_FileType get_file_type_by_ext(char *inName); + + +char outfile[GF_MAX_PATH]; +#ifndef GPAC_DISABLE_SCENE_ENCODER +GF_SMEncodeOptions smenc_opts; +u32 swf_flags = GF_SM_SWF_SPLIT_TIMELINE; +#endif +GF_Fraction import_fps; + +//things to free upon cleanup +MetaAction *metas = NULL; +TrackAction *tracks = NULL; +TSELAction *tsel_acts = NULL; +SDPLine *sdp_lines = NULL; +u32 *brand_add = NULL; +u32 *brand_rem = NULL; +char **mpd_base_urls = NULL; +u32 nb_mpd_base_urls = 0; +GF_DashSegmenterInput *dash_inputs = NULL; +u32 nb_dash_inputs = 0; + + + +//all other options values - keep options with assignment other than 0 on a single line + +FILE *helpout = NULL; +u32 help_flags = 0; + +Double interleaving_time=0.0, split_duration=0.0, split_start=-1.0, dash_duration=0.0, dash_subduration=0.0, swf_flatten_angle=0.0; +Bool dash_duration_strict=0, dvbhdemux=0, keep_sys_tracks=0; + +u64 initial_tfdt=0; +s32 subsegs_per_sidx=0, laser_resolution=0, ast_offset_ms=0; +const char *split_range_str = NULL; +GF_DashSwitchingMode bitstream_switching_mode = GF_DASH_BSMODE_DEFAULT; +u32 stat_level=0, hint_flags=0, info_track_id=0, import_flags=0, nb_add=0, nb_cat=0, crypt=0, agg_samples=0, nb_sdp_ex=0, max_ptime=0, split_size=0, nb_meta_act=0, nb_track_act=0, rtp_rate=0, major_brand=0, nb_alt_brand_add=0, nb_alt_brand_rem=0, old_interleave=0, minor_version=0, conv_type=0, nb_tsel_acts=0, program_number=0, dump_nal=0, time_shift_depth=0, initial_moof_sn=0, dump_std=0, import_subtitle=0, dump_saps=0, dump_saps_mode=0, force_new=0; +GF_DashDynamicMode dash_mode=GF_DASH_STATIC; +#ifndef GPAC_DISABLE_SCENE_DUMP +GF_SceneDumpFormat dump_mode=GF_SM_DUMP_NONE; +#endif + +/*align cat is the new default behavior for -cat*/ +Bool align_cat=GF_TRUE; + +Double mpd_live_duration=0; +Bool do_hint=0, do_save=0, full_interleave=0, do_frag=0, hint_interleave=0, dump_rtp=0, regular_iod=0, remove_sys_tracks=0, remove_hint=0, remove_root_od=0; +Bool print_sdp=0, open_edit=0, dump_cr=0, force_ocr=0, encode=0, do_scene_log=0, dump_srt=0, dump_ttxt=0, do_saf=0, dump_m2ts=0, dump_cart=0, dump_chunk=0, dump_check_xml=0; +Bool do_hash=0, verbose=0, force_cat=0, pack_wgt=0, single_group=0, clean_groups=0, dash_live=0, no_fragments_defaults=0; +Bool single_traf_per_moof=0, tfdt_per_traf=0, hls_clock=0, do_mpd_rip=0, merge_vtt_cues=0, get_nb_tracks=0; +u32 compress_moov=0; +char *inName=NULL, *outName=NULL, *mediaSource=NULL, *input_ctx=NULL, *output_ctx=NULL, *drm_file=NULL, *avi2raw=NULL, *cprt=NULL; +char *chap_file=NULL, *chap_file_qt=NULL, *itunes_tags=NULL, *pack_file=NULL, *raw_cat=NULL, *seg_name=NULL, *dash_ctx_file=NULL; +char *compress_top_boxes=NULL, *high_dynamc_range_filename=NULL, *use_init_seg=NULL, *box_patch_filename=NULL, *udp_dest = NULL; + +GF_ISOTrackID trackID=0; +u32 track_dump_type=0, dump_isom=0, dump_timestamps=0, dump_nal_type=0, do_flat=0, box_patch_trackID=0, print_info=0; +Bool no_inplace=0, merge_last_seg=0, freeze_box_order=0, no_odf_conf=0; +Double min_buffer = 1.5; +u32 size_top_box=0, fs_dump_flags=0, dump_chap=0, dump_udta_type=0, dump_udta_track=0, moov_pading=0, sdtp_in_traf=0, segment_marker=0, timescale=0; + +u32 dash_scale = 1000; +GF_ISOFile *file = NULL; + +Bool insert_utc=0, chunk_mode=0, HintCopy=0, hint_no_offset=0, do_bin_xml=0, frag_real_time=0, force_co64=0, live_scene=0, use_mfra=0; +Bool dump_iod=0, samplegroups_in_traf=0, mvex_after_traks=0, daisy_chain_sidx=0, use_ssix=0, single_segment=0, single_file=0, segment_timeline=0; +Bool has_add_image=0; + +char *do_mpd_conv=NULL; +u32 MTUSize = 1450; +char *dash_start_date=NULL; +GF_DASH_ContentLocationMode cp_location_mode = GF_DASH_CPMODE_ADAPTATION_SET; +Double mpd_update_time = 0.0; +GF_MemTrackerType mem_track = GF_MemTrackerNone; +GF_DASHPSSHMode pssh_mode=0; + +GF_DashProfile dash_profile=GF_DASH_PROFILE_AUTO; +char *dash_profile_extension = NULL; +char *dash_cues = NULL; +Bool strict_cues=0, use_url_template=0, seg_at_rap=0, frag_at_rap=0, memory_frags=0, keep_utc=0, has_next_arg=0, no_cache=0, no_loop=0; +u32 adjust_split_end=0; +char *do_wget = NULL; +char *mux_name = NULL; +char *seg_ext = NULL; +char *init_seg_ext = NULL; +char *dash_title = NULL; +char *dash_source = NULL; +char *dash_more_info = NULL; + +FILE *logfile = NULL; +u32 run_for=0, dash_cumulated_time=0, dash_prev_time=0, dash_now_time=0; +GF_DASH_SplitMode dash_split_mode = GF_DASH_SPLIT_OUT; + + +typedef u32 (*parse_arg_fun)(char *arg_val, u32 param); +typedef u32 (*parse_arg_fun2)(char *arg_name, char *arg_val, u32 param); + +//other custom option parsing functions definitions are in mp4box.h +static u32 parse_meta_args(char *opts, MetaActionType act_type); +static Bool parse_tsel_args(char *opts, TSELActionType act); + + + +/* + * START OF ARGS PARSING AND HELP + */ + + +Bool print_version(char *arg_val, u32 param) +{ + fprintf(stderr, "MP4Box - GPAC version %s\n" + "%s\n" + "GPAC Configuration: " GPAC_CONFIGURATION "\n" + "Features: %s %s\n", gf_gpac_version(), gf_gpac_copyright_cite(), gf_sys_features(GF_FALSE), gf_sys_features(GF_TRUE)); + return GF_TRUE; +} + +//arg will toggle open_edit +#define ARG_OPEN_EDIT 1 +//arg will toggle do_save +#define ARG_NEED_SAVE 1<<1 +#define ARG_NO_INPLACE 1<<2 +#define ARG_BIT_MASK 1<<3 +#define ARG_BIT_MASK_REM 1<<4 +#define ARG_HAS_VALUE 1<<5 +#define ARG_DIV_1000 1<<6 +#define ARG_NON_ZERO 1<<7 +#define ARG_64BITS 1<<8 +#define ARG_IS_4CC 1<<9 +#define ARG_BOOL_REV 1<<10 +#define ARG_INT_INC 1<<11 +#define ARG_IS_FUN 1<<12 +#define ARG_EMPTY 1<<13 +#define ARG_PUSH_SYSARGS 1<<14 +#define ARG_IS_FUN2 1<<15 + + + +typedef struct +{ + GF_GPAC_ARG_BASE + + void *arg_ptr; + u32 argv_val; + u16 parse_flags; +} MP4BoxArg; + +#define MP4BOX_ARG(_a, _c, _f, _g, _h, _i, _j) {_a, NULL, _c, NULL, NULL, _f, _g, _h, _i, _j} +#define MP4BOX_ARG_ALT(_a, _b, _c, _f, _g, _h, _i, _j) {_a, _b, _c, NULL, NULL, _f, _g, _h, _i, _j} +#define MP4BOX_ARG_S(_a, _s, _c, _g, _h, _i, _j) {_a, NULL, _c, _s, NULL, GF_ARG_CUSTOM, _g, _h, _i, _j} +#define MP4BOX_ARG_S_ALT(_a, _b, _s, _c, _g, _h, _i, _j) {_a, _b, _c, _s, NULL, GF_ARG_CUSTOM, _g, _h, _i, _j} + + +MP4BoxArg m4b_gen_args[] = +{ +#ifdef GPAC_MEMORY_TRACKING + MP4BOX_ARG("mem-track", "enable memory tracker", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, NULL, 0, 0), + MP4BOX_ARG("mem-track-stack", "enable memory tracker with stack dumping", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, NULL, 0, 0), +#endif + MP4BOX_ARG("p", "use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. Reserved name `0` means a new profile, not stored to disk. Works using -p=NAME or -p NAME", GF_ARG_STRING, GF_ARG_HINT_EXPERT, NULL, 0, 0), + {"inter", NULL, "interleave file, producing track chunks with given duration in ms. A value of 0 disables interleaving ", "0.5", NULL, GF_ARG_DOUBLE, 0, parse_store_mode, 0, ARG_IS_FUN}, + MP4BOX_ARG("old-inter", "same as [-inter]() but without drift correction", GF_ARG_DOUBLE, GF_ARG_HINT_EXPERT, parse_store_mode, 1, ARG_IS_FUN), + MP4BOX_ARG("tight", "tight interleaving (sample based) of the file. This reduces disk seek operations but increases file size", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &full_interleave, 0, ARG_OPEN_EDIT|ARG_NEED_SAVE), + MP4BOX_ARG("flat", "store file with all media data first, non-interleaved. This speeds up writing time when creating new files", GF_ARG_BOOL, 0, &do_flat, 0, ARG_NO_INPLACE), + MP4BOX_ARG("frag", "fragment file, producing track fragments of given duration in ms. This disables interleaving", GF_ARG_DOUBLE, 0, parse_store_mode, 2, ARG_IS_FUN), + MP4BOX_ARG("out", "specify ISOBMFF output file name. By default input file is overwritten", GF_ARG_STRING, 0, &outName, 0, 0), + MP4BOX_ARG("co64","force usage of 64-bit chunk offsets for ISOBMF files", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &force_co64, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("new", "force creation of a new destination file", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &force_new, 0, 0), + MP4BOX_ARG("newfs", "force creation of a new destination file without temp file but interleaving support", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, parse_store_mode, 3, ARG_IS_FUN), + MP4BOX_ARG_ALT("no-sys", "nosys", "remove all MPEG-4 Systems info except IOD, kept for profiles. This is the default when creating regular AV content", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &remove_sys_tracks, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("no-iod", "remove MPEG-4 InitialObjectDescriptor from file", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &remove_root_od, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("mfra", "insert movie fragment random offset when fragmenting file (ignored in dash mode)", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &use_mfra, 0, 0), + MP4BOX_ARG("isma", "rewrite the file as an ISMA 1.0 file", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &conv_type, GF_ISOM_CONV_TYPE_ISMA, ARG_OPEN_EDIT), + MP4BOX_ARG("ismax", "same as [-isma]() and remove all clock references", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &conv_type, GF_ISOM_CONV_TYPE_ISMA_EX, ARG_OPEN_EDIT), + MP4BOX_ARG("3gp", "rewrite as 3GPP(2) file (no more MPEG-4 Systems Info), always enabled if destination file extension is `.3gp`, `.3g2` or `.3gpp`. Some tracks may be removed in the process", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &conv_type, GF_ISOM_CONV_TYPE_3GPP, ARG_OPEN_EDIT), + MP4BOX_ARG("ipod", "rewrite the file for iPod/old iTunes", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &conv_type, GF_ISOM_CONV_TYPE_IPOD, ARG_OPEN_EDIT), + MP4BOX_ARG("psp", "rewrite the file for PSP devices", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &conv_type, GF_ISOM_CONV_TYPE_PSP, ARG_OPEN_EDIT), + MP4BOX_ARG("brand", "set major brand of file (`ABCD`) or brand with optional version (`ABCD:v`)", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, parse_brand, 0, ARG_IS_FUN), + MP4BOX_ARG("ab", "add given brand to file's alternate brand list", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, parse_brand, 1, ARG_IS_FUN), + MP4BOX_ARG("rb", "remove given brand to file's alternate brand list", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, parse_brand, 2, ARG_IS_FUN), + MP4BOX_ARG("cprt", "add copyright string to file", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, &cprt, 0, 0), + MP4BOX_ARG("chap", "set chapter information from given file. The following formats are supported (but cannot be mixed) in the chapter text file:\n" + " - ZoomPlayer: `AddChapter(nb_frames,chapter name)`, `AddChapterBySeconds(nb_sec,chapter name)` and `AddChapterByTime(h,m,s,chapter name)` with 1 chapter per line\n" + " - Time codes: `h:m:s chapter_name`, `h:m:s:ms chapter_name` and `h:m:s.ms chapter_name` with 1 chapter per line\n" + " - SMPTE codes: `h:m:s;nb_f/fps chapter_name` and `h:m:s;nb_f chapter_name` with `nb_f` the number of frames and `fps` the framerate with 1 chapter per line\n" + " - Common syntax: `CHAPTERX=h:m:s[:ms or .ms]` on first line and `CHAPTERXNAME=name` on next line (reverse order accepted)", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, &chap_file, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("chapqt", "set chapter information from given file, using QT signaling for text tracks", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, &chap_file_qt, 0, ARG_OPEN_EDIT), + MP4BOX_ARG_S("set-track-id", "id1:id2", "change id of track with id1 to id2", 0, parse_track_action, TRACK_ACTION_SET_ID, ARG_IS_FUN), + MP4BOX_ARG_S("swap-track-id", "id1:id2", "swap the id between tracks with id1 to id2", 0, parse_track_action, TRACK_ACTION_SWAP_ID, ARG_IS_FUN), + MP4BOX_ARG("rem", "remove given track from file", GF_ARG_INT, 0, parse_track_action, TRACK_ACTION_REM_TRACK, ARG_IS_FUN), + MP4BOX_ARG("rap", "remove all non-RAP samples from given track", GF_ARG_INT, GF_ARG_HINT_ADVANCED, parse_rap_ref, 0, ARG_IS_FUN | ARG_EMPTY), + MP4BOX_ARG("refonly", "remove all non-reference pictures from given track", GF_ARG_INT, GF_ARG_HINT_ADVANCED, parse_rap_ref, 1, ARG_IS_FUN | ARG_EMPTY), + MP4BOX_ARG("enable", "enable given track", GF_ARG_INT, 0, parse_track_action, TRACK_ACTION_ENABLE, ARG_IS_FUN), + MP4BOX_ARG("disable", "disable given track", GF_ARG_INT, 0, parse_track_action, TRACK_ACTION_DISABLE, ARG_IS_FUN), + {"timescale", NULL, "set movie timescale to given value (ticks per second)", "600", NULL, GF_ARG_INT, 0, ×cale, 0, ARG_OPEN_EDIT}, + MP4BOX_ARG_S("lang", "[tkID=]LAN", "set language. LAN is the BCP-47 code (eng, en-UK, ...). If no track ID is given, sets language to all tracks", 0, parse_track_action, TRACK_ACTION_SET_LANGUAGE, ARG_IS_FUN), + MP4BOX_ARG_S("delay", "tkID=TIME", "set track start delay (>0) or initial skip (<0) in ms or in fractional seconds (`N/D`)", 0, parse_track_action, TRACK_ACTION_SET_DELAY, ARG_IS_FUN), + MP4BOX_ARG_S("par", "tkID=PAR", "set visual track pixel aspect ratio. PAR is:\n" + " - N:D: set PAR to N:D in track, do not modify the bitstream\n" + " - wN:D: set PAR to N:D in track and try to modify the bitstream\n" + " - none: remove PAR info from track, do not modify the bitstream\n" + " - auto: retrieve PAR info from bitstream and set it in track\n" + " - force: force 1:1 PAR in track, do not modify the bitstream", GF_ARG_HINT_ADVANCED, parse_track_action, TRACK_ACTION_SET_PAR, ARG_IS_FUN + ), + MP4BOX_ARG_S("clap", "tkID=CLAP", "set visual track clean aperture. CLAP is `Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd` or `none`\n" + "- n, d: numerator, denominator\n" + "- W, H, HO, VO: clap width, clap height, clap horizontal offset, clap vertical offset\n" + , GF_ARG_HINT_ADVANCED, parse_track_action, TRACK_ACTION_SET_CLAP, ARG_IS_FUN), + MP4BOX_ARG_S("mx", "tkID=MX", "set track matrix, with MX is M1:M2:M3:M4:M5:M6:M7:M8:M9 in 16.16 fixed point integers or hexa" + , GF_ARG_HINT_ADVANCED, parse_track_action, TRACK_ACTION_SET_MX, ARG_IS_FUN), + MP4BOX_ARG_S("kind", "tkID=schemeURI=value", "set kind for the track or for all tracks using `all=schemeURI=value`", 0, parse_track_action, TRACK_ACTION_SET_KIND, ARG_IS_FUN), + MP4BOX_ARG_S("kind-rem", "tkID=schemeURI=value", "remove kind if given schemeID for the track or for all tracks with `all=schemeURI=value`", 0, parse_track_action, TRACK_ACTION_REM_KIND, ARG_IS_FUN), + MP4BOX_ARG_S("name", "tkID=NAME", "set track handler name to NAME (UTF-8 string)", GF_ARG_HINT_ADVANCED, parse_track_action, TRACK_ACTION_SET_HANDLER_NAME, ARG_IS_FUN), + MP4BOX_ARG("itags", "set iTunes tags to file, see `-h tags`", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, &itunes_tags, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("group-add", "create a new grouping information in the file. Format is a colon-separated list of following options:\n" + "- refTrack=ID: ID of the track used as a group reference. If not set, the track will belong to the same group as the " + "previous trackID specified. If 0 or no previous track specified, a new alternate group will be created\n" + "- switchID=ID: ID of the switch group to create. If 0, a new ID will be computed for you. If <0, disables SwitchGroup\n" + "- criteria=string: list of space-separated 4CCs\n" + "- trackID=ID: ID of the track to add to this group\n" + " \n" + "Warning: Options modify state as they are parsed, `trackID=1:criteria=lang:trackID=2` is different from `criteria=lang:trackID=1:trackID=2`" + "\n", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, parse_tsel_args, TSEL_ACTION_SET_PARAM, ARG_IS_FUN), + + MP4BOX_ARG("group-rem-track", "remove given track from its group", GF_ARG_INT, GF_ARG_HINT_ADVANCED, parse_tsel_args, TSEL_ACTION_REMOVE_TSEL, ARG_IS_FUN), + MP4BOX_ARG("group-rem", "remove the track's group", GF_ARG_INT, GF_ARG_HINT_ADVANCED, parse_tsel_args, TSEL_ACTION_REMOVE_ALL_TSEL_IN_GROUP, ARG_IS_FUN), + MP4BOX_ARG("group-clean", "remove all group information from all tracks", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &clean_groups, 0, ARG_OPEN_EDIT), + MP4BOX_ARG_S("ref", "id:XXXX:refID", "add a reference of type 4CC from track ID to track refID", GF_ARG_HINT_ADVANCED, parse_track_action, TRACK_ACTION_REFERENCE, ARG_IS_FUN), + MP4BOX_ARG("keep-utc", "keep UTC timing in the file after edit", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &keep_utc, 0, 0), + MP4BOX_ARG_S("udta", "tkID:[OPTS]", "set udta for given track or movie if tkID is 0. OPTS is a colon separated list of:\n" + "- type=CODE: 4CC code of the UDTA (not needed for `box=` option)\n" + "- box=FILE: location of the udta data, formatted as serialized boxes\n" + "- box=base64,DATA: base64 encoded udta data, formatted as serialized boxes\n" + "- src=FILE: location of the udta data (will be stored in a single box of type CODE)\n" + "- src=base64,DATA: base64 encoded udta data (will be stored in a single box of type CODE)\n" + "- str=STRING: use the given string as payload for the udta box\n" + "Note: If no source is set, UDTA of type CODE will be removed\n", GF_ARG_HINT_ADVANCED, parse_track_action, TRACK_ACTION_SET_UDTA, ARG_IS_FUN|ARG_OPEN_EDIT), + MP4BOX_ARG_S("patch", "[tkID=]FILE", "apply box patch described in FILE, for given trackID if set", GF_ARG_HINT_ADVANCED, parse_boxpatch, 0, ARG_IS_FUN), + MP4BOX_ARG("bo", "freeze the order of boxes in input file", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, &freeze_box_order, 0, 0), + MP4BOX_ARG("init-seg", "use the given file as an init segment for dumping or for encryption", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, &use_init_seg, 0, 0), + MP4BOX_ARG("zmov", "compress movie box according to ISOBMFF box compression", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, parse_compress, 0, ARG_IS_FUN), + MP4BOX_ARG("xmov", "same as zmov and wraps ftyp in otyp", GF_ARG_BOOL, GF_ARG_HINT_ADVANCED, parse_compress, 1, ARG_IS_FUN), + MP4BOX_ARG_S("edits", "tkID=EDITS", "set edit list. The following syntax is used (no separators between entries):\n" + " - `r`: removes all edits\n" + " - `eSTART`: add empty edit with given start time. START can be\n" + " - `VAL`: start time in seconds (int, double, fraction), media duration used as edit duration\n" + " - `VAL-DUR`: start time and duration in seconds (int, double, fraction)\n" + " - `eSTART,MEDIA[,RATE]`: add regular edit with given start, media start time in seconds (int, double, fraction) and rate (fraction or INT)\n" + " - Examples: \n" + " - `re0-5e5-3,4`: remove edits, add empty edit at 0s for 5s, then add regular edit at 5s for 3s starting at 4s in media track\n" + " - `re0-4,0,0.5`: remove edits, add single edit at 0s for 4s starting at 0s in media track and playing at speed 0.5\n" + , 0, parse_track_action, TRACK_ACTION_SET_EDITS, ARG_IS_FUN), + MP4BOX_ARG("moovpad", "specify amount of padding to keep after moov box for later inplace editing - if 0, moov padding is disabled", GF_ARG_INT, GF_ARG_HINT_EXPERT, &moov_pading, 0, ARG_NEED_SAVE), + MP4BOX_ARG("no-inplace", "disable inplace rewrite", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &no_inplace, 0, 0), + MP4BOX_ARG("hdr", "update HDR information based on given XML, 'none' removes HDR info", GF_ARG_STRING, GF_ARG_HINT_EXPERT, &high_dynamc_range_filename, 0, ARG_OPEN_EDIT), + MP4BOX_ARG_S("time", "[tkID=]DAY/MONTH/YEAR-H:M:S", "set movie or track creation time", GF_ARG_HINT_EXPERT, parse_track_action, TRACK_ACTION_SET_TIME, ARG_IS_FUN), + MP4BOX_ARG_S("mtime", "tkID=DAY/MONTH/YEAR-H:M:S", "set media creation time", GF_ARG_HINT_EXPERT, parse_track_action, TRACK_ACTION_SET_MEDIA_TIME, ARG_IS_FUN), + {0} +}; + +void PrintGeneralUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# General Options\n" + "MP4Box is a multimedia packager, with a vast number of functionalities: conversion, splitting, hinting, dumping, DASH-ing, encryption, transcoding and others.\n" + "MP4Box provides a large set of options, classified by categories (see [-h]()). These options do not follow any particular ordering.\n" + " \n" + "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" + " \n" + "Option values:\n" + "Unless specified otherwise, an option of type `integer` expects a trackID value following it." + "An option of type `boolean` expects no following value." + "Note: Track operations identify tracks through their ID (usually referred to as tkID in the help), not their order.\n" + " \n" + ); + + + while (m4b_gen_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_gen_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-gen"); + } +} + + +MP4BoxArg m4b_split_args[] = +{ + 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-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), + MP4BOX_ARG_ALT("split-chunk", "splitx", "extract the specified time range as follows:\n" + "- the start time is moved to the RAP sample closest to the specified start time\n" + "- the end time is kept as requested" + , GF_ARG_STRING, 0, parse_split, 3, ARG_IS_FUN), + MP4BOX_ARG("splitz", "extract the specified time range so that ranges `A:B` and `B:C` share exactly the same boundary `B`:\n" + "- the start time is moved to the RAP sample at or after the specified start time\n" + "- the end time is moved to the frame preceding the RAP sample at or following the specified end time" + , GF_ARG_STRING, 0, parse_split, 4, ARG_IS_FUN), + MP4BOX_ARG("splitg", "extract the specified time range as follows:\n" + "- the start time is moved to the RAP sample at or before the specified start time\n" + "- the end time is moved to the frame preceding the RAP sample at or following the specified end time" + , GF_ARG_STRING, 0, parse_split, 5, ARG_IS_FUN), + MP4BOX_ARG("splitf", "extract the specified time range and insert edits such that the extracted output is exactly the specified range\n", GF_ARG_STRING, 0, parse_split, 6, ARG_IS_FUN), + {0} +}; + + +static void PrintSplitUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, " \n" + "# File splitting\n" + "MP4Box can split input files by size, duration or extract a given part of the file to new IsoMedia file(s).\n" + "This requires that at most one track in the input file has non random-access points (typically one video track at most).\n" + "Splitting will ignore all MPEG-4 Systems tracks and hint tracks, but will try to split private media tracks.\n" + "The input file must have enough random access points in order to be split. If this is not the case, you will have to re-encode the content.\n" + "You can add media to a file and split it in the same pass. In this case, the destination file (the one which would be obtained without splitting) will not be stored.\n" + " \n" + "Time ranges are specified as follows:\n" + "- `S-E`: `S` start and `E` end times, formatted as `HH:MM:SS.ms`, `MM:SS.ms` or time in seconds (int, double, fraction)\n" + "- `S:E`: `S` start time and `E` end times in seconds (int, double, fraction)\n" + "- `S:end` or `S:end-N`: `S` start time in seconds (int, double), `N` number of seconds (int, double) before the end\n" + " \n" + "MP4Box splitting runs a filter session using the `reframer` filter as follows:\n" + "- `splitrange` option of the reframer is always set\n" + "- source is demultiplexed with `alltk` option set\n" + "- start and end ranges are passed to `xs` and `xe` options of the reframer\n" + "- for `-splitz`, options `xadjust` and `xround=after` are enforced\n" + "- for `-splitg`, options `xadjust` and `xround=before` are enforced\n" + "- for `-splitf`, option `xround=seek` is enforced and `propbe_ref`set if not specified at prompt\n" + "- for `-splitx`, option `xround=closest` and `propbe_ref` are enforced if not specified at prompt\n" + " \n" + "The default output storage mode is to full interleave and will require a temp file for each output. This behavior can be modified using `-flat`, `-newfs`, `-inter` and `-frag`.\n" + "The output file name(s) can be specified using `-out` and templates (e.g. `-out split$num%%04d$.mp4` produces split0001.mp4, split0002.mp4, ...).\n" + " \n" + ); + + i=0; + while (m4b_split_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_split_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-split"); + } + +} + + +MP4BoxArg m4b_dash_args[] = +{ + MP4BOX_ARG ("dash", "create DASH from input files with given segment (subsegment for onDemand profile) duration in ms", GF_ARG_DOUBLE, 0, &dash_duration, 0, ARG_NON_ZERO), + MP4BOX_ARG("dash-live", "generate a live DASH session using the given segment duration in ms; using `-dash-live=F` will also write the live context to `F`. MP4Box will run the live session until `q` is pressed or a fatal error occurs", GF_ARG_DOUBLE, 0, parse_dashlive, 0, ARG_IS_FUN2), + MP4BOX_ARG("ddbg-live", "same as [-dash-live]() without time regulation for debug purposes", GF_ARG_DOUBLE, 0, parse_dashlive, 1, ARG_IS_FUN2), + MP4BOX_ARG("frag", "specify the fragment duration in ms. If not set, this is the DASH duration (one fragment per segment)", GF_ARG_DOUBLE, 0, parse_store_mode, 2, ARG_IS_FUN), + MP4BOX_ARG("out", "specify the output MPD file name", GF_ARG_STRING, 0, &outName, 0, 0), + MP4BOX_ARG_ALT("profile", "dash-profile", "specify the target DASH profile, and set default options to ensure conformance to the desired profile. Default profile is `full` in static mode, `live` in dynamic mode (old syntax using `:live` instead of `.live` as separator still possible). Defined values are onDemand, live, main, simple, full, hbbtv1.5.live, dashavc264.live, dashavc264.onDemand, dashif.ll", GF_ARG_STRING, 0, parse_dash_profile, 0, ARG_IS_FUN), + MP4BOX_ARG("profile-ext", "specify a list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used", GF_ARG_STRING, 0, &dash_profile_extension, 0, 0), + MP4BOX_ARG("rap", "ensure that segments begin with random access points, segment durations might vary depending on the source encoding", GF_ARG_BOOL, 0, parse_rap_ref, 0, ARG_IS_FUN | ARG_EMPTY), + MP4BOX_ARG("frag-rap", "ensure that all fragments begin with random access points (duration might vary depending on the source encoding)", GF_ARG_BOOL, 0, &frag_at_rap, 0, 0), + MP4BOX_ARG("segment-name", "set the segment name for generated segments. If not set (default), segments are concatenated in output file except in `live` profile where `dash_%%s`. Supported replacement strings are:\n" + "- $Number[%%0Nd]$ is replaced by the segment number, possibly prefixed with 0\n" + "- $RepresentationID$ is replaced by representation name\n" + "- $Time$ is replaced by segment start time\n" + "- $Bandwidth$ is replaced by representation bandwidth\n" + "- $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" + "- $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}, + MP4BOX_ARG("segment-timeline", "use `SegmentTimeline` when generating segments", GF_ARG_BOOL, 0, &segment_timeline, 0, 0), + MP4BOX_ARG("segment-marker", "add a box of given type (4CC) at the end of each DASH segment", GF_ARG_STRING, 0, &segment_marker, 0, ARG_IS_4CC), + MP4BOX_ARG("insert-utc", "insert UTC clock at the beginning of each ISOBMF segment", GF_ARG_BOOL, 0, &insert_utc, 0, 0), + MP4BOX_ARG("base-url", "set Base url at MPD level. Can be used several times. \nWarning: this does not modify generated files location", GF_ARG_STRING, 0, parse_base_url, 0, ARG_IS_FUN), + MP4BOX_ARG("mpd-title", "set MPD title", GF_ARG_STRING, 0, &dash_title, 0, 0), + MP4BOX_ARG("mpd-source", "set MPD source", GF_ARG_STRING, 0, &dash_source, 0, 0), + MP4BOX_ARG("mpd-info-url", "set MPD info url", GF_ARG_STRING, 0, &dash_more_info, 0, 0), + MP4BOX_ARG("cprt", "add copyright string to MPD", GF_ARG_STRING, GF_ARG_HINT_ADVANCED, &cprt, 0, 0), + MP4BOX_ARG("dash-ctx", "store/restore DASH timing from indicated file", GF_ARG_STRING, 0, &dash_ctx_file, 0, 0), + MP4BOX_ARG("dynamic", "use dynamic MPD type instead of static", GF_ARG_BOOL, 0, &dash_mode, GF_DASH_DYNAMIC, 0), + 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("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("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" + "- f: movie fragments\n" + "- m: MPD\n" + "- mv, vm: in initial movie and MPD\n" + "- mf, fm: in movie fragments and MPD", GF_ARG_INT, 0, parse_pssh, 0, ARG_IS_FUN), + MP4BOX_ARG("sample-groups-traf", "store sample group descriptions in traf (duplicated for each traf). If not set, sample group descriptions are stored in the initial movie", GF_ARG_BOOL, 0, &samplegroups_in_traf, 0, 0), + MP4BOX_ARG("mvex-after-traks", "store `mvex` box after `trak` boxes within the moov box. If not set, `mvex` is before", GF_ARG_BOOL, 0, &mvex_after_traks, 0, 0), + MP4BOX_ARG("sdtp-traf", "use `sdtp` box in `traf` (Smooth-like)\n" + "- no: do not use sdtp\n" + "- sdtp: use sdtp box to indicate sample dependencies and do not write info in trun sample flags\n" + "- both: use sdtp box to indicate sample dependencies and also write info in trun sample flags\n", GF_ARG_INT, 0, parse_sdtp, 0, ARG_IS_FUN), + MP4BOX_ARG("no-cache", "disable file cache for dash inputs", GF_ARG_BOOL, 0, &no_cache, 0, 0), + MP4BOX_ARG("no-loop", "disable looping content in live mode and uses period switch instead", GF_ARG_BOOL, 0, &no_loop, 0, 0), + MP4BOX_ARG("hlsc", "insert UTC in variant playlists for live HLS", GF_ARG_BOOL, 0, &hls_clock, 0, 0), + MP4BOX_ARG("bound", "segmentation will always try to split before or at, but never after, the segment boundary", GF_ARG_BOOL, 0, &dash_split_mode, GF_DASH_SPLIT_IN, 0), + MP4BOX_ARG("closest", "segmentation will use the closest frame to the segment boundary (before or after)", GF_ARG_BOOL, 0, &dash_split_mode, GF_DASH_SPLIT_CLOSEST, 0), + MP4BOX_ARG_ALT("subsegs-per-sidx", "frags-per-sidx", "set the number of subsegments to be written in each SIDX box\n" + "- 0: a single SIDX box is used per segment\n" + "- -1: no SIDX box is used", GF_ARG_INT, GF_ARG_HINT_EXPERT, &subsegs_per_sidx, 0, 0), + MP4BOX_ARG("ssix", "enable SubsegmentIndexBox describing 2 ranges, first one from moof to end of first I-frame, second one unmapped. This does not work with daisy chaining mode enabled", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &use_ssix, 0, 0), + MP4BOX_ARG("url-template", "use SegmentTemplate instead of explicit sources in segments. Ignored if segments are stored in the output file", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &use_url_template, 1, 0), + MP4BOX_ARG("url-template-sim", "use SegmentTemplate simulation while converting HLS to MPD", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &use_url_template, 2, 0), + MP4BOX_ARG("daisy-chain", "use daisy-chain SIDX instead of hierarchical. Ignored if frags/sidx is 0", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &daisy_chain_sidx, 0, 0), + MP4BOX_ARG("single-segment", "use a single segment for the whole file (OnDemand profile)", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &single_segment, 0, 0), + {"single-file", NULL, "use a single file for the whole file (default)", "yes", NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &single_file, 0, 0}, + {"bs-switching", NULL, "set bitstream switching mode\n" + "- inband: use inband param set and a single init segment\n" + "- merge: try to merge param sets in a single sample description, fallback to `no`\n" + "- multi: use several sample description, one per quality\n" + "- no: use one init segment per quality\n" + "- pps: use out of band VPS,SPS,DCI, inband for PPS,APS and a single init segment\n" + "- single: to test with single input", "inband", "inband|merge|multi|no|single", GF_ARG_STRING, GF_ARG_HINT_EXPERT, parse_bs_switch, 0, ARG_IS_FUN}, + MP4BOX_ARG("moof-sn", "set sequence number of first moof to given value", GF_ARG_INT, GF_ARG_HINT_EXPERT, &initial_moof_sn, 0, 0), + MP4BOX_ARG("tfdt", "set TFDT of first traf to given value in SCALE units (cf -dash-scale)", GF_ARG_INT, GF_ARG_HINT_EXPERT, &initial_tfdt, 0, ARG_64BITS), + MP4BOX_ARG("no-frags-default", "disable default fragments flags in trex (required by some dash-if profiles and CMAF/smooth streaming compatibility)", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &no_fragments_defaults, 0, 0), + MP4BOX_ARG("single-traf", "use a single track fragment per moof (smooth streaming and derived specs may require this)", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &single_traf_per_moof, 0, 0), + MP4BOX_ARG("tfdt-traf", "use a tfdt per track fragment (when -single-traf is used)", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &tfdt_per_traf, 0, 0), + MP4BOX_ARG("dash-ts-prog", "program_number to be considered in case of an MPTS input file", GF_ARG_INT, GF_ARG_HINT_EXPERT, &program_number, 0, 0), + MP4BOX_ARG("frag-rt", "when using fragments in live mode, flush fragments according to their timing", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &frag_real_time, 0, 0), + MP4BOX_ARG("cp-location", "set ContentProtection element location\n" + "- as: sets ContentProtection in AdaptationSet element\n" + "- rep: sets ContentProtection in Representation element\n" + "- both: sets ContentProtection in both elements", GF_ARG_STRING, GF_ARG_HINT_EXPERT, parse_cp_loc, 0, ARG_IS_FUN), + MP4BOX_ARG("start-date", "for live mode, set start date (as xs:date, e.g. YYYY-MM-DDTHH:MM:SSZ). Default is current UTC\n" + "Warning: Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size", GF_ARG_STRING, GF_ARG_HINT_EXPERT, &dash_start_date, 0, 0), + MP4BOX_ARG("cues", "ignore dash duration and segment according to cue times in given XML file (tests/media/dash_cues for examples)", GF_ARG_STRING, GF_ARG_HINT_EXPERT, &dash_cues, 0, 0), + MP4BOX_ARG("strict-cues", "throw error if something is wrong while parsing cues or applying cue-based segmentation", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &strict_cues, 0, 0), + MP4BOX_ARG("merge-last-seg", "merge last segment if shorter than half the target duration", GF_ARG_BOOL, GF_ARG_HINT_EXPERT, &merge_last_seg, 0, 0), + {0} +}; + +void PrintDASHUsage() +{ + 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" + "- [[DASH wiki|DASH-intro]].\n" + "\n" + "# Specifying input files\n" + "Input media files to dash can use the following modifiers\n" + "- #trackID=N: only use the track ID N from the source file\n" + "- #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" + "- :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" + "- :BaseURL=NAME: set the BaseURL. Set multiple times for multiple BaseURLs\nWarning: This does not modify generated files location (see segment template).\n" + "- :bandwidth=VALUE: set the representation's bandwidth to a given value\n" + "- :pdur=VALUE: sets the duration of the associated period to VALUE seconds (fraction) (alias for period_duration:VALUE). This is only used when no input media is specified (remote period insertion), e.g. `:period=X:xlink=Z:pdur=Y`\n" + "- :ddur=VALUE: override target DASH segment duration to VALUE seconds (fraction) for this input (alias for duration:VALUE)\n" + "- :xlink=VALUE: set the xlink value for the period containing this element. Only the xlink declared on the first rep of a period will be used\n" + "- :asID=VALUE: set the AdaptationSet ID to NAME\n" + "- :role=VALUE: set the role of this representation (cf DASH spec). Media with different roles belong to different adaptation sets.\n" + "- :desc_p=VALUE: add a descriptor at the Period level. Value must be a properly formatted XML element.\n" + "- :desc_as=VALUE: add a descriptor at the AdaptationSet level. Value must be a properly formatted XML element. Two input files with different values will be in different AdaptationSet elements.\n" + "- :desc_as_c=VALUE: add a descriptor at the AdaptationSet level. Value must be a properly formatted XML element. Value is ignored while creating AdaptationSet elements.\n" + "- :desc_rep=VALUE: add a descriptor at the Representation level. Value must be a properly formatted XML element. 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" + "- @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" + "EX source.mp4:@c=avc:b=1M@@c=avc:b=500k\n" + "This will load a filter chain with two encoders connected to the source and to the dasher.\n" + "EX source.mp4:@c=avc:b=1M@c=avc:b=500k\n" + "This will load a filter chain with the second encoder connected to the output of the first (!!).\n" + "\n" + "Note: `@f` must be placed after all other options.\n" + "\n" + "# Options\n" + ); + + + while (m4b_dash_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_dash_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-dash"); + } +} + + +MP4BoxArg m4b_imp_args[] = +{ + MP4BOX_ARG("add", "add given file tracks to file. Multiple inputs can be specified using `+`, e.g. `-add url1+url2`", GF_ARG_STRING, 0, &nb_add, 0, ARG_INT_INC), + MP4BOX_ARG("cat", "concatenate given file samples to file, creating tracks if needed. Multiple inputs can be specified using `+`, e.g/ `-cat url1+url2`. \nNote: This aligns initial timestamp of the file to be concatenated", GF_ARG_STRING, 0, &nb_cat, 0, ARG_INT_INC), + MP4BOX_ARG("catx", "same as [-cat]() but new tracks can be imported before concatenation by specifying `+ADD_COMMAND` where `ADD_COMMAND` is a regular [-add]() syntax", GF_ARG_STRING, 0, &nb_cat, 0, ARG_INT_INC), + MP4BOX_ARG("catpl", "concatenate files listed in the given playlist file (one file per line, lines starting with # are comments). \nNote: Each listed file is concatenated as if called with -cat", GF_ARG_STRING, 0, &nb_cat, 0, ARG_INT_INC), + MP4BOX_ARG("unalign-cat", "do not attempt to align timestamps of samples in-between tracks", GF_ARG_BOOL, 0, &align_cat, 0, ARG_BOOL_REV), + MP4BOX_ARG("force-cat", "skip media configuration check when concatenating file. \nWarning: THIS MAY BREAK THE CONCATENATED TRACK(S)", GF_ARG_BOOL, 0, &force_cat, 0, 0), + MP4BOX_ARG("keep-sys", "keep all MPEG-4 Systems info when using [-add]() and [-cat]() (only used when adding IsoMedia files)", GF_ARG_BOOL, 0, &keep_sys_tracks, 0, 0), + MP4BOX_ARG("dref", "keep media data in original file using `data referencing`. The resulting file only contains the meta-data of the presentation (frame sizes, timing, etc...) and references media data in the original file. This is extremely useful when developing content, since importing and storage of the MP4 file is much faster and the resulting file much smaller. \nNote: Data referencing may fail on some files because it requires the framed data (e.g. an IsoMedia sample) to be continuous in the original file, which is not always the case depending on the original interleaving or bitstream format (__AVC__ or __HEVC__ cannot use this option)", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_USE_DATAREF, ARG_BIT_MASK), + MP4BOX_ARG_ALT("no-drop", "nodrop", "force constant FPS when importing AVI video", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_NO_FRAME_DROP, ARG_BIT_MASK), + MP4BOX_ARG("packed", "force packed bitstream when importing raw MPEG-4 part 2 Advanced Simple Profile", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_FORCE_PACKED, ARG_BIT_MASK), + MP4BOX_ARG("sbr", "backward compatible signaling of AAC-SBR", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_SBR_IMPLICIT, ARG_BIT_MASK), + MP4BOX_ARG("sbrx", "non-backward compatible signaling of AAC-SBR", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_SBR_EXPLICIT, ARG_BIT_MASK), + MP4BOX_ARG("ps", "backward compatible signaling of AAC-PS", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_PS_IMPLICIT, ARG_BIT_MASK), + MP4BOX_ARG("psx", "non-backward compatible signaling of AAC-PS", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_PS_EXPLICIT, ARG_BIT_MASK), + MP4BOX_ARG("ovsbr", "oversample SBR import (SBR AAC, PS AAC and oversampled SBR cannot be detected at import time)", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_OVSBR, ARG_BIT_MASK), + MP4BOX_ARG("fps", "force frame rate for video and SUB subtitles import to the given value, expressed as a number, as `TS-inc` or `TS/inc`. \nNote: For raw H263 import, default FPS is `15`, otherwise `25`. This is ignored for ISOBMFF import, use `:rescale` option for that", GF_ARG_STRING, 0, parse_fps, 0, ARG_IS_FUN), + MP4BOX_ARG("mpeg4", "force MPEG-4 sample descriptions when possible. For AAC, forces MPEG-4 AAC signaling even if MPEG-2", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_FORCE_MPEG4, ARG_BIT_MASK), + MP4BOX_ARG("agg", "aggregate N audio frames in 1 sample (3GP media only, maximum value is 15)", GF_ARG_INT, 0, &agg_samples, 0, 0), + {0} +}; + + +static MP4BoxArg m4b_imp_fileopt_args [] = { + GF_DEF_ARG("dur", NULL, "`XC` 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("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("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("fps", NULL, "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), + GF_DEF_ARG("trailing", NULL, "keep trailing 0-bytes in AVC/HEVC samples", NULL, NULL, GF_ARG_BOOL, 0), + 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("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), + GF_DEF_ARG("ovsbr", NULL, "same as [-ovsbr]()", NULL, NULL, GF_ARG_BOOL, 0), + 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-2: use MPEG AudioSampleEntry v0 and the channel count is forced to 2\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), + GF_DEF_ARG("roll", NULL, "`S` add a roll sample group with roll_distance `N`", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("proll", NULL, "`S` add a preroll sample group with roll_distance `N`", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("mpeg4", NULL, "`X` same as [-mpeg4]() option", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("nosei", NULL, "discard all SEI messages during import", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("svc", NULL, "import SVC/LHVC with explicit signaling (no AVC base compatibility)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("nosvc", NULL, "discard SVC/LHVC data when importing", NULL, NULL, GF_ARG_BOOL, 0), + 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" + " - 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" + " - split: each sublayer is in its own track\n" + " - splitbase: all sublayers are merged in a track, and the HEVC base in another\n" + " - splitnox: each layer is in its own track, and no extractors are written", NULL, NULL, GF_ARG_STRING, 0), + 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" + "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), + GF_DEF_ARG("au_delim", NULL, "keep AU delimiter NAL units in the imported file", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("max_lid", NULL, "set HEVC max layer ID to be imported to `N` (by default imports all layers)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("max_tid", NULL, "set HEVC max temporal ID to be imported to `N` (by default imports all temporal sublayers)", NULL, NULL, GF_ARG_INT, 0), + 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 `WxH[xXxY][xLAYER]`. 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("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), + GF_DEF_ARG("profile", NULL, "`S` override AVC profile. Integer value, or `high444`, `high`, `extended`, `main`, `baseline`", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("level", NULL, "`S` override AVC level, if value < 6, interpreted as decimal expression", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("compat", NULL, "`S` force the profile compatibility flags for the H.264 content", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("novpsext", NULL, "remove VPS extensions from HEVC VPS", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("keepav1t", NULL, "keep AV1 temporal delimiter OBU in samples, might help if source file had losses", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("font", NULL, "specify font name for text import (default `Serif`)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("size", NULL, "specify font size for text import (default `18`)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("text_layout", NULL, "specify the track text layout as WxHxXxY\n" + " - if W (resp H) = 0: the max width (resp height) of the tracks in the file are used\n" + " - if Y=-1: the layout is moved to the bottom of the track area\n" + " - X and Y can be omitted: `:layout=WxH`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("swf-global", NULL, "all SWF defines are placed in first scene replace rather than when needed", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-ctrl", NULL, "use a single stream for movie control and dictionary (this will disable ActionScript)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-text", NULL, "remove all SWF text", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-font", NULL, "remove all embedded SWF Fonts (local playback host fonts used)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-line", NULL, "remove all lines from SWF shapes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-grad", NULL, "remove all gradients from SWF shapes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-quad", NULL, "use quadratic bezier curves instead of cubic ones", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-xlp", NULL, "support for lines transparency and scalability", NULL, NULL, GF_ARG_BOOL, 0), + 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("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), + GF_DEF_ARG("bitdepth", NULL, "set bit depth to VAL for imported video content (default is 24)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("colr", NULL, "`S` set color profile for imported video content (see ISO/IEC 23001-8). Value is formatted as:\n" + " - nclc,p,t,m: with p colour primary (int or string), t transfer characteristics (int or string) and m matrix coef (int or string)\n" + " - nclx,p,t,m,r: same as `nclx` with r full range flag (`yes`, `on` or `no`, `off`)\n" + " - prof,path: with path indicating the file containing the ICC color profile\n" + " - rICC,path: with path indicating the file containing the restricted ICC color profile\n" + " - 'none': removes color info", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("hdr", NULL, "`S` set HDR info on track (see [-hdr](MP4B_GEN) ), 'none' removes HDR info", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dv-profile", NULL, "`S` set the Dolby Vision profile on imported track\n" + "- Profile is an integer, or `none` to remove DV signaling\n" + "- Profile can be suffixed with compatibility ID, e.g. `5.hdr10`\n" + "- Allowed compatibility ID are `none`, `hdr10`, `bt709`, `hlg709`, `hlg2100`, `bt2020`, `brd`, or integer value as per DV spec\n" + "- Profile can be prefixed with 'f' to force DV codec type signaling, e.g. `f8.2`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("fullrange", NULL, "`S` force the video fullrange type in VUI for the AVC|H264 content (value `yes`, `on` or `no`, `off`)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("videofmt", NULL, "`S` force the video format in VUI for AVC|H264 and HEVC content, value can be `component`, `pal`, `ntsc`, `secam`, `mac`, `undef`", NULL, NULL, GF_ARG_STRING, 0), + 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" + " - [d]FPS[/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("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" + " - 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("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)"}, + {"dopt:[OPTS]", NULL, "`X` set `OPTS` as additional arguments to [destination filter](mp4mx). OPTS can be any usual filter argument, see [filter doc `gpac -h doc`](Filters)"}, + {"@f1[:args][@fN:args]", NULL, "set a filter chain to insert before the multiplexer. Each filter in the chain is formatted as a regular filter, see [filter doc `gpac -h doc`](Filters). A `@@` separator starts a new chain (see DASH help). The last filter in each chain shall not have any ID specified"}, + {0} +}; + +void PrintImportUsage() +{ + u32 i; + + gf_sys_format_help(helpout, help_flags, "# Importing Options\n" + "# File importing\n" + "Syntax is [-add]() / [-cat]() `URL[#FRAGMENT][:opt1...:optN=val]`\n" + "This process will create the destination file if not existing, and add the track(s) to it. If you wish to always create a new destination file, add [-new](MP4B_GEN).\n" + "The supported input media types depend on your installation, check [filters documentation](Filters) for more info.\n" + " \n" + "To select a desired media track from a source, a fragment identifier '#' can be specified, before any other options. The following syntax is used:\n" + "- `#video`: adds the first video track found in source\n" + "- `#audio`: adds the first audio track found in source\n" + "- `#auxv`: adds the first auxiliary video track found in source\n" + "- `#pict`: adds the first picture track found in source\n" + "- `#trackID=ID` or `#ID`: adds the specified track. For IsoMedia files, ID is the track ID. For other media files, ID is the value indicated by `MP4Box -info inputFile`\n" + "- `#pid=ID`: number of desired PID for MPEG-2 TS sources\n" + "- `#prog_id=ID`: number of desired program for MPEG-2 TS sources\n" + "- `#program=NAME`: name of desired program for MPEG-2 TS sources\n" + " \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" + " - 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" + " \n" + "Source URL can be any URL supported by GPAC, not limited to local files.\n" + " \n" + "Note: When importing SRT or SUB files, MP4Box will choose default layout options to make the subtitle appear at the bottom of the video. You SHOULD NOT import such files before any video track is added to the destination file, otherwise the results will likely not be useful (default SRT/SUB importing uses default serif font, fontSize 18 and display size 400x60). For more details, check [TTXT doc](Subtitling-with-GPAC).\n" + " \n" + "When importing several tracks/sources in one pass, all options will be applied if relevant to each source. These options are set for all imported streams. If you need to specify these options per stream, set per-file options using the syntax `-add stream[:opt1:...:optN]`.\n" + " \n" + "The import file name may be set to empty or `self`, indicating that the import options should be applied to the destination file track(s).\n" + "EX -add self:moovts=-1:noedit src.mp4\n" + "This will apply `moovts` and `noedit` option to all tracks in src.mp4\n" + "EX -add self#2:moovts=-1:noedit src.mp4\n" + "This will apply `moovts` and `noedit` option to track with `ID=2` in src.mp4\n" + "Only per-file options marked with a `S` are possible in this mode.\n" + " \n" + "When importing an ISOBMFF/QT file, only options marked as `C` or `S` can be used.\n" + " \n" + "Allowed per-file options:\n\n" + ); + + i=0; + while (m4b_imp_fileopt_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_imp_fileopt_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags | GF_PRINTARG_NO_DASH, arg, "mp4box-import"); + } + + gf_sys_format_help(helpout, help_flags, "\n" + "Note: `sopt`, `dopt` and `@f` must be placed after all other options.\n" + "# Global import options\n" + ); + + i=0; + while (m4b_imp_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_imp_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-import"); + } +} + +Bool mp4box_check_isom_fileopt(char *opt) +{ + GF_GPACArg *arg = NULL; + u32 i=0; + + while (m4b_imp_fileopt_args[i].name) { + arg = (GF_GPACArg *) &m4b_imp_fileopt_args[i]; + i++; + if (!stricmp(arg->name, opt)) break; + arg = NULL; + } + if (!arg) { + fprintf(stderr, "Option %s not described in doc, please report to GPAC devs!\n", opt); + return GF_FALSE; + } + if (arg->description[0] != '`') + return GF_FALSE; + const char *d = arg->description+1; + while (d[0] != '`') { + if (d[0]=='S') return GF_TRUE; + if (d[0]=='C') return GF_TRUE; + d++; + } + return GF_FALSE; +} + + +MP4BoxArg m4b_senc_args[] = +{ +#ifndef GPAC_DISABLE_SCENE_ENCODER + MP4BOX_ARG("mp4", "specify input file is for BIFS/LASeR encoding", GF_ARG_BOOL, 0, &encode, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("def", "encode DEF names in BIFS", GF_ARG_BOOL, 0, &smenc_opts.flags, GF_SM_ENCODE_USE_NAMES, ARG_BIT_MASK), + MP4BOX_ARG("sync", "force BIFS sync sample generation every given time in ms (cannot be used with [-shadow]() or [-carousel]() )", GF_ARG_INT, 0, parse_senc_param, 0, ARG_IS_FUN), + MP4BOX_ARG("shadow", "force BIFS sync shadow sample generation every given time in ms (cannot be used with [-sync]() or [-carousel]() )", GF_ARG_INT, 0, parse_senc_param, 1, ARG_IS_FUN), + MP4BOX_ARG("carousel", "use BIFS carousel (cannot be used with [-sync]() or [-shadow]() )", GF_ARG_INT, 0, parse_senc_param, 2, ARG_IS_FUN), + + MP4BOX_ARG("sclog", "generate scene codec log file if available", GF_ARG_BOOL, 0, &do_scene_log, 0, 0), + MP4BOX_ARG("ms", "import tracks from the given file", GF_ARG_STRING, 0, &mediaSource, 0, 0), + MP4BOX_ARG("ctx-in", "specify initial context (MP4/BT/XMT) file for chunk processing. Input file must be a commands-only file", GF_ARG_STRING, 0, parse_senc_param, 5, ARG_IS_FUN), + MP4BOX_ARG("ctx-out", "specify storage of updated context (MP4/BT/XMT) file for chunk processing, optional", GF_ARG_STRING, 0, &output_ctx, 0, 0), + MP4BOX_ARG("resolution", "resolution factor (-8 to 7, default 0) for LASeR encoding, and all coordinates are multiplied by `2^res` before truncation (LASeR encoding)", GF_ARG_INT, 0, &smenc_opts.resolution, 0, 0), + MP4BOX_ARG("coord-bits", "number of bits used for encoding truncated coordinates (0 to 31, default 12) (LASeR encoding)", GF_ARG_INT, 0, &smenc_opts.coord_bits, 0, 0), + MP4BOX_ARG("scale-bits", "extra bits used for encoding truncated scales (0 to 4, default 0) (LASeR encoding)", GF_ARG_INT, 0, &smenc_opts.scale_bits, 0, 0), + MP4BOX_ARG("auto-quant", "resolution is given as if using [-resolution]() but coord-bits and scale-bits are inferred (LASeR encoding)", GF_ARG_INT, 0, parse_senc_param, 3, ARG_IS_FUN), + MP4BOX_ARG("global-quant", "resolution is given as if using [-resolution]() but the res is inferred (BIFS encoding)", GF_ARG_INT, 0, parse_senc_param, 4, ARG_IS_FUN), +#endif + {0} +}; + + +void PrintEncodeUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# MPEG-4 Scene Encoding Options\n" + "## 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" + "## 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" + "The BIFS chunk encoding mode allows encoding single BIFS access units from an initial context and a set of commands.\n" + "The generated AUs are raw BIFS (not SL-packetized), in files called FILE-ESID-AUIDX.bifs, with FILE the basename of the input file.\n" + "Commands with a timing of 0 in the input will modify the carousel version only (i.e. output context).\n" + "Commands with a timing different from 0 in the input will generate new AUs.\n" + " \n" + "Options:\n" + ); + + while (m4b_senc_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_senc_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-senc"); + } +} + +MP4BoxArg m4b_crypt_args[] = +{ + MP4BOX_ARG("crypt", "encrypt the input file using the given `CryptFile`", GF_ARG_STRING, 0, parse_cryp, 0, ARG_IS_FUN), + MP4BOX_ARG("decrypt", "decrypt the input file, potentially using the given `CryptFile`. If `CryptFile` is not given, will fail if the key management system is not supported", GF_ARG_STRING, 0, parse_cryp, 1, ARG_IS_FUN | ARG_EMPTY), + MP4BOX_ARG_S("set-kms", "tkID=kms_uri", "change ISMA/OMA KMS location for a given track or for all tracks if `all=` is used", 0, parse_track_action, TRACK_ACTION_SET_KMS_URI, ARG_IS_FUN), + {0} +}; + +void PrintEncryptUsage() +{ + 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" + "Image files (HEIF) can also be crypted / decrypted, using CENC only.\n" + " \n" + "Options:\n" + ); + while (m4b_crypt_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_crypt_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-crypt"); + } +} + +MP4BoxArg m4b_hint_args[] = +{ +#ifndef GPAC_DISABLE_STREAMING + MP4BOX_ARG("hint", "hint the file for RTP/RTSP", GF_ARG_BOOL, 0, &do_hint, 0, ARG_OPEN_EDIT), + {"mtu", NULL, "specify RTP MTU (max size) in bytes (this includes 12 bytes RTP header)", "1450", NULL, GF_ARG_INT, 0, &MTUSize, 0, 0}, + MP4BOX_ARG("copy", "copy media data to hint track rather than reference (speeds up server but takes much more space)", GF_ARG_BOOL, 0, &HintCopy, 0, 0), + MP4BOX_ARG_S("multi", "[maxptime]", "enable frame concatenation in RTP packets if possible (with max duration 100 ms or `maxptime` ms if given)", 0, parse_multi_rtp, 0, ARG_IS_FUN), + {"rate", NULL, "specify rtp rate in Hz when no default for payload", "90000", NULL, GF_ARG_INT, 0, &rtp_rate, 0, 0}, + MP4BOX_ARG("mpeg4", "force MPEG-4 generic payload whenever possible", GF_ARG_BOOL, 0, &import_flags, GF_IMPORT_FORCE_MPEG4, ARG_BIT_MASK), + MP4BOX_ARG("latm", "force MPG4-LATM transport for AAC streams", GF_ARG_BOOL, 0, &hint_flags, GP_RTP_PCK_USE_LATM_AAC, ARG_BIT_MASK), + MP4BOX_ARG("static", "enable static RTP payload IDs whenever possible (by default, dynamic payloads are always used)", GF_ARG_BOOL, 0, &hint_flags, GP_RTP_PCK_USE_STATIC_ID, ARG_BIT_MASK), + MP4BOX_ARG("add-sdp", "add given SDP string to movie (`string`) or track (`tkID:string`), `tkID` being the track ID or the hint track ID", GF_ARG_STRING, 0, parse_sdp_ext, 0, ARG_IS_FUN), + MP4BOX_ARG("no-offset", "signal no random offset for sequence number and timestamp (support will depend on server)", GF_ARG_BOOL, 0, &hint_no_offset, 0, 0), + MP4BOX_ARG("unhint", "remove all hinting information from file", GF_ARG_BOOL, 0, &remove_hint, 0, ARG_OPEN_EDIT), + MP4BOX_ARG("group-single", "put all tracks in a single hint group", GF_ARG_BOOL, 0, &single_group, 0, 0), + MP4BOX_ARG("ocr", "force all MPEG-4 streams to be synchronized (MPEG-4 Systems only)", GF_ARG_BOOL, 0, &force_ocr, 0, 0), + MP4BOX_ARG("rap", "signal random access points in RTP packets (MPEG-4 Systems)", GF_ARG_BOOL, 0, parse_rap_ref, 0, ARG_IS_FUN | ARG_EMPTY), + 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), +#endif + {0} +}; + +void PrintHintUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Hinting Options\n" + "IsoMedia hinting consists in creating special tracks in the file that contain transport protocol specific information and optionally multiplexing information. These tracks are then used by the server to create the actual packets being sent over the network, in other words they provide the server with hints on how to build packets, hence their names `hint tracks`.\n" + "MP4Box supports creation of hint tracks for RTSP servers supporting these such as QuickTime Streaming Server, DarwinStreaming Server or 3GPP-compliant RTSP servers.\n" + "Note: GPAC streaming tools [rtp output](rtpout) and [rtsp server](rtspout) do not use hint tracks, they use on-the-fly packetization " + "from any media sources, not just MP4\n" + " \n" + "Options:\n" + ); + while (m4b_hint_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_hint_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-hint"); + } +} + + +MP4BoxArg m4b_extr_args[] = +{ + MP4BOX_ARG("raw", "extract given track in raw format when supported. Use `tkID:output=FileName` to set output file name", GF_ARG_STRING, 0, parse_track_dump, GF_EXPORT_NATIVE, ARG_IS_FUN), + MP4BOX_ARG("raws", "extract each sample of the given track to a file. Use `tkID:N` to extract the Nth sample", GF_ARG_STRING, 0, parse_track_dump, GF_EXPORT_RAW_SAMPLES, ARG_IS_FUN), + MP4BOX_ARG("nhnt", "extract given track to [NHNT](nhntr) format", GF_ARG_INT, 0, parse_track_dump, GF_EXPORT_NHNT, ARG_IS_FUN), + MP4BOX_ARG("nhml", "extract given track to [NHML](nhmlr) format. Use `tkID:full` for full NHML dump with all packet properties", GF_ARG_STRING, 0, parse_track_dump, GF_EXPORT_NHML, ARG_IS_FUN), + MP4BOX_ARG("webvtt-raw", "extract given track as raw media in WebVTT as metadata. Use `tkID:embedded` to include media data in the WebVTT file", GF_ARG_STRING, 0, parse_track_dump, GF_EXPORT_WEBVTT_META, ARG_IS_FUN), + MP4BOX_ARG("single", "extract given track to a new mp4 file", GF_ARG_INT, 0, parse_track_dump, GF_EXPORT_MP4, ARG_IS_FUN), + MP4BOX_ARG("six", "extract given track as raw media in **experimental** XML streaming instructions", GF_ARG_INT, 0, parse_track_dump, GF_EXPORT_SIX, ARG_IS_FUN), + MP4BOX_ARG("mux", "multiplex input file to given destination", GF_ARG_STRING, 0, &mux_name, 0, 0), + MP4BOX_ARG("qcp", "same as [-raw]() but defaults to QCP file for EVRC/SMV", GF_ARG_INT, 0, parse_track_dump, GF_EXPORT_NATIVE | GF_EXPORT_USE_QCP, ARG_IS_FUN), + MP4BOX_ARG("saf", "multiplex input file to SAF multiplex", GF_ARG_BOOL, 0, &do_saf, 0, 0), + MP4BOX_ARG("dvbhdemux", "demultiplex DVB-H file into IP Datagrams sent on the network", GF_ARG_BOOL, 0, &dvbhdemux, 0, 0), + MP4BOX_ARG("raw-layer", "same as [-raw]() but skips SVC/MVC/LHVC extractors when extracting", GF_ARG_INT, 0, parse_track_dump, GF_EXPORT_NATIVE | GF_EXPORT_SVC_LAYER, ARG_IS_FUN), + MP4BOX_ARG("diod", "extract file IOD in raw format", GF_ARG_BOOL, 0, &dump_iod, 0, 0), + MP4BOX_ARG("mpd", "convert given HLS or smooth manifest (local or remote http) to MPD. \nWarning: This is not compatible with other DASH options and does not convert associated segments", GF_ARG_STRING, 0, &do_mpd_conv, 0, 0), + {0} +}; + +void PrintExtractUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Extracting Options\n" + "MP4Box can be used to extract media tracks from MP4 files. If you need to convert these tracks however, please check the [filters doc](Filters).\n" + " \n" + "Options:\n" + ); + while (m4b_extr_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_extr_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } +} + +MP4BoxArg m4b_dump_args[] = +{ + MP4BOX_ARG("std", "dump/write to stdout and assume stdout is opened in binary mode", GF_ARG_BOOL, 0, &dump_std, 2, 0), + MP4BOX_ARG("stdb", "dump/write to stdout and try to reopen stdout in binary mode", GF_ARG_BOOL, 0, &dump_std, 1, 0), + MP4BOX_ARG("tracks", "print the number of tracks on stdout", GF_ARG_BOOL, 0, &get_nb_tracks, 0, 0), + MP4BOX_ARG("info", "print movie info (no parameter) or track extended info with specified ID", GF_ARG_STRING, 0, parse_file_info, 0, ARG_IS_FUN|ARG_EMPTY), + MP4BOX_ARG("infon", "print track info for given track number, 1 being the first track in the file", GF_ARG_STRING, 0, parse_file_info, 1, ARG_IS_FUN|ARG_EMPTY), + MP4BOX_ARG("infox", "print movie and track extended info (same as -info N` for each track)", GF_ARG_BOOL, 0, parse_file_info, 2, ARG_IS_FUN|ARG_EMPTY), + 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-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), + MP4BOX_ARG("xmt", "dump scene to XMT format", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_XMTA, 0), + MP4BOX_ARG("wrl", "dump scene to VRML format", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_VRML, 0), + MP4BOX_ARG("x3d", "dump scene to X3D XML format", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_X3D_XML, 0), + MP4BOX_ARG("x3dv", "dump scene to X3D VRML format", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_X3D_VRML, 0), + MP4BOX_ARG("lsr", "dump scene to LASeR XML (XSR) format", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_LASER, 0), + MP4BOX_ARG("svg", "dump scene to SVG", GF_ARG_BOOL, 0, &dump_mode, GF_SM_DUMP_SVG, 0), +#endif + MP4BOX_ARG("drtp", "dump rtp hint samples structure to XML output", GF_ARG_BOOL, 0, &dump_rtp, 0, 0), + MP4BOX_ARG("dts", "print sample timing, size and position in file to text output", GF_ARG_BOOL, 0, parse_dump_ts, 0, ARG_IS_FUN), + MP4BOX_ARG("dtsx", "same as [-dts]() but does not print offset", GF_ARG_BOOL, 0, &dump_timestamps, 2, 0), + MP4BOX_ARG("dtsc", "same as [-dts]() but analyses each sample for duplicated dts/cts (__slow !__)", GF_ARG_BOOL, 0, &dump_timestamps, 3, 0), + MP4BOX_ARG("dtsxc", "same as [-dtsc]() but does not print offset (__slow !__)", GF_ARG_BOOL, 0, &dump_timestamps, 4, 0), + MP4BOX_ARG("dnal", "print NAL sample info of given track", GF_ARG_INT, 0, parse_dnal, 0, ARG_IS_FUN), + MP4BOX_ARG("dnalc", "print NAL sample info of given track, adding CRC for each nal", GF_ARG_INT, 0, parse_dnal, 1, ARG_IS_FUN), + MP4BOX_ARG("dnald", "print NAL sample info of given track without DTS and CTS info", GF_ARG_INT, 0, parse_dnal, 2, ARG_IS_FUN), + MP4BOX_ARG("dnalx", "print NAL sample info of given track without DTS and CTS info and adding CRC for each nal", GF_ARG_INT, 0, parse_dnal, 2|1, ARG_IS_FUN), + MP4BOX_ARG("sdp", "dump SDP description of hinted file", GF_ARG_BOOL, 0, &print_sdp, 0, 0), + MP4BOX_ARG("dsap", "dump DASH SAP cues (see -cues) for a given track", GF_ARG_INT, 0, parse_dsap, 0, ARG_IS_FUN), + MP4BOX_ARG("dsaps", "same as [-dsap]() but only print sample number", GF_ARG_INT, 0, parse_dsap, 1, ARG_IS_FUN), + MP4BOX_ARG("dsapc", "same as [-dsap]() but only print CTS", GF_ARG_INT, 0, parse_dsap, 2, ARG_IS_FUN), + MP4BOX_ARG("dsapd", "same as [-dsap]() but only print DTS", GF_ARG_INT, 0, parse_dsap, 3, ARG_IS_FUN), + MP4BOX_ARG("dsapp", "same as [-dsap]() but only print presentation time", GF_ARG_INT, 4, parse_dsap, 4, ARG_IS_FUN), + MP4BOX_ARG("dcr", "dump ISMACryp samples structure to XML output", GF_ARG_BOOL, 0, &dump_cr, 0, 0), + MP4BOX_ARG("dchunk", "dump chunk info", GF_ARG_BOOL, 0, &dump_chunk, 0, 0), + MP4BOX_ARG("dump-cover", "extract cover art", GF_ARG_BOOL, 0, &dump_cart, 0, 0), + MP4BOX_ARG("dump-chap", "extract chapter file as TTXT format", GF_ARG_BOOL, 0, &dump_chap, 1, 0), + MP4BOX_ARG("dump-chap-ogg", "extract chapter file as OGG format", GF_ARG_BOOL, 0, &dump_chap, 2, 0), + MP4BOX_ARG("dump-chap-zoom", "extract chapter file as zoom format", GF_ARG_BOOL, 0, &dump_chap, 3, 0), + MP4BOX_ARG_S("dump-udta", "[tkID:]4cc", "extract user data for the given 4CC. If `tkID` is given, dumps from UDTA of the given track ID, otherwise moov is used", 0, parse_dump_udta, 0, ARG_IS_FUN), + MP4BOX_ARG("mergevtt", "merge vtt cues while dumping", GF_ARG_BOOL, 0, &merge_vtt_cues, 0, 0), + MP4BOX_ARG("ttxt", "convert input subtitle to GPAC TTXT format if no parameter. Otherwise, dump given text track to GPAC TTXT format", GF_ARG_INT, 0, parse_ttxt, 0, ARG_IS_FUN), + MP4BOX_ARG("srt", "convert input subtitle to SRT format if no parameter. Otherwise, dump given text track to SRT format", GF_ARG_INT, 0, parse_ttxt, 1, ARG_IS_FUN), + MP4BOX_ARG("nstat", "generate node/field statistics for scene", GF_ARG_BOOL, 0, &stat_level, 1, 0), + MP4BOX_ARG("nstats", "generate node/field statistics per Access Unit", GF_ARG_BOOL, 0, &stat_level, 2, 0), + MP4BOX_ARG("nstatx", "generate node/field statistics for scene after each AU", GF_ARG_BOOL, 0, &stat_level, 3, 0), + MP4BOX_ARG("hash", "generate SHA-1 Hash of the input file", GF_ARG_BOOL, 0, &do_hash, 0, 0), + MP4BOX_ARG("comp", "replace with compressed version all top level box types given as parameter, formatted as `orig_4cc_1=comp_4cc_1[,orig_4cc_2=comp_4cc_2]`", GF_ARG_STRING, 0, parse_comp_box, 0, ARG_IS_FUN), + MP4BOX_ARG("topcount", "print to stdout the number of top-level boxes matching box types given as parameter, formatted as `4cc_1,4cc_2N`", GF_ARG_STRING, 0, parse_comp_box, 2, ARG_IS_FUN), + MP4BOX_ARG("topsize", "print to stdout the number of bytes of top-level boxes matching types given as parameter, formatted as `4cc_1,4cc_2N` or `all` for all boxes", GF_ARG_STRING, 0, parse_comp_box, 1, ARG_IS_FUN), + MP4BOX_ARG("bin", "convert input XML file using NHML bitstream syntax to binary", GF_ARG_BOOL, 0, &do_bin_xml, 0, 0), + MP4BOX_ARG("mpd-rip", "fetch MPD and segment to disk", GF_ARG_BOOL, 0, &do_mpd_rip, 0, 0), + //MP4BOX_ARG_S("udp-write", "IP[:port]", "write input name to UDP (default port 2345)", GF_FS_ARG_HINT_EXPERT, &udp_dest, 0, GF_ARG_STRING), + {"udp-write", NULL, "write input name to UDP (default port 2345)", "IP[:port]", NULL, GF_ARG_STRING, GF_FS_ARG_HINT_EXPERT, &udp_dest, 0, 0}, + MP4BOX_ARG("raw-cat", "raw concatenation of given file with input file", GF_ARG_STRING, GF_FS_ARG_HINT_EXPERT, &raw_cat, 0, 0), + MP4BOX_ARG("wget", "fetch resource from http(s) URL", GF_ARG_STRING, GF_FS_ARG_HINT_EXPERT, &do_wget, 0, 0), + MP4BOX_ARG("dm2ts", "dump timing of an input MPEG-2 TS stream sample timing", GF_ARG_BOOL, 0, &dump_m2ts, 0, 0), + MP4BOX_ARG("check-xml", "check XML output format for -dnal*, -diso* and -dxml options", GF_ARG_BOOL, 0, &dump_check_xml, 0, 0), + {0} +}; + +void PrintDumpUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# File Dumping\n" + " \n" + "MP4Box has many dump functionalities, from simple track listing to more complete reporting of special tracks.\n" + " \n" + "Options:\n" + ); + while (m4b_dump_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_dump_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } +} + +MP4BoxArg m4b_meta_args[] = +{ + MP4BOX_ARG_S("set-meta", "ABCD[:tk=tkID]", "set meta box type, with `ABCD` the four char meta type (NULL or 0 to remove meta)\n" + "- tk not set: use root (file) meta\n" + "- tkID == 0: use moov meta\n" + "- tkID != 0: use meta of given track", 0, parse_meta_args, META_ACTION_SET_TYPE, ARG_IS_FUN), + MP4BOX_ARG("add-item", "add resource to meta, with parameter syntax `file_path[:opt1:optN]`\n" + "- file_path `this` or `self`: item is the file itself\n" + "- tk=tkID: meta location (file, moov, track)\n" + "- name=str: item name, none if not set\n" + "- 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" + "- 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" + "- 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" + " - If `i` is set (float, in sec), sets time increment between samples to import\n" + "- split_tiles: for an HEVC tiled image, each tile is stored as a separate item\n" + "- image-size=wxh: force setting the image size and ignoring the bitstream info, used for grid, overlay and identity derived images also\n" + "- rotation=a: set the rotation angle for this image to 90*a degrees anti-clockwise\n" + "- mirror-axis=axis: set the mirror axis: vertical, horizontal\n" + "- clap=Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd: see track clap\n" + "- image-pasp=axb: force the aspect ratio of the image\n" + "- image-pixi=(a|a,b,c): force the bit depth (1 or 3 channels)\n" + "- hidden: indicate that this image item should be hidden\n" + "- 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" + "- 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" + "- ref: do not copy the data but refer to the final sample/item location, ignored if `filepath` is set\n" + "- agrid[=AR]: creates an automatic grid from the image items present in the file, in their declaration order. The grid will **try to** have `AR` aspect ratio if specified (float), or the aspect ratio of the source otherwise. The grid will be the primary item and all other images will be hidden\n" + "- av1_op_index: select the AV1 operating point to use via a1op box\n" + "- replace: replace existing image by new image, keeping props listed in `keep_props`\n" + "- 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]()" + , 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" + "- image-overlay-offsets=h,v[,h,v]*: set the horizontal and vertical offsets of the images in the overlay\n" + "- image-overlay-color=r,g,b,a: set the canvas color of the overlay [0-65535]\n" + "- any other options from [-add-image]() can be used\n", GF_ARG_STRING, 0, parse_meta_args, META_ACTION_ADD_IMAGE_DERIVED, ARG_IS_FUN), + MP4BOX_ARG_S_ALT("rem-item", "rem-image", "item_ID[:tk=tkID]", "remove resource from meta", 0, parse_meta_args, META_ACTION_REM_ITEM, ARG_IS_FUN), + MP4BOX_ARG_S("set-primary", "item_ID[:tk=tkID]", "set item as primary for meta", 0, parse_meta_args, META_ACTION_SET_PRIMARY_ITEM, ARG_IS_FUN), + MP4BOX_ARG_S("set-xml", "xml_file_path[:tk=tkID][:binary]", "set meta XML data", 0, parse_meta_args, META_ACTION_SET_XML, ARG_IS_FUN), + MP4BOX_ARG_S("rem-xml", "[tk=tkID]", "remove meta XML data", 0, parse_meta_args, META_ACTION_REM_XML, ARG_IS_FUN), + MP4BOX_ARG_S("dump-xml", "file_path[:tk=tkID]", "dump meta XML to file", 0, parse_meta_args, META_ACTION_DUMP_XML, ARG_IS_FUN), + MP4BOX_ARG_S("dump-item", "item_ID[:tk=tkID][:path=fileName]", "dump item to file", 0, parse_meta_args, META_ACTION_DUMP_ITEM, ARG_IS_FUN), + MP4BOX_ARG("package", "package input XML file into an ISO container, all media referenced except hyperlinks are added to file", GF_ARG_STRING, 0, &pack_file, 0, 0), + MP4BOX_ARG("mgt", "package input XML file into an MPEG-U widget with ISO container, all files contained in the current folder are added to the widget package", GF_ARG_STRING, 0, parse_mpegu, 0, ARG_IS_FUN), + {0} +}; + +void PrintMetaUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Meta and HEIF Options\n" + "IsoMedia files can be used as generic meta-data containers, for examples storing XML information and sample images for a movie. The resulting file may not always contain a movie as is the case with some HEIF files or MPEG-21 files.\n" + " \n" + "These information can be stored at the file root level, as is the case for HEIF/IFF and MPEG-21 file formats, or at the movie or track level for a regular movie." + " \n \n"); + while (m4b_meta_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_meta_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } +} + +MP4BoxArg m4b_swf_args[] = +{ +#ifndef GPAC_DISABLE_SCENE_ENCODER + MP4BOX_ARG("global", "all SWF defines are placed in first scene replace rather than when needed", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_STATIC_DICT, ARG_BIT_MASK), + MP4BOX_ARG("no-ctrl", "use a single stream for movie control and dictionary (this will disable ActionScript)", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_SPLIT_TIMELINE, ARG_BIT_MASK_REM), + MP4BOX_ARG("no-text", "remove all SWF text", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_NO_TEXT, ARG_BIT_MASK), + MP4BOX_ARG("no-font", "remove all embedded SWF Fonts (local playback host fonts used)", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_NO_FONT, ARG_BIT_MASK), + MP4BOX_ARG("no-line", "remove all lines from SWF shapes", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_NO_LINE, ARG_BIT_MASK), + MP4BOX_ARG("no-grad", "remove all gradients from swf shapes", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_NO_GRADIENT, ARG_BIT_MASK), + MP4BOX_ARG("quad", "use quadratic bezier curves instead of cubic ones", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_QUAD_CURVE, ARG_BIT_MASK), + MP4BOX_ARG("xlp", "support for lines transparency and scalability", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_SCALABLE_LINE, ARG_BIT_MASK), + MP4BOX_ARG("ic2d", "use indexed curve 2D hardcoded proto", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_USE_IC2D, ARG_BIT_MASK), + MP4BOX_ARG("same-app", "appearance nodes are reused", GF_ARG_BOOL, 0, &swf_flags, GF_SM_SWF_REUSE_APPEARANCE, ARG_BIT_MASK), + MP4BOX_ARG("flatten", "complementary angle below which 2 lines are merged, value `0` means no flattening", GF_ARG_DOUBLE, 0, &swf_flatten_angle, 0, 0), +#endif + {0} +}; + +void PrintSWFUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# SWF Importer Options\n" + "\n" + "MP4Box can import simple Macromedia Flash files (\".SWF\")\n" + "You can specify a SWF input file with \'-bt\', \'-xmt\' and \'-mp4\' options\n" + " \n" + "Options:\n" + ); + while (m4b_swf_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_swf_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } +} + +MP4BoxArg m4b_liveenc_args[] = +{ + MP4BOX_ARG("live", "enable live BIFS/LASeR encoder", GF_ARG_BOOL, 0, &live_scene, 0, 0), + GF_DEF_ARG("dst", NULL, "destination IP", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("port", NULL, "destination port", "7000", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("mtu", NULL, "path MTU for RTP packets", "1450", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("ifce", NULL, "IP address of the physical interface to use", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("ttl", NULL, "time to live for multicast packets", "1", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("sdp", NULL, "output SDP file", "session.sdp", NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dims", NULL, "turn on DIMS mode for SVG input", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-rap", NULL, "disable RAP sending and carousel generation", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("src", NULL, "source of scene updates", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rap", NULL, "duration in ms of base carousel; you can specify the RAP period of a single ESID (not in DIMS) using `ESID=X:time`", NULL, NULL, GF_ARG_INT, 0), + {0} +}; + +void PrintLiveUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Live Scene Encoder Options\n" + "The options shall be specified as `opt_name=opt_val.\n" + "Options:\n" + "\n" + ); + while (m4b_liveenc_args[i].name) { + GF_GPACArg *arg = (GF_GPACArg *) &m4b_liveenc_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } + + gf_sys_format_help(helpout, help_flags, " \n" + "Runtime options:\n" + "- q: quits\n" + "- u: inputs some commands to be sent\n" + "- U: same as u but signals the updates as critical\n" + "- e: inputs some commands to be sent without being aggregated\n" + "- E: same as e but signals the updates as critical\n" + "- f: forces RAP sending\n" + "- F: forces RAP regeneration and sending\n" + "- p: dumps current scene\n" + ); +} + +void PrintCoreUsage() +{ + gf_sys_format_help(helpout, help_flags, "# libgpac core options\n"); + gf_sys_print_core_help(helpout, 0, GF_ARGMODE_ALL, 0); +} + +void PrintTags() +{ + u32 i = 0; + + gf_sys_format_help(helpout, help_flags, "# Tagging support\n" + "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" + "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" + "Tags can also be loaded from a text file using `-itags filename`. The file must be in UTF8 with:\n" + "- lines starting with `tag_name=value` specify the start of a tag\n" + "- other lines specify the remainder of the last declared tag\n" + " \n" + "If tag name starts with `WM/`, the tag is added to `Xtra` box (WMA tag, string only).\n" + " \n" + "Supported tag names, values, types, aliases:\n" + ); + + while (1) { + s32 type = gf_itags_get_type(i); + if (type<0) break; + const char *name = gf_itags_get_name(i); + u32 itag = gf_itags_get_itag(i); + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST , "%s", name); + gf_sys_format_help(helpout, help_flags, " (%s) ", gf_4cc_to_str(itag) ); + switch (type) { + case GF_ITAG_STR: + gf_sys_format_help(helpout, help_flags, "string"); break; + case GF_ITAG_INT8: + case GF_ITAG_INT16: + case GF_ITAG_INT32: + case GF_ITAG_INT64: + gf_sys_format_help(helpout, help_flags, "integer"); break; + case GF_ITAG_FRAC6: + case GF_ITAG_FRAC8: + gf_sys_format_help(helpout, help_flags, "fraction (syntax: `A/B` or `A`, B will be 0)"); break; + case GF_ITAG_BOOL: + gf_sys_format_help(helpout, help_flags, "bool (`yes` or `no`)"); break; + case GF_ITAG_ID3_GENRE: + gf_sys_format_help(helpout, help_flags, "string (ID3 genre tag)"); break; + case GF_ITAG_FILE: + gf_sys_format_help(helpout, help_flags, "file path"); break; + } + name = gf_itags_get_alt_name(i); + if (name) { + gf_sys_format_help(helpout, help_flags, " (`alias` %s)", name); + } + + gf_sys_format_help(helpout, help_flags, "\n"); + i++; + } +} + +void PrintCICP() +{ + u32 i; + gf_sys_format_help(helpout, help_flags, "# Video CICP (ISO/IEC 23091-2) Constants\n"); + gf_sys_format_help(helpout, help_flags, "CICP Color Primaries:\n"); + for (i=0; iname, alen) && ((arg->name[alen]==0) || (arg->name[alen]=='='))) + do_match = GF_TRUE; + } + else if (!strcmp(arg_name, arg->name)) + do_match = GF_TRUE; + else if ((alen < (u32) strlen(arg->name)) && (arg->name[alen]==' ') && !strncmp(arg_name, arg->name, alen)) + do_match = GF_TRUE; + + if (arg_name[0] == '@') + do_match = GF_TRUE; + + if ((search_type==SEARCH_ARG_EXACT) && !do_match) { + i++; + continue; + } + if ((search_type==SEARCH_ARG_CLOSE) && !gf_sys_word_match(arg_name, arg->name)) { + i++; + continue; + } + if ((search_type==SEARCH_DESC) && strcmp(arg->name, arg_name) && !gf_strnistr(arg->description, arg_name, alen)) { + i++; + continue; + } + + an_arg = *arg; + if (search_type!=SEARCH_ARG_EXACT) { + if (search_type == SEARCH_DESC) + an_arg.description = szDesc; + else + an_arg.description = NULL; + an_arg.type = GF_ARG_BOOL; + gf_sys_print_arg(helpout, flags, (GF_GPACArg *) &an_arg, ""); + + } else { + gf_sys_print_arg(helpout, flags, (GF_GPACArg *) &an_arg, ""); + } + res++; + i++; + } + return res; +} +static Bool PrintHelpArg(char *arg_name, u32 search_type, GF_FilterSession *fs) +{ + char szDesc[100]; + Bool first=GF_TRUE; + GF_GPACArg an_arg; + u32 i, count; + u32 res = 0; + u32 alen = (u32) strlen(arg_name); + res += PrintHelpForArgs(arg_name, m4b_gen_args, NULL, search_type, "general"); + res += PrintHelpForArgs(arg_name, m4b_split_args, NULL, search_type, "split"); + res += PrintHelpForArgs(arg_name, m4b_dash_args, NULL, search_type, "dash"); + res += PrintHelpForArgs(arg_name, m4b_imp_args, NULL, search_type, "import"); + res += PrintHelpForArgs(arg_name, m4b_imp_fileopt_args, NULL, search_type, "import (per-file option)"); + res += PrintHelpForArgs(arg_name, m4b_senc_args, NULL, search_type, "encode"); + res += PrintHelpForArgs(arg_name, m4b_crypt_args, NULL, search_type, "crypt"); + res += PrintHelpForArgs(arg_name, m4b_hint_args, NULL, search_type, "hint"); + res += PrintHelpForArgs(arg_name, m4b_extr_args, NULL, search_type, "extract"); + res += PrintHelpForArgs(arg_name, m4b_dump_args, NULL, search_type, "dump"); + res += PrintHelpForArgs(arg_name, m4b_meta_args, NULL, search_type, "meta"); + res += PrintHelpForArgs(arg_name, m4b_swf_args, NULL, search_type, "swf"); + res += PrintHelpForArgs(arg_name, m4b_liveenc_args, NULL, search_type, "live"); + res += PrintHelpForArgs(arg_name, m4b_usage_args, NULL, search_type, ""); + res += PrintHelpForArgs(arg_name, NULL, (GF_GPACArg *) gf_sys_get_options(), search_type, "core"); + + if (!fs) return res; + + memset(&an_arg, 0, sizeof(GF_GPACArg)); + count = gf_fs_filters_registers_count(fs); + for (i=0; iargs) { + u32 len; + const GF_FilterArgs *arg = ®->args[j]; + if (!arg || !arg->arg_name) break; + j++; + if ((search_type==SEARCH_ARG_EXACT) && strcmp(arg->arg_name, arg_name)) continue; + + if ((search_type==SEARCH_ARG_CLOSE) && !gf_sys_word_match(arg->arg_name, arg_name)) continue; + + if (search_type==SEARCH_DESC) { + if (stricmp(arg->arg_name, arg_name) && !gf_strnistr(arg->arg_desc, arg_name, alen)) continue; + } + + an_arg.name = arg->arg_name; + if (search_type==SEARCH_ARG_EXACT) { + an_arg.description = arg->arg_desc; + switch (arg->arg_type) { + case GF_PROP_BOOL: + an_arg.type = GF_ARG_BOOL; + break; + case GF_PROP_UINT: + case GF_PROP_SINT: + an_arg.type = GF_ARG_INT; + break; + case GF_PROP_DOUBLE: + an_arg.type = GF_ARG_DOUBLE; + break; + case GF_PROP_STRING_LIST: + case GF_PROP_UINT_LIST: + case GF_PROP_SINT_LIST: + case GF_PROP_VEC2I_LIST: + an_arg.type = GF_ARG_STRINGS; + break; + case GF_PROP_4CC: + an_arg.type = GF_ARG_4CC; + break; + case GF_PROP_4CC_LIST: + an_arg.type = GF_ARG_4CCS; + break; + default: + an_arg.type = GF_ARG_STRING; + break; + } + if (first) { + first = GF_FALSE; + gf_sys_format_help(helpout, 0, "\nGlobal filter session arguments matching %s:\n", arg_name); + //. Syntax is `--arg` or `--arg=VAL`. `[F]` indicates filter name. See `gpac -h` and `gpac -h F` for more info.\n"); + } + fprintf(helpout, "[%s]", reg->name); + len = (u32)strlen(reg->name); + while (len<10) { + len++; + fprintf(helpout, " "); + } + fprintf(helpout, " "); + } else if (search_type==SEARCH_DESC) { + sprintf(szDesc, "see filter %s", reg->name); + an_arg.description = szDesc; + } + + gf_sys_print_arg(helpout, GF_PRINTARG_ADD_DASH, &an_arg, "TEST"); + res++; + } + } + if (res) return GF_TRUE; + return GF_FALSE; +} + +static void PrintHelp(char *arg_name, Bool search_desc, Bool no_match) +{ + GF_FilterSession *fs; + Bool res; + + fs = gf_fs_new_defaults(0); + + if (arg_name[0]=='-') + arg_name++; + + if (search_desc) { + char *_arg_name = gf_strdup(arg_name); + strlwr(_arg_name); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Possible options mentioning `%s`:\n", arg_name)); + PrintHelpArg(_arg_name, SEARCH_DESC, fs); + gf_free(_arg_name); + } else { + res = no_match ? GF_FALSE : PrintHelpArg(arg_name, SEARCH_ARG_EXACT, fs); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Option -%s unknown, please check usage.\n", arg_name)); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Possible options are:\n")); + + PrintHelpArg(arg_name, SEARCH_ARG_CLOSE, fs); + } + } + if (fs) + gf_fs_del(fs); +} + + +u32 parse_sdp_ext(char *arg_val, u32 param) +{ + char *id; + sdp_lines = gf_realloc(sdp_lines, sizeof(SDPLine) * (nb_sdp_ex + 1)); + if (!sdp_lines) return 2; + id = strchr(arg_val, ':'); + if (id) { + id[0] = 0; + if (sscanf(arg_val, "%u", &sdp_lines[nb_sdp_ex].trackID) == 1) { + id[0] = ':'; + sdp_lines[nb_sdp_ex].line = id + 1; + } + else { + id[0] = ':'; + sdp_lines[nb_sdp_ex].line = arg_val; + sdp_lines[nb_sdp_ex].trackID = 0; + } + } + else { + sdp_lines[nb_sdp_ex].line = arg_val; + sdp_lines[nb_sdp_ex].trackID = 0; + } + open_edit = GF_TRUE; + nb_sdp_ex++; + return GF_FALSE; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE +static u32 parse_meta_args(char *opts, MetaActionType act_type) +{ + MetaAction *meta; + + metas = gf_realloc(metas, sizeof(MetaAction) * (nb_meta_act + 1)); + if (!metas) return 2; + meta = &metas[nb_meta_act]; + nb_meta_act ++; + + memset(meta, 0, sizeof(MetaAction)); + meta->act_type = act_type; + meta->trackID = 0; + meta->root_meta = 1; + open_edit = GF_TRUE; + + if (!opts) return 2; + + if (act_type == META_ACTION_ADD_IMAGE_ITEM) + has_add_image = GF_TRUE; + +#define CHECK_IMGPROP\ + if (!meta->image_props) {\ + GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties);\ + if (!meta->image_props) return 2;\ + }\ + + while (1) { + char *next; + char *szSlot; + if (!opts || !opts[0]) return 0; + if (opts[0]==':') opts += 1; + + szSlot = opts; + next = gf_url_colon_suffix(opts, '='); + if (next) next[0] = 0; + if (next && !strncmp(szSlot, "auxt", 4)) { + next[0] = ':'; + char *sep = strchr(next, '='); + if (sep) { + next = sep; + while (next[0] != ':') + next--; + + next[0] = 0; + } + } + + if (!strnicmp(szSlot, "tk=", 3)) { + sscanf(szSlot, "tk=%u", &meta->trackID); + if (act_type == META_ACTION_ADD_ITEM) + meta->root_meta = 0; + } + else if (!strnicmp(szSlot, "id=", 3)) { + meta->item_id = atoi(szSlot+3); + } + else if (!strnicmp(szSlot, "type=", 5)) { + meta->item_type = GF_4CC(szSlot[5], szSlot[6], szSlot[7], szSlot[8]); + } + //"ref" (without '=') is for data reference, "ref=" is for item references + else if (!strnicmp(szSlot, "ref=", 4)) { + char type[5]; + MetaRef *ref; + if (!meta->item_refs) { + meta->item_refs = gf_list_new(); + if (!meta->item_refs) return 2; + } + GF_SAFEALLOC(ref, MetaRef); + if (!ref) return 2; + sscanf(szSlot, "ref=%4s,%u", type, &(ref->ref_item_id)); + ref->ref_type = GF_4CC(type[0], type[1], type[2], type[3]); + gf_list_add(meta->item_refs, ref); + } + else if (!strnicmp(szSlot, "name=", 5)) { + meta->szName = gf_strdup(szSlot+5); + } + else if (!strnicmp(szSlot, "path=", 5)) { + meta->szPath = gf_strdup(szSlot+5); + } + else if (!strnicmp(szSlot, "mime=", 5)) { + meta->item_type = GF_META_ITEM_TYPE_MIME; + meta->mime_type = gf_strdup(szSlot+5); + } + else if (!strnicmp(szSlot, "encoding=", 9)) { + meta->enc_type = gf_strdup(szSlot+9); + } + else if (!strnicmp(szSlot, "image-size=", 11)) { + CHECK_IMGPROP + sscanf(szSlot+11, "%dx%d", &meta->image_props->width, &meta->image_props->height); + } + else if (!strnicmp(szSlot, "image-grid-size=", 16)) { + CHECK_IMGPROP + sscanf(szSlot+16, "%dx%d", &meta->image_props->num_grid_rows, &meta->image_props->num_grid_columns); + } + else if (!strnicmp(szSlot, "image-overlay-color=", 20)) { + CHECK_IMGPROP + sscanf(szSlot+20, "%d,%d,%d,%d", &meta->image_props->overlay_canvas_fill_value_r,&meta->image_props->overlay_canvas_fill_value_g,&meta->image_props->overlay_canvas_fill_value_b,&meta->image_props->overlay_canvas_fill_value_a); + } + else if (!strnicmp(szSlot, "image-overlay-offsets=", 22)) { + CHECK_IMGPROP + u32 position = 22; + u32 comma_count = 0; + u32 offset_index = 0; + char *prev = szSlot+position; + char *sub_next = strchr(szSlot+position, ','); + while (sub_next != NULL) { + comma_count++; + sub_next++; + sub_next = strchr(sub_next, ','); + } + meta->image_props->overlay_count = comma_count/2+1; + meta->image_props->overlay_offsets = (GF_ImageItemOverlayOffset *)gf_malloc(meta->image_props->overlay_count*sizeof(GF_ImageItemOverlayOffset)); + if (!meta->image_props->overlay_offsets) { + return 0; + } + sub_next = strchr(szSlot+position, ','); + while (sub_next != NULL) { + *sub_next = 0; + meta->image_props->overlay_offsets[offset_index].horizontal = atoi(prev); + *sub_next = ','; + sub_next++; + prev = sub_next; + if (sub_next) { + sub_next = strchr(sub_next, ','); + if (sub_next) *sub_next = 0; + meta->image_props->overlay_offsets[offset_index].vertical = atoi(prev); + if (sub_next) { + *sub_next = ','; + sub_next++; + prev = sub_next; + sub_next = strchr(sub_next, ','); + } + } else { + meta->image_props->overlay_offsets[offset_index].vertical = 0; + } + offset_index++; + } + } + else if (!strnicmp(szSlot, "image-pasp=", 11)) { + CHECK_IMGPROP + sscanf(szSlot+11, "%dx%d", &meta->image_props->hSpacing, &meta->image_props->vSpacing); + } + else if (!strnicmp(szSlot, "image-pixi=", 11)) { + CHECK_IMGPROP + if (strchr(szSlot+11, ',') == NULL) { + meta->image_props->num_channels = 1; + meta->image_props->bits_per_channel[0] = atoi(szSlot+11); + } else { + meta->image_props->num_channels = 3; + sscanf(szSlot+11, "%u,%u,%u", &(meta->image_props->bits_per_channel[0]), &(meta->image_props->bits_per_channel[1]), &(meta->image_props->bits_per_channel[2])); + } + } + else if (!strnicmp(szSlot, "image-rloc=", 11)) { + CHECK_IMGPROP + sscanf(szSlot+11, "%dx%d", &meta->image_props->hOffset, &meta->image_props->vOffset); + } + else if (!strnicmp(szSlot, "rotation=", 9)) { + CHECK_IMGPROP + meta->image_props->angle = atoi(szSlot+9); + } + else if (!strnicmp(szSlot, "mirror-axis=", 12)) { + CHECK_IMGPROP + meta->image_props->mirror = (!strnicmp(szSlot+12, "vertical", 8) ? 1 : 2); + } + else if (!strnicmp(szSlot, "clap=", 5)) { + CHECK_IMGPROP + sscanf(szSlot + 5, "%d,%d,%d,%d,%d,%d,%d,%d", &meta->image_props->clap_wnum, &meta->image_props->clap_wden, + &meta->image_props->clap_hnum, &meta->image_props->clap_hden, + &meta->image_props->clap_honum, &meta->image_props->clap_hoden, + &meta->image_props->clap_vonum, &meta->image_props->clap_voden); + } + else if (!stricmp(szSlot, "hidden")) { + CHECK_IMGPROP + meta->image_props->hidden = GF_TRUE; + } + else if (!stricmp(szSlot, "alpha")) { + CHECK_IMGPROP + meta->image_props->alpha = GF_TRUE; + } + else if (!stricmp(szSlot, "depth")) { + CHECK_IMGPROP + meta->image_props->depth = GF_TRUE; + } + //"ref" (without '=') is for data reference, "ref=" is for item references + else if (!stricmp(szSlot, "ref")) { + CHECK_IMGPROP + meta->image_props->use_reference = GF_TRUE; + } + else if (!strnicmp(szSlot, "it=", 3)) { + CHECK_IMGPROP + meta->image_props->item_ref_id = atoi(szSlot+3); + } + else if (!strnicmp(szSlot, "itp=", 4)) { + CHECK_IMGPROP + meta->image_props->item_ref_id = atoi(szSlot+4); + meta->image_props->copy_props = 1; + } + else if (!strnicmp(szSlot, "time=", 5)) { + Float s=0, e=0, step=0; + CHECK_IMGPROP + if (sscanf(szSlot+5, "%f-%f/%f", &s, &e, &step)==3) { + meta->image_props->time = s; + meta->image_props->end_time = e; + meta->image_props->step_time = step; + } else if (sscanf(szSlot+5, "%f-%f", &s, &e)==2) { + meta->image_props->time = s; + meta->image_props->end_time = e; + } else if (sscanf(szSlot+5, "%f/%f", &s, &step)==2) { + meta->image_props->time = s; + meta->image_props->step_time = step; + } else if (sscanf(szSlot+5, "%f", &s)==1) { + meta->image_props->time = s; + } + } + else if (!strnicmp(szSlot, "samp=", 5)) { + CHECK_IMGPROP + meta->image_props->sample_num = atoi(szSlot+5); + meta->root_meta = 1; + } + else if (!strnicmp(szSlot, "group=", 6)) { + char type[5]; + sscanf(szSlot, "group=%4s,%u", type, &meta->group_id); + meta->group_type = GF_4CC(type[0], type[1], type[2], type[3]); + } + else if (!stricmp(szSlot, "split_tiles")) { + CHECK_IMGPROP + meta->image_props->tile_mode = TILE_ITEM_ALL_BASE; + } + else if (!stricmp(szSlot, "dref")) { + meta->use_dref = 1; + } + else if (!stricmp(szSlot, "primary")) { + meta->primary = 1; + } + else if (!stricmp(szSlot, "binary")) { + if (meta->act_type==META_ACTION_SET_XML) meta->act_type=META_ACTION_SET_BINARY_XML; + } + else if (!strnicmp(szSlot, "icc_path=", 9)) { + CHECK_IMGPROP + strcpy(meta->image_props->iccPath, szSlot+9); + } + else if (!stricmp(szSlot, "agrid") || !strnicmp(szSlot, "agrid=", 6)) { + CHECK_IMGPROP + meta->image_props->auto_grid = GF_TRUE; + if (!strnicmp(szSlot, "agrid=", 6)) + meta->image_props->auto_grid_ratio = atof(szSlot+6); + } + else if (!strnicmp(szSlot, "av1_op_index=", 13)) { + CHECK_IMGPROP + meta->image_props->av1_op_index = atoi(szSlot+13); + } + else if (!stricmp(szSlot, "replace")) { + meta->replace = GF_TRUE; + } + else if (!strnicmp(szSlot, "keep_props=", 11)) { + CHECK_IMGPROP + meta->keep_props = gf_strdup(szSlot+11); + } + else if (!strnicmp(szSlot, "auxt=", 5)) { + CHECK_IMGPROP + meta->image_props->aux_urn = gf_strdup(szSlot+5); + } + else if (!strnicmp(szSlot, "auxd=", 5)) { + CHECK_IMGPROP + GF_Err e = gf_file_load_data(szSlot+5, (u8 **) &meta->image_props->aux_data, &meta->image_props->aux_data_len); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Failed to load auxiliary data file %s: %s\n", szSlot+5, gf_error_to_string(e) )); + return 1; + } + } + else if (!strchr(szSlot, '=')) { + switch (meta->act_type) { + case META_ACTION_SET_TYPE: + if (!stricmp(szSlot, "null") || !stricmp(szSlot, "0")) meta->meta_4cc = 0; + else meta->meta_4cc = GF_4CC(szSlot[0], szSlot[1], szSlot[2], szSlot[3]); + break; + case META_ACTION_ADD_ITEM: + case META_ACTION_ADD_IMAGE_ITEM: + case META_ACTION_SET_XML: + case META_ACTION_DUMP_XML: + if (!strncmp(szSlot, "dopt", 4) || !strncmp(szSlot, "sopt", 4) || !strncmp(szSlot, "@", 1)) { + if (next) next[0]=':'; + next=NULL; + } + //cat as -add arg + gf_dynstrcat(&meta->szPath, szSlot, ":"); + if (!meta->szPath) return 2; + break; + case META_ACTION_REM_ITEM: + case META_ACTION_SET_PRIMARY_ITEM: + case META_ACTION_DUMP_ITEM: + meta->item_id = atoi(szSlot); + break; + default: + break; + } + } + if (!next) break; + opts += strlen(szSlot); + next[0] = ':'; + } + return 0; +} +#endif //GPAC_DISABLE_ISOM_WRITE + + +#ifndef GPAC_DISABLE_ISOM_WRITE +static Bool parse_tsel_args(char *opts, TSELActionType act) +{ + GF_ISOTrackID refTrackID = 0; + Bool has_switch_id; + u32 switch_id = 0; + u32 criteria[30]; + u32 nb_criteria = 0; + TSELAction *tsel_act; + char szSlot[1024]; + + has_switch_id = 0; + + if (!opts) return 0; + while (1) { + char *next; + if (!opts || !opts[0]) return 0; + if (opts[0]==':') opts += 1; + strcpy(szSlot, opts); + next = gf_url_colon_suffix(szSlot, '='); + if (next) next[0] = 0; + + + if (!strnicmp(szSlot, "refTrack=", 9)) refTrackID = atoi(szSlot+9); + else if (!strnicmp(szSlot, "switchID=", 9)) { + if (atoi(szSlot+9)<0) { + switch_id = 0; + has_switch_id = 0; + } else { + switch_id = atoi(szSlot+9); + has_switch_id = 1; + } + } + else if (!strnicmp(szSlot, "switchID", 8)) { + switch_id = 0; + has_switch_id = 1; + } + else if (!strnicmp(szSlot, "criteria=", 9)) { + u32 j=9; + nb_criteria = 0; + while (j+3representationID = gf_strdup(opts+3); + /*we allow the same repID to be set to force muxed representations*/ + } + else if (!strnicmp(opts, "dur=", 4)) gf_parse_lfrac(opts+4, &di->media_duration); + else if (!strnicmp(opts, "period=", 7)) di->periodID = gf_strdup(opts+7); + else if (!strnicmp(opts, "BaseURL=", 8)) { + di->baseURL = (char **)gf_realloc(di->baseURL, (di->nb_baseURL+1)*sizeof(char *)); + di->baseURL[di->nb_baseURL] = gf_strdup(opts+8); + di->nb_baseURL++; + } else if (!strnicmp(opts, "bandwidth=", 10)) di->bandwidth = atoi(opts+10); + else if (!strnicmp(opts, "role=", 5)) { + di->roles = gf_realloc(di->roles, sizeof (char *) * (di->nb_roles+1)); + di->roles[di->nb_roles] = gf_strdup(opts+5); + di->nb_roles++; + } else if (!strnicmp(opts, "desc", 4)) { + u32 *nb_descs=NULL; + char ***descs=NULL; + u32 opt_offset=0; + u32 len; + if (!strnicmp(opts, "desc_p=", 7)) { + nb_descs = &di->nb_p_descs; + descs = &di->p_descs; + opt_offset = 7; + } else if (!strnicmp(opts, "desc_as=", 8)) { + nb_descs = &di->nb_as_descs; + descs = &di->as_descs; + opt_offset = 8; + } else if (!strnicmp(opts, "desc_as_c=", 8)) { + nb_descs = &di->nb_as_c_descs; + descs = &di->as_c_descs; + opt_offset = 10; + } else if (!strnicmp(opts, "desc_rep=", 8)) { + nb_descs = &di->nb_rep_descs; + descs = &di->rep_descs; + opt_offset = 9; + } + if (opt_offset) { + (*nb_descs)++; + opts += opt_offset; + len = (u32) strlen(opts); + (*descs) = (char **)gf_realloc((*descs), (*nb_descs)*sizeof(char *)); + (*descs)[(*nb_descs)-1] = (char *)gf_malloc((len+1)*sizeof(char)); + memcpy((*descs)[(*nb_descs)-1], opts, len); + (*descs)[(*nb_descs)-1][len] = 0; + } + + } + else if (!strnicmp(opts, "xlink=", 6)) di->xlink = gf_strdup(opts+6); + else if (!strnicmp(opts, "sscale", 6)) di->sscale = GF_TRUE; + else if (!strnicmp(opts, "pdur=", 5)) gf_parse_frac(opts+5, &di->period_duration); + else if (!strnicmp(opts, "period_duration=", 16)) gf_parse_frac(opts+16, &di->period_duration); + else if (!strnicmp(opts, "ddur=", 5)) gf_parse_frac(opts+5, &di->dash_duration); + else if (!strnicmp(opts, "duration=", 9)) gf_parse_frac(opts+9, &di->dash_duration); + else if (!strnicmp(opts, "asID=", 5)) di->asID = atoi(opts+5); + else if (!strnicmp(opts, "sn=", 3)) di->startNumber = atoi(opts+3); + else if (!strnicmp(opts, "tpl=", 4)) di->seg_template = gf_strdup(opts+4); + else if (!strnicmp(opts, "hls=", 4)) di->hls_pl = gf_strdup(opts+4); + else if (!strnicmp(opts, "trackID=", 8)) di->track_id = atoi(opts+8); + else if (!strnicmp(opts, "@", 1)) { + Bool old_syntax = (opts[1]=='@') ? GF_TRUE : GF_FALSE; + if (sep) sep[0] = ':'; + di->filter_chain = gf_strdup(opts + (old_syntax ? 2 : 1) ); + sep = NULL; + if (!old_syntax && (strstr(di->filter_chain, "@@")!=NULL)) { + skip_rep_id = GF_TRUE; + } + } else { + gf_dynstrcat(&other_opts, opts, ":"); + } + + if (!sep) break; + sep[0] = ':'; + opts = sep+1; + } + first_opt[0] = '\0'; + } + di->file_name = name; + di->source_opts = other_opts; + + if (!skip_rep_id && !di->representationID) { + char szRep[100]; + sprintf(szRep, "%d", *nb_dash_inputs); + di->representationID = gf_strdup(szRep); + } + + return dash_inputs; +} + +static Bool create_new_track_action(char *arg_val, u32 act_type, u32 dump_type) +{ + TrackAction *tka; + char *param = arg_val; + tracks = (TrackAction *)gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act+1)); + if (!tracks) return GF_FALSE; + + tka = & tracks[nb_track_act]; + nb_track_act++; + + memset(tka, 0, sizeof(TrackAction) ); + tka->act_type = act_type; + tka->dump_type = dump_type; + if (act_type != TRACK_ACTION_RAW_EXTRACT) { + open_edit = GF_TRUE; + do_save = GF_TRUE; + } + + if ((act_type==TRACK_ACTION_SET_ID) || (act_type==TRACK_ACTION_SWAP_ID)) { + if (sscanf(param, "%d:%u", &tka->trackID, &tka->newTrackID) != 2) { + M4_LOG(GF_LOG_ERROR, ("Bad format for -set-track-id - expecting \"id1:id2\" got \"%s\"\n", param)); + return GF_FALSE; + } + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_PAR) { + char *ext; + ext = strchr(param, '='); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track par - expecting tkID=none or tkID=PAR_NUM:PAR_DEN got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + + if (!stricmp(ext+1, "none")) + tka->par_num = tka->par_den = 0; + else if (!stricmp(ext+1, "auto")) { + tka->par_num = tka->par_den = -1; + tka->force_par = 1; + } + else if (!stricmp(ext+1, "force")) { + tka->par_num = tka->par_den = 1; + tka->force_par = 1; + } + else { + if (ext[1]=='w') { + tka->rewrite_bs = 1; + ext++; + } + if (sscanf(ext+1, "%d:%d", &tka->par_num, &tka->par_den) != 2) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track par - expecting tkID=PAR_NUM:PAR_DEN got %s\n", param)); + return GF_FALSE; + } + } + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_CLAP) { + char *ext = strchr(param, '='); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track clap - expecting tkID=none or tkID=Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + if (stricmp(ext + 1, "none")) { + if (sscanf(ext + 1, "%d,%d,%d,%d,%d,%d,%d,%d", &tka->clap_wnum, &tka->clap_wden, &tka->clap_hnum, &tka->clap_hden, &tka->clap_honum, &tka->clap_hoden, &tka->clap_vonum, &tka->clap_voden) != 8) { + + M4_LOG(GF_LOG_ERROR, ("Bad format for track clap - expecting tkID=none or tkID=Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd got %s\n", param)); + return GF_FALSE; + } + } + return GF_TRUE; + } + + if (act_type==TRACK_ACTION_SET_MX) { + char *ext = strchr(param, '='); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track matrix - expecting ID=none or ID=M1:M2:M3:M4:M5:M6:M7:M8:M9 got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + if (!stricmp(ext + 1, "none")) { + memset(tka->mx, 0, sizeof(s32)*9); + tka->mx[0] = tka->mx[4] = tka->mx[8] = 1; + } else { + s32 res; + if (strstr(ext+1, "0x")) { + res = sscanf(ext + 1, "0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d", &tka->mx[0], &tka->mx[1], &tka->mx[2], &tka->mx[3], &tka->mx[4], &tka->mx[5], &tka->mx[6], &tka->mx[7], &tka->mx[8]); + } else { + res = sscanf(ext + 1, "%d:%d:%d:%d:%d:%d:%d:%d:%d", &tka->mx[0], &tka->mx[1], &tka->mx[2], &tka->mx[3], &tka->mx[4], &tka->mx[5], &tka->mx[6], &tka->mx[7], &tka->mx[8]); + } + if (res != 9) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track matrix - expecting ID=none or ID=M1:M2:M3:M4:M5:M6:M7:M8:M9 got %s\n", param)); + return GF_FALSE; + } + } + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_EDITS) { + char *ext = strchr(param, '='); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track edits - expecting ID=EDITS got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + tka->string = gf_strdup(ext+1); + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_LANGUAGE) { + char *ext = strchr(param, '='); + if (!strnicmp(param, "all=", 4)) { + strncpy(tka->lang, param + 4, 10-1); + } + else if (!ext) { + strncpy(tka->lang, param, 10-1); + } else { + strncpy(tka->lang, ext + 1, 10-1); + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + } + return GF_TRUE; + } + if ((act_type==TRACK_ACTION_SET_KIND) || (act_type==TRACK_ACTION_REM_KIND)) { + char *ext; + char *scheme_start = NULL; + + //extract trackID + if (!strnicmp(param, "all=", 4)) { + scheme_start = param + 4; + } else { + ext = strchr(param, '='); + if (ext) { + ext[0] = 0; + if (sscanf(param, "%d", &tka->trackID) == 1) { + scheme_start = ext + 1; + } else { + scheme_start = param; + } + ext[0] = '='; + } else { + scheme_start = param; + } + } + + //extract scheme and value - if not, remove kind + if (!scheme_start || !scheme_start[0]) { + M4_LOG(GF_LOG_ERROR, ("Missing kind scheme - expecting ID=schemeURI=value got %s\n", param)); + return GF_FALSE; + } else { + ext = strchr(scheme_start, '='); + if (!ext) { + tka->kind_scheme = gf_strdup(scheme_start); + } else { + ext[0] = 0; + tka->kind_scheme = gf_strdup(scheme_start); + ext[0] = '='; + tka->kind_value = gf_strdup(ext + 1); + } + } + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_DELAY) { + char *ext = strchr(param, '='); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track delay - expecting tkID=DLAY got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + if (sscanf(ext+1, "%d/%u", &tka->delay.num, &tka->delay.den) != 2) { + tka->delay.num = atoi(ext + 1); + tka->delay.den = 1000; + } + return GF_TRUE; + } + if (act_type==TRACK_ACTION_REFERENCE) { + char *ext = strchr(param, '='); + if (!ext) ext = strchr(param, ':'); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track reference - expecting tkID:XXXX:refID got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + + char *ext2 = strchr(ext, ':'); + if (!ext2) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track reference - expecting tkID:XXXX:refID got %s\n", param)); + return GF_FALSE; + } + ext2[0] = 0; + strncpy(tka->lang, ext+1, 9); + ext2[0] = ':'; + tka->newTrackID = (s32) atoi(ext2 + 1); + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_HANDLER_NAME) { + char *ext = strchr(param, '='); + if (!ext) { + M4_LOG(GF_LOG_ERROR, ("Bad format for track name - expecting tkID=name got %s\n", param)); + return GF_FALSE; + } + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + tka->hdl_name = ext + 1; + return GF_TRUE; + } + if (act_type==TRACK_ACTION_SET_KMS_URI) { + char *ext = strchr(param, '='); + + if (!strnicmp(param, "all=", 4)) { + tka->kms = param + 4; + } else if (!ext) { + tka->kms = param; + } else { + tka->kms = ext + 1; + ext[0] = 0; + tka->trackID = atoi(param); + ext[0] = '='; + } + return GF_TRUE; + } + if ((act_type==TRACK_ACTION_SET_TIME) || (act_type==TRACK_ACTION_SET_MEDIA_TIME)) { + struct tm time; + char *ext = strchr(arg_val, '='); + if (ext) { + ext[0] = 0; + tka->trackID = atoi(arg_val); + ext[0] = '='; + arg_val = ext+1; + } + memset(&time, 0, sizeof(struct tm)); + sscanf(arg_val, "%d/%d/%d-%d:%d:%d", &time.tm_mday, &time.tm_mon, &time.tm_year, &time.tm_hour, &time.tm_min, &time.tm_sec); + time.tm_isdst = 0; + time.tm_year -= 1900; + time.tm_mon -= 1; + tka->time = 2082758400; + tka->time += mktime(&time); + return GF_TRUE; + } + + while (param) { + param = gf_url_colon_suffix(param, '='); + if (param) { + *param = 0; + param++; +#ifndef GPAC_DISABLE_MEDIA_EXPORT + if (!strncmp("vttnomerge", param, 10)) { + tka->dump_type |= GF_EXPORT_WEBVTT_NOMERGE; + } else if (!strncmp("layer", param, 5)) { + tka->dump_type |= GF_EXPORT_SVC_LAYER; + } else if (!strncmp("full", param, 4)) { + tka->dump_type |= GF_EXPORT_NHML_FULL; + } else if (!strncmp("embedded", param, 8)) { + tka->dump_type |= GF_EXPORT_WEBVTT_META_EMBEDDED; + } else if (!strncmp("output=", param, 7)) { + tka->out_name = gf_strdup(param+7); + } else if (!strncmp("src=", param, 4)) { + tka->src_name = gf_strdup(param+4); + } else if (!strncmp("str=", param, 4)) { + tka->string = gf_strdup(param+4); + } else if (!strncmp("box=", param, 4)) { + tka->src_name = gf_strdup(param+4); + tka->sample_num = 1; + } else if (!strncmp("type=", param, 4)) { + tka->udta_type = GF_4CC(param[5], param[6], param[7], param[8]); + } else if (tka->dump_type == GF_EXPORT_RAW_SAMPLES) { + tka->sample_num = atoi(param); + } +#endif + } + } + if (arg_val) { + if (!strcmp(arg_val, "*")) { + tka->trackID = (u32) -1; + } else { + if (act_type==TRACK_ACTION_RAW_EXTRACT) { + if (!strncmp(arg_val, "video", 5)) { + arg_val += 5; + tka->dump_track_type = 1; + } + else if (!strncmp(arg_val, "audio", 5)) { + arg_val += 5; + tka->dump_track_type = 2; + } + } + if (arg_val[0]) + tka->trackID = atoi(arg_val); + } + } + return GF_TRUE; +} + +u32 parse_track_dump(char *arg, u32 dump_type) +{ + if (!create_new_track_action(arg, TRACK_ACTION_RAW_EXTRACT, dump_type)) + return 2; + track_dump_type = dump_type; + return 0; +} +u32 parse_track_action(char *arg, u32 act_type) +{ + if (!create_new_track_action(arg, act_type, 0)) { + return 2; + } + return 0; +} + +u32 parse_comp_box(char *arg_val, u32 opt) +{ + compress_top_boxes = arg_val; + size_top_box = opt; + return 0; +} +u32 parse_dnal(char *arg_val, u32 opt) +{ + dump_nal = atoi(arg_val); + dump_nal_type = opt; + return 0; +} +u32 parse_dsap(char *arg_val, u32 opt) +{ + dump_saps = atoi(arg_val); + dump_saps_mode = opt; + return 0; +} + +u32 parse_bs_switch(char *arg_val, u32 opt) +{ + if (!stricmp(arg_val, "no") || !stricmp(arg_val, "off")) bitstream_switching_mode = GF_DASH_BSMODE_NONE; + else if (!stricmp(arg_val, "merge")) bitstream_switching_mode = GF_DASH_BSMODE_MERGED; + else if (!stricmp(arg_val, "multi")) bitstream_switching_mode = GF_DASH_BSMODE_MULTIPLE_ENTRIES; + else if (!stricmp(arg_val, "single")) bitstream_switching_mode = GF_DASH_BSMODE_SINGLE; + else if (!stricmp(arg_val, "inband")) bitstream_switching_mode = GF_DASH_BSMODE_INBAND; + else if (!stricmp(arg_val, "pps")) bitstream_switching_mode = GF_DASH_BSMODE_INBAND_PPS; + else { + M4_LOG(GF_LOG_ERROR, ("Unrecognized bitstream switching mode \"%s\" - please check usage\n", arg_val)); + return 2; + } + return 0; +} + +u32 parse_cp_loc(char *arg_val, u32 opt) +{ + if (!strcmp(arg_val, "both")) cp_location_mode = GF_DASH_CPMODE_BOTH; + else if (!strcmp(arg_val, "as")) cp_location_mode = GF_DASH_CPMODE_ADAPTATION_SET; + else if (!strcmp(arg_val, "rep")) cp_location_mode = GF_DASH_CPMODE_REPRESENTATION; + else { + M4_LOG(GF_LOG_ERROR, ("Unrecognized ContentProtection loction mode \"%s\" - please check usage\n", arg_val)); + return 2; + } + return 0; +} + +u32 parse_pssh(char *arg_val, u32 opt) +{ + if (!strcmp(arg_val, "f")) pssh_mode = GF_DASH_PSSH_MOOF; + else if (!strcmp(arg_val, "v")) pssh_mode = GF_DASH_PSSH_MOOV; + else if (!strcmp(arg_val, "m")) pssh_mode = GF_DASH_PSSH_MPD; + else if (!strcmp(arg_val, "mf") || !strcmp(arg_val, "fm")) pssh_mode = GF_DASH_PSSH_MOOF_MPD; + else if (!strcmp(arg_val, "mv") || !strcmp(arg_val, "vm")) pssh_mode = GF_DASH_PSSH_MOOV_MPD; + else pssh_mode = GF_DASH_PSSH_MOOV; + return 0; +} +u32 parse_sdtp(char *arg_val, u32 opt) +{ + if (!stricmp(arg_val, "both")) sdtp_in_traf = 2; + else if (!stricmp(arg_val, "sdtp")) sdtp_in_traf = 1; + else sdtp_in_traf = 0; + return 0; +} + +u32 parse_rap_ref(char *arg_val, u32 opt) +{ + if (arg_val) { + if (sscanf(arg_val, "%d", &trackID) == 1) { + parse_track_action(arg_val, opt ? TRACK_ACTION_REM_NON_REFS : TRACK_ACTION_REM_NON_RAP); + } + } +#ifndef GPAC_DISABLE_STREAMING + hint_flags |= GP_RTP_PCK_SIGNAL_RAP; +#endif + seg_at_rap = 1; + return 0; +} +u32 parse_store_mode(char *arg_val, u32 opt) +{ + do_save = GF_TRUE; + if ((opt == 0) || (opt == 1)) { + interleaving_time = atof(arg_val) / 1000; + if (!interleaving_time) do_flat = 2; + open_edit = GF_TRUE; + no_inplace = GF_TRUE; + if (opt==1) old_interleave = 1; + } else if (opt==2) { + interleaving_time = atof(arg_val); + do_frag = GF_TRUE; + } else { + force_new = 2; + interleaving_time = 0.5; + do_flat = 1; + } + return 0; +} +u32 parse_base_url(char *arg_val, u32 opt) +{ + mpd_base_urls = gf_realloc(mpd_base_urls, (nb_mpd_base_urls + 1)*sizeof(char**)); + if (!mpd_base_urls) return 2; + mpd_base_urls[nb_mpd_base_urls] = arg_val; + nb_mpd_base_urls++; + return 0; +} +u32 parse_multi_rtp(char *arg_val, u32 opt) +{ +#ifndef GPAC_DISABLE_STREAMING + hint_flags |= GP_RTP_PCK_USE_MULTI; +#endif + if (arg_val) + max_ptime = atoi(arg_val); + return 0; +} + + +u32 parse_senc_param(char *arg_val, u32 opt) +{ +#ifndef GPAC_DISABLE_SCENE_ENCODER + switch (opt) { + case 0: //-sync + smenc_opts.flags |= GF_SM_ENCODE_RAP_INBAND; + smenc_opts.rap_freq = atoi(arg_val); + break; + case 1: //-shadow + smenc_opts.flags &= ~GF_SM_ENCODE_RAP_INBAND; + smenc_opts.flags |= GF_SM_ENCODE_RAP_SHADOW; + smenc_opts.rap_freq = atoi(arg_val); + break; + case 2: //-carousel + smenc_opts.flags &= ~(GF_SM_ENCODE_RAP_INBAND | GF_SM_ENCODE_RAP_SHADOW); + smenc_opts.rap_freq = atoi(arg_val); + break; + case 3: //-auto-quant + smenc_opts.resolution = atoi(arg_val); + smenc_opts.auto_quant = 1; + break; + case 4: //-global-quant + smenc_opts.resolution = atoi(arg_val); + smenc_opts.auto_quant = 2; + break; + case 5: //-ctx-in or -inctx + chunk_mode = GF_TRUE; + input_ctx = arg_val; + } +#endif + return 0; +} +u32 parse_cryp(char *arg_val, u32 opt) +{ + open_edit = GF_TRUE; + if (!opt) { + crypt = 1; + drm_file = arg_val; + open_edit = GF_TRUE; + return 0; + } + crypt = 2; + if (arg_val && get_file_type_by_ext(arg_val) != 1) { + drm_file = arg_val; + return 0; + } + return 3; +} + +u32 parse_dash_profile(char *arg_val, u32 opt) +{ + if (!stricmp(arg_val, "live") || !stricmp(arg_val, "simple")) dash_profile = GF_DASH_PROFILE_LIVE; + else if (!stricmp(arg_val, "onDemand")) dash_profile = GF_DASH_PROFILE_ONDEMAND; + else if (!stricmp(arg_val, "hbbtv1.5:live") || !stricmp(arg_val, "hbbtv1.5.live")) + dash_profile = GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE; + else if (!stricmp(arg_val, "dashavc264:live") || !stricmp(arg_val, "dashavc264.live")) + dash_profile = GF_DASH_PROFILE_AVC264_LIVE; + else if (!stricmp(arg_val, "dashavc264:onDemand") || !stricmp(arg_val, "dashavc264.onDemand")) + dash_profile = GF_DASH_PROFILE_AVC264_ONDEMAND; + else if (!stricmp(arg_val, "dashif.ll")) dash_profile = GF_DASH_PROFILE_DASHIF_LL; + else if (!stricmp(arg_val, "main")) dash_profile = GF_DASH_PROFILE_MAIN; + else if (!stricmp(arg_val, "full")) dash_profile = GF_DASH_PROFILE_FULL; + else { + M4_LOG(GF_LOG_ERROR, ("Unrecognized DASH profile \"%s\" - please check usage\n", arg_val)); + return 2; + } + return 0; +} + +u32 parse_fps(char *arg_val, u32 opt) +{ + u32 ticks, dts_inc; + if (!strcmp(arg_val, "auto")) { + M4_LOG(GF_LOG_WARNING, ("Warning, fps=auto option is deprecated\n")); + } + else if ((sscanf(arg_val, "%u-%u", &ticks, &dts_inc)==2) || (sscanf(arg_val, "%u/%u", &ticks, &dts_inc)==2) ) { + if (!dts_inc) dts_inc = 1; + import_fps.num = ticks; + import_fps.den = dts_inc; + } else { + gf_parse_frac(arg_val, &import_fps); + } + return 0; +} + +u32 parse_split(char *arg_val, u32 opt) +{ + u32 scale=1; + char *unit; + switch (opt) { + case 0://-split + unit = arg_val + (u32) (strlen(arg_val) - 1); + if (strchr("Mm", unit[0])) scale = 60; + else if (strchr("Hh", unit[0])) scale = 3600; + if (scale > 1) { + char c = unit[0]; + unit[0] = 0; + split_duration = scale * atof(arg_val); + unit[0] = c; + } else { + split_duration = atof(arg_val); + } + if (split_duration < 0) split_duration = 0; + split_size = 0; + break; + case 1: //-split-rap, -splitr + split_duration = -1; + split_size = -1; + break; + case 2: //-split-size, -splits + unit = arg_val + (u32) (strlen(arg_val) - 1); + if (strchr("Mm", unit[0])) scale = 1000; + else if (strchr("Gg", unit[0])) scale = 1000000; + if (scale > 1) { + char c = unit[0]; + unit[0] = 0; + split_size = scale * (u32)atoi(arg_val); + unit[0] = c; + } else { + split_size = (u32)atoi(arg_val); + } + split_duration = 0; + break; + case 3: //-split-chunk, -splitx + case 4: //-splitz + case 5: //-splitg + case 6: //-splitf + adjust_split_end = opt-3; + if (!strstr(arg_val, ":") && !strstr(arg_val, "-")) { + M4_LOG(GF_LOG_ERROR, ("Chunk extraction usage: \"-split* start:end\" expressed in seconds\n")); + return 2; + } + if (strstr(arg_val, "end")) { + if (strstr(arg_val, "end-")) { + Double dur_end=0; + sscanf(arg_val, "%lf:end-%lf", &split_start, &dur_end); + split_duration = -2 - dur_end; + } else { + sscanf(arg_val, "%lf:end", &split_start); + split_duration = -2; + } + } else { + split_range_str = arg_val; + } + split_size = 0; + break; + } + return 0; +} + +u32 parse_brand(char *b, u32 opt) +{ + open_edit = GF_TRUE; + switch (opt) { + case 0: //-brand + major_brand = GF_4CC(b[0], b[1], b[2], b[3]); + if (b[4] == ':') { + if (!strncmp(b+5, "0x", 2)) + sscanf(b+5, "0x%x", &minor_version); + else + minor_version = atoi(b + 5); + } + break; + case 1: //-ab + brand_add = (u32*)gf_realloc(brand_add, sizeof(u32) * (nb_alt_brand_add + 1)); + if (!brand_add) return 2; + brand_add[nb_alt_brand_add] = GF_4CC(b[0], b[1], b[2], b[3]); + nb_alt_brand_add++; + break; + case 2: //-rb + brand_rem = (u32*)gf_realloc(brand_rem, sizeof(u32) * (nb_alt_brand_rem + 1)); + if (!brand_rem) return 2; + brand_rem[nb_alt_brand_rem] = GF_4CC(b[0], b[1], b[2], b[3]); + nb_alt_brand_rem++; + break; + } + return 0; +} + +u32 parse_mpegu(char *arg_val, u32 opt) +{ + pack_file = arg_val; + pack_wgt = GF_TRUE; + return 0; +} + +u32 parse_file_info(char *arg_val, u32 opt) +{ + print_info = opt ? 2 : 1; + if (opt==2) { + print_info = 2; + if (arg_val) return 3; + return 0; + } + if (arg_val) { + if (sscanf(arg_val, "%u", &info_track_id) == 1) { + char szTk[20]; + sprintf(szTk, "%u", info_track_id); + if (strcmp(szTk, arg_val)) info_track_id = 0; + } + if (!info_track_id) return 3; + } + return 0; +} +u32 parse_boxpatch(char *arg_val, u32 opt) +{ + box_patch_filename = arg_val; + char *sep = strchr(box_patch_filename, '='); + if (sep) { + sep[0] = 0; + box_patch_trackID = atoi(box_patch_filename); + sep[0] = '='; + box_patch_filename = sep+1; + } + open_edit = GF_TRUE; + return 0; +} +u32 parse_compress(char *arg_val, u32 opt) +{ + compress_moov = opt ? 2 : 1; + open_edit = GF_TRUE; + return 0; +} + + +u32 parse_dump_udta(char *code, u32 opt) +{ + char *sep; + sep = strchr(code, ':'); + if (sep) { + sep[0] = 0; + dump_udta_track = atoi(code); + sep[0] = ':'; + code = sep + 1; + } + + if (strlen(code) == 4) { + dump_udta_type = GF_4CC(code[0], code[1], code[2], code[3]); + } else if (strlen(code) == 8) { + // hex representation on 8 chars + u32 hex1, hex2, hex3, hex4; + if (sscanf(code, "%02x%02x%02x%02x", &hex1, &hex2, &hex3, &hex4) != 4) { + M4_LOG(GF_LOG_ERROR, ("udta code is either a 4CC or 8 hex chars for non-printable 4CC\n")); + return 2; + } + dump_udta_type = GF_4CC(hex1, hex2, hex3, hex4); + } else { + M4_LOG(GF_LOG_ERROR, ("udta code is either a 4CC or 8 hex chars for non-printable 4CC\n")); + return 2; + } + return 0; +} + +u32 parse_dump_ts(char *arg_val, u32 opt) +{ + dump_timestamps = 1; + if (arg_val) { + if (isdigit(arg_val[0])) { + program_number = atoi(arg_val); + } else { + return 3; + } + } + return 0; +} + +u32 parse_ttxt(char *arg_val, u32 opt) +{ + if (opt) //-srt + dump_srt = GF_TRUE; + else + dump_ttxt = GF_TRUE; + + import_subtitle = 1; + trackID = 0; + + if (arg_val && (!strcmp(arg_val, "*") || !strcmp(arg_val, "@") || !strcmp(arg_val, "all")) ) { + trackID = (u32)-1; + } else if (arg_val) { + if (sscanf(arg_val, "%u", &trackID) == 1) { + char szTk[20]; + sprintf(szTk, "%d", trackID); + if (strcmp(szTk, arg_val)) + trackID = 0; + } + if (!trackID) return 3; + } + return 0; +} + +u32 parse_dashlive(char *arg, char *arg_val, u32 opt) +{ + dash_mode = opt ? GF_DASH_DYNAMIC_DEBUG : GF_DASH_DYNAMIC; + dash_live = 1; + if (arg[10] == '=') { + dash_ctx_file = arg + 11; + } + dash_duration = atof(arg_val); + return 0; +} + +u32 parse_help(char *arg_val, u32 opt) +{ + if (!arg_val) PrintUsage(); + else if (opt) PrintHelp(arg_val, GF_TRUE, GF_FALSE); + else if (!strcmp(arg_val, "general")) PrintGeneralUsage(); + else if (!strcmp(arg_val, "extract")) PrintExtractUsage(); + else if (!strcmp(arg_val, "split")) PrintSplitUsage(); + else if (!strcmp(arg_val, "dash")) PrintDASHUsage(); + 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, ("deprecated, see [filters documentation](Filters)\n")); + } + else if (!strcmp(arg_val, "hint")) PrintHintUsage(); + else if (!strcmp(arg_val, "encode")) PrintEncodeUsage(); + else if (!strcmp(arg_val, "crypt")) PrintEncryptUsage(); + else if (!strcmp(arg_val, "meta")) PrintMetaUsage(); + else if (!strcmp(arg_val, "swf")) PrintSWFUsage(); +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + else if (!strcmp(arg_val, "rtp")) { + M4_LOG(GF_LOG_WARNING, ("RTP streaming deprecated in MP4Box, use gpac application\n")); + } + else if (!strcmp(arg_val, "live")) PrintLiveUsage(); +#endif + else if (!strcmp(arg_val, "core")) PrintCoreUsage(); + else if (!strcmp(arg_val, "tags")) PrintTags(); + else if (!strcmp(arg_val, "cicp")) PrintCICP(); + else if (!strcmp(arg_val, "all")) { + PrintGeneralUsage(); + PrintExtractUsage(); + PrintDASHUsage(); + PrintSplitUsage(); + PrintDumpUsage(); + PrintImportUsage(); + PrintHintUsage(); + PrintEncodeUsage(); + PrintEncryptUsage(); + PrintMetaUsage(); + PrintSWFUsage(); +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + PrintLiveUsage(); +#endif + PrintCoreUsage(); + PrintTags(); + PrintCICP(); + } else if (!strcmp(arg_val, "opts")) { + PrintHelp("@", GF_FALSE, GF_FALSE); + } else { + PrintHelp(arg_val, GF_FALSE, GF_FALSE); + } + return 1; +} + +u32 parse_gendoc(char *name, u32 opt) +{ + u32 i=0; + //gen MD + if (!opt) { + help_flags = GF_PRINTARG_MD | GF_PRINTARG_IS_APP; + helpout = gf_fopen("mp4box-gen-opts.md", "w"); + + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » General"); + fprintf(helpout, "\n"); + fprintf(helpout, "# Syntax\n"); + gf_sys_format_help(helpout, help_flags, "MP4Box [option] input [option] [other_dash_inputs]\n" + " \n" + ); + PrintGeneralUsage(); + PrintEncryptUsage(); + fprintf(helpout, "# Help Options\n"); + while (m4b_usage_args[i].name) { + GF_GPACArg *g_arg = (GF_GPACArg *) &m4b_usage_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, g_arg, "mp4box-general"); + } + + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-import-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Media Import"); + fprintf(helpout, "\n"); + PrintImportUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-dash-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Media DASH"); + fprintf(helpout, "\n"); + PrintDASHUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-dump-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Media Dump and Export"); + fprintf(helpout, "\n"); + PrintExtractUsage(); + PrintDumpUsage(); + PrintSplitUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-meta-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Meta and HEIF/IFF"); + fprintf(helpout, "\n"); + PrintMetaUsage(); + gf_fclose(helpout); + + + helpout = gf_fopen("mp4box-scene-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Scene Description"); + fprintf(helpout, "\n"); + PrintEncodeUsage(); +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + PrintLiveUsage(); +#endif + PrintSWFUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-other-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Other Features"); + fprintf(helpout, "\n"); + PrintHintUsage(); + PrintTags(); + gf_fclose(helpout); + } + //gen man + else { + help_flags = GF_PRINTARG_MAN; + helpout = gf_fopen("mp4box.1", "w"); + + + fprintf(helpout, ".TH MP4Box 1 2019 MP4Box GPAC\n"); + fprintf(helpout, ".\n.SH NAME\n.LP\nMP4Box \\- GPAC command-line media packager\n.SH SYNOPSIS\n.LP\n.B MP4Box\n.RI [options] \\ [file] \\ [options]\n.br\n.\n"); + + PrintGeneralUsage(); + PrintExtractUsage(); + PrintDASHUsage(); + PrintSplitUsage(); + PrintDumpUsage(); + PrintImportUsage(); + PrintHintUsage(); + PrintEncodeUsage(); + PrintEncryptUsage(); + PrintMetaUsage(); + PrintSWFUsage(); + PrintTags(); +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + PrintLiveUsage(); +#endif + + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/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" + ".br\nCopyright: %s\n.br\n" + ".SH SEE ALSO\n" + ".LP\ngpac(1), MP4Client(1)\n", gf_gpac_version(), gf_gpac_copyright()); + + gf_fclose(helpout); + } + return 1; +} + +static Bool arg_parse_res = 0; +u32 mp4box_parse_single_arg_class(int argc, char **argv, char *arg, u32 *arg_index, MP4BoxArg *arg_class) +{ + MP4BoxArg *arg_desc = NULL; + char *arg_val = NULL; + u32 i=0; + while (arg_class[i].name) { + arg_desc = (MP4BoxArg *) &arg_class[i]; + i++; + +#ifdef TEST_ARGS + char *sep = strchr(arg_desc->name, ' '); + if (sep) { + M4_LOG(GF_LOG_ERROR, ("Invalid arg %s, space not allowed\n", arg_desc->name)); + exit(1); + } +#endif + if (!strcmp(arg_desc->name, arg+1)) + break; + if (arg_desc->altname && !strcmp(arg_desc->altname, arg+1)) + break; + + if (arg_desc->parse_flags & ARG_IS_FUN2) { + if (!strncmp(arg_desc->name, arg+1, strlen(arg_desc->name) )) + break; + } + arg_desc = NULL; + } + if (!arg_desc) + return GF_FALSE; + + if (arg_desc->parse_flags & ARG_OPEN_EDIT) open_edit = GF_TRUE; + if (arg_desc->parse_flags & ARG_NEED_SAVE) do_save = GF_TRUE; + if (arg_desc->parse_flags & ARG_NO_INPLACE) no_inplace = GF_TRUE; + + if (arg_desc->type != GF_ARG_BOOL) { + Bool has_next = GF_TRUE; + if (*arg_index + 1 == (u32) argc) + has_next = GF_FALSE; + else if (argv[*arg_index + 1][0] == '-') { + s32 v; + if (sscanf(argv[*arg_index + 1], "%d", &v)!=1) + has_next = GF_FALSE; + } + if (!has_next && ! (arg_desc->parse_flags & ARG_EMPTY) ) { + M4_LOG(GF_LOG_ERROR, ("Missing argument value for %s - please check usage\n", arg)); + arg_parse_res = 2; + return GF_TRUE; + } + + if (has_next && (arg_desc->parse_flags & ARG_EMPTY) && (arg_desc->type==GF_ARG_INT)) { + s32 ival; + if (sscanf(argv[*arg_index + 1], "%d", &ival) != 1) { + has_next = GF_FALSE; + arg_val = NULL; + } + } + if (has_next) { + has_next_arg = GF_TRUE; + *arg_index += 1; + arg_val = argv[*arg_index]; + } + } + if (!arg_desc->arg_ptr) return GF_TRUE; + + if (arg_desc->parse_flags & (ARG_IS_FUN|ARG_IS_FUN2) ) { + u32 res; + if (arg_desc->parse_flags & ARG_PUSH_SYSARGS) + gf_sys_set_args(argc, (const char**) argv); + + if (arg_desc->parse_flags & ARG_IS_FUN) { + parse_arg_fun fun = (parse_arg_fun) arg_desc->arg_ptr; + res = fun(arg_val, arg_desc->argv_val); + } else { + parse_arg_fun2 fun2 = (parse_arg_fun2) arg_desc->arg_ptr; + res = fun2(arg, arg_val, arg_desc->argv_val); + } + //rewind, not our arg + if ((res==3) && argv) { + *arg_index -= 1; + res = 0; + } + arg_parse_res = res; + return GF_TRUE; + } + + if (arg_desc->parse_flags & ARG_INT_INC) { + * (u32 *) arg_desc->arg_ptr += 1; + return GF_TRUE; + } + + if (arg_desc->type == GF_ARG_BOOL) { + if (!arg_desc->parse_flags) { + if (arg_desc->argv_val) { + * (u32 *) arg_desc->arg_ptr = arg_desc->argv_val; + } else { + * (Bool *) arg_desc->arg_ptr = GF_TRUE; + } + } else if (arg_desc->parse_flags & ARG_BOOL_REV) { + * (Bool *) arg_desc->arg_ptr = GF_FALSE; + } else if (arg_desc->parse_flags & ARG_HAS_VALUE) { + * (u32 *) arg_desc->arg_ptr = 0; + } else if (arg_desc->parse_flags & ARG_BIT_MASK) { + * (u32 *) arg_desc->arg_ptr |= arg_desc->argv_val; + } else if (arg_desc->parse_flags & ARG_BIT_MASK_REM) { + * (u32 *) arg_desc->arg_ptr &= ~arg_desc->argv_val; + } else if (arg_desc->argv_val) { + * (u32 *) arg_desc->arg_ptr = arg_desc->argv_val; + } else { + * (u32 *) arg_desc->arg_ptr = GF_TRUE; + } + return GF_TRUE; + } + + if (arg_desc->type == GF_ARG_STRING) { + if (arg_desc->parse_flags & ARG_IS_4CC) { + u32 alen = arg_val ? (u32) strlen(arg_val) : 0; + if ((alen<3) || (alen>4)) { + M4_LOG(GF_LOG_ERROR, ("Value for %s must be a 4CC, %s is not - please check usage\n", arg, arg_val)); + arg_parse_res = 2; + return GF_TRUE; + } + * (u32 *) arg_desc->arg_ptr = GF_4CC(arg_val[0], arg_val[1], arg_val[2], arg_val[3]); + return GF_TRUE; + } + + * (char **) arg_desc->arg_ptr = arg_val; + return GF_TRUE; + } + if (!arg_val) { + M4_LOG(GF_LOG_ERROR, ("Missing value for %s - please check usage\n", arg)); + arg_parse_res = 2; + return GF_TRUE; + } + + if (arg_desc->type == GF_ARG_DOUBLE) { + Double v = atof(arg_val); + if (arg_desc->parse_flags & ARG_DIV_1000) { + v /= 1000; + } + if ((arg_desc->parse_flags & ARG_NON_ZERO) && !v) { + M4_LOG(GF_LOG_ERROR, ("Value for %s shall not be 0 - please check usage\n", arg)); + arg_parse_res = 2; + return GF_TRUE; + } + * (Double *) arg_desc->arg_ptr = v; + return GF_TRUE; + } + + if (arg_desc->type != GF_ARG_INT) { + M4_LOG(GF_LOG_ERROR, ("Unsupported argument type for %s - please report to gpac devs\n", arg)); + arg_parse_res = 2; + return GF_TRUE; + } + if (arg_desc->parse_flags & ARG_64BITS) { + u64 v; + sscanf(arg_val, LLU, &v); + if (arg_desc->parse_flags & ARG_DIV_1000) { + v /= 1000; + } + if ((arg_desc->parse_flags & ARG_NON_ZERO) && !v) { + M4_LOG(GF_LOG_ERROR, ("Value for %s shall not be 0 - please check usage\n", arg)); + arg_parse_res = 2; + return GF_TRUE; + } + * (u64 *) arg_desc->arg_ptr = v; + } else { + u32 v = atoi(arg_val); + if (arg_desc->parse_flags & ARG_DIV_1000) { + v /= 1000; + } + if ((arg_desc->parse_flags & ARG_NON_ZERO) && !v) { + M4_LOG(GF_LOG_ERROR, ("Value for %s shall not be 0 - please check usage\n", arg)); + arg_parse_res = 2; + return GF_TRUE; + } + * (s32 *) arg_desc->arg_ptr = v; + } + return GF_TRUE; +} + +Bool mp4box_parse_single_arg(int argc, char **argv, char *arg, u32 *arg_index) +{ + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_gen_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_split_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_dash_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_imp_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_senc_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_crypt_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_hint_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_extr_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_dump_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_meta_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_swf_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_liveenc_args)) return GF_TRUE; + if (mp4box_parse_single_arg_class(argc, argv, arg, arg_index, m4b_usage_args)) return GF_TRUE; + + return GF_FALSE; +} + + +u32 mp4box_parse_args(int argc, char **argv) +{ + u32 i; + /*parse our args*/ + for (i = 1; i < (u32)argc; i++) { + char *arg = argv[i]; + /*input file(s)*/ + if ((arg[0] != '-') || !stricmp(arg, "--")) { + char *arg_val = arg; + if (!stricmp(arg, "--")) { + if (i+1==(u32)argc) { + M4_LOG(GF_LOG_ERROR, ("Missing arg for `--` - please check usage\n")); + return 2; + } + has_next_arg = GF_TRUE; + arg_val = argv[i + 1]; + i++; + } + if (argc < 3) { + M4_LOG(GF_LOG_ERROR, ("Error - only one input file found as argument, please check usage\n")); + return 2; + } + else if (inName) { + if (dash_duration) { + if (!nb_dash_inputs) { + dash_inputs = set_dash_input(dash_inputs, inName, &nb_dash_inputs); + } + dash_inputs = set_dash_input(dash_inputs, arg_val, &nb_dash_inputs); + } + else { + M4_LOG(GF_LOG_ERROR, ("Error - 2 input names specified, please check usage\n")); + return 2; + } + } + else { + inName = arg_val; + } + } + //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)); + return 2; + } + else if (!stricmp(arg, "-write-buffer")) { + M4_LOG(GF_LOG_WARNING, ("`%s` option deprecated, use `-bs-cache-size`", arg)); + gf_opts_set_key("temp", "bs-cache-size", argv[i + 1]); + i++; + } + else if (!stricmp(arg, "-pssh-moof")) { + M4_LOG(GF_LOG_ERROR, ("`-pssh-moof` option deprecated , use `-pssh` option\n")); + return 2; + } + else if (!stricmp(arg, "-tag-list")) { + M4_LOG(GF_LOG_ERROR, ("`-tag-list` option deprecated, use `-h tags`\n")); + return 2; + } + else if (!stricmp(arg, "-aviraw")) { + M4_LOG(GF_LOG_ERROR, ("`-aviraw` option deprecated, use `-raw`\n")); + return 2; + } + else if (!stricmp(arg, "-avi")) { + M4_LOG(GF_LOG_ERROR, ("`-avi` option deprecated, use `-mux`\n")); + return 2; + } + else if (!strncmp(arg, "-p=", 3)) { + continue; + } + + //parse argument + else if (mp4box_parse_single_arg(argc, argv, arg, &i)) { + if (arg_parse_res) + return mp4box_cleanup(arg_parse_res); + } + //not a MP4Box arg + else { + u32 res = gf_sys_is_gpac_arg(arg); + if (res==0) { + PrintHelp(arg, GF_FALSE, GF_TRUE); + return 2; + } else if (res==2) { + i++; + } + } + //live scene encoder does not use the unified parsing and should be moved as a scene encoder filter + if (live_scene) return 0; + } + return 0; +} + +/* + END OF OPTION PARSING CODE +*/ + + + +void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist) +{ + FILE *logs = cbk; + if (log_tool != GF_LOG_CODING) return; + vfprintf(logs, fmt, vlist); + fflush(logs); +} + + +#ifndef GPAC_DISABLE_ISOM_HINTING + +/* + MP4 File Hinting +*/ + +void SetupClockReferences(GF_ISOFile *file) +{ + u32 i, count, ocr_id; + count = gf_isom_get_track_count(file); + if (count==1) return; + ocr_id = 0; + for (i=0; iOCRESID = ocr_id; + gf_isom_change_mpeg4_description(file, i+1, 1, esd); + gf_odf_desc_del((GF_Descriptor *) esd); + } + } +} + +/*base RTP payload type used (you can specify your own types if needed)*/ +#define BASE_PAYT 96 + +GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 base_flags, Bool copy_data, Bool interleave, Bool regular_iod, Bool single_group, Bool hint_no_offset) +{ + GF_ESD *esd; + GF_InitialObjectDescriptor *iod; + u32 i, val, res, streamType; + u32 sl_mode, prev_ocr, single_ocr, nb_done, tot_bw, bw, flags, spec_type; + GF_Err e; + char szPayload[30]; + GF_RTPHinter *hinter; + Bool copy, has_iod, single_av; + u8 init_payt = BASE_PAYT; + u32 mtype; + GF_SDP_IODProfile iod_mode = GF_SDP_IOD_NONE; + u32 media_group = 0; + u8 media_prio = 0; + + tot_bw = 0; + prev_ocr = 0; + single_ocr = 1; + + has_iod = 1; + iod = (GF_InitialObjectDescriptor *) gf_isom_get_root_od(file); + if (!iod) has_iod = 0; + else { + if (!gf_list_count(iod->ESDescriptors)) has_iod = 0; + gf_odf_desc_del((GF_Descriptor *) iod); + } + + spec_type = gf_isom_guess_specification(file); + single_av = single_group ? 1 : gf_isom_is_single_av(file); + + /*first make sure we use a systems track as base OCR*/ + for (i=0; idecoderConfig) { + streamType = esd->decoderConfig->streamType; + if (!prev_ocr) { + prev_ocr = esd->OCRESID; + if (!esd->OCRESID) prev_ocr = esd->ESID; + } else if (esd->OCRESID && prev_ocr != esd->OCRESID) { + single_ocr = 0; + } + /*OD MUST BE WITHOUT REFERENCES*/ + if (streamType==1) copy = 1; + } + gf_odf_desc_del((GF_Descriptor *) esd); + + if (!regular_iod && gf_isom_is_track_in_root_od(file, i+1)) { + /*single AU - check if base64 would fit in ESD (consider 33% overhead of base64), otherwise stream*/ + if (gf_isom_get_sample_count(file, i+1)==1) { + GF_ISOSample *samp = gf_isom_get_sample(file, i+1, 1, &val); + if (streamType && samp) { + res = gf_hinter_can_embbed_data(samp->data, samp->dataLength, streamType); + } else { + /*not a system track, we shall hint it*/ + res = 0; + } + if (samp) gf_isom_sample_del(&samp); + if (res) continue; + } + } + if (interleave) sl_mode |= GP_RTP_PCK_USE_INTERLEAVING; + + hinter = gf_hinter_track_new(file, i+1, MTUSize, max_ptime, rtp_rate, sl_mode, init_payt, copy, media_group, media_prio, &e); + + if (!hinter) { + if (e) { + M4_LOG(nb_done ? GF_LOG_WARNING : GF_LOG_ERROR, ("Cannot create hinter (%s)\n", gf_error_to_string(e) )); + if (!nb_done) return e; + } + continue; + } + + if (hint_no_offset) + gf_hinter_track_force_no_offsets(hinter); + + bw = gf_hinter_track_get_bandwidth(hinter); + tot_bw += bw; + flags = gf_hinter_track_get_flags(hinter); + + //set extraction mode for AVC/SVC + gf_isom_set_nalu_extract_mode(file, i+1, GF_ISOM_NALU_EXTRACT_LAYER_ONLY); + + gf_hinter_track_get_payload_name(hinter, szPayload); + M4_LOG(GF_LOG_INFO, ("Hinting track ID %d - Type \"%s:%s\" (%s) - BW %d kbps\n", gf_isom_get_track_id(file, i+1), gf_4cc_to_str(mtype), gf_4cc_to_str(mtype), szPayload, bw)); + if (flags & GP_RTP_PCK_SYSTEMS_CAROUSEL) M4_LOG(GF_LOG_INFO, ("\tMPEG-4 Systems stream carousel enabled\n")); + e = gf_hinter_track_process(hinter); + + if (!e) e = gf_hinter_track_finalize(hinter, has_iod); + gf_hinter_track_del(hinter); + + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error while hinting (%s)\n", gf_error_to_string(e))); + if (!nb_done) return e; + } + init_payt++; + nb_done ++; + } + + if (has_iod) { + iod_mode = GF_SDP_IOD_ISMA; + if (regular_iod) iod_mode = GF_SDP_IOD_REGULAR; + } else { + iod_mode = GF_SDP_IOD_NONE; + } + gf_hinter_finalize(file, iod_mode, tot_bw); + + if (!single_ocr) + M4_LOG(GF_LOG_WARNING, ("Warning: at least 2 timelines found in the file\nThis may not be supported by servers/players\n\n")); + + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_HINTING*/ + +#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_AV_PARSERS) + +static void check_media_profile(GF_ISOFile *file, u32 track) +{ + u8 PL; + GF_ESD *esd = gf_isom_get_esd(file, track, 1); + if (!esd) return; + + switch (esd->decoderConfig->streamType) { + case 0x04: + PL = gf_isom_get_pl_indication(file, GF_ISOM_PL_VISUAL); + if (esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) { + GF_M4VDecSpecInfo vdsi; + gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &vdsi); + if (vdsi.VideoPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, vdsi.VideoPL); + } else if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_AVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_SVC)) { + gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, 0x15); + } else if (!PL) { + gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, 0xFE); + } + break; + case 0x05: + PL = gf_isom_get_pl_indication(file, GF_ISOM_PL_AUDIO); + switch (esd->decoderConfig->objectTypeIndication) { + case GF_CODECID_AAC_MPEG2_MP: + 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); + } + break; + default: + if (!PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, 0xFE); + } + break; + } + gf_odf_desc_del((GF_Descriptor *) esd); +} +void remove_systems_tracks(GF_ISOFile *file) +{ + u32 i, count; + + count = gf_isom_get_track_count(file); + if (count==1) return; + + /*force PL rewrite*/ + gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, 0); + gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, 0); + gf_isom_set_pl_indication(file, GF_ISOM_PL_OD, 1); /*the lib always remove IOD when no profiles are specified..*/ + + for (i=0; ibuf_alloc) nbytes=buf_alloc; + gf_bs_read_data(bs_in, buf, nbytes); + gf_bs_write_data(bs_out, buf, nbytes); + size-=nbytes; + } + continue; + } + orig_box_overhead += size; + + size-=8; + + if (size>buf_alloc) { + buf_alloc = size; + buf = gf_realloc(buf, buf_alloc); + } + gf_bs_read_data(bs_in, buf, size); + + replace+=5; + + comp_size = buf_alloc; + + e = gf_gz_compress_payload(&buf, size, &comp_size); + if (e) break; + + if (comp_size>buf_alloc) { + buf_alloc = comp_size; + } + bytes_uncomp += size; + bytes_comp += comp_size; + + //write size + gf_bs_write_u32(bs_out, comp_size+8); + //write type + gf_bs_write_data(bs_out, replace, 4); + //write data + gf_bs_write_data(bs_out, buf, comp_size); + + final_box_overhead += 8+comp_size; + } + dst_size = gf_bs_get_position(bs_out); + + if (buf) gf_free(buf); + gf_bs_del(bs_in); + gf_bs_del(bs_out); + gf_fclose(in); + gf_fclose(out); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error compressing: %s\n", gf_error_to_string(e))); + return e; + } + + if (has_mov) { + u32 i, nb_tracks, nb_samples; + GF_ISOFile *mov; + Double rate, new_rate, duration; + + mov = gf_isom_open(inName, GF_ISOM_OPEN_READ, NULL); + duration = (Double) gf_isom_get_duration(mov); + duration /= gf_isom_get_timescale(mov); + + nb_samples = 0; + nb_tracks = gf_isom_get_track_count(mov); + for (i=0; i= to_copy) break; + } + gf_fclose(fin); + gf_fclose(fout); + return mp4box_cleanup(0); +} + +static u32 do_write_udp() +{ + GF_Err e; + u32 res = 0; + GF_Socket *sock = gf_sk_new(GF_SOCK_TYPE_UDP); + u16 port = 2345; + char *sep = strrchr(udp_dest, ':'); + if (sep) { + sep[0] = 0; + port = atoi(sep+1); + } + e = gf_sk_bind( sock, NULL, port, udp_dest, port, 0); + if (sep) sep[0] = ':'; + if (e) { + M4_LOG(GF_LOG_ERROR, ("Failed to bind socket to %s: %s\n", udp_dest, gf_error_to_string(e) )); + res = 1; + } else { + e = gf_sk_send(sock, (u8 *) inName, (u32)strlen(inName)); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Failed to send datagram: %s\n", gf_error_to_string(e) )); + res = 1; + } + } + gf_sk_del(sock); + return res; +} + +#ifndef GPAC_DISABLE_MPD +static u32 convert_mpd() +{ + GF_Err e; + Bool remote = GF_FALSE; + GF_MPD *mpd; + char *mpd_base_url = NULL; + if (!strnicmp(inName, "http://", 7) || !strnicmp(inName, "https://", 8)) { +#if !defined(GPAC_DISABLE_CORE_TOOLS) + e = gf_dm_wget(inName, "tmp_main.m3u8", 0, 0, &mpd_base_url); + if (e != GF_OK) { + M4_LOG(GF_LOG_ERROR, ("Cannot retrieve M3U8 (%s): %s\n", inName, gf_error_to_string(e))); + if (mpd_base_url) gf_free(mpd_base_url); + return mp4box_cleanup(1); + } + remote = GF_TRUE; +#else + gf_free(mpd_base_url); + M4_LOG(GF_LOG_ERROR, ("HTTP Downloader disabled in this build\n")); + return mp4box_cleanup(1); +#endif + + if (outName) + strcpy(outfile, outName); + else { + const char *sep = gf_file_basename(inName); + char *ext = gf_file_ext_start(sep); + if (ext) ext[0] = 0; + sprintf(outfile, "%s.mpd", sep); + if (ext) ext[0] = '.'; + } + } else { + if (outName) + strcpy(outfile, outName); + else { + char *dst = strdup(inName); + char *ext = strstr(dst, ".m3u8"); + if (ext) ext[0] = 0; + sprintf(outfile, "%s.mpd", dst); + gf_free(dst); + } + } + + mpd = gf_mpd_new(); + if (!mpd) { + e = GF_OUT_OF_MEM; + M4_LOG(GF_LOG_ERROR, ("[DASH] Error: MPD creation problem %s\n", gf_error_to_string(e))); + mp4box_cleanup(1); + } + FILE *f = gf_fopen(remote ? "tmp_main.m3u8" : inName, "r"); + u32 manif_type = 0; + if (f) { + char szDATA[1000]; + s32 read; + szDATA[999]=0; + read = (s32) gf_fread(szDATA, 999, f); + if (read<0) read = 0; + szDATA[read]=0; + gf_fclose(f); + if (strstr(szDATA, "SmoothStreamingMedia")) + manif_type = 2; + else if (strstr(szDATA, "#EXTM3U")) + manif_type = 1; + } + + if (manif_type==1) { + e = gf_m3u8_to_mpd(remote ? "tmp_main.m3u8" : inName, mpd_base_url ? mpd_base_url : inName, outfile, 0, "video/mp2t", GF_TRUE, use_url_template, segment_timeline, NULL, mpd, GF_TRUE, GF_TRUE); + } else if (manif_type==2) { + e = gf_mpd_smooth_to_mpd(remote ? "tmp_main.m3u8" : inName, mpd, mpd_base_url ? mpd_base_url : inName); + } else { + e = GF_NOT_SUPPORTED; + } + if (!e) + gf_mpd_write_file(mpd, outfile); + + if (mpd) + gf_mpd_del(mpd); + if (mpd_base_url) + gf_free(mpd_base_url); + + if (remote) { + gf_file_delete("tmp_main.m3u8"); + } + if (e != GF_OK) { + M4_LOG(GF_LOG_ERROR, ("Error converting %s (%s) to MPD (%s): %s\n", (manif_type==1) ? "HLS" : "Smooth", inName, outfile, gf_error_to_string(e))); + return mp4box_cleanup(1); + } else { + M4_LOG(GF_LOG_INFO, ("Done converting %s (%s) to MPD (%s)\n", (manif_type==1) ? "HLS" : "Smooth", inName, outfile)); + return mp4box_cleanup(0); + } +} +#endif + +static u32 do_import_sub() +{ + /* We import the subtitle file, + i.e. we parse it and store the content as samples of a 3GPP Timed Text track in an ISO file, + possibly for later export (e.g. when converting SRT to TTXT, ...) */ +#ifndef GPAC_DISABLE_MEDIA_IMPORT + GF_Err e; + GF_MediaImporter import; + /* Prepare the importer */ + 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; + /* Start the 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"); + return mp4box_cleanup(1); + } + /* Prepare the export */ + strcpy(outfile, inName); + if (strchr(outfile, '.')) { + while (outfile[strlen(outfile)-1] != '.') outfile[strlen(outfile)-1] = 0; + outfile[strlen(outfile)-1] = 0; + } +#ifndef GPAC_DISABLE_ISOM_DUMP + /* Start the export of the track #1, in the appropriate dump type, indicating it's a conversion */ + dump_isom_timed_text(file, gf_isom_get_track_id(file, 1), + dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, + GF_TRUE, + (import_subtitle==2) ? GF_TEXTDUMPTYPE_SVG : (dump_srt ? GF_TEXTDUMPTYPE_SRT : GF_TEXTDUMPTYPE_TTXT)); +#endif + /* Clean the importer */ + gf_isom_delete(file); + gf_file_delete("ttxt_convert"); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error converting %s: %s\n", inName, gf_error_to_string(e))); + return mp4box_cleanup(1); + } + return mp4box_cleanup(0); +#else + M4_LOG(GF_LOG_ERROR, ("Feature not supported\n")); + return mp4box_cleanup(1); +#endif +} + +#if !defined(GPAC_DISABLE_MEDIA_IMPORT) && !defined(GPAC_DISABLE_ISOM_WRITE) +static u32 do_add_cat(int argc, char **argv) +{ + GF_Err e; + u32 i, ipass, nb_pass = 1; + char *mux_args=NULL; + char *mux_sid=NULL; + GF_FilterSession *fs = NULL; + if (nb_add) { + + GF_ISOOpenMode open_mode = GF_ISOM_OPEN_EDIT; + if (force_new) { + open_mode = (do_flat || (force_new==2)) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; + } else { + FILE *test = gf_fopen(inName, "rb"); + if (!test) { + open_mode = (do_flat) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; + if (!outName) outName = inName; + } else { + gf_fclose(test); + if (! gf_isom_probe_file(inName) ) { + open_mode = (do_flat) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; + if (!outName) outName = inName; + } + } + } + open_edit = do_flat ? GF_FALSE : GF_TRUE; + file = gf_isom_open(inName, open_mode, NULL); + if (!file) { + M4_LOG(GF_LOG_ERROR, ("Cannot open destination file %s: %s\n", inName, gf_error_to_string(gf_isom_last_error(NULL)) )); + return mp4box_cleanup(1); + } + + if (freeze_box_order) + gf_isom_freeze_order(file); + + if (do_flat) { + if (major_brand) + gf_isom_set_brand_info(file, major_brand, minor_version); + for (i=0; i0) && (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; + } + + if (dash_mode && dash_live) + M4_LOG(GF_LOG_INFO, ("Live DASH-ing - press 'q' to quit, 's' to save context and quit\n")); + + if (!dash_ctx_file && dash_live) { + u32 r1; + u64 add = (u64) (intptr_t) &dasher; + add ^= gf_net_get_utc(); + r1 = (u32) add ^ (u32) (add/0xFFFFFFFF); + r1 ^= gf_rand(); + sprintf(szStateFile, "%s/dasher_%X.xml", gf_get_default_cache_directory(), r1 ); + dash_ctx_file = szStateFile; + dyn_state_file = GF_TRUE; + } else if (dash_ctx_file) { + if (force_new) + gf_file_delete(dash_ctx_file); + } + + if (dash_profile==GF_DASH_PROFILE_AUTO) + dash_profile = dash_mode ? GF_DASH_PROFILE_LIVE : GF_DASH_PROFILE_FULL; + + if (!dash_mode) { + time_shift_depth = 0; + mpd_update_time = 0; + } else if ((dash_profile>=GF_DASH_PROFILE_MAIN) && !use_url_template && !mpd_update_time) { + /*use a default MPD update of dash_duration sec*/ + mpd_update_time = (Double) (dash_subduration ? dash_subduration : dash_duration); + M4_LOG(GF_LOG_INFO, ("Using default MPD refresh of %g seconds\n", mpd_update_time)); + } + + if (file && do_save) { + gf_isom_close(file); + file = NULL; + del_file = GF_TRUE; + } + + /*setup dash*/ + dasher = gf_dasher_new(szMPD, dash_profile, NULL, dash_scale, dash_ctx_file); + if (!dasher) { + return mp4box_cleanup(1); + } + e = gf_dasher_set_info(dasher, dash_title, cprt, dash_more_info, dash_source, NULL); + if (e) { + M4_LOG(GF_LOG_ERROR, ("DASH Error: %s\n", gf_error_to_string(e))); + gf_dasher_del(dasher); + return e; + } + + gf_dasher_set_start_date(dasher, dash_start_date); + gf_dasher_set_location(dasher, dash_source); + for (i=0; i < nb_mpd_base_urls; i++) { + e = gf_dasher_add_base_url(dasher, mpd_base_urls[i]); + if (e) { + M4_LOG(GF_LOG_ERROR, ("DASH Error: %s\n", gf_error_to_string(e))); + gf_dasher_del(dasher); + return e; + } + } + + if (segment_timeline && !use_url_template) { + M4_LOG(GF_LOG_WARNING, ("DASH Warning: using -segment-timeline with no -url-template. Forcing URL template.\n")); + use_url_template = GF_TRUE; + } + + e = gf_dasher_enable_url_template(dasher, (Bool) use_url_template, seg_name, seg_ext, init_seg_ext); + if (!e) e = gf_dasher_enable_segment_timeline(dasher, segment_timeline); + if (!e) e = gf_dasher_enable_single_segment(dasher, single_segment); + if (!e) e = gf_dasher_enable_single_file(dasher, single_file); + if (!e) e = gf_dasher_set_switch_mode(dasher, bitstream_switching_mode); + if (!e) e = gf_dasher_set_durations(dasher, dash_duration, interleaving_time, dash_subduration); + if (!e) e = gf_dasher_enable_rap_splitting(dasher, seg_at_rap, frag_at_rap); + if (!e) e = gf_dasher_set_segment_marker(dasher, segment_marker); + if (!e) e = gf_dasher_enable_sidx(dasher, (subsegs_per_sidx>=0) ? 1 : 0, (u32) subsegs_per_sidx, daisy_chain_sidx, use_ssix); + if (!e) e = gf_dasher_set_dynamic_mode(dasher, dash_mode, mpd_update_time, time_shift_depth, mpd_live_duration); + if (!e) e = gf_dasher_set_min_buffer(dasher, min_buffer); + if (!e) e = gf_dasher_set_ast_offset(dasher, ast_offset_ms); + if (!e) e = gf_dasher_enable_memory_fragmenting(dasher, memory_frags); + if (!e) e = gf_dasher_set_initial_isobmf(dasher, initial_moof_sn, initial_tfdt); + if (!e) e = gf_dasher_configure_isobmf_default(dasher, no_fragments_defaults, pssh_mode, samplegroups_in_traf, single_traf_per_moof, tfdt_per_traf, mvex_after_traks, sdtp_in_traf); + if (!e) e = gf_dasher_enable_utc_ref(dasher, insert_utc); + if (!e) e = gf_dasher_enable_real_time(dasher, frag_real_time); + if (!e) e = gf_dasher_set_content_protection_location_mode(dasher, cp_location_mode); + if (!e) e = gf_dasher_set_profile_extension(dasher, dash_profile_extension); + if (!e) e = gf_dasher_enable_cached_inputs(dasher, no_cache); + if (!e) e = gf_dasher_enable_loop_inputs(dasher, ! no_loop); + if (!e) e = gf_dasher_set_split_mode(dasher, dash_split_mode); + if (!e) e = gf_dasher_set_last_segment_merge(dasher, merge_last_seg); + if (!e) e = gf_dasher_set_hls_clock(dasher, hls_clock); + if (!e && dash_cues) e = gf_dasher_set_cues(dasher, dash_cues, strict_cues); + if (!e) e = gf_dasher_print_session_info(dasher, fs_dump_flags); + if (!e) e = gf_dasher_keep_source_utc(dasher, keep_utc); + + for (i=0; i < nb_dash_inputs; i++) { + if (!e) e = gf_dasher_add_input(dasher, &dash_inputs[i]); + } + if (e) { + M4_LOG(GF_LOG_ERROR, ("DASH Setup Error: %s\n", gf_error_to_string(e))); + gf_dasher_del(dasher); + return e; + } + + dash_cumulated_time=0; + + while (1) { + if (run_for && (dash_cumulated_time >= run_for)) { + M4_LOG(GF_LOG_INFO, ("Done running, computing static MPD\n")); + do_abort = 3; + } + + dash_prev_time=gf_sys_clock(); + if (do_abort>=2) { + e = gf_dasher_set_dynamic_mode(dasher, GF_DASH_DYNAMIC_LAST, 0, time_shift_depth, mpd_live_duration); + } + + if (!e) e = gf_dasher_process(dasher); + if (!dash_live && (e==GF_EOS) ) { + M4_LOG(GF_LOG_INFO, ("Nothing to dash, too early ...\n")); + e = GF_OK; + } + + if (do_abort) + break; + + //this happens when reading file while writing them (local playback of the live session ...) + if (dash_live && (e==GF_IO_ERR) ) { + M4_LOG(GF_LOG_WARNING, ("Error dashing file (%s) but continuing ...\n", gf_error_to_string(e) )); + e = GF_OK; + } + + if (e) break; + + if (dash_live) { + u64 ms_in_session=0; + u32 slept = gf_sys_clock(); + u32 sleep_for = gf_dasher_next_update_time(dasher, &ms_in_session); + M4_LOG(GF_LOG_INFO, ("Next generation scheduled in %u ms (DASH time "LLU" ms)\r", sleep_for, ms_in_session)); + if (run_for && (ms_in_session>=run_for)) { + dash_cumulated_time = 1+run_for; + continue; + } + + while (1) { + if (gf_prompt_has_input()) { + char c = (char) gf_prompt_get_char(); + if (c=='X') { + do_abort = 1; + break; + } + if (c=='q') { + do_abort = 2; + break; + } + if (c=='s') { + do_abort = 3; + break; + } + } + + if (dash_mode == GF_DASH_DYNAMIC_DEBUG) { + break; + } + if (!sleep_for) break; + + gf_sleep(sleep_for/10); + sleep_for = gf_dasher_next_update_time(dasher, NULL); + if (sleep_for<=1) { + dash_now_time=gf_sys_clock(); + dash_cumulated_time+=(dash_now_time-dash_prev_time); + M4_LOG(GF_LOG_INFO, ("Slept for %d ms before generation, dash cumulated time %d\n", dash_now_time - slept, dash_cumulated_time)); + break; + } + } + } else { + break; + } + } + + gf_dasher_del(dasher); + + if (!run_for && dash_ctx_file && (do_abort==3) && (dyn_state_file) && !gf_sys_is_test_mode() ) { + char szName[1024]; + M4_LOG(GF_LOG_INFO, ("Enter file name to save dash context:\n")); + if (scanf("%1023s", szName) == 1) { + gf_file_move(dash_ctx_file, szName); + } + } + if (e) M4_LOG(GF_LOG_ERROR, ("Error DASHing file: %s\n", gf_error_to_string(e))); + if (file) gf_isom_delete(file); + if (del_file) + gf_file_delete(inName); + + return e; +} + + +static GF_Err do_export_tracks_non_isobmf() +{ + u32 i; + + GF_MediaExporter mdump; + char szFile[GF_MAX_PATH+24]; + for (i=0; iact_type != TRACK_ACTION_RAW_EXTRACT) continue; + memset(&mdump, 0, sizeof(mdump)); + mdump.in_name = inName; + mdump.flags = tka->dump_type; + mdump.trackID = tka->trackID; + mdump.track_type = tka->dump_track_type; + mdump.sample_num = tka->sample_num; + + if (dump_std) { + mdump.out_name = "std"; + } + else if (outName) { + mdump.out_name = outName; + mdump.flags |= GF_EXPORT_MERGE; + } else if (nb_track_act>1) { + sprintf(szFile, "%s_track%d", outfile, mdump.trackID); + mdump.out_name = szFile; + } else { + mdump.out_name = outfile; + } + mdump.print_stats_graph = fs_dump_flags; + e = gf_media_export(&mdump); + if (e) return e; + } + return GF_OK; +} + + +static GF_Err do_dump_iod() +{ + GF_Err e = GF_OK; + GF_InitialObjectDescriptor *iod = (GF_InitialObjectDescriptor *)gf_isom_get_root_od(file); + if (!iod) { + M4_LOG(GF_LOG_WARNING, ("File %s has no IOD\n", inName)); + } else { + char szName[GF_MAX_PATH+10]; + FILE *iodf; + sprintf(szName, "%s.iod", outfile); + iodf = gf_fopen(szName, "wb"); + if (!iodf) { + M4_LOG(GF_LOG_ERROR, ("Cannot open destination %s\n", szName)); + e = GF_IO_ERR; + } else { + u8 *desc; + u32 size; + GF_BitStream *bs = gf_bs_from_file(iodf, GF_BITSTREAM_WRITE); + if (gf_odf_desc_write((GF_Descriptor *)iod, &desc, &size)==GF_OK) { + gf_fwrite(desc, size, iodf); + gf_free(desc); + } else { + M4_LOG(GF_LOG_ERROR, ("Error writing IOD %s\n", szName)); + e = GF_IO_ERR; + } + gf_bs_del(bs); + gf_fclose(iodf); + } + gf_odf_desc_del((GF_Descriptor*)iod); + } + return e; +} + +static GF_Err do_export_tracks() +{ + GF_Err e; + u32 i; + char szFile[GF_MAX_PATH+24]; + GF_MediaExporter mdump; + for (i=0; iact_type != TRACK_ACTION_RAW_EXTRACT) continue; + memset(&mdump, 0, sizeof(mdump)); + mdump.file = file; + mdump.flags = tka->dump_type; + mdump.trackID = tka->trackID; + mdump.sample_num = tka->sample_num; + if (tka->out_name) { + mdump.out_name = tka->out_name; + } else if (outName) { + mdump.out_name = outName; + mdump.flags |= GF_EXPORT_MERGE; + /*don't infer extension on user-given filename*/ + mdump.flags |= GF_EXPORT_NO_FILE_EXT; + } else if (mdump.trackID) { + sprintf(szFile, "%s_track%d", outfile, mdump.trackID); + mdump.out_name = szFile; + } else { + sprintf(szFile, "%s_export", outfile); + mdump.out_name = szFile; + } + if (tka->trackID==(u32) -1) { + for (j=0; jtrackID) tk = gf_isom_get_track_by_id(file, meta->trackID); + + switch (meta->act_type) { +#ifndef GPAC_DISABLE_ISOM_WRITE + case META_ACTION_SET_TYPE: + /*note: we don't handle file brand modification, this is an author stuff and cannot be guessed from meta type*/ + e = gf_isom_set_meta_type(file, meta->root_meta, tk, meta->meta_4cc); + gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_ISO2, GF_TRUE); + do_save = GF_TRUE; + break; + case META_ACTION_ADD_ITEM: + + if (meta->replace) { + if (!gf_isom_get_meta_item_by_id(file, meta->root_meta, tk, meta->item_id)) { + M4_LOG(GF_LOG_WARNING, ("No item with ID %d, ignoring replace\n", meta->item_id)); + meta->item_id = 0; + } else { + e = gf_isom_remove_meta_item(file, meta->root_meta, tk, meta->item_id, GF_TRUE, meta->keep_props); + if (e) break; + } + } + + 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); + 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++) { + MetaRef *ref_entry = gf_list_get(meta->item_refs, ref_i); + 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); + } + } + do_save = GF_TRUE; + break; + case META_ACTION_ADD_IMAGE_ITEM: + { + u32 old_tk_count = gf_isom_get_track_count(file); + u32 src_tk_id = 1; + GF_Fraction _frac = {0,0}; + GF_ISOFile *fsrc = file; + self_ref = GF_FALSE; + + tk = 0; + if (meta->image_props && meta->image_props->auto_grid) { + e = GF_OK; + self_ref = GF_TRUE; + } else if (!meta->szPath || (meta->image_props && meta->image_props->sample_num && meta->image_props->use_reference)) { + e = GF_OK; + self_ref = GF_TRUE; + src_tk_id = meta->trackID; + } 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); + fsrc = meta->image_props->src_file; + if (meta->image_props->item_ref_id) + src_tk_id = 0; + } else { + gf_isom_disable_brand_rewrite(file, GF_TRUE); + e = import_file(file, meta->szPath, 0, _frac, 0, NULL, NULL, NULL, 0); + gf_isom_disable_brand_rewrite(file, GF_FALSE); + } + } else { + M4_LOG(GF_LOG_ERROR, ("Missing file name to import\n")); + e = GF_BAD_PARAM; + } + if (e == GF_OK) { + u32 meta_type = gf_isom_get_meta_type(file, meta->root_meta, tk); + if (!meta_type) { + e = gf_isom_set_meta_type(file, meta->root_meta, tk, GF_META_ITEM_TYPE_PICT); + } else { + if (meta_type != GF_META_ITEM_TYPE_PICT) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Warning: file already has a root 'meta' box of type %s\n", gf_4cc_to_str(meta_type))); + e = GF_BAD_PARAM; + } + } + if (e == GF_OK) { + if (!meta->item_id) { + 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) ) { + u32 j; + for (j=0; jreplace) { + if (!gf_isom_get_meta_item_by_id(file, meta->root_meta, tk, meta->item_id)) { + M4_LOG(GF_LOG_WARNING, ("No item with ID %d, ignoring replace\n", meta->item_id)); + meta->item_id = 0; + } else { + e = gf_isom_remove_meta_item(file, meta->root_meta, tk, meta->item_id, GF_TRUE, meta->keep_props); + } + } + if (e==GF_OK) + e = gf_isom_iff_create_image_item_from_track(file, meta->root_meta, tk, src_tk_id, meta->szName, meta->item_id, meta->image_props, NULL); + + if (e == GF_OK && meta->primary) { + e = gf_isom_set_meta_primary_item(file, meta->root_meta, tk, meta->item_id); + } + if (e == GF_OK && 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++) { + MetaRef *ref_entry = gf_list_get(meta->item_refs, ref_i); + 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); + } + } + } + } + if (meta->image_props && meta->image_props->src_file) { + gf_isom_delete(meta->image_props->src_file); + meta->image_props->src_file = NULL; + } else if (!self_ref) { + gf_isom_remove_track(file, old_tk_count+1); + if (do_flat) { + M4_LOG(GF_LOG_ERROR, ("Warning: -flat storage cannot be used when using -add-image on external file\n")); + e = GF_NOT_SUPPORTED; + } + } + do_save = GF_TRUE; + } + break; + case META_ACTION_ADD_IMAGE_DERIVED: + { + u32 meta_type = gf_isom_get_meta_type(file, meta->root_meta, tk); + e = GF_OK; + if (!meta_type) { + e = gf_isom_set_meta_type(file, meta->root_meta, tk, GF_META_ITEM_TYPE_PICT); + } else { + if (meta_type != GF_META_ITEM_TYPE_PICT) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Warning: file already has a root 'meta' box of type %s\n", gf_4cc_to_str(meta_type))); + e = GF_BAD_PARAM; + } + } + if (e) break; + if (!meta->item_id) { + e = gf_isom_meta_get_next_item_id(file, meta->root_meta, tk, &meta->item_id); + } + if (e == GF_OK && meta->item_type) { + if (meta->item_type == GF_4CC('g','r','i','d')) { + e = gf_isom_iff_create_image_grid_item(file, meta->root_meta, tk, + meta->szName && strlen(meta->szName) ? meta->szName : NULL, + meta->item_id, + meta->image_props); + } else if (meta->item_type == GF_4CC('i','o','v','l')) { + e = gf_isom_iff_create_image_overlay_item(file, meta->root_meta, tk, + meta->szName && strlen(meta->szName) ? meta->szName : NULL, + meta->item_id, + meta->image_props); + } else if (meta->item_type == GF_4CC('i','d','e','n')) { + e = gf_isom_iff_create_image_identity_item(file, meta->root_meta, tk, + meta->szName && strlen(meta->szName) ? meta->szName : NULL, + meta->item_id, + meta->image_props); + } else { + e = GF_NOT_SUPPORTED; + } + if (e) break; + if (meta->primary) { + e = gf_isom_set_meta_primary_item(file, meta->root_meta, tk, meta->item_id); + if (e) break; + } + 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++) { + MetaRef *ref_entry = gf_list_get(meta->item_refs, ref_i); + 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) break; + } + } + } + do_save = GF_TRUE; + } + break; + case META_ACTION_REM_ITEM: + e = gf_isom_remove_meta_item(file, meta->root_meta, tk, meta->item_id, GF_FALSE, NULL); + do_save = GF_TRUE; + break; + case META_ACTION_SET_PRIMARY_ITEM: + e = gf_isom_set_meta_primary_item(file, meta->root_meta, tk, meta->item_id); + do_save = GF_TRUE; + break; + case META_ACTION_SET_XML: + case META_ACTION_SET_BINARY_XML: + e = gf_isom_set_meta_xml(file, meta->root_meta, tk, meta->szPath, NULL, 0, (meta->act_type==META_ACTION_SET_BINARY_XML) ? 1 : 0); + do_save = GF_TRUE; + break; + case META_ACTION_REM_XML: + if (gf_isom_get_meta_item_count(file, meta->root_meta, tk)) { + e = gf_isom_remove_meta_xml(file, meta->root_meta, tk); + do_save = GF_TRUE; + } else { + M4_LOG(GF_LOG_WARNING, ("No meta box in input file\n")); + e = GF_OK; + } + break; + case META_ACTION_DUMP_ITEM: + if (gf_isom_get_meta_item_count(file, meta->root_meta, tk)) { + e = gf_isom_extract_meta_item(file, meta->root_meta, tk, meta->item_id, meta->szPath && strlen(meta->szPath) ? meta->szPath : NULL); + } else { + M4_LOG(GF_LOG_WARNING, ("No meta box in input file\n")); + e = GF_OK; + } + break; +#endif // GPAC_DISABLE_ISOM_WRITE + + case META_ACTION_DUMP_XML: + if (gf_isom_has_meta_xml(file, meta->root_meta, tk)) { + e = gf_isom_extract_meta_xml(file, meta->root_meta, tk, meta->szPath, NULL); + } else { + M4_LOG(GF_LOG_WARNING, ("No meta box in input file\n")); + e = GF_OK; + } + break; + default: + break; + } + if (meta->item_refs) { + while (gf_list_count(meta->item_refs)) { + gf_free(gf_list_pop_back(meta->item_refs)); + } + gf_list_del(meta->item_refs); + meta->item_refs = NULL; + } + // meta->image_props is freed in mp4box_cleanup + if (e) return e; + } + return GF_OK; +} + +static GF_Err do_tsel_act() +{ + u32 i; + GF_Err e; + for (i=0; i0) { + u32 tk, k; + for (k=0; k<(u32) count; k++) { + gf_isom_get_reference(file, j+1, GF_ISOM_REF_CHAP, k+1, &tk); + if (tk==i+1) { + is_chap = 1; + break; + } + } + if (is_chap) break; + } + if (is_chap) break; + } + /*this is a subtitle track*/ + if (!is_chap) + gf_isom_set_media_type(file, i+1, GF_ISOM_MEDIA_SUBT); + } + break; + } + } + gf_isom_set_brand_info(file, ipod_major_brand, 1); + gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_MP42, GF_TRUE); + do_save = GF_TRUE; +} + +static GF_Err do_track_act() +{ + u32 j; + for (j=0; jtrackID ? gf_isom_get_track_by_id(file, tka->trackID) : 0; + + timescale = gf_isom_get_timescale(file); + switch (tka->act_type) { + case TRACK_ACTION_REM_TRACK: + e = gf_isom_remove_track(file, track); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error Removing track ID %d: %s\n", tka->trackID, gf_error_to_string(e))); + } else { + M4_LOG(GF_LOG_INFO, ("Removing track ID %d\n", tka->trackID)); + } + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_LANGUAGE: + for (i=0; ilang); + if (e) return e; + do_save = GF_TRUE; + } + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_KIND: + for (i=0; ikind_scheme, tka->kind_value); + if (e) return e; + do_save = GF_TRUE; + } + do_save = GF_TRUE; + break; + case TRACK_ACTION_REM_KIND: + for (i=0; ikind_scheme, tka->kind_value); + if (e) return e; + do_save = GF_TRUE; + } + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_DELAY: + if (tka->delay.num && tka->delay.den) { + u64 tk_dur; + + e = gf_isom_remove_edits(file, track); + if (e) return e; + tk_dur = gf_isom_get_track_duration(file, track); + if (gf_isom_get_edits_count(file, track)) + do_save = GF_TRUE; + if (tka->delay.num>0) { + //cast to u64, delay_ms * timescale can be quite big before / 1000 + e = gf_isom_append_edit(file, track, ((u64) tka->delay.num) * timescale / tka->delay.den, 0, GF_ISOM_EDIT_EMPTY); + if (e) return e; + e = gf_isom_append_edit(file, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); + if (e) return e; + do_save = GF_TRUE; + } else { + //cast to u64, delay_ms * timescale can be quite big before / 1000 + u64 to_skip = ((u64) -tka->delay.num) * timescale / tka->delay.den; + if (to_skipdelay.num) * gf_isom_get_media_timescale(file, track) / tka->delay.den; + e = gf_isom_append_edit(file, track, tk_dur-to_skip, media_time, GF_ISOM_EDIT_NORMAL); + if (e) return e; + do_save = GF_TRUE; + } else { + M4_LOG(GF_LOG_WARNING, ("Warning: request negative delay longer than track duration - ignoring\n")); + } + } + } else if (gf_isom_get_edits_count(file, track)) { + e = gf_isom_remove_edits(file, track); + if (e) return e; + do_save = GF_TRUE; + } + break; + case TRACK_ACTION_SET_KMS_URI: + for (i=0; ikms); + if (e) return e; + do_save = GF_TRUE; + } + break; + case TRACK_ACTION_SET_ID: + if (!tka->trackID && (gf_isom_get_track_count(file) == 1)) { + M4_LOG(GF_LOG_WARNING, ("Warning: track id is not specified, but file has only one track - assume that you want to change id for this track\n")); + track = 1; + } + if (track) { + u32 newTrack; + newTrack = gf_isom_get_track_by_id(file, tka->newTrackID); + if (newTrack != 0) { + M4_LOG(GF_LOG_WARNING, ("Cannot set track id with value %d because a track already exists - ignoring", tka->newTrackID)); + } else { + e = gf_isom_set_track_id(file, track, tka->newTrackID); + if (e) return e; + do_save = GF_TRUE; + } + } else { + M4_LOG(GF_LOG_WARNING, ("Error: Cannot change id for track %d because it does not exist - ignoring", tka->trackID)); + } + break; + case TRACK_ACTION_SWAP_ID: + if (track) { + u32 tk1, tk2; + tk1 = gf_isom_get_track_by_id(file, tka->trackID); + tk2 = gf_isom_get_track_by_id(file, tka->newTrackID); + if (!tk1 || !tk2) { + M4_LOG(GF_LOG_WARNING, ("Error: Cannot swap track IDs because not existing - ignoring")); + } else { + e = gf_isom_set_track_id(file, tk2, 0); + if (e) return e; + e = gf_isom_set_track_id(file, tk1, tka->newTrackID); + if (e) return e; + e = gf_isom_set_track_id(file, tk2, tka->trackID); + if (e) return e; + do_save = GF_TRUE; + } + } else { + M4_LOG(GF_LOG_WARNING, ("Error: Cannot change id for track %d because it does not exist - ignoring", tka->trackID)); + } + break; + case TRACK_ACTION_SET_PAR: + e = gf_media_change_par(file, track, tka->par_num, tka->par_den, tka->force_par, tka->rewrite_bs); + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_CLAP: + e = gf_isom_set_clean_aperture(file, track, 1, tka->clap_wnum, tka->clap_wden, tka->clap_hnum, tka->clap_hden, tka->clap_honum, tka->clap_hoden, tka->clap_vonum, tka->clap_voden); + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_MX: + e = gf_isom_set_track_matrix(file, track, tka->mx); + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_HANDLER_NAME: + e = gf_isom_set_handler_name(file, track, tka->hdl_name); + do_save = GF_TRUE; + break; + case TRACK_ACTION_ENABLE: + if (!gf_isom_is_track_enabled(file, track)) { + e = gf_isom_set_track_enabled(file, track, GF_TRUE); + do_save = GF_TRUE; + } + break; + case TRACK_ACTION_DISABLE: + if (gf_isom_is_track_enabled(file, track)) { + e = gf_isom_set_track_enabled(file, track, GF_FALSE); + do_save = GF_TRUE; + } + break; + case TRACK_ACTION_REFERENCE: + e = gf_isom_set_track_reference(file, track, GF_4CC(tka->lang[0], tka->lang[1], tka->lang[2], tka->lang[3]), tka->newTrackID); + do_save = GF_TRUE; + break; + case TRACK_ACTION_REM_NON_RAP: + e = gf_media_remove_non_rap(file, track, GF_FALSE); + do_save = GF_TRUE; + break; + case TRACK_ACTION_REM_NON_REFS: + e = gf_media_remove_non_rap(file, track, GF_TRUE); + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_UDTA: + e = set_file_udta(file, track, tka->udta_type, tka->string ? tka->string : tka->src_name , tka->sample_num ? GF_TRUE : GF_FALSE, tka->string ? GF_TRUE : GF_FALSE); + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_EDITS: + e = apply_edits(file, track, tka->string); + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_TIME: + if (!tka->trackID) { + e = gf_isom_set_creation_time(file, tka->time, tka->time); + if (e) return e; + for (i=0; itime, tka->time); + if (e) return e; + } + } else { + e = gf_isom_set_track_creation_time(file, track, tka->time, tka->time); + } + do_save = GF_TRUE; + break; + case TRACK_ACTION_SET_MEDIA_TIME: + for (i=0; itime, tka->time); + if (e) return e; + } + do_save = GF_TRUE; + break; + default: + break; + } + if (e) return e; + } + return GF_OK; +} + +static GF_Err do_itunes_tag() +{ + GF_Err e; + char *itunes_data = NULL; + char *tags = itunes_tags; + + if (gf_file_exists(itunes_tags)) { + u32 len; + e = gf_file_load_data(itunes_tags, (u8 **) &itunes_data, &len); + if (e) return e; + tags = itunes_data; + } + + while (tags) { + char *val; + Bool clear = GF_FALSE; + Bool is_wma = GF_FALSE; + u32 tlen, tagtype=0, itag = 0; + s32 tag_idx=-1; + char *sep = itunes_data ? strchr(tags, '\n') : gf_url_colon_suffix(tags, '='); + while (sep) { + char *eq = strchr(sep+1, '='); + if (eq) eq[0] = 0; + s32 next_tag_idx = gf_itags_find_by_name(sep+1); + if (next_tag_idx<0) { + //4CC tag + if ( strlen(sep+1)==4) { + next_tag_idx = 0; + } + //3CC tag, changed to @tag + if ( strlen(sep+1)==3) { + next_tag_idx = 0; + } + //unrecognized tag tag + else { + M4_LOG(GF_LOG_WARNING, ("Unrecognize tag \"%s\", assuming part of previous tag\n", sep+1)); + } + } + + if (eq) eq[0] = '='; + if (next_tag_idx>=0) { + sep[0] = 0; + break; + } + sep = itunes_data ? strchr(sep+1, '\n') : gf_url_colon_suffix(sep+1, '='); + } + val = strchr(tags, '='); + if (val) val[0] = 0; + if (!strcmp(tags, "clear") || !strcmp(tags, "reset")) { + clear = GF_TRUE; + } else if (!strncmp(tags, "WM/", 3) ) { + is_wma = GF_TRUE; + } else { + tag_idx = gf_itags_find_by_name(tags); + if (tag_idx<0) { + if (strlen(tags)==4) { + itag = GF_4CC(tags[0], tags[1], tags[2], tags[3]); + tagtype = GF_ITAG_STR; + } else if (strlen(tags)==3) { + itag = GF_4CC(0xA9, tags[0], tags[1], tags[2]); + tagtype = GF_ITAG_STR; + } + } + } + if (val) { + val[0] = '='; + val++; + } + if (!itag && !clear && !is_wma) { + if (tag_idx<0) { + M4_LOG(GF_LOG_WARNING, ("Invalid iTune tag name \"%s\" - ignoring\n", tags)); + break; + } + itag = gf_itags_get_itag(tag_idx); + tagtype = gf_itags_get_type(tag_idx); + } + if (!val || (val[0]==':') || !val[0] || !stricmp(val, "NULL") ) val = NULL; + + //if val is NULL, use tlen=1 to force removal of tag + tlen = val ? (u32) strlen(val) : 1; + if (clear) { + e = gf_isom_apple_set_tag(file, GF_ISOM_ITUNE_RESET, NULL, 0, 0, 0); + } + else if (is_wma) { + if (val) val[-1] = 0; + e = gf_isom_wma_set_tag(file, tags, val); + if (val) val[-1] = '='; + } + else if (val && (tagtype==GF_ITAG_FILE)) { + u32 flen = (u32) strlen(val); + u8 *d=NULL; + while (flen && val[flen-1]=='\n') flen--; + val[flen] = 0; + e = gf_file_load_data(val, (u8 **) &d, &tlen); + val[flen] = '\n'; + + if (!e) + e = gf_isom_apple_set_tag(file, itag, d, tlen, 0, 0); + + if (d) gf_free(d); + } else { + e = gf_isom_apple_set_tag(file, itag, (u8 *) val, tlen, 0, 0); + } + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error assigning tag %s: %s\n", tags, gf_error_to_string(e) )); + } + + do_save = GF_TRUE; + + if (sep) { + sep[0] = itunes_data ? '\n' : ':'; + tags = sep+1; + } else { + tags = NULL; + } + } + if (itunes_data) gf_free(itunes_data); + return GF_OK; +} + +#if !defined(GPAC_DISABLE_ISOM_HINTING) && !defined(GPAC_DISABLE_SENG) +static void set_sdp_ext() +{ + u32 i, j; + for (i=0; ioverlay_offsets) gf_free(iprops->overlay_offsets); + if (iprops->aux_urn) gf_free((char *) iprops->aux_urn); + if (iprops->aux_data) gf_free((char *) iprops->aux_data); + gf_free(iprops); + } + } + gf_free(metas); + metas = NULL; + } + if (tracks) { + u32 i; + for (i = 0; inb_baseURL) { + for (j = 0; jnb_baseURL; j++) { + gf_free(di->baseURL[j]); + } + gf_free(di->baseURL); + } + if (di->rep_descs) { + for (j = 0; jnb_rep_descs; j++) { + gf_free(di->rep_descs[j]); + } + gf_free(di->rep_descs); + } + if (di->as_descs) { + for (j = 0; jnb_as_descs; j++) { + gf_free(di->as_descs[j]); + } + gf_free(di->as_descs); + } + if (di->as_c_descs) { + for (j = 0; jnb_as_c_descs; j++) { + gf_free(di->as_c_descs[j]); + } + gf_free(di->as_c_descs); + } + if (di->p_descs) { + for (j = 0; jnb_p_descs; j++) { + gf_free(di->p_descs[j]); + } + gf_free(di->p_descs); + } + if (di->representationID) gf_free(di->representationID); + if (di->periodID) gf_free(di->periodID); + if (di->xlink) gf_free(di->xlink); + if (di->seg_template) gf_free(di->seg_template); + if (di->hls_pl) gf_free(di->hls_pl); + if (di->source_opts) gf_free(di->source_opts); + if (di->filter_chain) gf_free(di->filter_chain); + + if (di->roles) { + for (j = 0; jnb_roles; j++) { + gf_free(di->roles[j]); + } + gf_free(di->roles); + } + } + gf_free(dash_inputs); + dash_inputs = NULL; + } + if (logfile) gf_fclose(logfile); + gf_sys_close(); + +#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(); + } +#endif + + return ret_code; +} + + + +int mp4boxMain(int argc, char **argv) +{ + u32 i, j; + const char *gpac_profile = "0"; + GF_Err e = GF_OK; + +#ifdef TEST_ARGS + i=0; + mp4box_parse_single_arg(argc, argv, "", &i); +#endif + + for (i = 1; i < (u32) argc ; i++) { + if (!strcmp(argv[i], "-mem-track") || !strcmp(argv[i], "-mem-track-stack")) { +#ifdef GPAC_MEMORY_TRACKING + mem_track = !strcmp(argv[i], "-mem-track-stack") ? GF_MemTrackerBackTrace : GF_MemTrackerSimple; +#else + M4_LOG(GF_LOG_WARNING, ("WARNING - GPAC not compiled with Memory Tracker - ignoring \"%s\"\n", argv[i])); +#endif + break; + } + else if (!strcmp(argv[i], "-p")) { + if (i+1<(u32) argc) + gpac_profile = argv[i+1]; + else { + M4_LOG(GF_LOG_ERROR, ("Bad argument for -p, expecting profile name but no more args\n")); + return 1; + } + } + else if (!strncmp(argv[i], "-p=", 3)) + gpac_profile = argv[i]+3; + } + +#ifdef _TWO_DIGIT_EXPONENT + _set_output_format(_TWO_DIGIT_EXPONENT); +#endif + + /*init libgpac*/ + gf_sys_init(mem_track, gpac_profile); + if (argc < 2) { + M4_LOG(GF_LOG_ERROR, ("Not enough arguments - check usage with -h\n")); + M4_LOG(GF_LOG_INFO, ("MP4Box - GPAC version %s\n" + "%s\n", gf_gpac_version(), gf_gpac_copyright_cite() )); + gf_sys_close(); + return 0; + } + + helpout = stdout; + + i = mp4box_parse_args(argc, argv); + if (i) { + return mp4box_cleanup(i - 1); + } +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + if (live_scene) { + int ret = live_session(argc, argv); + return mp4box_cleanup(ret); + } +#endif + + if (!dash_duration && interleaving_time && do_frag) + interleaving_time /= 1000; + + if (do_mpd_conv) inName = do_mpd_conv; + +#ifndef GPAC_DISABLE_STREAMING + if (import_flags & GF_IMPORT_FORCE_MPEG4) + hint_flags |= GP_RTP_PCK_FORCE_MPEG4; +#endif + + if (!inName && dump_std) + inName = "std"; + + if (!dash_duration && cprt) + open_edit = GF_TRUE; + + if (!inName) { + if (has_next_arg) { + M4_LOG(GF_LOG_ERROR, ("Broken argument specifier or file name missing - check usage with -h\n")); + } else { + PrintUsage(); + } + return mp4box_cleanup(1); + } + if (!strcmp(inName, "std")) dump_std = 2; + if (!strcmp(inName, "stdb")) { + inName = "std"; + dump_std = 1; + } + + if (!interleaving_time) { + /*by default use single fragment per dash segment*/ + if (dash_duration) + interleaving_time = dash_duration; + else if (!do_flat && !(split_duration || split_size || split_range_str)) { + interleaving_time = DEFAULT_INTERLEAVING_IN_SEC; + } + } + + if (dump_std) + outName = "std"; + + if (dump_std==2) { +#ifdef WIN32 + if ( _setmode(_fileno(stdout), _O_BINARY) == -1 ) +#else + if ( freopen(NULL, "wb", stdout) == NULL) +#endif + { + M4_LOG(GF_LOG_ERROR, ("Fatal error: cannot reopen stdout in binary mode.\n")); + return mp4box_cleanup(1); + } + } + + 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); + } + + if (raw_cat) + return do_raw_cat(); + + if (compress_top_boxes) { + if (size_top_box) { + u64 top_size = do_size_top_boxes(inName, compress_top_boxes, size_top_box); + fprintf(stdout, LLU"\n", top_size); + return mp4box_cleanup(e ? 1 : 0); + } else { + e = do_compress_top_boxes(inName, outName); + return mp4box_cleanup(e ? 1 : 0); + } + } + + if (do_mpd_rip) { + e = rip_mpd(inName, outName); + return mp4box_cleanup(e ? 1 : 0); + } + +#ifndef GPAC_DISABLE_CORE_TOOLS + if (do_wget != NULL) { + e = gf_dm_wget(do_wget, inName, 0, 0, NULL); + if (e != GF_OK) { + M4_LOG(GF_LOG_ERROR, ("Cannot retrieve %s: %s\n", do_wget, gf_error_to_string(e) )); + return mp4box_cleanup(1); + } + return mp4box_cleanup(0); + } +#endif + + if (udp_dest) + return do_write_udp(); + +#ifndef GPAC_DISABLE_MPD + if (do_mpd_conv) + return convert_mpd(); +#endif + + if (dash_duration && !nb_dash_inputs) { + dash_inputs = set_dash_input(dash_inputs, inName, &nb_dash_inputs); + } + + if (do_saf && !encode) { + switch (get_file_type_by_ext(inName)) { + case GF_FILE_TYPE_BT_WRL_X3DV: + case GF_FILE_TYPE_XMT_X3D: + case GF_FILE_TYPE_SVG: + encode = GF_TRUE; + break; + case GF_FILE_TYPE_NOT_SUPPORTED: + case GF_FILE_TYPE_ISO_MEDIA: + case GF_FILE_TYPE_SWF: + case GF_FILE_TYPE_LSR_SAF: + break; + } + } + +#ifndef GPAC_DISABLE_SCENE_DUMP + if (dump_mode == GF_SM_DUMP_SVG) { + if (strstr(inName, ".srt") || strstr(inName, ".ttxt")) import_subtitle = 2; + } +#endif + + if (import_subtitle && !trackID) + return do_import_sub(); + + +#if !defined(GPAC_DISABLE_MEDIA_IMPORT) && !defined(GPAC_DISABLE_ISOM_WRITE) + if (nb_add || nb_cat) { + u32 res = do_add_cat(argc, argv); + if (res) return res; + } +#endif /*!GPAC_DISABLE_MEDIA_IMPORT && !GPAC_DISABLE_ISOM_WRITE*/ + +#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_SCENE_ENCODER) && !defined(GPAC_DISABLE_MEDIA_IMPORT) + else if (chunk_mode) { + if (!inName) { + M4_LOG(GF_LOG_ERROR, ("chunk encoding syntax: [-outctx outDump] -inctx inScene auFile\n")); + return mp4box_cleanup(1); + } + e = EncodeFileChunk(inName, outName ? outName : inName, input_ctx, output_ctx); + if (e) { + M4_LOG(GF_LOG_ERROR, ("Error encoding chunk file %s\n", gf_error_to_string(e))); + return mp4box_cleanup(1); + } + goto exit; + } +#endif // !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_SCENE_ENCODER) && !defined(GPAC_DISABLE_MEDIA_IMPORT) + + else if (encode) { +#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_SCENE_ENCODER) && !defined(GPAC_DISABLE_MEDIA_IMPORT) + e = do_scene_encode(); + if (e) goto err_exit; +#endif //!defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_SCENE_ENCODER) && !defined(GPAC_DISABLE_MEDIA_IMPORT) + } + +#ifndef GPAC_DISABLE_ISOM_WRITE + else if (pack_file) { + //don't use any check for ':', the first word must be the four CC + char *fileName = gf_url_colon_suffix(pack_file, 0); + if (fileName && ((fileName - pack_file)==4)) { + fileName[0] = 0; + file = package_file(fileName + 1, pack_file, pack_wgt); + fileName[0] = ':'; + } else { + file = package_file(pack_file, NULL, pack_wgt); + if (!file) { + M4_LOG(GF_LOG_ERROR, ("Failed to package file\n")); + return mp4box_cleanup(1); + } + } + if (!outName) outName = inName; + do_save = GF_TRUE; + open_edit = GF_TRUE; + } +#endif //GPAC_DISABLE_ISOM_WRITE + + if (dash_duration) { + e = do_dash(); + if (e) return mp4box_cleanup(1); + goto exit; + } + + if (split_duration || split_size || split_range_str) { + if (force_new==2) { + do_flat = 3; + force_new = 0; + } + } else { + if (do_flat) + open_edit = GF_TRUE; + } + + //need to open input + if (!file && !do_hash) { + FILE *st = gf_fopen(inName, "rb"); + Bool file_exists = 0; + GF_ISOOpenMode omode; + if (st) { + file_exists = 1; + 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) ) ); + + if (crypt) { + //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 (file) { + e = gf_isom_open_segment(file, inName, 0, 0, 0); + if (e==GF_ISOM_INCOMPLETE_FILE) { + M4_LOG(GF_LOG_WARNING, ("Segment %s: %s\n", inName, gf_error_to_string(e) )); + } else if (e) { + M4_LOG(GF_LOG_ERROR, ("Error opening segment %s: %s\n", inName, gf_error_to_string(e) )); + gf_isom_delete(file); + file = NULL; + } + } + } + if (!file) + file = gf_isom_open(inName, omode, NULL); + + if (!file && (gf_isom_last_error(NULL) == GF_ISOM_INCOMPLETE_FILE) && !open_edit) { + u64 missing_bytes; + gf_isom_open_progressive(inName, 0, 0, GF_FALSE, &file, &missing_bytes); + if (missing_bytes) + M4_LOG(GF_LOG_ERROR, ("Truncated file - missing "LLD" bytes\n", missing_bytes)); + } + + if (!file) { + if (open_edit && nb_meta_act) { + file = gf_isom_open(inName, GF_ISOM_WRITE_EDIT, NULL); + if (!outName && file) outName = inName; + } + + if (!file) { + M4_LOG(GF_LOG_ERROR, ("Error opening file %s: %s\n", inName, gf_error_to_string(gf_isom_last_error(NULL)))); + return mp4box_cleanup(1); + } + } + if (freeze_box_order) + gf_isom_freeze_order(file); + break; + /*allowed for bt<->xmt*/ + case 2: + case 3: + /*allowed for svg->lsr**/ + case 4: + /*allowed for swf->bt, swf->xmt, swf->svg*/ + case 5: + break; + /*used for .saf / .lsr dump*/ + case 6: +#ifndef GPAC_DISABLE_SCENE_DUMP + if ((dump_mode==GF_SM_DUMP_LASER) || (dump_mode==GF_SM_DUMP_SVG)) { + break; + } +#endif + + default: + if (!open_edit && file_exists && !gf_isom_probe_file(inName) && track_dump_type) { + } +#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 +#endif //GPAC_DISABLE_SCENE_DUMP + ) { + /*************************************************************************************************/ +#ifndef GPAC_DISABLE_MEDIA_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); + 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"); + return mp4box_cleanup(1); + } + } +#endif /*GPAC_DISABLE_MEDIA_IMPORT*/ + + if (dump_m2ts) { +#ifndef GPAC_DISABLE_MPEG2TS + dump_mpeg2_ts(inName, outName, program_number); +#endif + } else if (dump_timestamps) { +#ifndef GPAC_DISABLE_MPEG2TS + dump_mpeg2_ts(inName, outName, program_number); +#endif +#ifndef GPAC_DISABLE_CORE_TOOLS + } else if (do_bin_xml) { + xml_bs_to_bin(inName, outName, dump_std); +#endif + } else if (do_hash) { + hash_file(inName, dump_std); + } else if (print_info) { +#ifndef GPAC_DISABLE_MEDIA_IMPORT + convert_file_info(inName, info_track_id); +#endif + } else { + if (mux_name) { + e = do_remux_file(); + if (e) goto err_exit; + if (file) gf_isom_delete(file); + goto exit; + } else { + M4_LOG(GF_LOG_ERROR, ("Input %s is not an MP4 file, operation not allowed\n", inName)); + return mp4box_cleanup(1); + } + } + goto exit; + } +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + else if (open_edit) { + file = gf_isom_open(inName, GF_ISOM_WRITE_EDIT, NULL); + if (!outName && file) outName = inName; + } else if (!file_exists) { + M4_LOG(GF_LOG_ERROR, ("Error %s file %s: %s\n", force_new ? "creating" : "opening", inName, gf_error_to_string(GF_URL_ERROR))); + return mp4box_cleanup(1); + } else { + M4_LOG(GF_LOG_ERROR, ("Cannot open %s - extension not supported\n", inName)); + return mp4box_cleanup(1); + } + } + } + + if (high_dynamc_range_filename) { + e = parse_high_dynamc_range_xml_desc(file, 0, high_dynamc_range_filename); + if (e) goto err_exit; + } + + if (file && keep_utc) { + gf_isom_keep_utc_times(file, 1); + } + + if ( gf_strlcpy(outfile, outName ? outName : inName, sizeof(outfile)) >= sizeof(outfile) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Filename too long (limit is %d)\n", GF_MAX_PATH)); + return mp4box_cleanup(1); + } + + char *szExt = gf_file_ext_start(outfile); + if (szExt) { + /*turn on 3GP saving*/ + if (!stricmp(szExt, ".3gp") || !stricmp(szExt, ".3gpp") || !stricmp(szExt, ".3g2")) + conv_type = GF_ISOM_CONV_TYPE_3GPP; + else if (!stricmp(szExt, ".m4a") || !stricmp(szExt, ".m4v")) + conv_type = GF_ISOM_CONV_TYPE_IPOD; + else if (!stricmp(szExt, ".psp")) + conv_type = GF_ISOM_CONV_TYPE_PSP; + else if (!stricmp(szExt, ".mov") || !stricmp(szExt, ".qt")) + conv_type = GF_ISOM_CONV_TYPE_MOV; + + //remove extension from outfile + *szExt = 0; + } + +#ifndef GPAC_DISABLE_MEDIA_EXPORT + if (!open_edit && track_dump_type && !gf_isom_probe_file(inName)) { + e = do_export_tracks_non_isobmf(); + if (e) goto err_exit; + goto exit; + } + if (mux_name) { + e = do_remux_file(); + if (e) goto err_exit; + if (file) gf_isom_delete(file); + goto exit; + } + + + +#endif /*GPAC_DISABLE_MEDIA_EXPORT*/ + +#ifndef GPAC_DISABLE_SCENE_DUMP + if (dump_mode != GF_SM_DUMP_NONE) { + e = dump_isom_scene(inName, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, dump_mode, do_scene_log, no_odf_conf); + if (e) goto err_exit; + } +#endif + +#ifndef GPAC_DISABLE_SCENE_STATS + if (stat_level) dump_isom_scene_stats(inName, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, stat_level); +#endif + +#ifndef GPAC_DISABLE_ISOM_HINTING + if (!do_hint && print_sdp) dump_isom_sdp(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE); +#endif + if (get_nb_tracks) { + fprintf(stdout, "%d\n", gf_isom_get_track_count(file)); + } + if (print_info) { + if (!file) { + M4_LOG(GF_LOG_ERROR, ("Cannot print info on a non ISOM file (%s)\n", inName)); + } else { + if (info_track_id) DumpTrackInfo(file, info_track_id, 1, (print_info==2) ? GF_TRUE : GF_FALSE, GF_FALSE); + else DumpMovieInfo(file, (print_info==2) ? GF_TRUE : GF_FALSE); + } + } +#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); + if (e) goto err_exit; + } + if (dump_cr) dump_isom_ismacryp(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE); + if ((dump_ttxt || dump_srt) && trackID) { + + if (trackID == (u32)-1) { + for (j=0; j +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_SMGR +#include +#endif + +#ifndef GPAC_DISABLE_LOG +#define M4_LOG(_a, _b) GF_LOG(_a, GF_LOG_APP, _b) +#else +void mp4box_log(const char *fmt, ...); + +#define M4_LOG(_a, _b) mp4box_log _b +#endif + + +typedef enum { + GF_FILE_TYPE_NOT_SUPPORTED = 0, + GF_FILE_TYPE_ISO_MEDIA = 1, + GF_FILE_TYPE_BT_WRL_X3DV = 2, + GF_FILE_TYPE_XMT_X3D = 3, + GF_FILE_TYPE_SVG = 4, + GF_FILE_TYPE_SWF = 5, + GF_FILE_TYPE_LSR_SAF = 6, +} GF_FileType; + + +#ifndef GPAC_DISABLE_MEDIA_IMPORT +void convert_file_info(char *inName, GF_ISOTrackID trackID); +#endif + +Bool mp4box_check_isom_fileopt(char *opt); + +GF_Err parse_high_dynamc_range_xml_desc(GF_ISOFile* movie, u32 track, char* file_name); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +#ifndef GPAC_DISABLE_MEDIA_IMPORT +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); +#else +GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, GF_FilterSession *fsess, Bool second_pass) { + return GF_NOT_SUPPORTED; +} +#endif +GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double interleaving_time, Double chunk_start, u32 adjust_split_end, char *outName, Bool force_rap_split, const char *split_range_str, u32 fs_dump_flags); +GF_Err cat_isomedia_file(GF_ISOFile *mp4, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command, Bool is_pl); + +GF_Err apply_edits(GF_ISOFile *dest, u32 track, char *edits); + +#if !defined(GPAC_DISABLE_SCENE_ENCODER) +GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *logs); +GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *outputContext); +#endif + +GF_ISOFile *package_file(char *file_name, char *fcc, Bool make_wgt); + +#endif + +GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name); +GF_Err dump_isom_chapters(GF_ISOFile *file, char *inName, Bool is_final_name, Bool dump_ogg); +GF_Err dump_isom_udta(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_udta_type, u32 dump_udta_track); + +GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, Bool is_box_array, Bool is_string); +u32 id3_get_genre_tag(const char *name); + +/*in filedump.c*/ +#ifndef GPAC_DISABLE_SCENE_DUMP +GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDumpFormat dump_mode, Bool do_log, Bool no_odf_conv); +//void gf_check_isom_files(char *conf_rules, char *inName); +#endif +#ifndef GPAC_DISABLE_SCENE_STATS +void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 stat_level); +#endif +u32 PrintNode(const char *name, u32 graph_type); +u32 PrintBuiltInNodes(char *arg_val, u32 dump_type); +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); +#endif + + +#ifndef GPAC_DISABLE_ISOM_HINTING +#ifndef GPAC_DISABLE_ISOM_DUMP +void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name); +#endif +void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name); +#endif + +void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name, Bool skip_offset); +GF_Err dump_isom_nal(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, u32 dump_flags); +void dump_isom_saps(GF_ISOFile *file, GF_ISOTrackID trackID, u32 dump_saps_mode, char *inName, Bool is_final_name); + +void dump_isom_chunks(GF_ISOFile *file, char *inName, Bool is_final_name); + +#ifndef GPAC_DISABLE_ISOM_DUMP +void dump_isom_ismacryp(GF_ISOFile *file, char *inName, Bool is_final_name); +void dump_isom_timed_text(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, Bool is_convert, GF_TextDumpType dump_type); +#endif /*GPAC_DISABLE_ISOM_DUMP*/ + + +void DumpTrackInfo(GF_ISOFile *file, GF_ISOTrackID trackID, Bool full_dump, Bool is_track_num, Bool dump_m4sys); +void DumpMovieInfo(GF_ISOFile *file, Bool full_dump); +u32 PrintLanguages(char *argv, u32 opt); + +#ifndef GPAC_DISABLE_MPEG2TS +void dump_mpeg2_ts(char *mpeg2ts_file, char *pes_out_name, Bool prog_num); +#endif + + +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) +void PrintStreamerUsage(); +int stream_file_rtp(int argc, char **argv); +int live_session(int argc, char **argv); +void PrintLiveUsage(); +#endif + +#if !defined(GPAC_DISABLE_STREAMING) +u32 grab_live_m2ts(const char *grab_m2ts, const char *outName); +#endif + +GF_Err rip_mpd(const char *mpd, const char *dst_file); + +GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); + + +u32 parse_track_dump(char *arg, u32 dump_type); +u32 parse_track_action(char *arg, u32 act_type); +u32 parse_sdp_ext(char *arg_val, u32 param); +u32 parse_help(char *arg_val, u32 opt); +u32 parse_gendoc(char *name, u32 opt); +u32 parse_comp_box(char *arg_val, u32 opt); +u32 parse_dnal(char *arg_val, u32 opt); +u32 parse_dsap(char *arg_val, u32 opt); +u32 parse_bs_switch(char *arg_val, u32 opt); +u32 parse_cp_loc(char *arg_val, u32 opt); +u32 parse_pssh(char *arg_val, u32 opt); +u32 parse_sdtp(char *arg_val, u32 opt); +u32 parse_dash_profile(char *arg_val, u32 opt); +u32 parse_rap_ref(char *arg_val, u32 opt); +u32 parse_store_mode(char *arg_val, u32 opt); +u32 parse_base_url(char *arg_val, u32 opt); +u32 parse_multi_rtp(char *arg_val, u32 opt); +u32 parse_senc_param(char *arg_val, u32 opt); +u32 parse_cryp(char *arg_val, u32 opt); +u32 parse_fps(char *arg_val, u32 opt); +u32 parse_split(char *arg_val, u32 opt); +u32 parse_brand(char *b, u32 opt); +u32 parse_mpegu(char *arg_val, u32 opt); +u32 parse_file_info(char *arg_val, u32 opt); +u32 parse_boxpatch(char *arg_val, u32 opt); +u32 parse_aviraw(char *arg_val, u32 opt); +u32 parse_dump_udta(char *code, u32 opt); +u32 parse_dump_ts(char *arg_val, u32 opt); +u32 parse_ttxt(char *arg_val, u32 opt); +u32 parse_dashlive(char *arg, char *arg_val, u32 opt); +u32 parse_compress(char *arg_val, u32 opt); + +#endif // _MP4BOX_H + diff --git a/applications/mp4client/Makefile b/applications/mp4client/Makefile new file mode 100644 index 0000000..f39c315 --- /dev/null +++ b/applications/mp4client/Makefile @@ -0,0 +1,88 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/applications/mp4client + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +LINKFLAGS=$(GPAC_SH_FLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(GPACREADONLY),yes) +CFLAGS+=-DGPAC_READ_ONLY +endif + +ifeq ($(CONFIG_WIN32),yes) +LINKFLAGS+=$(UNICODEFLAGS) +EXE=.exe +PROG=MP4Client$(EXE) + +ifeq ($(HAS_WMAIN),no) +CFLAGS+=-DNO_WMAIN +endif + +else +EXT= +PROG=MP4Client +endif + +ifeq ($(STATICBUILD),yes) +##include static modules and other deps for libgpac +include ../../static.mak +LINKFLAGS+=$(shell pkg-config ../../gpac.pc --libs --static | sed 's/-lgpac //' ) +LINKFLAGS+=$(GPAC_SH_FLAGS) $(EXTRALIBS) +else +LINKFLAGS+=-lgpac +ifeq ($(CONFIG_DARWIN),yes) +#LINKFLAGS+= -Wl,-rpath,'@loader_path' +else +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +endif +endif + +#common obj +OBJS= main.o +ifeq ($(CONFIG_DARWIN),yes) +OBJS+= carbon_events.o +LDFLAGS += -framework Carbon +endif + + +SRCS := $(OBJS:.o=.c) + +ifeq ($(CONFIG_WIN32),yes) +OBJS+=$(SRC_PATH)/manifest.o +endif + +all: $(PROG) + +MP4Client$(EXE): $(OBJS) + $(CC) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(PROG) + +install: clean + install -m 755 $(INSTFLAGS) ../../bin/gcc/MP4Client "$(DESTDIR)$(prefix)/bin" + +uninstall: + rm -rf $(DESTDIR)$(prefix)/bin/MP4Client + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/mp4client/carbon_events.c b/applications/mp4client/carbon_events.c new file mode 100644 index 0000000..5903d6b --- /dev/null +++ b/applications/mp4client/carbon_events.c @@ -0,0 +1,109 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2012 + * All rights reserved + * + * This file is part of GPAC / command-line client + * + * 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. + * + */ + +#if defined(__DARWIN__) || defined(__APPLE__) +#include +#endif + + +int gf_sys_set_args(int argc, const char **argv); +void send_open_url(const char *url); + +void RunApplicationEventLoop(void); +void QuitApplicationEventLoop(void); + +char *my_argv[2]; + +static int main_evt_loop_run = 1; + +static AEEventHandlerUPP open_app_UPP, open_doc_UPP; + +static pascal OSErr ae_open_app (const AppleEvent *ae_event, AppleEvent *ae_reply, long ae_ref_count) +{ + if (main_evt_loop_run) { + QuitApplicationEventLoop(); + main_evt_loop_run = 0; + } + return (noErr); +} + +static pascal OSErr ae_open_doc (const AppleEvent *ae_event, AppleEvent *ae_reply, long ae_ref_count) +{ + OSErr err; + FSRef ref; + AEDescList docList; + long count; + + err = AEGetParamDesc(ae_event, keyDirectObject, typeAEList, &docList); + if (err) + return (noErr); + + err = AECountItems(&docList, &count); + if (err == noErr) { + err = AEGetNthPtr(&docList, 1, typeFSRef, NULL, NULL, &ref, sizeof(FSRef), NULL); + if (err == noErr) { + char path[4096]; + FSRefMakePath(&ref, (UInt8 *) path, 4096); + if (main_evt_loop_run) { + my_argv[1] = strdup(path); + gf_sys_set_args(2, (const char **) my_argv); + } else { + send_open_url(path); + } + } + } + AEDisposeDesc(&docList); + + if (main_evt_loop_run) { + QuitApplicationEventLoop(); + main_evt_loop_run = 0; + } + return (noErr); +} + +void carbon_init () +{ + my_argv[0] = "GPAC"; + my_argv[1] = NULL; + + open_app_UPP = NewAEEventHandlerUPP(ae_open_app); + AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, open_app_UPP, 0L, false); + open_doc_UPP = NewAEEventHandlerUPP(ae_open_doc); + AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, open_doc_UPP, 0L, false); + + main_evt_loop_run = 1; + RunApplicationEventLoop(); +} + +void carbon_uninit() +{ + + DisposeAEEventHandlerUPP(open_app_UPP); + DisposeAEEventHandlerUPP(open_doc_UPP); + + if (my_argv[1]) free(my_argv[1]); +} + + diff --git a/applications/mp4client/main.c b/applications/mp4client/main.c new file mode 100644 index 0000000..d270560 --- /dev/null +++ b/applications/mp4client/main.c @@ -0,0 +1,2811 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2022 + * All rights reserved + * + * This file is part of GPAC / command-line client + * + * 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. + * + */ + + +/*includes both terminal and od browser*/ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/*ISO 639 languages*/ +#include + + +#ifndef WIN32 +#include +#include +#include +#if defined(__DARWIN__) || defined(__APPLE__) +#include +#include + +void carbon_init(); +void carbon_uninit(); + +#endif + +#else +#include /*for GetModuleFileName*/ +#endif //WIN32 + +/*local prototypes*/ +static void PrintWorldInfo(GF_Terminal *term); +static void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *URL); +static void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 indent, char *root_name); + +static void ViewODs(GF_Terminal *term, Bool show_timing); +static void MakeScreenshot(Bool for_coverage); +static void PrintAVInfo(Bool final); + +static u32 gui_mode = 0; +static int ret_val = 0; + +static Bool restart = GF_FALSE; +static Bool reload = GF_FALSE; +Bool do_coverage = GF_FALSE; + +Bool no_prog = 0; + +static FILE *helpout = NULL; +u32 help_flags = 0; + +#if defined(__DARWIN__) || defined(__APPLE__) +#define VK_MOD GF_KEY_MOD_ALT +#else +#define VK_MOD GF_KEY_MOD_CTRL +#endif + +static Bool no_audio = GF_FALSE; +static u32 bench_mode = 0; +static u32 bench_mode_start = 0; +static u32 bench_buffer = 0; +static Bool eos_seen = GF_FALSE; +static Bool addon_visible = GF_TRUE; +Bool is_connected = GF_FALSE; +Bool startup_file = GF_FALSE; +GF_User user; +GF_Terminal *term; +u64 Duration; +GF_Err last_error = GF_OK; +static Bool enable_add_ons = GF_TRUE; +static Fixed playback_speed = FIX_ONE; + +static s32 request_next_playlist_item = GF_FALSE; +FILE *playlist = NULL; +static Bool readonly_playlist = GF_FALSE; + +static u32 display_rti = 0; +static Bool Run; +static Bool CanSeek = GF_FALSE; +static char the_url[GF_MAX_PATH]; +static char pl_path[GF_MAX_PATH]; +static Bool no_mime_check = GF_TRUE; +static u64 log_rti_time_start = 0; +static Bool loop_at_end = GF_FALSE; +static u32 forced_width=0; +static u32 forced_height=0; + +/*windowless options*/ +u32 align_mode = 0; +u32 init_w = 0; +u32 init_h = 0; +u32 last_x, last_y; +Bool right_down = GF_FALSE; + +Float scale = 1; + +static Bool shell_visible = GF_TRUE; +#if defined(WIN32) && !defined(_WIN32_WCE) + +static HWND console_hwnd = NULL; +static Bool owns_wnd = GF_FALSE; +#include +#include +static DWORD getParentPID(DWORD pid) +{ + DWORD ppid = 0; + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h) { + PROCESSENTRY32 pe = { 0 }; + pe.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(h, &pe)) { + do { + if (pe.th32ProcessID == pid) { + ppid = pe.th32ParentProcessID; + break; + } + } while (Process32Next(h, &pe)); + } + CloseHandle(h); + } + return (ppid); +} + +static void getProcessName(DWORD pid, PUCHAR fname, DWORD sz) +{ + HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (h) { + GetModuleFileNameEx(h, NULL, fname, sz); + CloseHandle(h); + } +} +static void w32_hide_shell(u32 cmd_type) +{ + typedef HWND (WINAPI *GetConsoleWindowT)(void); + HMODULE hk32 = GetModuleHandle("kernel32.dll"); + if (!console_hwnd) { + char parentName[GF_MAX_PATH]; + DWORD dwProcessId = 0; + DWORD dwParentProcessId = 0; + DWORD dwParentParentProcessId = 0; + GetConsoleWindowT GetConsoleWindow = (GetConsoleWindowT)GetProcAddress(hk32, "GetConsoleWindow"); + console_hwnd = GetConsoleWindow(); + dwProcessId = GetCurrentProcessId(); + dwParentProcessId = getParentPID(dwProcessId); + if (dwParentProcessId) + dwParentParentProcessId = getParentPID(dwParentProcessId); + //get parent process name, check for explorer + parentName[0] = 0; + getProcessName(dwParentProcessId, parentName, GF_MAX_PATH); + if (strstr(parentName, "explorer")) { + owns_wnd = GF_TRUE; + } + //get parent parent process name, check for devenv (or any other ide name ...) + else if (dwParentParentProcessId) { + owns_wnd = GF_FALSE; + parentName[0] = 0; + getProcessName(dwParentParentProcessId, parentName, GF_MAX_PATH); + if (strstr(parentName, "devenv")) { + owns_wnd = GF_TRUE; + } + } else { + owns_wnd = GF_FALSE; + } + } + if (!owns_wnd || !console_hwnd) return; + + if (cmd_type==0) { + ShowWindow(console_hwnd, SW_SHOW); + shell_visible = GF_TRUE; + } + else if (cmd_type==1) { + ShowWindow(console_hwnd, SW_HIDE); + shell_visible = GF_FALSE; + } + else if (cmd_type == 2) { + PostMessage(GetConsoleWindow(), WM_CLOSE, 0, 0); + } +} + +#define hide_shell w32_hide_shell +#else + +#define hide_shell(_var) + +#endif + + +void send_open_url(const char *url) +{ + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_NAVIGATE; + evt.navigate.to_url = url; + gf_term_send_event(term, &evt); +} + +GF_GPACArg mp4client_args[] = +{ +#ifdef GPAC_MEMORY_TRACKING + GF_DEF_ARG("mem-track", NULL, "enable memory tracker", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + 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("rti", NULL, "log run-time info (FPS, CPU, Mem usage) to given file", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("rtix", NULL, "same as -rti but driven by GPAC logs", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("size", NULL, "specify visual size WxH. If not set, scene size or video size is used", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("rti-refresh", NULL, "set refresh time in ms between two runt-time counters queries (default is 200)", NULL, NULL, GF_ARG_INT, 0), + + GF_DEF_ARG("no-thread", NULL, "disable thread usage (except for depending on driver audio)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("no-audio", NULL, "disable audio", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + +#ifdef GPAC_CONFIG_WIN32 + GF_DEF_ARG("no-wnd", NULL, "use windowless mode (Win32 only)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERIMENTAL), + GF_DEF_ARG("no-back", NULL, "use transparent background for output window when no background is specified (Win32 only)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERIMENTAL), + GF_DEF_ARG("align", NULL, "specify v and h alignment for windowless mode\n" + "- tl: top/left\n" + "- tm: top/horizontal middle\n" + "- tr: top/right\n" + "- ml: vertical middle/left\n" + "- mm: vertical middle/horizontal middle\n" + "- mr: vertical middle/right\n" + "- bl: bottom/left\n" + "- bm: bottom/horizontal middle\n" + "- br: bottom/right", "tl", "tl|tm|tr|ml|mm|mr|bl|bm|br", GF_ARG_INT, GF_ARG_HINT_EXPERIMENTAL), +#endif // GPAC_CONFIG_WIN32 + + GF_DEF_ARG("pause", NULL, "pause at first frame", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("play-from", NULL, "start playback from given time in seconds in media", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("speed", NULL, "start playback wit given speed", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("loop", NULL, "loop playback", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("fs", NULL, "start in fullscreen mode", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("exit", NULL, "exit when presentation is over", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("run-for", NULL, "run for indicated time in seconds and exits", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("service", NULL, "auto-tune to given service ID in a multiplex", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("no-save", NULL, "do not save configuration file on exit", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("no-addon", NULL, "disable automatic loading of media addons declared in source URL", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("gui", NULL, "start in GUI mode. The GUI is indicated in the [configuration](core_config) file __[General]StartupFile__", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("p", NULL, "use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. Reserved name `0` means a new profile, not stored to disk. Works using -p=NAME or -p NAME", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("stats", NULL, "dump filter session stats after playback", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("graph", NULL, "dump filter session graph after playback", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("nk", NULL, "disable keyboard interaction", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("h", "help", "show this help. Use `-hx` to show expert help", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("hc", NULL, "show libgpac core options", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("hr", NULL, "show runtime options when keybard interaction is enabled", NULL, NULL, GF_ARG_BOOL, 0), + {0} +}; + + +void PrintUsage(Bool show_all) +{ + u32 i=0; + + if (help_flags & GF_PRINTARG_MAN) { + fprintf(helpout, ".SH \"DESCRIPTION\"\n.LP\nMP4Client is GPAC command-line media player. It supports all GPAC playback features (2D and 3D support, local playback, RTP streaming, HTTP faststart, many audio and video codecs ...).\n.br\nSpecific URLs shortcuts are available, see gpac -h compositor\n.\n.\n.SH OPTIONS\n"); + } else { + gf_sys_format_help(helpout, help_flags, "Usage: MP4Client [options] [filename]\n" + "# General\n" + "The player accepts any URL supported by GPAC.\n" + "Specific URLs shortcuts are available, see [GPAC Compositor (gpac -h compositor)](compositor)\n" + "\n" + "Warning: MP4Client is being deprecated, use `gpac -play URL`, `gpac -gui URL` or `gpac -mp4c URL`.\n" + "\n" + "Version: %s\n" + "%s\n" + "For more info on GPAC configuration, use `gpac ` [-h](GPAC) `bin` \n \n" + "# Options \n \n", + (help_flags == GF_PRINTARG_MD) ? GPAC_VERSION : gf_gpac_version(), + gf_gpac_copyright_cite() + ); + } + + while (mp4client_args[i].name) { + GF_GPACArg *arg = &mp4client_args[i]; + i++; + if (!show_all && (arg->flags & (GF_ARG_HINT_EXPERIMENTAL|GF_ARG_HINT_EXPERT) )) + continue; + gf_sys_print_arg(helpout, help_flags, arg, "mp4client"); + } +} + +typedef enum +{ + MP4C_QUIT = 0, + MP4C_KILL, + MP4C_RELOAD, + MP4C_OPEN, + MP4C_OPEN_PL, + MP4C_PL_NEXT, + MP4C_PL_JUMP, + MP4C_DISCONNECT, + MP4C_SELECT, + MP4C_PAUSE_RESUME, + MP4C_STEP, + MP4C_SEEK, + MP4C_SEEK_TIME, + MP4C_TIME, + MP4C_UPDATE, + MP4C_EVALJS, + MP4C_SCREENSHOT, + MP4C_WORLDINFO, + MP4C_ODLIST, + MP4C_ODINFO_ID, + MP4C_ODINFO_NUM, + MP4C_ODTIME, + MP4C_ODBUF, + MP4C_DUMPSCENE, + MP4C_STRESSMODE, + MP4C_NAVMODE, + MP4C_LASTVP, + MP4C_OGL2D, + MP4C_AR_4_3, + MP4C_AR_16_9, + MP4C_AR_NONE, + MP4C_AR_ORIG, + MP4C_LOGS, + MP4C_RELOAD_OPTS, + MP4C_DISP_RTI, + MP4C_DISP_FPS, + MP4C_DISP_STATS, + MP4C_DISP_GRAPH, + MP4C_HELP, + MP4C_DOWNRATE, + MP4C_VMEM_CACHE, + +} MP4C_Command; + +struct _mp4c_key +{ + u8 char_code; + MP4C_Command cmd_type; + const char *cmd_help; + u32 flags; +} MP4C_Keys[] = { + {'q', MP4C_QUIT, "quit", 0}, + {'X', MP4C_KILL, "kill", 0}, + {'r', MP4C_KILL, "reload current presentation", 0}, + {'o', MP4C_OPEN, "connect to the specified URL", 0}, + {'O', MP4C_OPEN_PL, "connect to the specified playlist", 0}, + {'N', MP4C_PL_NEXT, "switch to the next URL in the playlist. Also works with `\\n`", 0}, + {'P', MP4C_PL_JUMP, "jump to a given number ahead in the playlist", 0}, + {'D', MP4C_DISCONNECT, "disconnect the current presentation", 0}, + {'G', MP4C_SELECT, "select object or service ID", 0}, + {'p', MP4C_PAUSE_RESUME, "play/pause the presentation", 0}, + {'s', MP4C_STEP, "step one frame ahead", 0}, + {'z', MP4C_SEEK, "seek into presentation by percentage", 0}, + {'T', MP4C_SEEK_TIME, "seek into presentation by time", 0}, + {'t', MP4C_TIME, "print current timing", 0}, + {'u', MP4C_UPDATE, "send a command (BIFS or LASeR) to the main scene", 0}, + {'e', MP4C_EVALJS, "evaluate JavaScript code in the main scene", 0}, + {'Z', MP4C_SCREENSHOT, "dump current output frame to PNG", 0}, + {'w', MP4C_WORLDINFO, "view world info", 0}, + { 'v', MP4C_ODLIST, "view list of active media objects in scene", 0}, + { 'i', MP4C_ODINFO_ID, "view Object Descriptor info (by ID)", 0}, + { 'j', MP4C_ODINFO_NUM, "view Object Descriptor info (by number)", 0}, + { 'b', MP4C_ODTIME, "view media objects timing and buffering info", 0}, + { 'm', MP4C_ODBUF, "view media objects buffering and memory info", 0}, + { 'd', MP4C_DUMPSCENE, "dump scene graph", 0}, + { 'k', MP4C_STRESSMODE, "turn stress mode on/off", 0}, + { 'n', MP4C_NAVMODE, "change navigation mode", 0}, + { 'x', MP4C_LASTVP, "reset to last active viewpoint", 0}, + { '3', MP4C_OGL2D, "switch OpenGL on or off for 2D scenes", 0}, + { '4', MP4C_AR_4_3, "force 4/3 Aspect Ratio", 0}, + { '5', MP4C_AR_16_9, "force 16/9 Aspect Ratio", 0}, + { '6', MP4C_AR_NONE, "force no Aspect Ratio (always fill screen)", 0}, + { '7', MP4C_AR_ORIG, "force original Aspect Ratio (default)", 0}, + { 'H', MP4C_DOWNRATE, "set HTTP max download rate", 0}, + { 'E', MP4C_RELOAD_OPTS, "force reload of compositor options", 0}, + + { 'L', MP4C_LOGS, "change to new log tool/level. CF MP4Client usage for possible values", 0}, + { 'R', MP4C_DISP_RTI, "toggle run-time info display in window title bar on/off", 0}, + { 'F', MP4C_DISP_FPS, "toggle displaying of FPS in stderr on/off", 0}, + { 'f', MP4C_DISP_STATS, "print filter session stats", 0}, + { 'g', MP4C_DISP_GRAPH, "print filter session graph", 0}, + + { 'h', MP4C_HELP, "print this message", 0}, + //below this, only experimental features + { 'M', MP4C_VMEM_CACHE, "specify video cache memory for 2D objects", 1}, + {0} +}; + +MP4C_Command get_cmd(u8 char_code) +{ + u32 i=0; + while (MP4C_Keys[i].char_code) { + if (MP4C_Keys[i].char_code == char_code) + return MP4C_Keys[i].cmd_type; + i++; + } + return 0; +} + +void PrintHelp() +{ + u32 i=0; + + gf_sys_format_help(helpout, help_flags, "# MP4Client runtime commands\n" + "## Prompt Interaction\n" + "The following keys are used for prompt interaction:\n"); + + while (MP4C_Keys[i].char_code) { + struct _mp4c_key *k = &MP4C_Keys[i]; + i++; + gf_sys_format_help(helpout, help_flags|GF_PRINTARG_HIGHLIGHT_FIRST, "%c: %s%s\n", k->char_code, k->cmd_help, k->flags ? " **! experimental !**" : ""); + } + + gf_sys_format_help(helpout, help_flags, "\n" + "## Content interaction\n" + "It is possible to interact with content (interactive or not) using mouse and keyboard.\n" + "The following commands are available:\n" + "TODO\n" + "\n" + ); +} + + +static void PrintTime(u64 time) +{ + u32 ms, h, m, s; + h = (u32) (time / 1000 / 3600); + m = (u32) (time / 1000 / 60 - h*60); + s = (u32) (time / 1000 - h*3600 - m*60); + ms = (u32) (time - (h*3600 + m*60 + s) * 1000); + fprintf(stderr, "%02d:%02d:%02d.%03d", h, m, s, ms); +} + + +static u32 rti_update_time_ms = 200; +static FILE *rti_logs = NULL; + +static void UpdateRTInfo(const char *legend) +{ + GF_SystemRTInfo rti; + + /*refresh every second*/ + if (!Run) return; + if (!display_rti && !rti_logs) return; + if (!gf_sys_get_rti(rti_update_time_ms, &rti, 0) && !legend) + return; + + if (display_rti) { + char szMsg[1024]; + + if (rti.total_cpu_usage && (bench_mode<2) ) { + sprintf(szMsg, "FPS %02.02f CPU %2d (%02d) Mem %d kB", + gf_term_get_framerate(term, 0), rti.total_cpu_usage, rti.process_cpu_usage, (u32) (rti.gpac_memory / 1024)); + } else { + sprintf(szMsg, "FPS %02.02f CPU %02d Mem %d kB", + gf_term_get_framerate(term, 0), rti.process_cpu_usage, (u32) (rti.gpac_memory / 1024) ); + } + + if (display_rti==2) { + if (bench_mode>=2) { + PrintAVInfo(GF_FALSE); + } + fprintf(stderr, "%s\r", szMsg); + } else { + GF_Event evt; + evt.type = GF_EVENT_SET_CAPTION; + evt.caption.caption = szMsg; + gf_term_user_event(term, &evt); + } + } + if (rti_logs) { + fprintf(rti_logs, "% 8d\t% 8d\t% 8d\t% 4d\t% 8d\t%s", + gf_sys_clock(), + gf_term_get_time_in_ms(term), + rti.total_cpu_usage, + (u32) gf_term_get_framerate(term, 0), + (u32) (rti.gpac_memory / 1024), + legend ? legend : "" + ); + if (!legend) fprintf(rti_logs, "\n"); + } +} + +#include +#define MP4CLIENT_CAPTION "GPAC MP4Client "GPAC_VERSION "-rev" GPAC_GIT_REVISION + +static void ResetCaption() +{ + GF_Event event; + if (display_rti) return; + event.type = GF_EVENT_SET_CAPTION; + event.caption.caption = NULL; + + if (is_connected) { + char szName[1024]; + GF_TermURLInfo urli; + + /*get any service info*/ + if (!startup_file && gf_term_get_service_info(term, gf_term_get_root_object(term), &urli) == GF_OK) { + strcpy(szName, ""); + if (urli.track_num) { + char szBuf[10]; + sprintf(szBuf, "%02d ", (u32) urli.track_num ); + strcat(szName, szBuf); + } + if (urli.artist) { + strcat(szName, urli.artist); + strcat(szName, " "); + } + if (urli.name) { + strcat(szName, urli.name); + strcat(szName, " "); + } + if (urli.album) { + strcat(szName, "("); + strcat(szName, urli.album); + strcat(szName, ")"); + } + if (urli.provider) { + strcat(szName, "("); + strcat(szName, urli.provider); + strcat(szName, ")"); + } + if (strlen(szName)) event.caption.caption = szName; + } + if (!event.caption.caption) { + char *str = strrchr(the_url, '\\'); + if (!str) str = strrchr(the_url, '/'); + event.caption.caption = str ? str+1 : the_url; + } + } else { + event.caption.caption = MP4CLIENT_CAPTION; + } + gf_term_user_event(term, &event); +} + +#ifdef WIN32 +u32 get_sys_col(int idx) +{ + u32 res; + DWORD val = GetSysColor(idx); + res = (val)&0xFF; + res<<=8; + res |= (val>>8)&0xFF; + res<<=8; + res |= (val>>16)&0xFF; + return res; +} +#endif + +void switch_bench(u32 is_on) +{ + bench_mode = is_on; + display_rti = is_on ? 2 : 0; + ResetCaption(); + gf_term_set_option(term, GF_OPT_VIDEO_BENCH, is_on); +} + +#ifdef GPAC_ENABLE_COVERAGE +#define getch() 0 +#define read_line_input(_line, _maxSize, _showContent) GF_FALSE + +#else + +#ifndef WIN32 +#include +int getch() { + struct termios old; + struct termios new; + int rc; + if (tcgetattr(0, &old) == -1) { + return -1; + } + new = old; + new.c_lflag &= ~(ICANON | ECHO); + new.c_cc[VMIN] = 1; + new.c_cc[VTIME] = 0; + if (tcsetattr(0, TCSANOW, &new) == -1) { + return -1; + } + rc = getchar(); + (void) tcsetattr(0, TCSANOW, &old); + return rc; +} +#else +int getch() { + return getchar(); +} +#endif + +/** + * Reads a line of input from stdin + * @param line the buffer to fill + * @param maxSize the maximum size of the line to read + * @param showContent boolean indicating if the line read should be printed on stderr or not + */ +static Bool read_line_input(char * line, int maxSize, Bool showContent) { + char read; + int i = 0; + if (fflush( stderr )) + perror("Failed to flush buffer %s"); + do { + line[i] = '\0'; + if (i >= maxSize - 1) + return GF_FALSE; + read = getch(); + if (read == 8 || read == 127) { + if (i > 0) { + fprintf(stderr, "\b \b"); + i--; + } + } else if (read > 32) { + fputc(showContent ? read : '*', stderr); + line[i++] = read; + } + fflush(stderr); + } while (read != '\n'); + if (!read) + return GF_FALSE; + return GF_TRUE; +} +#endif //!GPAC_ENABLE_COVERAGE + +static void do_set_speed(Fixed desired_speed) +{ + if (gf_term_set_speed(term, desired_speed) == GF_OK) { + playback_speed = desired_speed; + fprintf(stderr, "Playing at %g speed\n", FIX2FLT(playback_speed)); + } else { + fprintf(stderr, "Adjusting speed to %g not supported for this content\n", FIX2FLT(desired_speed)); + } +} + +Bool GPAC_EventProc(void *ptr, GF_Event *evt) +{ + if (!term) return 0; + + if (gui_mode==1) { + if (evt->type==GF_EVENT_QUIT) { + Run = 0; + if (evt->message.error>0) + ret_val = evt->message.error; + } else if (evt->type==GF_EVENT_KEYDOWN) { + switch (evt->key.key_code) { + case GF_KEY_C: + if (evt->key.flags & (GF_KEY_MOD_CTRL|GF_KEY_MOD_ALT)) { + hide_shell(shell_visible ? 1 : 0); + if (shell_visible) gui_mode=2; + } + break; + default: + break; + } + } + return 0; + } + + switch (evt->type) { + case GF_EVENT_DURATION: + Duration = (u64) ( 1000 * (s64) evt->duration.duration); + CanSeek = evt->duration.can_seek; + break; + case GF_EVENT_MESSAGE: + { + const char *servName; + if (!evt->message.service || !strcmp(evt->message.service, the_url)) { + servName = ""; + } else if (!strnicmp(evt->message.service, "data:", 5)) { + servName = "(embedded data)"; + } else { + servName = evt->message.service; + } + + + if (!evt->message.message) return 0; + + if (evt->message.error) { + if (!is_connected) last_error = evt->message.error; + if (evt->message.error==GF_SCRIPT_INFO) { + GF_LOG(GF_LOG_INFO, GF_LOG_CONSOLE, ("%s\n", evt->message.message)); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONSOLE, ("%s %s: %s\n", servName, evt->message.message, gf_error_to_string(evt->message.error))); + } + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_CONSOLE, ("%s %s\n", servName, evt->message.message)); + } + } + break; + case GF_EVENT_PROGRESS: + { + char *szTitle = ""; + if (evt->progress.progress_type==0) { + szTitle = "Buffer "; + if (bench_mode && (bench_mode!=3) ) { + if (evt->progress.done >= evt->progress.total) bench_buffer = 0; + else bench_buffer = 1 + 100*evt->progress.done / evt->progress.total; + break; + } + } + else if (evt->progress.progress_type==1) { + if (bench_mode) break; + szTitle = "Download "; + } + else if (evt->progress.progress_type==2) szTitle = "Import "; + gf_set_progress(szTitle, evt->progress.done, evt->progress.total); + } + break; + + + case GF_EVENT_DBLCLICK: + gf_term_set_option(term, GF_OPT_FULLSCREEN, !gf_term_get_option(term, GF_OPT_FULLSCREEN)); + return 0; + + case GF_EVENT_MOUSEDOWN: + if (evt->mouse.button==GF_MOUSE_RIGHT) { + right_down = 1; + last_x = evt->mouse.x; + last_y = evt->mouse.y; + } + return 0; + case GF_EVENT_MOUSEUP: + if (evt->mouse.button==GF_MOUSE_RIGHT) { + right_down = 0; + last_x = evt->mouse.x; + last_y = evt->mouse.y; + } + return 0; + case GF_EVENT_MOUSEMOVE: + if (right_down && (user.init_flags & GF_TERM_WINDOWLESS) ) { + GF_Event move; + move.move.x = evt->mouse.x - last_x; + move.move.y = last_y-evt->mouse.y; + move.type = GF_EVENT_MOVE; + move.move.relative = 1; + gf_term_user_event(term, &move); + } + return 0; + + case GF_EVENT_KEYUP: + switch (evt->key.key_code) { + case GF_KEY_SPACE: + if (evt->key.flags & GF_KEY_MOD_CTRL) switch_bench(!bench_mode); + break; + } + break; + case GF_EVENT_KEYDOWN: + switch (evt->key.key_code) { + case GF_KEY_SPACE: + if (evt->key.flags & GF_KEY_MOD_CTRL) { + /*ignore key repeat*/ + if (!bench_mode) switch_bench(!bench_mode); + } + break; + case GF_KEY_PAGEDOWN: + case GF_KEY_MEDIANEXTTRACK: + request_next_playlist_item = 1; + break; + case GF_KEY_MEDIAPREVIOUSTRACK: + break; + case GF_KEY_ESCAPE: + gf_term_set_option(term, GF_OPT_FULLSCREEN, !gf_term_get_option(term, GF_OPT_FULLSCREEN)); + break; + case GF_KEY_C: + if (evt->key.flags & (GF_KEY_MOD_CTRL|GF_KEY_MOD_ALT)) { + hide_shell(shell_visible ? 1 : 0); + if (!shell_visible) gui_mode=1; + } + break; + case GF_KEY_F: + if (evt->key.flags & GF_KEY_MOD_CTRL) fprintf(stderr, "Rendering rate: %f FPS\n", gf_term_get_framerate(term, 0)); + break; + case GF_KEY_T: + if (evt->key.flags & GF_KEY_MOD_CTRL) fprintf(stderr, "Scene Time: %f \n", gf_term_get_time_in_ms(term)/1000.0); + break; + case GF_KEY_D: + if (evt->key.flags & GF_KEY_MOD_CTRL) gf_term_set_option(term, GF_OPT_DRAW_MODE, (gf_term_get_option(term, GF_OPT_DRAW_MODE)==GF_DRAW_MODE_DEFER) ? GF_DRAW_MODE_IMMEDIATE : GF_DRAW_MODE_DEFER ); + break; + case GF_KEY_4: + if (evt->key.flags & GF_KEY_MOD_CTRL) + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); + break; + case GF_KEY_5: + if (evt->key.flags & GF_KEY_MOD_CTRL) + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); + break; + case GF_KEY_6: + if (evt->key.flags & GF_KEY_MOD_CTRL) + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); + break; + case GF_KEY_7: + if (evt->key.flags & GF_KEY_MOD_CTRL) + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); + break; + case GF_KEY_O: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) { + if (gf_term_get_option(term, GF_OPT_MAIN_ADDON)) { + fprintf(stderr, "Resuming to main content\n"); + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_PLAY_LIVE); + } else { + fprintf(stderr, "Main addon not enabled\n"); + } + } + break; + case GF_KEY_P: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) { + u32 pause_state = gf_term_get_option(term, GF_OPT_PLAY_STATE) ; + fprintf(stderr, "[Status: %s]\n", pause_state ? "Playing" : "Paused"); + if ((pause_state == GF_STATE_PAUSED) && (evt->key.flags & GF_KEY_MOD_SHIFT)) { + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_PLAY_LIVE); + } else { + gf_term_set_option(term, GF_OPT_PLAY_STATE, (pause_state==GF_STATE_PAUSED) ? GF_STATE_PLAYING : GF_STATE_PAUSED); + } + } + break; + case GF_KEY_S: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) { + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + fprintf(stderr, "Step time: "); + PrintTime(gf_term_get_time_in_ms(term)); + fprintf(stderr, "\n"); + } + break; + case GF_KEY_B: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) + ViewODs(term, 1); + break; + case GF_KEY_M: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) + ViewODs(term, 0); + break; + case GF_KEY_H: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) { + gf_term_switch_quality(term, 1); + // gf_term_set_option(term, GF_OPT_MULTIVIEW_MODE, 0); + } + break; + case GF_KEY_L: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && is_connected) { + gf_term_switch_quality(term, 0); + // gf_term_set_option(term, GF_OPT_MULTIVIEW_MODE, 1); + } + break; + case GF_KEY_F5: + if (is_connected) + reload = 1; + break; + case GF_KEY_A: + addon_visible = !addon_visible; + gf_term_toggle_addons(term, addon_visible); + break; + case GF_KEY_UP: + if ((evt->key.flags & VK_MOD) && is_connected) { + do_set_speed(playback_speed * 2); + } + break; + case GF_KEY_DOWN: + if ((evt->key.flags & VK_MOD) && is_connected) { + do_set_speed(playback_speed / 2); + } + break; + case GF_KEY_LEFT: + if ((evt->key.flags & VK_MOD) && is_connected) { + do_set_speed(-1 * playback_speed ); + } + break; + + } + break; + + case GF_EVENT_CONNECT: + if (evt->connect.is_connected) { + is_connected = 1; + fprintf(stderr, "Service Connected\n"); + eos_seen = GF_FALSE; + if (playback_speed != FIX_ONE) + gf_term_set_speed(term, playback_speed); + + if (do_coverage) { + gf_term_switch_quality(term, 1); + } + + } else if (is_connected) { + fprintf(stderr, "Service %s\n", is_connected ? "Disconnected" : "Connection Failed"); + is_connected = 0; + Duration = 0; + } + if (init_w && init_h) { + gf_term_set_size(term, init_w, init_h); + } + ResetCaption(); + break; + case GF_EVENT_EOS: + eos_seen = GF_TRUE; + if (playlist) { + if (Duration>1500) + request_next_playlist_item = GF_TRUE; + } + else if (loop_at_end) { + restart = 1; + } + break; + case GF_EVENT_SIZE: + if (user.init_flags & GF_TERM_WINDOWLESS) { + GF_Event move; + move.type = GF_EVENT_MOVE; + move.move.align_x = align_mode & 0xFF; + move.move.align_y = (align_mode>>8) & 0xFF; + move.move.relative = 2; + gf_term_user_event(term, &move); + } + break; + case GF_EVENT_SCENE_SIZE: + + if ((forced_width && forced_height) || scale) { + GF_Event size; + u32 nw = forced_width ? forced_width : evt->size.width; + u32 nh = forced_height ? forced_height : evt->size.height; + + if (scale != 1) { + nw = (u32)(nw * scale); + nh = (u32)(nh * scale); + } + if ((nw != evt->size.width) || (nh != evt->size.height)) { + size.type = GF_EVENT_SIZE; + size.size.width = nw; + size.size.height = nh; + gf_term_user_event(term, &size); + } + } + break; + + case GF_EVENT_METADATA: + ResetCaption(); + break; + case GF_EVENT_DROPFILE: + { + u32 i, pos; + /*todo - force playlist mode*/ + if (readonly_playlist) { + gf_fclose(playlist); + playlist = NULL; + } + readonly_playlist = 0; + if (!playlist) { + readonly_playlist = 0; + playlist = gf_file_temp(NULL); + } + pos = (u32) gf_ftell(playlist); + i=0; + while (iopen_file.nb_files) { + if (evt->open_file.files[i] != NULL) { + fprintf(playlist, "%s\n", evt->open_file.files[i]); + } + i++; + } + gf_fseek(playlist, pos, SEEK_SET); + request_next_playlist_item = 1; + } + return 1; + + case GF_EVENT_QUIT: + if (evt->message.error<0) { + fprintf(stderr, "A fatal error was encoutered: %s (%s) - exiting ...\n", evt->message.message ? evt->message.message : "no details", gf_error_to_string(evt->message.error) ); + } else { + ret_val = evt->message.error; + } + Run = 0; + break; + case GF_EVENT_DISCONNECT: + gf_term_disconnect(term); + break; + case GF_EVENT_MIGRATE: + { + } + break; + case GF_EVENT_NAVIGATE_INFO: + if (evt->navigate.to_url) + fprintf(stderr, "Go to URL: \"%s\"\r", evt->navigate.to_url); + break; + case GF_EVENT_NAVIGATE: + if (gf_term_is_supported_url(term, evt->navigate.to_url, 1, no_mime_check)) { + strncpy(the_url, evt->navigate.to_url, sizeof(the_url)-1); + the_url[sizeof(the_url) - 1] = 0; + fprintf(stderr, "Navigating to URL %s\n", the_url); + gf_term_navigate_to(term, evt->navigate.to_url); + return 1; + } else { + fprintf(stderr, "Navigation destination not supported\nGo to URL: %s\n", evt->navigate.to_url); + } + break; + case GF_EVENT_SET_CAPTION: + gf_term_user_event(term, evt); + break; + case GF_EVENT_AUTHORIZATION: + { + u32 nb_retry = 4; + assert( evt->type == GF_EVENT_AUTHORIZATION); + assert( evt->auth.user); + assert( evt->auth.password); + assert( evt->auth.site_url); + while ((!strlen(evt->auth.user) || !strlen(evt->auth.password)) && (nb_retry > 0) ) { + nb_retry--; + fprintf(stderr, "**** Authorization required for site %s ****\n", evt->auth.site_url); + fprintf(stderr, "login : "); + if (!read_line_input(evt->auth.user, 50, 1)) + continue; + fprintf(stderr, "\npassword: "); + if (!read_line_input(evt->auth.password, 50, 0)) + continue; + fprintf(stderr, "*********\n"); + } + if (nb_retry == 0) { + fprintf(stderr, "**** No User or password has been filled, aborting ***\n"); + return 0; + } + return 1; + } + case GF_EVENT_ADDON_DETECTED: + if (enable_add_ons) { + fprintf(stderr, "Media Addon %s detected - enabling it\n", evt->addon_connect.addon_url); + addon_visible = 1; + } + return enable_add_ons; + } + return 0; +} + + +void list_modules() +{ + u32 i; + fprintf(stderr, "\rAvailable modules:\n"); + for (i=0; i +#endif + +int mp4client_main(int argc, char **argv) +{ + char c; + MP4C_Command cmdtype; + const char *str; + GF_Err e; + u32 i, ll; + u32 simulation_time_in_ms = 0; + u32 initial_service_id = 0; + Bool auto_exit = GF_FALSE; + Bool start_fs = GF_FALSE; + Bool use_rtix = GF_FALSE; + Bool pause_at_first = GF_FALSE; + Bool no_cfg_save = GF_FALSE; + Bool print_stats = GF_FALSE; + Bool print_graph = GF_FALSE; + Bool no_keyboard = GF_FALSE; + Double play_from = 0; +#ifdef GPAC_MEMORY_TRACKING + GF_MemTrackerType mem_track = GF_MemTrackerNone; +#endif + Bool has_command; + char *url_arg, *gpac_profile, *rti_file; + FILE *logfile = NULL; +#ifndef WIN32 + dlopen(NULL, RTLD_NOW|RTLD_GLOBAL); +#endif + + helpout = stdout; + + /*by default use current dir*/ + strcpy(the_url, "."); + + memset(&user, 0, sizeof(GF_User)); + + has_command = GF_FALSE; + url_arg = gpac_profile = rti_file = NULL; + + /*first identify profile and mem tracking */ + for (i=1; i<(u32) argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "-p")) { + gpac_profile = argv[i+1]; + i++; + } else if (!strncmp(arg, "-p=", 3)) { + gpac_profile = argv[i]+3; + } else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack")) { +#ifdef GPAC_MEMORY_TRACKING + mem_track = !strcmp(arg, "-mem-track-stack") ? GF_MemTrackerBackTrace : GF_MemTrackerSimple; +#else + fprintf(stderr, "WARNING - GPAC not compiled with Memory Tracker - ignoring \"%s\"\n", arg); +#endif + } else if (!strcmp(arg, "-gui")) { + gui_mode = 1; + } else if (!strcmp(arg, "-guid")) { + gui_mode = 2; + } else if (!strcmp(arg, "-h") || !strcmp(arg, "-help")) { + PrintUsage(GF_FALSE); + return 0; + } else if (!strcmp(arg, "-hx")) { + PrintUsage(GF_TRUE); + return 0; + } else if (!strcmp(arg, "-hr")) { + PrintHelp(); + return 0; + } else if (!strcmp(arg, "-hc")) { + fprintf(helpout, "libgpac options:\n"); + gf_sys_print_core_help(helpout, help_flags, GF_ARGMODE_ALL, 0); + return 0; + } + } + +#ifdef GPAC_MEMORY_TRACKING + gf_sys_init(mem_track, gpac_profile); +#else + gf_sys_init(GF_MemTrackerNone, gpac_profile); +#endif + + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_WARNING); + //we by default want 2 additional threads: + //main thread might get locked on vsync + //second thread might be busy decoding audio/video + //third thread will then be able to refill all buffers/perform networks tasks + gf_opts_set_key("temp", "threads", "2"); + + e = gf_sys_set_args(argc, (const char **) argv); + if (e) { + fprintf(stderr, "Error assigning libgpac arguments: %s\n", gf_error_to_string(e) ); + gf_sys_close(); + return 1; + } + + if (!gui_mode) { + str = gf_opts_get_key("General", "ForceGUI"); + if (str && !strcmp(str, "yes")) gui_mode = 1; + } + + for (i=1; i<(u32) argc; i++) { + char *arg = argv[i]; + + if (!strcmp(arg, "-genmd")) { + help_flags = GF_PRINTARG_MD; + helpout = gf_fopen("mp4client.md", "w"); + + fprintf(helpout, "[**HOME**](Home) » MP4Client \n"); + fprintf(helpout, "\n"); + PrintUsage(GF_TRUE); + PrintHelp(); + gf_fclose(helpout); + gf_sys_close(); + return 0; + } else if (!strcmp(arg, "-genman")) { + help_flags = GF_PRINTARG_MAN; + helpout = gf_fopen("mp4client.1", "w"); + + + fprintf(helpout, ".TH MP4Client 1 2019 MP4Client GPAC\n"); + fprintf(helpout, ".\n.SH NAME\n.LP\nMP4Client \\- GPAC command-line media player\n.SH SYNOPSIS\n.LP\n.B MP4Client\n.RI [options] \\ [file]\n.br\n.\n"); + + PrintUsage(GF_TRUE); + PrintHelp(); + + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/mp4client\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" + ".br\nCopyright: %s\n.br\n" + ".SH SEE ALSO\n" + ".LP\ngpac(1), MP4Box(1)\n", gf_gpac_version(), gf_gpac_copyright()); + + gf_fclose(helpout); + gf_sys_close(); + return 0; + } else if (!strcmp(arg, "-rti")) { + rti_file = argv[i+1]; + i++; + } else if (!strcmp(arg, "-rtix")) { + rti_file = argv[i+1]; + i++; + use_rtix = GF_TRUE; + } else if (!strcmp(arg, "-rti-refresh")) { + rti_update_time_ms = atoi(argv[i+1]); + i++; + } else if (!stricmp(arg, "-size")) { + /*usage of %ud breaks sscanf on MSVC*/ + if (sscanf(argv[i+1], "%dx%d", &forced_width, &forced_height) != 2) { + forced_width = forced_height = 0; + } + i++; + } + //libgpac opts using an argument + else if (!strcmp(arg, "-log-file") || !strcmp(arg, "-lf") || !strcmp(arg, "-logs") || !strcmp(arg, "-cfg") || !strcmp(arg, "-ifce") ) { + i++; + } + + else if (!strcmp(arg, "-no-thread")) { + gf_opts_set_key("temp", "threads", "0"); + } + else if (!strcmp(arg, "-no-audio")) { + no_audio = GF_TRUE; + } + else if (!strcmp(arg, "-fs")) start_fs = 1; + else if (!stricmp(arg, "-no-save") || !stricmp(arg, "--no-save") /*old versions used --n-save ...*/) { + no_cfg_save=1; + } + else if (!stricmp(arg, "-run-for")) { + simulation_time_in_ms = (u32) (atof(argv[i+1]) * 1000); + if (!simulation_time_in_ms) + simulation_time_in_ms = 1; /*1ms*/ + i++; + } else if (!stricmp(arg, "-scale")) { + sscanf(argv[i+1], "%f", &scale); + i++; + } + /* already parsed */ + else if (!strcmp(arg, "-nk")) { + no_keyboard = GF_TRUE; + } + /* already parsed */ + else if (!strcmp(arg, "-p")) { + i++; + } + /* already parsed */ + else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack") || !strcmp(arg, "-gui") || !strcmp(arg, "-guid") + || !strncmp(arg, "-p=", 3) + ) { + } + + /*arguments only used in non-gui mode*/ + else if (!gui_mode) { + if (arg[0] != '-') { + if (url_arg) { + fprintf(stderr, "Several input URLs provided (\"%s\", \"%s\"). Check your command-line.\n", url_arg, arg); + return 1; + } + url_arg = arg; + } + else if (!strcmp(arg, "-loop")) loop_at_end = 1; + else if (!strcmp(arg, "-bench")) bench_mode = 1; + else if (!strcmp(arg, "-vbench")) bench_mode = 2; + else if (!strcmp(arg, "-sbench")) bench_mode = 3; + else if (!strcmp(arg, "-no-addon")) enable_add_ons = GF_FALSE; + + else if (!strcmp(arg, "-pause")) pause_at_first = 1; + else if (!strcmp(arg, "-play-from")) { + play_from = atof((const char *) argv[i+1]); + i++; + } + else if (!strcmp(arg, "-speed")) { + playback_speed = FLT2FIX( atof((const char *) argv[i+1]) ); + if (playback_speed <= 0) playback_speed = FIX_ONE; + i++; + } + else if (!strcmp(arg, "-no-wnd")) user.init_flags |= GF_TERM_WINDOWLESS; + else if (!strcmp(arg, "-no-back")) user.init_flags |= GF_TERM_WINDOW_TRANSPARENT; + else if (!strcmp(arg, "-align")) { + if (argv[i+1][0]=='m') align_mode = 1; + else if (argv[i+1][0]=='b') align_mode = 2; + align_mode <<= 8; + if (argv[i+1][1]=='m') align_mode |= 1; + else if (argv[i+1][1]=='r') align_mode |= 2; + i++; + } + else if (!strcmp(arg, "-exit")) auto_exit = GF_TRUE; + else if (!stricmp(arg, "-com")) { + has_command = GF_TRUE; + i++; + } + else if (!stricmp(arg, "-service")) { + initial_service_id = atoi(argv[i+1]); + i++; + } else if (!stricmp(arg, "-stats")) { + print_stats=GF_TRUE; + } else if (!stricmp(arg, "-graph")) { + print_graph=GF_TRUE; + } else if (!stricmp(arg, "-cov")) { + do_coverage = GF_TRUE; + print_stats = GF_TRUE; + print_graph = GF_TRUE; + } else { + u32 res = gf_sys_is_gpac_arg(arg); + if (!res) { + fprintf(stderr, "Unrecognized option %s\n", arg); + } else if (res==2) { + i++; + } + } + } else if (gf_sys_is_gpac_arg(arg)==2) { + i++; + } + } + + //will run switch helpout to stderr + helpout = stderr; + + if (!gui_mode && !url_arg && (gf_opts_get_key("General", "StartupFile") != NULL)) { + gui_mode=1; + } + +#ifdef WIN32 + if (gui_mode==1) { + const char *opt; + TCHAR buffer[1024]; + DWORD res = GetCurrentDirectory(1024, buffer); + buffer[res] = 0; + opt = gf_opts_get_key("core", "module-dir"); + if (strstr(opt, buffer)) { + gui_mode=1; + } else { + gui_mode=2; + } + } +#endif + + if (gui_mode==1) { + hide_shell(1); + } + if (gui_mode) { + gf_sys_set_cfg_option("core:noprog=yes"); + } + +#if defined(__DARWIN__) || defined(__APPLE__) + carbon_init(); +#endif + + + if (rti_file) init_rti_logs(rti_file, url_arg, use_rtix); + + { + GF_SystemRTInfo rti; + if (gf_sys_get_rti(0, &rti, 0)) + fprintf(stderr, "System info: %d MB RAM - %d cores\n", (u32) (rti.physical_memory/1024/1024), rti.nb_cores); + } + + + init_w = forced_width; + init_h = forced_height; + + user.EventProc = GPAC_EventProc; + /*dummy in this case (global vars) but MUST be non-NULL*/ + user.opaque = &user; + + if (no_audio) user.init_flags |= GF_TERM_NO_AUDIO; + + if (bench_mode) { + gf_opts_discard_changes(); + auto_exit = GF_TRUE; + if (bench_mode!=2) user.init_flags |= GF_TERM_NO_VIDEO; + } + + if (forced_width && forced_height) { + char dim[50]; + sprintf(dim, "%d", forced_width); + gf_opts_set_key("Temp", "DefaultWidth", dim); + sprintf(dim, "%d", forced_height); + gf_opts_set_key("Temp", "DefaultHeight", dim); + } + + fprintf(stderr, "Loading GPAC Terminal\n"); + i = gf_sys_clock(); + + term = gf_term_new(&user); + if (!term) { + fprintf(stderr, "\nInit error - check you have at least one video out and one rasterizer...\nFound modules:\n"); + list_modules(); + gf_opts_discard_changes(); + gf_sys_close(); + if (logfile) gf_fclose(logfile); + return 1; + } + fprintf(stderr, "Terminal Loaded in %d ms\n", gf_sys_clock()-i); + + if (bench_mode) { + display_rti = 2; + gf_term_set_option(term, GF_OPT_VIDEO_BENCH, (bench_mode==3) ? 2 : 1); + if (bench_mode==1) bench_mode=2; + } + + str = gf_opts_get_key("General", "NoMIMETypeFetch"); + no_mime_check = (str && !stricmp(str, "yes")) ? 1 : 0; + + 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); + } + + if (rti_file) { + UpdateRTInfo("At GPAC load time\n"); + } + + Run = 1; + + if (url_arg && !strcmp(url_arg, "NOGUI")) { + url_arg = NULL; + } + /*connect if requested*/ + else if (!gui_mode && url_arg) { + char *ext; + + if (strlen(url_arg) >= sizeof(the_url)) { + fprintf(stderr, "Input url %s is too long, truncating to %d chars.\n", url_arg, (int)(sizeof(the_url) - 1)); + strncpy(the_url, url_arg, sizeof(the_url)-1); + the_url[sizeof(the_url) - 1] = 0; + } + else { + strcpy(the_url, url_arg); + } + ext = strrchr(the_url, '.'); + //only local playlist - maybe we could remove and control through flist ? + if (ext && !strncmp("http", the_url, 4) && (!stricmp(ext, ".m3u") || !stricmp(ext, ".pls"))) { + e = GF_OK; + fprintf(stderr, "Opening Playlist %s\n", the_url); + + strcpy(pl_path, the_url); + playlist = e ? NULL : gf_fopen(the_url, "rt"); + readonly_playlist = 1; + if (playlist) { + request_next_playlist_item = GF_TRUE; + } else { + if (e) + fprintf(stderr, "Failed to open playlist %s: %s\n", the_url, gf_error_to_string(e) ); + fprintf(stderr, "Hit 'h' for help\n\n"); + } + } else { + fprintf(stderr, "Opening URL %s\n", the_url); + if (pause_at_first) fprintf(stderr, "[Status: Paused]\n"); + gf_term_connect_from_time(term, the_url, (u64) (play_from*1000), pause_at_first); + } + } else { + fprintf(stderr, "Hit 'h' for help\n\n"); + str = gf_opts_get_key("General", "StartupFile"); + if (str) { + snprintf(the_url, sizeof(the_url)-1, "MP4Client %s", gf_gpac_version() ); + the_url[sizeof(the_url) - 1] = 0; + gf_term_connect(term, str); + startup_file = 1; + is_connected = 1; + } + } + if (gui_mode==2) gui_mode=0; + + if (start_fs) gf_term_set_option(term, GF_OPT_FULLSCREEN, 1); + + if (bench_mode) { + rti_update_time_ms = 500; + bench_mode_start = gf_sys_clock(); + } + + if (simulation_time_in_ms) + simulation_time_in_ms += gf_sys_clock(); + + + while (Run) { + + /*we don't want getchar to block*/ + if ((gui_mode==1) || (no_keyboard || !gf_prompt_has_input()) ) { + if (reload) { + reload = 0; + gf_term_disconnect(term); + gf_term_connect(term, startup_file ? gf_opts_get_key("General", "StartupFile") : the_url); + } + if (restart && gf_term_get_option(term, GF_OPT_IS_OVER)) { + restart = 0; + gf_term_play_from_time(term, 0, 0); + } + if (request_next_playlist_item) { + c = '\n'; + request_next_playlist_item = 0; + goto force_input; + } + + if (has_command && is_connected) { + has_command = GF_FALSE; + for (i=0; i<(u32)argc; i++) { + if (!strcmp(argv[i], "-com")) { + gf_term_scene_update(term, NULL, argv[i+1]); + i++; + } + } + } + if (initial_service_id && is_connected) { + GF_ObjectManager *root_od = gf_term_get_root_object(term); + if (root_od) { + gf_term_select_service(term, root_od, initial_service_id); + initial_service_id = 0; + } + } + + if (!use_rtix || display_rti) UpdateRTInfo(NULL); + + + gf_term_process_step(term); + + if (auto_exit && eos_seen && gf_term_get_option(term, GF_OPT_IS_OVER)) { + Run = GF_FALSE; + } + + /*sim time*/ + if (simulation_time_in_ms + && ( (gf_term_get_elapsed_time_in_ms(term)>simulation_time_in_ms) || (!url_arg && gf_sys_clock()>simulation_time_in_ms)) + ) { + Run = GF_FALSE; + } + continue; + } + + c = gf_prompt_get_char(); + +force_input: + if (c=='\n') cmdtype=MP4C_PL_NEXT; + else cmdtype = get_cmd(c); + + switch (cmdtype) { + case MP4C_QUIT: + { + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + gf_term_send_event(term, &evt); + } +// Run = 0; + break; + case MP4C_KILL: + exit(0); + break; + + case MP4C_OPEN: + startup_file = 0; + gf_term_disconnect(term); + fprintf(stderr, "Enter the absolute URL\n"); + if (1 > scanf("%1023s", the_url)) { + fprintf(stderr, "Cannot read absolute URL, aborting\n"); + break; + } + if (rti_file) init_rti_logs(rti_file, the_url, use_rtix); + gf_term_connect(term, the_url); + break; + case MP4C_OPEN_PL: + gf_term_disconnect(term); + fprintf(stderr, "Enter the absolute URL to the playlist\n"); + if (1 > scanf("%1023s", the_url)) { + fprintf(stderr, "Cannot read the absolute URL, aborting.\n"); + break; + } + playlist = gf_fopen(the_url, "rt"); + if (playlist) { + if (1 > fscanf(playlist, "%1023s", the_url)) { + fprintf(stderr, "Cannot read any URL from playlist, aborting.\n"); + gf_fclose( playlist); + break; + } + fprintf(stderr, "Opening URL %s\n", the_url); + gf_term_connect(term, the_url); + } + break; + case MP4C_PL_NEXT: + if (playlist) { + int res; + gf_term_disconnect(term); + + res = fscanf(playlist, "%1023s", the_url); + if ((res == EOF) && loop_at_end) { + gf_fseek(playlist, 0, SEEK_SET); + res = fscanf(playlist, "%1023s", the_url); + } + if (res == EOF) { + fprintf(stderr, "No more items - exiting\n"); + Run = 0; + } else if (the_url[0] == '#') { + request_next_playlist_item = GF_TRUE; + } else { + fprintf(stderr, "Opening URL %s\n", the_url); + gf_term_connect_with_path(term, the_url, pl_path); + } + } + break; + case MP4C_PL_JUMP: + if (playlist) { + u32 count; + gf_term_disconnect(term); + if (1 > scanf("%u", &count)) { + fprintf(stderr, "Cannot read number, aborting.\n"); + break; + } + while (count) { + if (fscanf(playlist, "%1023s", the_url)) { + fprintf(stderr, "Failed to read line, aborting\n"); + break; + } + count--; + } + fprintf(stderr, "Opening URL %s\n", the_url); + gf_term_connect(term, the_url); + } + break; + case MP4C_RELOAD: + if (is_connected) + reload = 1; + break; + + case MP4C_DISCONNECT: + if (is_connected) gf_term_disconnect(term); + break; + + case MP4C_PAUSE_RESUME: + if (is_connected) { + Bool is_pause = gf_term_get_option(term, GF_OPT_PLAY_STATE); + fprintf(stderr, "[Status: %s]\n", is_pause ? "Playing" : "Paused"); + gf_term_set_option(term, GF_OPT_PLAY_STATE, is_pause ? GF_STATE_PLAYING : GF_STATE_PAUSED); + } + break; + case MP4C_STEP: + if (is_connected) { + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + fprintf(stderr, "Step time: "); + PrintTime(gf_term_get_time_in_ms(term)); + fprintf(stderr, "\n"); + } + break; + + case MP4C_SEEK: + case MP4C_SEEK_TIME: + if (!CanSeek || (Duration<=2000)) { + fprintf(stderr, "scene not seekable\n"); + } else { + Double res; + s32 seekTo; + fprintf(stderr, "Duration: "); + PrintTime(Duration); + res = gf_term_get_time_in_ms(term); + if (cmdtype==MP4C_SEEK) { + res *= 100; + res /= (s64)Duration; + fprintf(stderr, " (current %.2f %%)\nEnter Seek percentage:\n", res); + if (scanf("%d", &seekTo) == 1) { + if (seekTo > 100) seekTo = 100; + res = (Double)(s64)Duration; + res /= 100; + res *= seekTo; + gf_term_play_from_time(term, (u64) (s64) res, 0); + } + } else { + u32 r, h, m, s; + fprintf(stderr, " - Current Time: "); + PrintTime((u64) res); + fprintf(stderr, "\nEnter seek time (Format: s, m:s or h:m:s):\n"); + h = m = s = 0; + r =scanf("%d:%d:%d", &h, &m, &s); + if (r==2) { + s = m; + m = h; + h = 0; + } + else if (r==1) { + s = h; + m = h = 0; + } + + if (r && (r<=3)) { + u64 time = h*3600 + m*60 + s; + gf_term_play_from_time(term, time*1000, 0); + } + } + } + break; + + case MP4C_TIME: + { + if (is_connected) { + fprintf(stderr, "Current Time: "); + PrintTime(gf_term_get_time_in_ms(term)); + fprintf(stderr, " - Duration: "); + PrintTime(Duration); + fprintf(stderr, "\n"); + } + } + break; + case MP4C_WORLDINFO: + if (is_connected) PrintWorldInfo(term); + break; + case MP4C_ODLIST: + if (is_connected) PrintODList(term, NULL, 0, 0, "Root"); + break; + case MP4C_ODINFO_ID: + if (is_connected) { + u32 ID; + fprintf(stderr, "Enter OD ID (0 for main OD): "); + fflush(stderr); + if (scanf("%ud", &ID) == 1) { + ViewOD(term, ID, (u32)-1, NULL); + } else { + char str_url[GF_MAX_PATH]; + if (scanf("%1023s", str_url) == 1) + ViewOD(term, 0, (u32)-1, str_url); + } + } + break; + case MP4C_ODINFO_NUM: + if (is_connected) { + u32 num; + do { + fprintf(stderr, "Enter OD number (0 for main OD): "); + fflush(stderr); + } while( 1 > scanf("%ud", &num)); + ViewOD(term, (u32)-1, num, NULL); + } + break; + case MP4C_ODTIME: + if (is_connected) ViewODs(term, 1); + break; + + case MP4C_ODBUF: + if (is_connected) ViewODs(term, 0); + break; + + case MP4C_NAVMODE: + if (is_connected) set_navigation(); + break; + case MP4C_LASTVP: + if (is_connected) gf_term_set_option(term, GF_OPT_NAVIGATION_TYPE, 0); + break; + + case MP4C_DUMPSCENE: + if (is_connected) { + GF_ObjectManager *odm = NULL; + char radname[GF_MAX_PATH], *sExt; + u32 count, odid; + Bool xml_dump, std_out; + radname[0] = 0; + do { + fprintf(stderr, "Enter Inline OD ID if any or 0 : "); + fflush(stderr); + } while( 1 > scanf("%ud", &odid)); + if (odid) { + GF_ObjectManager *root_odm = gf_term_get_root_object(term); + if (!root_odm) break; + count = gf_term_get_object_count(term, root_odm); + for (i=0; i scanf("%1023s", radname)); + sExt = strrchr(radname, '.'); + xml_dump = 0; + if (sExt) { + if (!stricmp(sExt, ".x")) xml_dump = 1; + sExt[0] = 0; + } + std_out = strnicmp(radname, "std", 3) ? 0 : 1; + e = gf_term_dump_scene(term, std_out ? NULL : radname, NULL, xml_dump, 0, odm); + fprintf(stderr, "Dump done (%s)\n", gf_error_to_string(e)); + } + break; + + case MP4C_OGL2D: + { + Bool use_3d = !gf_term_get_option(term, GF_OPT_USE_OPENGL); + if (gf_term_set_option(term, GF_OPT_USE_OPENGL, use_3d)==GF_OK) { + fprintf(stderr, "Using %s for 2D drawing\n", use_3d ? "OpenGL" : "2D rasterizer"); + } + } + break; + case MP4C_STRESSMODE: + { + Bool opt = gf_term_get_option(term, GF_OPT_STRESS_MODE); + opt = !opt; + fprintf(stderr, "Turning stress mode %s\n", opt ? "on" : "off"); + gf_term_set_option(term, GF_OPT_STRESS_MODE, opt); + } + break; + case MP4C_AR_4_3: + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); + break; + case MP4C_AR_16_9: + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); + break; + case MP4C_AR_NONE: + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); + break; + case MP4C_AR_ORIG: + gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); + break; + + case MP4C_DISP_RTI: + display_rti = !display_rti; + ResetCaption(); + break; + case MP4C_DISP_FPS: + if (display_rti) display_rti = 0; + else display_rti = 2; + ResetCaption(); + break; + case MP4C_DISP_STATS: + case MP4C_DISP_GRAPH: + ll = gf_log_get_tool_level(GF_LOG_APP); + gf_log_set_tool_level(GF_LOG_APP, GF_LOG_INFO); + if (cmdtype==MP4C_DISP_STATS) + gf_term_print_stats(term); + else + gf_term_print_graph(term); + gf_log_set_tool_level(GF_LOG_APP, ll); + break; + case MP4C_UPDATE: + { + char szCom[8192]; + fprintf(stderr, "Enter command to send:\n"); +// fflush(stdin); + szCom[0] = 0; + if (1 > scanf("%8191[^\t\n]", szCom)) { + fprintf(stderr, "Cannot read command to send, aborting.\n"); + break; + } + e = gf_term_scene_update(term, NULL, szCom); + if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); + } + break; + case MP4C_EVALJS: + { + char jsCode[8192]; + fprintf(stderr, "Enter JavaScript code to evaluate:\n"); +// fflush(stdin); + jsCode[0] = 0; + if (1 > scanf("%8191[^\t\n]", jsCode)) { + fprintf(stderr, "Cannot read code to evaluate, aborting.\n"); + break; + } + e = gf_term_scene_update(term, "application/ecmascript", jsCode); + if (e) fprintf(stderr, "Processing JS code failed: %s\n", gf_error_to_string(e)); + } + break; + + case MP4C_LOGS: + { + char szLog[1024], *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) { + fprintf(stderr, "Cannot read new log level, aborting.\n"); + break; + } + gf_log_modify_tools_levels(szLog); + } + break; + + case MP4C_VMEM_CACHE: + { + u32 size; + do { + fprintf(stderr, "Enter new video cache memory in kBytes (current %ud):\n", gf_term_get_option(term, GF_OPT_VIDEO_CACHE_SIZE)); + } while (1 > scanf("%ud", &size)); + gf_term_set_option(term, GF_OPT_VIDEO_CACHE_SIZE, size); + } + break; + + case MP4C_DOWNRATE: + { + u32 http_bitrate = gf_term_get_option(term, GF_OPT_HTTP_MAX_RATE); + do { + fprintf(stderr, "Enter new http bitrate in bps (0 for none) - current limit: %d\n", http_bitrate); + } while (1 > scanf("%ud", &http_bitrate)); + + gf_term_set_option(term, GF_OPT_HTTP_MAX_RATE, http_bitrate); + } + break; + + case MP4C_RELOAD_OPTS: + gf_term_set_option(term, GF_OPT_RELOAD_CONFIG, 1); + break; + + /*extract to PNG*/ + case MP4C_SCREENSHOT: + MakeScreenshot(GF_FALSE); + break; + + case MP4C_SELECT: + { + GF_ObjectManager *root_od, *odm; + u32 index; + char szOpt[8192]; + fprintf(stderr, "Enter 0-based index of object to select or service ID:\n"); +// fflush(stdin); + szOpt[0] = 0; + if (1 > scanf("%8191[^\t\n]", szOpt)) { + fprintf(stderr, "Cannot read OD ID\n"); + break; + } + index = atoi(szOpt); + odm = NULL; + root_od = gf_term_get_root_object(term); + if (root_od) { + if ( gf_term_find_service(term, root_od, index)) { + gf_term_select_service(term, root_od, index); + } else { + fprintf(stderr, "Cannot find service %d - trying with object index\n", index); + odm = gf_term_get_object(term, root_od, index); + if (odm) { + gf_term_select_object(term, odm); + } else { + fprintf(stderr, "Cannot find object at index %d\n", index); + } + } + } + } + break; + + case MP4C_HELP: + PrintHelp(); + break; + default: + break; + } + } + + if (bench_mode) { + PrintAVInfo(GF_TRUE); + } + + /*FIXME: we have an issue in cleaning up after playing in bench mode and run-for 0 (buildbot tests). We for now disable error checks after run-for is done*/ + if (simulation_time_in_ms) { + gf_log_set_strict_error(0); + } + + if (print_graph || print_stats) { + ll = gf_log_get_tool_level(GF_LOG_APP); + gf_log_set_tool_level(GF_LOG_APP, GF_LOG_INFO); + if (print_graph) + gf_term_print_graph(term); + if (print_stats) + gf_term_print_stats(term); + gf_log_set_tool_level(GF_LOG_APP, ll); + } + +#ifdef GPAC_ENABLE_COVERAGE + if (do_coverage) { + u32 w, h, k, nb_drawn; + GF_Event evt; + Bool is_bound; + const char *outName; + GF_ObjectManager *root_odm, *odm; + PrintAVInfo(GF_TRUE); + PrintODList(term, NULL, 0, 0, "Root"); + ViewODs(term, GF_TRUE); + ViewODs(term, GF_FALSE); + ViewOD(term, 0, (u32) -1, NULL); + ViewOD(term, 0, 1, NULL); + PrintUsage(0); + PrintHelp(); + PrintWorldInfo(term); + gf_term_dump_scene(term, NULL, NULL, GF_FALSE, 0, NULL); + gf_term_get_current_service_id(term); + gf_term_toggle_addons(term, GF_FALSE); + set_navigation(); + get_cmd('v'); + + gf_term_play_from_time(term, 0, 0); + + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_MOUSEUP; + evt.mouse.x = 20; + evt.mouse.y = 20; + gf_term_send_event(term, &evt); + + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + //exercise step clocks + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + root_odm = gf_term_get_root_object(term); + gf_term_find_service(term, root_odm, 0); + gf_term_select_service(term, root_odm, 0); + odm = gf_term_get_object(term, root_odm, 0); + gf_term_select_object(term, odm ); + gf_term_object_subscene_type(term, odm); + gf_term_get_visual_output_size(term, &w, &h); + + gf_term_is_type_supported(term, "video/mp4"); + gf_term_get_url(term); + gf_term_get_simulation_frame_rate(term, &nb_drawn); + + gf_term_get_text_selection(term, GF_TRUE); + gf_term_paste_text(term, "test", GF_TRUE); + + gf_term_set_option(term, GF_OPT_AUDIO_MUTE, 1); + + + MakeScreenshot(GF_TRUE); + + gf_term_scene_update(term, NULL, "REPLACE DYN_TRANS.translation BY 10 10"); + gf_term_add_object(term, NULL, GF_TRUE); + + gf_term_get_viewpoint(term, 1, &outName, &is_bound); + gf_term_set_viewpoint(term, 1, "testvp"); + + for (k=GF_NAVIGATE_WALK; k<=GF_NAVIGATE_VR; k++) { + gf_term_set_option(term, GF_OPT_NAVIGATION, k); + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_MOUSEDOWN; + evt.mouse.x = 100; + evt.mouse.y = 100; + gf_term_user_event(term, &evt); + evt.type = GF_EVENT_MOUSEMOVE; + evt.mouse.x += 10; + gf_term_user_event(term, &evt); + evt.mouse.y += 10; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_KEYDOWN; + evt.key.key_code = GF_KEY_CONTROL; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_MOUSEMOVE; + evt.mouse.x = 120; + evt.mouse.y = 110; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_KEYUP; + evt.key.key_code = GF_KEY_CONTROL; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_KEYDOWN; + evt.key.key_code = GF_KEY_J; + gf_term_user_event(term, &evt); + + } + gf_term_set_option(term, GF_OPT_NAVIGATION_TYPE, 0); + + gf_term_connect_with_path(term, "logo.jpg", "./media/auxiliary_files/"); + gf_term_navigate_to(term, "./media/auxiliary_files/logo.jpg"); + send_open_url("./media/auxiliary_files/logo.jpg"); + switch_bench(1); + do_set_speed(1.0); + hide_shell(0); + list_modules(); + } +#endif + + i = gf_sys_clock(); + gf_term_disconnect(term); + if (rti_file) UpdateRTInfo("Disconnected\n"); + + if (playlist) gf_fclose(playlist); + +#if defined(__DARWIN__) || defined(__APPLE__) + carbon_uninit(); +#endif + + //special condition for immediate exit without terminal deletion + if (ret_val==3) { + fprintf(stderr, "Exit forced, no cleanup\n"); + exit(0); + } + + fprintf(stderr, "Deleting terminal... "); + gf_term_del(term); + fprintf(stderr, "done (in %d ms) - ran for %d ms\n", gf_sys_clock() - i, gf_sys_clock()); + + fprintf(stderr, "GPAC cleanup ...\n"); + + if (no_cfg_save) + gf_opts_discard_changes(); + + gf_sys_close(); + + if (rti_logs) gf_fclose(rti_logs); + if (logfile) gf_fclose(logfile); + + if (gui_mode) { + hide_shell(2); + } + +#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 ret_val; +} + +GF_MAIN_FUNC(mp4client_main) + + +static void MakeScreenshot(Bool for_coverage) +{ + char szFileName[100]; + u32 nb_pass, nb_views, offscreen_view = 0; + GF_VideoSurface fb; + GF_Err e; + nb_pass = 1; + nb_views = gf_term_get_option(term, GF_OPT_NUM_STEREO_VIEWS); + if (nb_views>1) { + fprintf(stderr, "Auto-stereo mode detected - type number of view to dump (0 is main output, 1 to %d offscreen view, %d for all offscreen, %d for all offscreen and main)\n", nb_views, nb_views+1, nb_views+2); + if (!for_coverage) { + if (scanf("%d", &offscreen_view) != 1) { + offscreen_view = 0; + } + } + if (offscreen_view==nb_views+1) { + offscreen_view = 1; + nb_pass = nb_views; + } + else if (offscreen_view==nb_views+2) { + offscreen_view = 0; + nb_pass = nb_views+1; + } + } + if (for_coverage && !offscreen_view) { + if (gf_term_get_offscreen_buffer(term, &fb, 0, 0)==GF_OK) + gf_term_release_screen_buffer(term, &fb); + } + while (nb_pass) { + nb_pass--; + if (offscreen_view) { + sprintf(szFileName, "view%d_dump.png", offscreen_view); + e = gf_term_get_offscreen_buffer(term, &fb, offscreen_view-1, 0); + } else { + sprintf(szFileName, "gpac_video_dump_"LLU".png", gf_net_get_utc() ); + e = gf_term_get_screen_buffer(term, &fb); + } + offscreen_view++; + if (e) { + fprintf(stderr, "Error dumping screen buffer %s\n", gf_error_to_string(e) ); + nb_pass = 0; + } else { +#ifndef GPAC_DISABLE_AV_PARSERS + u32 dst_size = fb.width*fb.height*4; + char *dst = (char*)gf_malloc(sizeof(char)*dst_size); + + e = gf_img_png_enc(fb.video_buffer, fb.width, fb.height, fb.pitch_y, fb.pixel_format, dst, &dst_size); + if (e) { + fprintf(stderr, "Error encoding PNG %s\n", gf_error_to_string(e) ); + nb_pass = 0; + } else { + FILE *png = gf_fopen(szFileName, "wb"); + if (!png) { + fprintf(stderr, "Error writing file %s\n", szFileName); + nb_pass = 0; + } else { + gf_fwrite(dst, dst_size, png); + gf_fclose(png); + fprintf(stderr, "Dump to %s\n", szFileName); + } + } + if (dst) gf_free(dst); + gf_term_release_screen_buffer(term, &fb); + + if (for_coverage) gf_file_delete(szFileName); +#endif //GPAC_DISABLE_AV_PARSERS + } + } + fprintf(stderr, "Done: %s\n", szFileName); +} + +static GF_ObjectManager *video_odm = NULL; +static GF_ObjectManager *audio_odm = NULL; +static GF_ObjectManager *scene_odm = NULL; +static u32 last_odm_count = 0; + +static void PrintAVInfo(Bool final) +{ + GF_MediaInfo a_odi, v_odi, s_odi; + Double avg_dec_time; + u32 tot_time; + + if (scene_odm) { + GF_ObjectManager *root_odm = gf_term_get_root_object(term); + u32 count = gf_term_get_object_count(term, root_odm); + if (last_odm_count != count) { + last_odm_count = count; + scene_odm = NULL; + } + } + if (!video_odm && !audio_odm && !scene_odm) { + u32 count, i; + GF_ObjectManager *root_odm = gf_term_get_root_object(term); + if (!root_odm) return; + + if (gf_term_get_object_info(term, root_odm, &v_odi)==GF_OK) { + if (!scene_odm && (v_odi.generated_scene== 0)) { + scene_odm = root_odm; + } + } + + count = gf_term_get_object_count(term, root_odm); + for (i=0; i1) || v_odi.direct_video_memory || (bench_mode == 3) )) { + video_odm = odm; + } + else if (!audio_odm && (v_odi.od_type == GF_STREAM_AUDIO)) { + audio_odm = odm; + } + else if (!scene_odm && (v_odi.od_type == GF_STREAM_SCENE)) { + scene_odm = odm; + } + } + } + } + + if (0 && bench_buffer) { + fprintf(stderr, "Buffering %d %% ", bench_buffer-1); + return; + } + + if (video_odm) { + if (gf_term_get_object_info(term, video_odm, &v_odi)!= GF_OK) { + video_odm = NULL; + return; + } + } else { + memset(&v_odi, 0, sizeof(v_odi)); + } + if (audio_odm) { + gf_term_get_object_info(term, audio_odm, &a_odi); + } else { + memset(&a_odi, 0, sizeof(a_odi)); + } + if (!video_odm && scene_odm) { + gf_term_get_object_info(term, scene_odm, &s_odi); + } else { + memset(&s_odi, 0, sizeof(s_odi)); + } + + if (final) { + tot_time = gf_sys_clock() - bench_mode_start; + fprintf(stderr, " \r"); + fprintf(stderr, "************** Bench Mode Done in %d ms ********************\n", tot_time); + if (bench_mode==3) fprintf(stderr, "** Systems layer only (no decoding) **\n"); + + if (!video_odm) { + u32 nb_frames_drawn; + Double FPS = gf_term_get_simulation_frame_rate(term, &nb_frames_drawn); + fprintf(stderr, "Drawn %d frames FPS %.2f (simulation FPS %.2f) - duration %d ms\n", nb_frames_drawn, ((Float)nb_frames_drawn*1000)/tot_time,(Float) FPS, gf_term_get_time_in_ms(term) ); + } + } + if (video_odm) { + fprintf(stderr, "%s %dx%d sar=%d:%d duration %.2fs\n", v_odi.codec_name, v_odi.width, v_odi.height, v_odi.par ? (v_odi.par>>16)&0xFF : 1, v_odi.par ? (v_odi.par)&0xFF : 1, v_odi.duration); + if (final) { + u32 dec_run_time = v_odi.last_frame_time - v_odi.first_frame_time; + if (!dec_run_time) dec_run_time = 1; + if (v_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*v_odi.current_time / v_odi.duration ) ); + fprintf(stderr, "%d frames FPS %.2f (max %d us/f) rate avg %d max %d", v_odi.nb_dec_frames, ((Float)v_odi.nb_dec_frames*1000) / dec_run_time, v_odi.max_dec_time, (u32) v_odi.avg_bitrate/1000, (u32) v_odi.max_bitrate/1000); + if (v_odi.nb_dropped) { + fprintf(stderr, " (Error during bench: %d frames drop)", v_odi.nb_dropped); + } + fprintf(stderr, "\n"); + } + } + if (audio_odm) { + fprintf(stderr, "%s SR %d num channels %d %s duration %.2fs\n", a_odi.codec_name, a_odi.sample_rate, a_odi.num_channels, gf_audio_fmt_name(a_odi.afmt), a_odi.duration); + if (final) { + u32 dec_run_time = a_odi.last_frame_time - a_odi.first_frame_time; + if (!dec_run_time) dec_run_time = 1; + if (a_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*a_odi.current_time / a_odi.duration ) ); + fprintf(stderr, "%d frames (ms/f %.2f avg %.2f max) rate avg %d max %d", a_odi.nb_dec_frames, ((Float)dec_run_time)/a_odi.nb_dec_frames, a_odi.max_dec_time/1000.0, (u32) a_odi.avg_bitrate/1000, (u32) a_odi.max_bitrate/1000); + if (a_odi.nb_dropped) { + fprintf(stderr, " (Error during bench: %d frames drop)", a_odi.nb_dropped); + } + fprintf(stderr, "\n"); + } + } + if (scene_odm) { + u32 w, h; + gf_term_get_visual_output_size(term, &w, &h); + fprintf(stderr, "%s scene size %dx%d rastered to %dx%d duration %.2fs\n", s_odi.codec_name ? s_odi.codec_name : "", s_odi.width, s_odi.height, w, h, s_odi.duration); + if (final) { + if (s_odi.nb_dec_frames>2 && s_odi.total_dec_time) { + u32 dec_run_time = s_odi.last_frame_time - s_odi.first_frame_time; + if (!dec_run_time) dec_run_time = 1; + fprintf(stderr, "%d frames FPS %.2f (max %d us/f) rate avg %d max %d", s_odi.nb_dec_frames, ((Float)s_odi.nb_dec_frames*1000) / dec_run_time, s_odi.max_dec_time, (u32) s_odi.avg_bitrate/1000, (u32) s_odi.max_bitrate/1000); + fprintf(stderr, "\n"); + } else { + u32 nb_frames_drawn; + Double FPS; + gf_term_get_simulation_frame_rate(term, &nb_frames_drawn); + tot_time = gf_sys_clock() - bench_mode_start; + FPS = gf_term_get_framerate(term, 0); + fprintf(stderr, "%d frames FPS %.2f (abs %.2f)\n", nb_frames_drawn, (1000.0*nb_frames_drawn / tot_time), FPS); + } + + fprintf(stderr, "**********************************************************\n\n"); + return; + } + } + + if (video_odm) { + tot_time = v_odi.last_frame_time - v_odi.first_frame_time; + if (!tot_time) tot_time=1; + if (v_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*v_odi.current_time / v_odi.duration ) ); + fprintf(stderr, "%d f FPS %.2f (%.2f ms max) rate %d ", v_odi.nb_dec_frames, ((Float)v_odi.nb_dec_frames*1000) / tot_time, v_odi.max_dec_time/1000.0, (u32) v_odi.avg_bitrate/1000); + } + else if (scene_odm) { + + if (s_odi.nb_dec_frames>2 && s_odi.total_dec_time) { + avg_dec_time = (Float) 1000000 * s_odi.nb_dec_frames; + avg_dec_time /= s_odi.total_dec_time; + if (s_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*s_odi.current_time / s_odi.duration ) ); + fprintf(stderr, "%d f %.2f (%d us max) - rate %d ", s_odi.nb_dec_frames, avg_dec_time, s_odi.max_dec_time, (u32) s_odi.avg_bitrate/1000); + } else { + u32 nb_frames_drawn; + Double FPS; + gf_term_get_simulation_frame_rate(term, &nb_frames_drawn); + tot_time = gf_sys_clock() - bench_mode_start; + FPS = gf_term_get_framerate(term, 1); + fprintf(stderr, "%d f FPS %.2f (abs %.2f) ", nb_frames_drawn, (1000.0*nb_frames_drawn / tot_time), FPS); + } + } + else if (audio_odm) { + gf_term_get_object_info(term, audio_odm, &a_odi); + + tot_time = a_odi.last_frame_time - a_odi.first_frame_time; + if (!tot_time) tot_time=1; + if (a_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*a_odi.current_time / a_odi.duration ) ); + fprintf(stderr, "%d frames (ms/f %.2f avg %.2f max)", a_odi.nb_dec_frames, ((Float)tot_time)/a_odi.nb_dec_frames, a_odi.max_dec_time/1000.0); + } +} + +static void PrintWorldInfo(GF_Terminal *term) +{ + u32 i; + const char *title; + GF_List *descs; + descs = gf_list_new(); + title = gf_term_get_world_info(term, NULL, descs); + if (!title && !gf_list_count(descs)) { + fprintf(stderr, "No World Info available\n"); + } else { + fprintf(stderr, "\t%s\n", title ? title : "No title available"); + for (i=0; i>16)&0xFF, (odi.par)&0xFF); + break; + case GF_STREAM_AUDIO: + fprintf(stderr, "Audio Object: Sample Rate %d - %d channels\r\n", odi.sample_rate, odi.num_channels); + fprintf(stderr, "Media Codec: %s\n", odi.codec_name); + break; + case GF_STREAM_SCENE: + case GF_STREAM_PRIVATE_SCENE: + if (odi.width && odi.height) { + fprintf(stderr, "Scene Description - Width %d - Height %d\n", odi.width, odi.height); + } else { + fprintf(stderr, "Scene Description - no size specified\n"); + } + fprintf(stderr, "Scene Codec: %s\n", odi.codec_name); + break; + case GF_STREAM_TEXT: + if (odi.width && odi.height) { + fprintf(stderr, "Text Object: Width %d - Height %d\n", odi.width, odi.height); + } else { + fprintf(stderr, "Text Object: No size specified\n"); + } + fprintf(stderr, "Text Codec %s\n", odi.codec_name); + break; + } + + avg_dec_time = 0; + if (odi.nb_dec_frames) { + avg_dec_time = (Float) odi.total_dec_time; + avg_dec_time /= odi.nb_dec_frames; + } + fprintf(stderr, "\tBitrate over last second: %d kbps\n\tMax bitrate over one second: %d kbps\n\tAverage Decoding Time %.2f us %d max)\n\tTotal decoded frames %d\n", + (u32) odi.avg_bitrate/1024, odi.max_bitrate/1024, avg_dec_time, odi.max_dec_time, odi.nb_dec_frames); + } + if (odi.protection) fprintf(stderr, "Encrypted Media scheme %s\n", gf_4cc_to_str(odi.protection) ); + + fprintf(stderr, "\nStream ID %d - %s - Clock ID %d\n", odi.pid_id, gf_stream_type_name(odi.od_type), odi.ocr_id); +// if (esd->dependsOnESID) fprintf(stderr, "\tDepends on Stream ID %d for decoding\n", esd->dependsOnESID); + + if (odi.lang_code) + fprintf(stderr, "\tStream Language: %s\n", odi.lang_code); + + fprintf(stderr, "\n"); + + switch (odi.status) { + case 0: + fprintf(stderr, "Stopped - "); + break; + case 1: + fprintf(stderr, "Playing - "); + break; + case 2: + fprintf(stderr, "Paused - "); + break; + case 3: + fprintf(stderr, "Not setup yet\n"); + return; + default: + fprintf(stderr, "Setup Failed\n"); + return; + } + if (odi.buffer>=0) fprintf(stderr, "Buffer: %d ms - ", odi.buffer); + else fprintf(stderr, "Not buffering - "); + fprintf(stderr, "Clock drift: %d ms\n", odi.clock_drift); + if (odi.db_unit_count) fprintf(stderr, "%d AU in DB\n", odi.db_unit_count); + if (odi.cb_max_count) fprintf(stderr, "Composition Buffer: %d CU (%d max)\n", odi.cb_unit_count, odi.cb_max_count); + fprintf(stderr, "\n"); + + if (odi.owns_service) { + const char *url; + u32 done, total, bps; + d_enum = 0; + while (gf_term_get_download_info(term, odm, &d_enum, &url, &done, &total, &bps)) { + if (d_enum==1) fprintf(stderr, "Current Downloads in service:\n"); + if (done && total) { + fprintf(stderr, "%s: %d / %d bytes (%.2f %%) - %.2f kBps\n", url, done, total, (100.0f*done)/total, ((Float)bps)/1024.0f); + } else { + fprintf(stderr, "%s: %.2f kbps\n", url, ((Float)8*bps)/1024.0f); + } + } + if (!d_enum) fprintf(stderr, "No Downloads in service\n"); + fprintf(stderr, "\n"); + } + + d_enum = 0; + while (gf_term_get_channel_net_info(term, odm, &d_enum, &id, &stats, &e)) { + if (e) continue; + if (!stats.bw_down && !stats.bw_up) continue; + + fprintf(stderr, "Stream ID %d statistics:\n", id); + if (stats.multiplex_port) { + fprintf(stderr, "\tMultiplex Port %d - multiplex ID %d\n", stats.multiplex_port, stats.port); + } else { + fprintf(stderr, "\tPort %d\n", stats.port); + } + fprintf(stderr, "\tPacket Loss Percentage: %.4f\n", stats.pck_loss_percentage); + fprintf(stderr, "\tDown Bandwidth: %d bps\n", stats.bw_down); + if (stats.bw_up) fprintf(stderr, "\tUp Bandwidth: %d bps\n", stats.bw_up); + if (stats.ctrl_port) { + if (stats.multiplex_port) { + fprintf(stderr, "\tControl Multiplex Port: %d - Control Multiplex ID %d\n", stats.multiplex_port, stats.ctrl_port); + } else { + fprintf(stderr, "\tControl Port: %d\n", stats.ctrl_port); + } + fprintf(stderr, "\tDown Bandwidth: %d bps\n", stats.ctrl_bw_down); + fprintf(stderr, "\tUp Bandwidth: %d bps\n", stats.ctrl_bw_up); + } + fprintf(stderr, "\n"); + } +} + +static void PrintODTiming(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) +{ + GF_MediaInfo odi; + u32 ind = indent; + u32 i, count; + if (!odm) return; + + if (gf_term_get_object_info(term, odm, &odi) != GF_OK) return; + if (!odi.ODID) { + fprintf(stderr, "Service not attached\n"); + return; + } + while (ind) { + fprintf(stderr, " "); + ind--; + } + + if (! odi.generated_scene) { + + fprintf(stderr, "- OD %d: ", odi.ODID); + switch (odi.status) { + case 1: + fprintf(stderr, "Playing - "); + break; + case 2: + fprintf(stderr, "Paused - "); + break; + default: + fprintf(stderr, "Stopped - "); + break; + } + if (odi.buffer>=0) fprintf(stderr, "Buffer: %d ms - ", odi.buffer); + else fprintf(stderr, "Not buffering - "); + fprintf(stderr, "Clock drift: %d ms", odi.clock_drift); + fprintf(stderr, " - time: "); + PrintTime((u32) (odi.current_time*1000)); + fprintf(stderr, "\n"); + + } else { + fprintf(stderr, "+ Service %s:\n", odi.service_url); + } + + count = gf_term_get_object_count(term, odm); + for (i=0; i=0) fprintf(stderr, " - Buffer: %d ms", odi.buffer); + if (odi.db_unit_count) fprintf(stderr, " - DB: %d AU", odi.db_unit_count); + if (odi.cb_max_count) fprintf(stderr, " - CB: %d/%d CUs", odi.cb_unit_count, odi.cb_max_count); + + fprintf(stderr, "\n"); + ind = indent; + while (ind) { + fprintf(stderr, " "); + ind--; + } + + fprintf(stderr, " %d decoded frames - %d dropped frames\n", odi.nb_dec_frames, odi.nb_dropped); + + ind = indent; + while (ind) { + fprintf(stderr, " "); + ind--; + } + + avg_dec_time = 0; + if (odi.nb_dec_frames) { + avg_dec_time = (Float) odi.total_dec_time; + avg_dec_time /= odi.nb_dec_frames; + } + fprintf(stderr, " Avg Bitrate %d kbps (%d max) - Avg Decoding Time %.2f us (%d max)\n", + (u32) odi.avg_bitrate/1024, odi.max_bitrate/1024, avg_dec_time, odi.max_dec_time); + } + + count = gf_term_get_object_count(term, odm); + for (i=0; i ftmp +rm $source +mv ftmp $source + + +#patch applications/osmo4_ios/osmo4ios-Info.plist + +source="applications/osmo4_ios/osmo4ios-Info.plist" +sed -e "/CFBundleShortVersionString/{n;s/.*/ $version<\/string>/;}" $source > ftmp +rm $source +mv ftmp $source + +# patch file gpac.pc +source="gpac.pc" +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%{?_with_amr:Source1:http:\/\/www.3gpp.org\/ftp\/Specs\/archive\/26_series\/26.073\/26073-700.zip}/;" > 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 +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 +rm $source +mv ftmp $source + +# patch file share/doc/configuration.html +source="share/doc/configuration.html" +sed -e "s/GPAC Version.* ftmp +rm $source +mv ftmp $source diff --git a/check_revision.sh b/check_revision.sh new file mode 100755 index 0000000..fd075a5 --- /dev/null +++ b/check_revision.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +version="`grep '#define GPAC_VERSION ' \"./include/gpac/version.h\" | cut -d '"' -f 2`" + +#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" + +rm gtmp + +echo "#define GPAC_GIT_REVISION \"$revision\"" > htmp + +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 + +if [ ! -e ".include/gpac/revision.h" ]; then + +if [[ "$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 diff --git a/configure b/configure new file mode 100755 index 0000000..25d67dc --- /dev/null +++ b/configure @@ -0,0 +1,3500 @@ +#!/bin/sh +# +# GPAC configure script +# (c) 2003-2022 Telecom ParisTech +# Authors: Jean Le Feuvre, Romain Bouqueau, Aurelien David +# +#set -v + +#set temporary file name +if test ! -z "$TMPDIR" ; then + TMPDIR1="${TMPDIR}" +elif test ! -z "$TEMPDIR" ; then + TMPDIR1="${TEMPDIR}" +else + TMPDIR1="/tmp" +fi + + +#remember the ./configure command line +GPAC_CONFIGURATION="$@" + +TMPC="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.c" +TMPH="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.h" +TMPCXX="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.cpp" +TMPE="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}" +TMPO="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.o" +TMPS="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.S" +TMPL="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.LOG" + +#default parameters +DESTDIR="" +prefix="/usr/local" +mandir="" +cross_prefix="" +targetos="" +dxsdk_path="" +moz_path="local" + +cc_orig=$CC +if test "$cc_orig" = "" ; then +cc_orig="gcc" +fi + +cxx_orig=$CXX +if test "$cxx_orig" = "" ; then +cxx_orig="g++" +fi + +ar="ar" +ranlib="ranlib" +make="make" +strip="strip" +pkg_config="pkg-config" +windres="windres" +readelf="readelf" +install="${INSTALL:=install}" +instflags="${INSTFLAGS:=-p}" +cpu=`uname -m` +debuginfo="no" +sdl_path="" +sdl_local="no" +sdl_static="no" +verbose="no" +xulsdk_path="/usr/lib/xulrunner/sdk/include" +xul_flags="" +libdir="lib" + +#GPAC module config +static_modules="no" +lm_lib="" +has_mingw_directx="no" +has_js="no" +has_platinum="no" +has_ft="no" +has_jpeg="no" +has_png="no" +has_xvid="no" +has_mad="no" +has_faad="no" +has_ffmpeg="no" +ffmpeg_vvc="no" +has_amr_nb_fixed="no" +has_amr_nb="no" +has_amr_wb="no" +has_ogg="no" +has_vorbis="no" +has_theora="no" +has_a52="no" +has_pulseaudio="no" +has_oss_audio="no" +has_alsa="no" +has_jack="no" +has_directfb="no" +has_x11="no" +has_x11_shm="no" +has_x11_xv="no" +no_gcc_opt="no" +use_fixed_point="no" +use_memory_tracking="no" +has_opengl="no" +has_tinygl="no" +enable_tinygl="no" +has_ssl="no" +has_ipv6="no" +has_dvb4linux="no" +has_openjpeg="no" +gprof_build="no" +static_build="no" +want_pic="no" +want_gcov="no" +has_joystick="no" +has_hid="no" +has_sock_un="no" +has_lzma="no" +has_nghttp2="no" +enable_joystick="no" +static_bin="no" +disable_core_tools="no" +disable_3d="no" +disable_svg="no" +disable_vrml="no" +disable_x3d="no" +disable_od="no" +disable_bifs="no" +disable_bifs_enc="no" +disable_laser="no" +disable_saf="no" +disable_smgr="no" +disable_seng="no" +disable_qtvr="no" +disable_avi="no" +disable_m2ps="no" +disable_m2ts="no" +disable_m2ts_mux="no" +disable_ogg="no" +disable_parsers="no" +disable_import="no" +disable_export="no" +disable_swf="no" +disable_scene_stats="no" +disable_scene_dump="no" +disable_scene_encode="no" +disable_loader_isoff="no" +disable_loader_bt="no" +disable_loader_xmt="no" +disable_od_dump="no" +disable_od_parse="no" +disable_isom_dump="no" +disable_crypto="no" +disable_mpd="no" +disable_dash="no" +disable_isoff="no" +disable_isoff_write="no" +disable_isoff_frag="no" +disable_isoff_hint="no" +disable_isoff_frag="no" +disable_isoff_hds="no" +disable_streaming="no" +disable_player="no" +disable_scenegraph="no" +disable_dvbx="yes" +disable_vobsub="no" +disable_ttxt="no" +disable_ttml="no" +disable_hevc="no" +disable_route="no" +disable_crypto="no" +disable_log="no" +disable_nvdec="no" +enable_qjs="yes" +enable_qjs_libc="yes" +enable_depth_compositor="no" +enable_sanitizer="no" +has_opensvc="no" +has_openhevc="no" +has_vtb="no" +has_strlcpy="no" +disable_codecs="no" +enable_qjs_stack_check="no" + +win32="no" +mingw32="no" +cygwin="no" +linux="no" +freebsd="no" +darwin="no" +sunos="no" +alt_macosx_dir="" +Mac_Applications="" +extralibs="-lm" +bigendian="no" +SHFLAGS=-shared +need_inet_aton="no" +CFLAGS="" +CXXFLAGS="" +GPAC_SH_FLAGS=-lpthread +DYN_LIB_SUFFIX=".so" +X11_PATH="/usr/X11R6" +OSS_CFLAGS="" +OSS_LDFLAGS="" +INSTFLAGS="" +is_64="no" +ffmpeg_extra_ldflags="" +has_st_nsec="no" + +logs="config.log" +echo "Logs for GPAC configure $GPAC_CONFIGURATION" > $logs + + +#configure usage +if test x"$1" = x"-h" -o x"$1" = x"--help" ; then + cat << EOF + +Usage: configure [options] +Options: [defaults in brackets after descriptions] + +GPAC configuration options: + --help print this message + --prefix=PREFIX install in PREFIX [$prefix] + --mandir=DIR man documentation in DIR [PREFIX/share/man] + + --verbose enable verbose building [$verbose] + --source-path=PATH path of source code [$source_path] + --cross-prefix=PREFIX use PREFIX for compile tools [$cross_prefix] + --target-os=TARGETOS use TARGETOS for compile tools [$targetos] + --cc=CC use C compiler CC [$cc] + --cxx=CXX use C++ compiler CXX [$cxx] + --make=MAKE use specified make [$make] + --extra-cflags=ECFLAGS add ECFLAGS to CFLAGS [$CFLAGS] + --extra-ldflags=ELDFLAGS add ELDFLAGS to LDFLAGS [$LDFLAGS] + --extra-libs=ELIBS add ELIBS [$ELIBS] + --cpu=CPU force cpu to CPU [$cpu] + --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] + --xulsdk-path=XUL_PATH specify Mozilla XUL (Gecko) SDK include path [$xulsdk_path] + --mozdir=MOZ_PATH specify mozilla main directory path for system install + --extra-ff-ldflags=ELDFLAGS add ELDFLAGS to FFMPEG LDFLAGS [$ffmpeg_extra_ldflags] + + --enable-debug produce debug version + --enable-gprof enable profiling + --enable-gcov enable coverage + --enable-pic enable Position Independant Code for shared objects + --strip enable strip + --std-allocator uses standard lib memory allocator + --enable-mem-track enable tracking of all memory allocated by gpac + --enable-sanitizer enable address sanitizer + --enable-afl enable instrumentation for American Fuzzy Lop + --enable-afl-clang enable instrumentation for American Fuzzy Lop using afl-clang(++) + --disable-opt disable GCC optimizations + --disable-ipv6 disable IPV6 support + --disable-platinum disable Platinum UPnP support + --disable-alsa disable Alsa audio + --disable-oss-audio disable OSS audio + --enable-jack enable Jack audio + --disable-jack disable Jack audio + --enable-pulseaudio enable Pulse audio + --disable-pulseaudio disable Pulse audio + --disable-x11 disable X11 + --disable-x11-shm disable X11 shared memory support + --disable-x11-xv disable X11 Xvideo support + --enable-fixed-point enable fixed-point math + --enable-tinygl enable TinyGL support + --enable-joystick enable joystick support + --disable-ssl disable OpenSSL support + --enable-amr-nb-fixed enable AMR NB fixed-point decoder + --enable-amr-nb enable AMR NB library + --enable-amr-wb enable AMR WB library + --enable-amr enable both AMR NB and WB libraries + --static-modules use static modules in libgpac rather than dynamic library modules + --static-build link statically against libgpac but still allow dependencies to shared libraries (enable --static-modules) + --enable-static-bin old name for --static-build, deprecated + --static-bin enable static linking of MP4Box and gpac only (enable --static-build), disable MP4Client and all libraries not linkable statically. + --static-mp4box old name for --static-bin, deprecated + --enable-depth enables depth handling in the compositor + --enable-qjs-stack enables stack depth checking in QuickJS (WILL CRASH in multithreaded modes) + +Configuration options for libgpac - all options can be enabled with --enable-optname + --disable-all disables all features in libgpac + --disable-3d disable 3D rendering + --disable-svg disable SVG + --disable-vrml disable MPEG-4/VRML/X3D + --disable-x3d disable X3D only + --disable-odf disable full support of MPEG-4 OD Framework + --disable-bifs disable BIFS + --disable-bifs-enc disable BIFS coder + --disable-laser disable LASeR coder + --disable-saf disable SAF container + --disable-seng disable scene encoder engine + --disable-qtvr disable import of Cubic QTVR files + --disable-avi disable AVI + --disable-ogg disable OGG + --disable-m2ps disable MPEG2 PS + --disable-m2ts disable MPEG2 TS + --disable-m2ts-mux disable MPEG2 TS Multiplexer + --disable-dvb4linux disable dvb4linux support + --disable-parsers disable AV parsers + --disable-import disable media importers + --disable-export disable media exporters + --disable-swf disable SWF import + --disable-scene-stats disable scene graph statistics + --disable-scene-dump disable scene graph dump + --disable-scene-encode disable BIFS & LASeR to ISO File Format encoding + --disable-loader-isoff disable scene loading from ISO File Format + --disable-loader-bt disable scene loading from ISO File Format + --disable-loader-xmt disable scene loading from ISO File Format + --disable-od-dump disable OD dump + --disable-isom-dump disable ISOM dump + --disable-crypt disable crypto tools + --disable-isoff disable ISO File Format + --disable-isoff-write disable ISO File Format edit/write + --disable-isoff-hint disable ISO File Format hinting + --disable-isoff-frag disable fragments in ISO File Format + --disable-isoff-hds disable HDS support in ISO File Format + --disable-streaming disable RTP/RTSP/SDP + --disable-dvbx disable DVB-specific tools (MPE, FEC, DSM-CC) + --disable-vobsub disable VobSub support + --disable-sman disable scene manager + --disable-ttxt disable TTXT (3GPP / MPEG-4 Timed Text) support + --disable-player disable player (terminal and compositor) + --disable-scenegraph disable scenegraph, scene parsers and player (terminal and compositor) + --disable-hevc disable HEVC support + --disable-route disable ROUTE (ATSC3) support + --disable-crypto disable crypto support + --disable-nvdec disable NVDec support + --disable-codecs disable all media decoders and encoders + +Extra libraries configuration. You can turn a library off or force using the local version in gpac/extra_lib/ + --use-ft=OPT force FreeType OPT=[no,local] + --use-zlib=OPT force ZLIB OPT=[no,system,local] + --use-jpeg=OPT force JPEG OPT=[no,local] + --use-png=OPT force PNG OPT=[no,local] + --use-faad=OPT force FAAD OPT=[no,local] + --use-mad=OPT force MAD OPT=[no,local] + --use-xvid=OPT force XVID OPT=[no,local] + --use-ffmpeg=OPT force FFMPEG OPT=[no,local] + --use-ogg=OPT force OGG OPT=[no,system,local] + --use-vorbis=OPT force vorbis OPT=[no,system,local] + --use-theora=OPT force theora OPT=[no,system,local] + --use-openjpeg=OPT force openjpeg OPT=[no,system,local] + --use-a52=OPT force a52 (ac3) OPT=[no,system,local] + +NOTE: The object files are built at the place where configure is launched +EOF +exit 1 +fi + +use_static="no" + +for opt do + case "$opt" in + --prefix=*) prefix=`echo $opt | cut -d '=' -f 2` + ;; + --libdir=*) libdir=`echo $opt | cut -d '=' -f 2` + ;; + --mandir=*) mandir=`echo $opt | cut -d '=' -f 2` + ;; + --cross-prefix=*) cross_prefix=`echo $opt | cut -d '=' -f 2` && echo "cross-prefix detected: $cross_prefix" + ;; + --target-os=*) targetos=`echo $opt | cut -d '=' -f 2` && echo "target-os detected: $targetos" + ;; + --cc=*) cc_orig=`echo $opt | cut -d '=' -f 2` + ;; + --cxx=*) cxx_orig=`echo $opt | cut -d '=' -f 2` + ;; + --cpp=*) cxx_orig=`echo $opt | cut -d '=' -f 2` + ;; + --make=*) make=`echo $opt | cut -d '=' -f 2` + ;; + --extra-cflags=*) CFLAGS="$CFLAGS ${opt#--extra-cflags=}" + ;; + --extra-ldflags=*) LDFLAGS="$LDFLAGS ${opt#--extra-ldflags=}" + ;; + --extra-ff-ldflags=*) ffmpeg_extra_ldflags=`echo $opt | cut -d '=' -f 2` + ;; + --extra-libs=*) extralibs=${opt#--extra-libs=} + ;; + --cpu=*) cpu=`echo $opt | cut -d '=' -f 2` + ;; + --enable-debug) debuginfo="yes"; no_gcc_opt="yes" + ;; + --disable-opt) no_gcc_opt="yes" + ;; + --enable-pic) want_pic="yes"; + ;; + --enable-gcov) want_gcov="yes"; + ;; + --enable-afl) cc_orig="afl-gcc"; cxx_orig="afl-g++" + ;; + --enable-afl-clang) cc_orig="afl-clang"; cxx_orig="afl-clang++" + ;; + --verbose) verbose="yes"; + ;; + --static-bin) use_static="yes" + ;; + --static-mp4box) use_static="yes" + ;; + --enable-sanitizer) enable_sanitizer="yes" + ;; + esac +done + + +cc="${cc_orig}" +cxx="${cxx_orig}" + +case "$cpu" in + i386|i486|i586|i686|i86pc|BePC) + cpu="x86" + ;; + x86_64|amd64) + cpu="x86" + if [ "$linux" = "yes" -o "$darwin" = "yes" ] ; then + is_64="yes" + fi + case `uname -s` in + SunOS) + canon_arch=`isainfo -n` + ;; + Darwin) + canon_arch="x86_64" + ;; + *) + canon_arch="`$cc -dumpmachine | sed -e 's,\([^-]*\)-.*,\1,'`" + ;; + esac + + if [ x"$canon_arch" = x"x86_64" -o x"$canon_arch" = x"amd64" ]; then + if [ -z "`echo $CFLAGS | grep -- -m32`" ]; then + cpu="x86_64" + want_pic="yes" + is_64="yes" + fi + fi + ;; + armv4l|arm) + cpu="armv4l" + ;; + alpha) + cpu="alpha" + ;; + ppc64) + cpu="powerpc" + ;; + "Power Macintosh"|ppc) + cpu="powerpc" + ;; + mips) + cpu="mips" + ;; + sh4|sh4a|sh) + cpu="sh4" + ;; + sun4u|sun4v) + cpu="sparc" + if [ -z "`echo $CFLAGS | grep -- -m32`" ]; then + is_64="yes" + PIC_CFLAGS="-fPIC -DPIC" + want_pic="yes" + fi + ;; + *) + cpu="unknown" + ;; +esac + +#checking for CFLAGS +if test -z "$CFLAGS"; then + CFLAGS="" +fi + + +cc="${cross_prefix}${cc}" +#for ccache +cc="${cc}" +cxx="${cross_prefix}${cxx}" +ar="${cross_prefix}${ar}" +ranlib="${cross_prefix}${ranlib}" +strip="${cross_prefix}${strip}" +windres="${cross_prefix}${windres}" + +if test "$enable_sanitizer" = "yes" ; then +if test "$use_static" = "yes" ; then +echo "Cannot use static bin with sanitizer, disabling static bin" +use_static="no" +fi +fi + +if test "$use_static" = "yes" ; then +pkg_config="${pkg_config} --static" +if test "$darwin" != "yes" ; then + LDFLAGS="-static $LDFLAGS" +fi +else +pkg_config="${pkg_config}" +fi + +#check pkg_config +if test "$cross_prefix" = "" ; then + if ! $pkg_config --version >/dev/null 2>>$logs ; then + pkg_config="no" + fi +fi + + +#find source path +source_path="`echo $0 | sed -e 's#/configure##'`" +source_path_used="yes" +if test -z "$source_path" -o "$source_path" = "." ; then + source_path="`pwd`" + source_path_used="no" + build_path=$source_path +else + source_path="`cd \"$source_path\"; pwd`" + build_path="`pwd`" +fi + + +#OS specific +if test "$targetos" = "" ; then + targetos=`uname -s` +fi +case $targetos in + BeOS) + prefix="/boot/home/config" + CFLAGS="$CFLAGS -DPIC -fomit-frame-pointer" + # 3 gcc releases known for BeOS, each with ugly bugs + gcc_version="$($cc -v 2>>$logs | grep version | cut -d ' ' -f3-)" + case "$gcc_version" in + 2.9-beos-991026*|2.9-beos-000224*) echo "R5/GG gcc" + ;; + *20010315*) echo "BeBits gcc" + CFLAGS="$CFLAGS -fno-expensive-optimizations" + ;; + esac + + SHFLAGS=-nostart + #no need for libm, but the inet stuff + #check for BONE + if (echo $BEINCLUDES|grep 'headers/be/bone' >/dev/null); then + extralibs="-lbind -lsocket" + else + need_inet_aton="yes" + extralibs="-lnet" + fi + ;; + + SunOS) + make="gmake" + readelf="greadelf" + LDFLAGS="$LDFLAGS" + instflags="" + #check for 64-bit + cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + CFLAGS_NO_LTO=$(echo ${CFLAGS} | sed -e 's/\ -flto[-A-Za-z0-9=]*//g') + $cc ${CFLAGS_NO_LTO} -o $TMPO $TMPC 2>>$logs && $($readelf -h $TMPO | grep "Class:.*ELF64$" >/dev/null 2>>$logs) + if test $? -eq 0; then + is_64="yes" + fi + if test "$is_64" = "yes"; then + if test "$cpu" = "x86_64"; then + libdir=lib/amd64 + elif test "$cpu" = "sparc"; then + libdir=lib/sparcv9 + fi + fi + sunos="yes" + need_inet_aton="yes" + extralibs="$extralibs -lsocket -lnsl" + ;; + + FreeBSD) + make="gmake" + LDFLAGS="$LDFLAGS -export-dynamic" + CFLAGS="$CFLAGS -pthread" + GPAC_SH_FLAGS=-pthread + freebsd="yes" + ;; + + BSD/OS) + extralibs="-lpoll -lgnugetopt -lm" + make="gmake" + ;; + + Darwin) + CFLAGS_DIR="-I$prefix/include" + LDFLAGS="-L$prefix/$libdir" + if test -d /sw/bin ; then + alt_macosx_dir="/sw" + CFLAGS_DIR="-I/sw/include $CFLAGS_DIR" + LDFLAGS="-L/sw/lib $LDFLAGS" + elif test -d /opt/local/bin ; then + alt_macosx_dir="/opt/local" + CFLAGS_DIR="-I/opt/local/include $CFLAGS_DIR" + LDFLAGS="-L/opt/local/lib $LDFLAGS" + fi + Mac_Applications="/Applications" + SHFLAGS="-dynamiclib" + DYN_LIB_SUFFIX=".dylib" + extralibs="" + GPAC_SH_FLAGS="" + strip="strip -x" + if test "$is_64" = "yes" ; then + LDFLAGS="$LDFLAGS" + fi + darwin="yes" + gcc_version=`$cc -v 2>>$logs | grep version | cut -d ' ' -f3` + case "$gcc_version" in + *2.95*) + CFLAGS="$CFLAGS -no-cpp-precomp -pipe -fomit-frame-pointer" + ;; + 3.*) + CFLAGS="$CFLAGS -no-cpp-precomp -pipe -fomit-frame-pointer -mdynamic-no-pic -fno-common" + ;; + 4.*) + CFLAGS="$CFLAGS -pipe -fomit-frame-pointer -fno-common" + ;; + esac + ;; + + MINGW32*|mingw32|MINGW64*|mingw64|msys*|MSYS*) + mingw32="yes" + win32="yes" + want_pic="no" + #ugly patch for msys2 mingw32/mingw64 where toolchain is not properly setup + if [ x"$MSYSTEM" = x"MSYS" ] && [ -e /mingw64 ]; then + extralibs="$extralibs -lws2_32 -lwinmm -limagehlp" + CFLAGS="$CFLAGS -I/mingw64/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" + LDFLAGS="$LDFLAGS -L/mingw64/lib" + elif [ x"$MSYSTEM" = x"MSYS" ] && [ -e /mingw32 ]; then + extralibs="-L/mingw32/lib $extralibs -lws2_32 -lwinmm -limagehlp" + CFLAGS="$CFLAGS -I/mingw32/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" + LDFLAGS="$LDFLAGS -L/mingw32/lib" + else + extralibs="$extralibs -lws2_32 -lwinmm -limagehlp -ldbghelp" + CFLAGS="$CFLAGS -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" + fi + LDFLAGS="$LDFLAGS" + if test "$cross_prefix" != "" ; then + GPAC_SH_FLAGS="" + fi + ;; + + CYGWIN*|cygwin*) + extralibs="$extralibs -lws2_32 -lwinmm" + cygwin="yes" + win32="yes" + ;; + + Linux|linux|android) + LDFLAGS="$LDFLAGS -Wl,--warn-common -Wl,-z,defs" + linux="yes" + case "$cpu" in + sh4) + CFLAGS="$CFLAGS -isystem \"$prefix/include\"" + ;; + esac + cat > $TMPC << EOF +#include +#if !defined (__BIONIC__) +#error +#endif +EOF + if $cc -c $CFLAGS $TMPC 0>/dev/null 2>$TMPL ; then + #bionic libc (android) + CFLAGS="$CFLAGS -DPTHREAD_HAS_NO_CANCEL" + GPAC_SH_FLAGS="" + fi + ;; + + *) ;; +esac + + +#defines directory for binaries and libs (ex. for TinyGL) +target_bin_dir="" +if test "$cross_prefix" = "" ; then + target_bin_dir=`${cc} -v 2>>$logs | sed -n '2p' | awk ' {print $2}'`-${cc_orig} +else + target_bin_dir=${cross_prefix}${cc_orig} +fi + + +#if test "$source_path_used" = "yes" ; then +mkdir -p extra_lib +mkdir -p extra_lib/lib +mkdir -p extra_lib/lib/gcc +#fi + + +#OK check for all local & systems lib +local_inc=$source_path/extra_lib/include +local_lib=$source_path/extra_lib/lib/gcc +execdir=$source_path/bin/gcc + + +dolog() { + rv=$? + if [ $rv -eq 0 ] ; then + return 0; + fi + + echo "*** CC/CXX Test Failed (args $@) : ">>$logs + echo "">>$logs + cat $TMPL >> $logs + echo "">>$logs + echo "Source was: ">>$logs + cat $TMPC >> $logs + echo "">>$logs + echo "">>$logs + return 1; +} + +docc() { + $cc -o $TMPO $TMPC $@ 0>/dev/null 2>$TMPL + dolog $@ +} + + +docxx() { + $cc -o $TMPO $TMPCXX $@ 0>/dev/null 2>$TMPL + dolog $@ +} + +#check GCC flags support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF +CFLAGS="$CFLAGS -Wall" +if docc -fno-strict-aliasing ; then + CFLAGS="$CFLAGS -fno-strict-aliasing" +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" +else + CFLAGS="-O0 $CFLAGS" +fi + + +#GCC PIC +if test "$cross_prefix" != "" ; then + want_pic="no" +fi +if test "$want_pic" = "yes" ; then + CFLAGS="$CFLAGS -fPIC -DPIC" + CXXFLAGS="$CXXFLAGS -fPIC -DPIC" +fi + +if test "$want_gcov" = "yes" ; then + CFLAGS="$CFLAGS --coverage" + CXXFLAGS="$CXXFLAGS --coverage" + LDFLAGS="$LDFLAGS --coverage" +fi + +#force use of cflags with cc +cc_naked=$cc +cxx_naked=$cxx +cc="$cc $CFLAGS" +cxx="$cxx $CXXFLAGS" + +#look for zlib +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc -msse2 $LDFLAGS ; then + CFLAGS="$CFLAGS -msse2" +fi + + +#look for zlib +cat > $TMPC << EOF +#include +#include +#include +int main( void ) { if (strcmp(zlibVersion(), ZLIB_VERSION)) { puts("zlib version differs !!!"); return 1; } return 0; } +EOF +has_zlib="no" +if docc -lz $LDFLAGS ; then + has_zlib="system" +fi +if test "$has_zlib" = "no" ; then + if docc -I"$local_inc/zlib" -L$local_lib -lz ; then + has_zlib="local" + fi +fi + +#check dlopen +cat > $TMPC << EOF +#include +int main( void ) { dlopen("foo", 0); return 0; } +EOF + +if docc ; then + dlopen="yes" +elif docc $LDFLAGS -ldl ; then + GPAC_SH_FLAGS="$GPAC_SH_FLAGS -ldl" +fi + + + +#check st_mtim.tv_nsec +cat > $TMPC << EOF +#include +int main( void ) { struct stat st; st.st_mtim.tv_nsec = 0; return 0; } +EOF + +if docc ; then + has_st_nsec="yes" +fi + + +#look for platinum support +cat > $TMPCXX << EOF +#include +int main( void ) { return 0; } +EOF +if docxx -I$local_inc/platinum $LDFLAGS -L$local_lib -lPlatinum -lPltMediaServer -lPltMediaConnect -lPltMediaRenderer -lNeptune -lZlib -lpthread ; then + has_platinum="yes" +fi + + +#look for opensvc support + +if test "$darwin" = "yes" ; then + osvc_cflags="-I/usr/include -I/usr/local/include" + osvc_ldflags="-L/usr/lib -L/usr/local/lib -lOpenSVCDec" +else + osvc_cflags="" + osvc_ldflags="-lOpenSVCDec" +fi + +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF +if docc $osvc_cflags $LDFLAGS $osvc_ldflags ; then + has_opensvc="yes" +else +osvc_cflags="-idirafter $local_inc" +osvc_ldflags="-lOpenSVCDec" + +if docc $osvc_cflags $LDFLAGS -L$local_lib $osvc_ldflags ; then + has_opensvc="yes" + osvc_ldflags="-L$local_lib $osvc_ldflags" +fi + +fi + +#look for openhevc support + +if test "$darwin" = "yes" ; then + ohevc_cflags="-I/usr/include -I/usr/local/include" + ohevc_ldflags="-L/usr/lib -L/usr/local/lib -lopenhevc -lm -lpthread " +elif test "$cross_prefix" = "" ; then + ohevc_cflags="-I/usr/include -I/usr/local/include" + ohevc_ldflags="-L/usr/lib -L/usr/local/lib -lopenhevc -lm -lpthread" +else + ohevc_cflags="-I${prefix}include" + ohevc_ldflags="-lopenhevc -lm -lpthread" +fi + +cat > $TMPC << EOF +#include +#include +int main( void ) { oh_init(1, 1); return 0; } +EOF +if docc $ohevc_cflags $ohevc_ldflags $LDFLAGS ; then + has_openhevc="yes" +else + ohevc_cflags="-I$local_inc" + ohevc_ldflags="-lopenhevc -lm -lpthread" + if docc $ohevc_cflags $ohevc_ldflags $LDFLAGS -L$local_lib ; then + has_openhevc="yes" + ohevc_ldflags="-L$local_lib $ohevc_ldflags" + elif docc $ohevc_cflags $ohevc_ldflags $LDFLAGS -L$execdir ; then + has_openhevc="yes" + ohevc_ldflags="-L$execdir $ohevc_ldflags" + fi +fi + +#look for freetype support +cat > $TMPC << EOF +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +int main( void ) { return 0; } +EOF +ft_cflags="-I$prefix/include " +ft_lflags="-L$prefix/$libdir -lfreetype" +if docc $CFLAGS_DIR $ft_cflags $ft_lflags $LDFLAGS ; then + has_ft="system" +fi + +if test "$has_ft" = "no" ; then + ft_cflags="`pkg-config --cflags freetype2 2>>$logs`" + ft_lflags="`pkg-config --libs freetype2 2>>$logs`" + if docc $ft_cflags $ft_lflags $LDFLAGS ; then + has_ft="system" + fi +fi + +if test "$has_ft" = "no" ; then + ft_cflags="-I$local_inc/freetype" + ft_lflags="-L$local_lib -lfreetype" + if docc $ft_cflags $ft_lflags $LDFLAGS ; then + has_ft="local" + fi +fi +#end freetype test + + +#look for OpenSSL support +cat > $TMPC << EOF +#include +#include +#include +#include +int main( void ) { return 0; } +EOF + +if test "$mingw32" = "yes" ; then + LINK_SSL="-lssl -lcrypto" +elif test "$win32" = "yes" ; then + LINK_SSL="-lssleay32 -leay32" +else + LINK_SSL="-lssl -lcrypto" +fi + +if docc $CFLAGS_DIR $LINK_SSL $LDFLAGS ; then + has_ssl="yes" +fi + + +#look for atomic.h +cat > $TMPC << EOF +#include +#include +int main( void ) { return 0; } +EOF + +has_atomic="no" +if docc $CFLAGS_DIR $LDFLAGS ; then + has_atomic="yes" +else + CFLAGS="$CFLAGS -DGPAC_NO_STDATOMIC" +fi + + +cat > $TMPC << EOF +#include +int main(void) { + int i4 = 4; + __int64_t i8 = 8; + __sync_add_and_fetch(&i4, 12); + __sync_add_and_fetch(&i8, 12); +} +EOF +has_builtinatomic="no" +if docc ; then + has_builtinatomic="yes" +else +cat > $TMPC << EOF +#include +int main(void) { + int i4 = 4; + __int64_t i8 = 8; + __atomic_add_fetch(&i4, 12, __ATOMIC_SEQ_CST); + __atomic_add_fetch(&i8, 12, __ATOMIC_SEQ_CST); +} +EOF + if docc -latomic ; then + CFLAGS="$CFLAGS -DGPAC_NEED_LIBATOMIC" + LDFLAGS="$LDFLAGS -latomic" + fi +fi + +#look for JPEG support +cat > $TMPC << EOF +#include +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -ljpeg ; then + has_jpeg="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_jpeg" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if cc -o $TMPO $TMPC -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -ljpeg 2>>$logs ; then + has_jpeg="system" + fi + elif test "`which $prefix/bin/jpeg-config 2>>$logs`" != ""; then + jpeg_cflags="`$prefix/bin/jpeg-config --cflags`" + jpeg_lflags="`$prefix/bin/jpeg-config --libs`" + if docc $jpeg_cflags $jpeg_lflags $LDFLAGS ; then + has_jpeg="system" + fi + else + jpeg_cflags="-I$prefix/include" + jpeg_lflags="-L$prefix/$libdir -ljpeg" + if docc $jpeg_cflags $jpeg_lflags $LDFLAGS ; then + has_jpeg="system" + fi + fi + fi +fi +if test "$has_jpeg" = "no" ; then + jpeg_cflags="-I$local_inc/jpeg" + jpeg_lflags="-L$local_lib -ljpeg" + if docc $jpeg_cflags $jpeg_lflags $LDFLAGS ; then + has_jpeg="local" + fi +fi + + + +#look for OpenJPEG support + +if test "$cross_prefix" = "" -a "$pkg_config" != "no"; then + if $pkg_config --exists libopenjp2 ; then + has_openjpeg="system" + openjpeg_cflags=`$pkg_config --cflags libopenjp2` + openjpeg_ldflags=`$pkg_config --libs libopenjp2` + elif $pkg_config --exists libopenjpeg ; then + has_openjpeg="system" + openjpeg_cflags=`$pkg_config --cflags libopenjpeg` + openjpeg_ldflags=`$pkg_config --libs libopenjpeg` + fi +fi + +if test "$has_openjpeg" = "no" ; then +cat > $TMPC << EOF +#include +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -lopenjpeg ; then + has_openjpeg="system" + openjpeg_ldflags="-lopenjpeg" +fi +if test "$cross_prefix" = "" ; then + if test "$has_openjpeg" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if cc -o $TMPO $TMPC -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lopenjpeg 2>>$logs ; then + has_openjpeg="system" + openjpeg_ldflags="-lopenjpeg" + fi + fi + fi +fi + +if test "$has_openjpeg" = "no" ; then + if docc -I$local_inc/openjpeg -L$local_lib -lopenjpeg $LDFLAGS ; then + has_openjpeg="local" + openjpeg_ldflags="-lopenjpeg" + fi +fi + +fi + + +#look for PNG support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +png_cflags="-I$prefix/include" +png_lflags="-L$prefix/$libdir -lpng -lz" +if docc $png_cflags $png_lflags $LDFLAGS ; then + has_png="system" +elif docc $LDFLAGS -lpng -lz ; then + has_png="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_png" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lpng 2>>$logs ; then + has_png="system" + fi + fi + fi +fi +if test "$has_png" = "no" ; then + if docc -I$local_inc/png -L$local_lib -lpng $LDFLAGS ; then + has_png="local" + fi +fi + + + +#look for MAD support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -lmad ; then + has_mad="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_mad" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lmad 2>>$logs ; then + has_mad="system" + fi + fi + fi +fi +if test "$has_mad" = "no" ; then + if docc -I$local_inc -L$local_lib -lmad $LDFLAGS ; then + has_mad="local" + fi +fi + + +#look for A52DEC support +cat > $TMPC << EOF +#include +#define uint32_t unsigned int +#define uint8_t unsigned char +#include +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -la52 ; then + has_a52="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_a52" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -la52 2>>$logs ; then + has_a52="system" + fi + fi + fi +fi +if test "$has_a52" = "no" ; then + if docc -I$local_inc -L$local_lib -la52 $LDFLAGS ; then + has_a52="local" + fi +fi + + +#look for XVID support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc -I$prefix/include -L$prefix/$libdir $LDFLAGS -lxvidcore -lpthread ; then + has_xvid="system" +elif docc $LDFLAGS -lxvidcore -lpthread ; then + has_xvid="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_xvid" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lxvidcore -lpthread 2>>$logs ; then + has_xvid="system" + fi + fi + fi +fi +if test "$has_xvid" = "no" ; then + if docc -I$local_inc -L$local_lib -lxvidcore -lpthread $LDFLAGS ; then + has_xvid="local" + fi +fi + + +#look for FAAD support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -lfaad -lm ; then + has_faad="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_faad" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lfaad 2>>$logs ; then + has_faad="system" + fi + fi + fi +fi +if test "$has_faad" = "no" ; then + if docc -I$local_inc -L$local_lib -lfaad -lm $LDFLAGS ; then + has_faad="local" + fi +fi + + +#look for FFMPEG support + +ffmpeg_cflags="" +ffmpeg_lflags="-lz -lavcodec -lavformat -lavutil -lavdevice -lswscale -lswresample -lavfilter $ffmpeg_extra_ldflags" + +if test "$cross_prefix" = "" -a "$pkg_config" != "no"; then + if $pkg_config --exists libavcodec libavformat libswscale libavdevice libavutil libswresample libavfilter ; then + ffmpeg_cflags=`$pkg_config --cflags libavcodec libavformat libavutil libavdevice libswscale libswresample libavfilter` + ffmpeg_lflags=`$pkg_config --libs libavcodec libavformat libavutil libavdevice libswscale libswresample libavfilter` + has_ffmpeg="system" + fi +fi + +cat > $TMPC << EOF +#include +int main(void) { + return 0; +} +EOF + +if docc $ffmpeg_cflags $ffmpeg_lflags $LDFLAGS ; then + old_ffmpeg_inc="no" +else + old_ffmpeg_inc="yes" + +# this is immediatly overwritten below?? +cat > $TMPC << EOF +#include +#include +int main(void) { + AVFrame *f1, *f2; + printf("ID %d", AV_CODEC_ID_H264); + f2 = av_frame_clone(f1); + return 0; +} +EOF + +fi + +cat > $TMPC << EOF +#include +#include +int main(void) { +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0 ) + printf("ID %d", AV_CODEC_ID_H264); +#else + printf("ID %d", CODEC_ID_H264); +#endif + return 0; +} +EOF + +if docc -I$prefix/include -L$prefix/$libdir $ffmpeg_lflags $LDFLAGS ; then + has_ffmpeg="system" + ffmpeg_cflags="-I$prefix/include" + ffmpeg_lflags="-L$prefix/$libdir $ffmpeg_lflags" +elif docc $ffmpeg_lflags $LDFLAGS ; then + has_ffmpeg="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_ffmpeg" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $ffmpeg_lflags $LDFLAGS 2>>$logs ; then + has_ffmpeg="system" + ffmpeg_cflags="-I$alt_macosx_dir/include" + ffmpeg_lflags="-L$alt_macosx_dir/lib $ffmpeg_lflags" + fi + fi + fi +fi +if test "$has_ffmpeg" = "no" ; then + if docc -I$local_inc -L$local_lib $ffmpeg_lflags $LDFLAGS ; then + has_ffmpeg="local" + ffmpeg_cflags="-I$local_inc" + ffmpeg_lflags="-L$local_lib $ffmpeg_lflags" + fi +fi + + +#now that cflags has been correctly set, retest +cat > $TMPC << EOF +#include +int main(void) { + return 0; +} +EOF + +if docc $ffmpeg_cflags $ffmpeg_lflags $LDFLAGS ; then + old_ffmpeg_inc="no" +else + old_ffmpeg_inc="yes" +fi + + +cat > $TMPC << EOF +#include +#include +#include +int main(void) { +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0 ) + printf("ID %d", AV_CODEC_ID_H264); +#else + printf("ID %d", CODEC_ID_H264); +#endif + return 0; +} +EOF +if docc $ffmpeg_cflags $ffmpeg_lflags ; then + is_libav="no" +else + +cat > $TMPC << EOF +#include +int main(void) { + printf("ID %d", CODEC_ID_H264); + return 0; +} +EOF + + if docc $ffmpeg_cflags $ffmpeg_lflags ; then + is_libav="yes" + else + is_libav="new" + fi +fi + +#detect libswresample for dashcast only +cat > $TMPC << EOF +#include "libswresample/swresample.h" +int main(void) { + SwrContext *aresampler = swr_alloc(); + free(aresampler); + return 0; +} +EOF + +if docc $ffmpeg_cflags $ffmpeg_lflags_dashcast -lswresample ; then + has_libswresample="yes" + ffmpeg_lflags_dashcast="$ffmpeg_lflags_dashcast -lswresample" +else + has_libswresample="no" +fi + + +#detect vvc support in ffmpegf +cat > $TMPC << EOF +#include +int main(void) { + AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_VVC); + return 0; +} +EOF + +if docc $ffmpeg_cflags $ffmpeg_lflags ; then + ffmpeg_vvc="yes" +fi + +#look for FREENECT support +freenect_flags="" +freenect_ld="-lfreenect" +has_freenect="no" +if test "$pkg_config" != "no"; then + if $pkg_config --exists libfreenect ; then + freenect_flags=`$pkg_config --cflags libfreenect` + freenect_libs=`$pkg_config --libs libfreenect` + has_freenect="system" + freenect_flags="-DFREENECT_FLAT_HEADERS $freenect_flags" + fi +fi + +if test "$has_freenect" = "no"; then + +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -lfreenect ; then + has_freenect="system" +fi +if test "$cross_prefix" = "" ; then + if test "$has_freenect" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lfreenect 2>>$logs ; then + has_freenect="system" + freenect_flags=-I$alt_macosx_dir/include + freenect_ld=-L$alt_macosx_dir/lib -lfreenect + fi + fi + fi +fi +if test "$has_freenect" = "no" ; then + if docc -I$local_inc/freenect -L$local_lib -lfreenect ; then + has_freenect="local" + freenect_flags=-I$local_inc/freenect + freenect_ld=-L$local_lib -lfreenect + fi +fi + +fi + + +#look for vorbis support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -lvorbis ; then + has_vorbis="system" +elif test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lvorbis 2>>$logs ; then + has_vorbis="system" + fi +elif docc -I$local_inc -L$local_lib -lvorbis -lm ; then + has_vorbis="local" +fi + + + +#look for theora support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -ltheora ; then + has_theora="system" +elif test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -ltheora -logg 2>>$logs ; then + has_theora="system" + fi +elif docc -I$local_inc -L$local_lib -ltheora -logg -lm ; then + has_theora="local" +fi + + + +#look for OGG support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -logg ; then + has_ogg="system" +elif test "$alt_macosx_dir" != "" ; then + if docc -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -logg 2>>$logs ; then + has_ogg="system" + fi +elif docc -I$local_inc -L$local_lib -logg -lm ; then + has_ogg="local" +else + has_vorbis=no + has_theora=no +fi + + +#look for VideoToolBox support + +if test "$darwin" = "yes" ; then +vtb_ldflags="-framework CoreFoundation -framework CoreVideo -framework CoreMedia -framework VideoToolBox" + cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS $vtb_ldflags ; then + has_vtb="yes" +fi + +fi + + + +#look for OSS support +if test "$darwin" = "yes" ; then + + cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + + if docc -DLIBOSS_INTERNAL -I$alt_macosx_dir/include/ -I$alt_macosx_dir/include/liboss -L$alt_macosx_dir/lib -loss $LDFLAGS 2>>$logs ; then + has_oss_audio="yes" + OSS_CFLAGS="-DLIBOSS_INTERNAL -I$alt_macosx_dir/include/ -I$alt_macosx_dir/include/liboss" + OSS_LDFLAGS="-L$alt_macosx_dir/lib -loss" + fi + +else + + cat > $TMPC << EOF +#include +#include +#include +#include +int main( void ) { return 0; } +EOF + + if docc ; then + has_oss_audio="yes" + else + cat > $TMPC << EOF +#include +#include +#include +#include +int main( void ) { return 0; } +EOF + + if docc $LDFLAGS ; then + has_oss_audio="yes" + fi + fi + +fi + +#look for IPv6 +cat > $TMPC << EOF +#include +#include +#include +#include +#include +int main( void ) { +struct sockaddr_storage saddr; +struct ipv6_mreq mreq6; +getaddrinfo(0,0,0,0); +getnameinfo(0,0,0,0,0,0,0); +memset(&saddr, 0, sizeof(saddr)); +memset(&mreq6, 0, sizeof(mreq6)); +IN6_IS_ADDR_MULTICAST( (struct in6_addr *) 0); +return 0; +} +EOF + +if docc $LDFLAGS $extralibs ; then + has_ipv6="yes" +fi + + + +#look for DVB4linux +cat > $TMPC << EOF +#include +#include +int main( void ) { +} +EOF + +if docc $LDFLAGS ; then + has_dvb4linux="yes" +fi + + +#look for alsa +cat > $TMPC << EOF +#include +int main( void ) { +return 0; +} +EOF + +if docc $LDFLAGS ; then + has_alsa="yes" +fi + + + +#look for pulseaudio +cat > $TMPC << EOF +#include +int main( void ) { +return 0; +} +EOF + +if docc $LDFLAGS ; then + has_pulseaudio="yes" +fi + + + +#look for jack +cat > $TMPC << EOF +#include +int main( void ) { +return 0; +} +EOF +if docc $LDFLAGS ; then + has_jack="yes" +fi + + + +#look for directfb support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF +directfb_inc="/usr/include/directfb" +directfb_lib="-ldirectfb -lfusion -ldirect" +if docc -I$directfb_inc -L$directfb_lib $LDFLAGS ; then + has_directfb="yes" +fi + + + +#look for X11 shared memory support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc -I$X11_PATH/include -L$X11_PATH/lib $LDFLAGS ; then + has_x11="yes" + + #look for X11 shared memory support + cat > $TMPC << EOF +#include +#include +#include +#include +int main( void ) { return 0; } +EOF + + if docc -I$X11_PATH/include -L$X11_PATH/lib $LDFLAGS ; then + has_x11_shm="yes" + fi + + #look for XVideo support + cat > $TMPC << EOF +#include +#include +#include +int main( void ) { return 0; } +EOF + + if docc -I$X11_PATH/include -L$X11_PATH/lib $LDFLAGS ; then + has_x11_xv="yes" + fi + +fi + + +#look for hid support +cat > $TMPC << EOF +#include +int main( void ) { hid_init(); hid_exit(); return 0; } +EOF + +if docc -lhidapi-hidraw $LDFLAGS ; then + hid_lib="-lhidapi-hidraw" + has_hid="yes" +fi + +#look for sys/un.h support +cat > $TMPC << EOF +#include +int main( void ) { struct sockaddr_un serv_add; return 0; } +EOF + +if docc $LDFLAGS ; then + has_sock_un="yes" +fi + + +#look for lzma support +cat > $TMPC << EOF +#include +int main( void ) { lzma_options_lzma opt_lzma2; lzma_lzma_preset(&opt_lzma2, 9); return 0; } +EOF + +if docc $CFLAGS_DIR -llzma $LDFLAGS ; then + has_lzma="yes" +fi + +#look for strlcpy support +cat > $TMPC << EOF +#include +int main( void ) { + char dest[1]; + strlcpy(dest, "1", 1); + return 0; +} +EOF + +if docc $LDFLAGS ; then + has_strlcpy="yes" +fi + + +#look for nghttp2 support + +if test "$cross_prefix" = "" -a "$pkg_config" != "no"; then + if $pkg_config --exists libnghttp2 ; then + has_nghttp2="system" + nghttp2_cflags=`$pkg_config --cflags libnghttp2` + nghttp2_ldflags=`$pkg_config --libs libnghttp2` + fi +fi + +if test "$has_nghttp2" = "no" ; then +cat > $TMPC << EOF +#include +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS -lnghttp2 ; then + has_nghttp2="system" + nghttp2_ldflags="-lnghttp2" +fi +if test "$cross_prefix" = "" ; then + if test "$has_nghttp2" = "no" ; then + if test "$alt_macosx_dir" != "" ; then + if cc -o $TMPO $TMPC -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lnghttp2 2>>$logs ; then + has_nghttp2="system" + nghttp2_ldflags="-lnghttp2" + fi + fi + fi +fi + +if test "$has_nghttp2" = "no" ; then + if docc -I$local_inc -L$local_lib -lnghttp2 $LDFLAGS ; then + has_nghttp2="local" + nghttp2_ldflags="-lnghttp2" + fi +fi + +fi + + +#overwrite detection with manual settings +for opt do + case "$opt" in + --static-modules) static_modules="yes" + ;; + --sdl-cfg=*) sdl_path=`echo $opt | cut -d '=' -f 2`; sdl_local="yes" + ;; + --enable-sdl-static=*) sdl_static="yes" + ;; + --enable-jack) has_jack="yes" + ;; + --X11-path=*) X11_PATH=`echo $opt | cut -d '=' -f 2` + ;; + --dxsdk-path=*) dxsdk_path=`echo $opt | cut -d '=' -f 2` + ;; + --xulsdk-path=*) xulsdk_path=`echo $opt | cut -d '=' -f 2` + ;; + --mozdir=*) moz_path=`echo $opt | cut -d '=' -f 2` + ;; + --enable-amr-nb-fixed) has_amr_nb_fixed="yes" + ;; + --disable-pulseaudio) has_pulseaudio="no" + ;; + --enable-amr-nb) has_amr_nb="yes" + ;; + --enable-amr-wb) has_amr_wb="yes" + ;; + --enable-amr) has_amr_wb="yes"; has_amr_nb="yes" + ;; + --disable-oggvorbis) has_oggvorbis="no" + ;; + --disable-jack) has_jack="no" + ;; + --disable-alsa) has_alsa="no" + ;; + --enable-gprof) gprof_build="yes"; + ;; + --static-build) static_build="yes"; + ;; + --enable-static-bin) + echo "$opt deprecated, use --static-build instead" + static_build="yes"; + ;; + --disable-ipv6) has_ipv6="no" + ;; + --disable-platinum) has_platinum="no" + ;; + --disable-oss-audio) has_oss_audio="no" + ;; + --disable-x11) has_x11="no" + ;; + --disable-x11-shm) has_x11_shm="no" + ;; + --disable-x11-xv) has_x11_xv="no" + ;; + --enable-fixed-point) use_fixed_point="yes" + ;; + --strip) INSTFLAGS="-s $INSTFLAGS" + ;; + --enable-mem-track) use_memory_tracking="yes" + ;; + --enable-tinygl) enable_tinygl="yes" + ;; + --disable-ssl) has_ssl="no" + ;; + --disable-lzma) has_lzma="no" + ;; + --enable-depth) enable_depth_compositor="yes" + ;; + --enable-qjs-stack) enable_qjs_stack_check="yes" + ;; + --static-bin) static_bin="yes" + ;; + --static-mp4box) + echo "$opt deprecated, use --static-bin instead" + static_bin="yes" + ;; + --use-faad=*) has_faad=${opt#--use-faad=} + ;; + --use-js=*) has_js=${opt#--use-js=} + ;; + --use-ft=*) has_ft=${opt#--use-ft=} + ;; + --use-mad=*) has_mad=${opt#--use-mad=} + ;; + --use-a52=*) has_a52=${opt#--use-a52=} + ;; + --use-xvid=*) has_xvid=${opt#--use-xvid=} + ;; + --use-jpeg=*) has_jpeg=${opt#--use-jpeg=} + ;; + --use-ffmpeg=*) has_ffmpeg=${opt#--use-ffmpeg=} + ;; + --use-freenect=*) has_freenect=${opt#--use-freenect=} + ;; + --use-png=*) tmp_has_png=${opt#--use-png=} + if test "$tmp_has_png" = "system" ; then + if test "$has_png" != "system" ; then + if test "$cross_prefix" != "" ; then + echo + echo "WARNING: PNG has been forced to system, but we are cross-compiling, it will have to be on target" + echo + else + echo + echo "WARNING!! : PNG has been forced to system even though it hasn't been found in this host" + echo + fi + fi + fi + has_png=$tmp_has_png + ;; + --use-zlib=*) tmp_has_zlib=${opt#--use-zlib=} + if test "$tmp_has_zlib" = "system" ; then + if test "$has_zlib" != "system" ; then + if test "$cross_prefix" != "" ; then + echo + echo "WARNING: ZLIB has been forced to system, but we are cross-compiling, it will have to be on target" + echo + else + echo + echo "WARNING!! : ZLIB has been forced to system even though it hasn't been found in this host" + echo + fi + fi + has_zlib=$tmp_has_zlib + elif test "$tmp_has_zlib" = "no" ; then + echo + echo "WARNING!! : you have forced not to use ZLIB. This will disable some core functionalities of GPAC." + echo + has_zlib="force-no" + elif test "$tmp_has_zlib" = "local" ; then + echo + echo "WARNING!! : zlib is forced to be local without check - make sure it is avaliable in $local_lib" + echo + has_zlib="local" + fi + ;; + --use-ogg=*) has_ogg=${opt#--use-ogg=} + ;; + --use-vorbis=*) has_vorbis=${opt#--use-vorbis=} + ;; + --use-theora=*) has_theora=${opt#--use-theora=} + ;; + --use-openjpeg=*) has_openjpeg=${opt#--use-openjpeg=} + ;; + --enable-joystick) enable_joystick="yes" + ;; + --enable-pulseaudio) has_pulseaudio="yes" + ;; + --disable-all) has_pulseaudio="no"; has_alsa="no"; disable_core_tools="yes"; disable_3d="yes"; disable_svg="yes"; disable_vrml="yes"; disable_od="yes"; disable_bifs="yes"; disable_bifs_enc="yes"; disable_laser="yes"; disable_seng="yes"; disable_qtvr="yes"; disable_avi="yes"; disable_ogg="yes"; disable_m2ps="yes"; disable_m2ts="yes"; disable_m2ts_mux="yes"; disable_parsers="yes"; disable_import="yes"; disable_export="yes"; disable_swf="yes"; disable_scene_stats="yes"; disable_scene_dump="yes"; disable_scene_encode="yes"; disable_loader_isoff="yes"; disable_log="yes"; disable_od_dump="yes"; disable_od_parse="yes"; disable_isom_dump="yes"; disable_crypto="yes"; disable_isoff="yes"; disable_isoff_write="yes"; disable_isoff_hint="yes"; disable_isoff_frag="yes"; disable_streaming="yes"; disable_x3d="yes"; disable_loader_bt="yes"; disable_loader_xmt="yes"; has_dvb4linux="no"; disable_player="yes"; disable_vobsub="yes"; disable_scenegraph="yes"; disable_dvbx="yes"; disable_ttxt="yes"; disable_vtt="yes"; disable_ttml="yes"; disable_saf="yes"; disable_smgr="yes"; disable_mpd="yes"; disable_dash="yes"; disable_isoff_hds="yes"; disable_hevc="yes" ; disable_nvdec="yes" ; disable_route="yes" ; enable_qjs="no" + ;; + --isomedia-only) has_pulseaudio="no"; has_alsa="no"; disable_core_tools="yes"; disable_3d="yes"; disable_svg="yes"; disable_vrml="yes"; disable_od="yes"; disable_bifs="yes"; disable_bifs_enc="yes"; disable_laser="yes"; disable_seng="yes"; disable_qtvr="yes"; disable_avi="yes"; disable_ogg="yes"; disable_m2ps="yes"; disable_m2ts="yes"; disable_m2ts_mux="yes"; disable_parsers="yes"; disable_import="yes"; disable_export="yes"; disable_swf="yes"; disable_scene_stats="yes"; disable_scene_dump="yes"; disable_scene_encode="yes"; disable_loader_isoff="yes"; disable_od_dump="yes"; disable_od_parse="yes"; disable_isom_dump="yes"; disable_crypto="yes"; disable_streaming="yes"; disable_x3d="yes"; disable_loader_bt="yes"; disable_loader_xmt="yes"; has_dvb4linux="no"; disable_player="yes"; disable_vobsub="yes"; disable_scenegraph="yes"; disable_dvbx="yes"; disable_ttxt="yes"; disable_saf="yes"; disable_smgr="yes"; disable_mpd="yes"; disable_dash="yes"; disable_hevc="no"; disable_isoff="no"; disable_isoff_hds="no"; disable_isoff_write="no"; disable_isoff_hint="no"; disable_isoff_frag="no" ; disable_route="yes" ; disable_nvdec="yes" ; enable_qjs="no" + ;; + --disable-3d) disable_3d="yes" + ;; + --enable-3d) disable_3d="no" + ;; + --disable-svg) disable_svg="yes" + ;; + --enable-svg) disable_svg="no" + ;; + --disable-vrml) disable_vrml="yes" + ;; + --enable-vrml) disable_vrml="no" + ;; + --disable-x3d) disable_x3d="yes" + ;; + --enable-x3d) disable_x3d="no" + ;; + --disable-odf) disable_od="yes" + ;; + --enable-odf) disable_od="no" + ;; + --disable-bifs) disable_bifs="yes" + ;; + --enable-bifs) disable_bifs="no" + ;; + --disable-bifs-enc) disable_bifs_enc="yes" + ;; + --enable-bifs-enc) disable_bifs_enc="no" + ;; + --disable-laser) disable_laser="yes" + ;; + --enable-laser) disable_laser="no" + ;; + --disable-seng) disable_seng="yes" + ;; + --enable-seng) disable_seng="no" + ;; + --disable-qtvr) disable_qtvr="yes" + ;; + --enable-qtvr) disable_qtvr="no" + ;; + --disable-avi) disable_avi="yes" + ;; + --enable-avi) disable_avi="no" + ;; + --disable-ogg) disable_ogg="yes" + ;; + --enable-ogg) disable_ogg="no" + ;; + --disable-m2ps) disable_m2ps="yes" + ;; + --enable-m2ps) disable_m2ps="no" + ;; + --disable-m2ts) disable_m2ts="yes" + ;; + --enable-m2ts) disable_m2ts="no" + ;; + --disable-m2ts-mux) disable_m2ts_mux="yes" + ;; + --enable-m2ts-mux) disable_m2ts_mux="no" + ;; + --disable-dvb4linux) has_dvb4linux="no" + ;; + --disable-parsers) disable_parsers="yes" + ;; + --enable-parsers) disable_parsers="no" + ;; + --disable-import) disable_import="yes" + ;; + --enable-import) disable_import="no" + ;; + --disable-export) disable_export="yes" + ;; + --enable-export) disable_export="no" + ;; + --disable-swf) disable_swf="yes" + ;; + --enable-swf) disable_swf="no" + ;; + --disable-scene-stats) disable_scene_stats="yes" + ;; + --enable-scene-stats) disable_scene_stats="no" + ;; + --disable-scene-dump) disable_scene_dump="yes" + ;; + --enable-scene-dump) disable_scene_dump="no" + ;; + --disable-scene-encode) disable_scene_encode="yes" + ;; + --enable-scene-encode) disable_scene_encode="no" + ;; + --disable-loader-isoff) disable_loader_isoff="yes" + ;; + --enable-loader-isoff) disable_loader_isoff="no" + ;; + --disable-loader-bt) disable_loader_bt="yes" + ;; + --enable-loader-bt) disable_loader_bt="no" + ;; + --disable-loader-xmt) disable_loader_xmt="yes" + ;; + --enable-loader-xmt) disable_loader_xmt="no" + ;; + --disable-od-dump) disable_od_dump="yes" + ;; + --enable-od-dump) disable_od_dump="no" + ;; + --disable-od-parse) disable_od_parse="yes" + ;; + --enable-od-parse) disable_od_parse="no" + ;; + --disable-isom-dump) disable_isom_dump="yes" + ;; + --enable-isom-dump) disable_isom_dump="no" + ;; + --disable-crypto) disable_crypto="yes" + ;; + --enable-crypto) disable_crypto="no" + ;; + --disable-isoff) disable_isoff="yes" + ;; + --enable-isoff) disable_isoff="no" + ;; + --disable-isoff-write) disable_isoff_write="yes" + ;; + --enable-isoff-write) disable_isoff_write="no" + ;; + --disable-isoff-hint) disable_isoff_hint="yes" + ;; + --enable-isoff-hint) disable_isoff_hint="no" + ;; + --disable-isoff-frag) disable_isoff_frag="yes" + ;; + --enable-isoff-frag) disable_isoff_frag="no" + ;; + --disable-isoff-hds) disable_isoff_hds="yes" + ;; + --enable-isoff-hds) disable_isoff_hds="no" + ;; + --disable-streaming) disable_streaming="yes" + ;; + --enable-streaming) disable_streaming="no" + ;; + --disable-player) disable_player="yes" + ;; + --enable-player) disable_player="no" + ;; + --disable-scenegraph) disable_scenegraph="yes" + ;; + --enable-scenegraph) disable_scenegraph="no" + ;; + --disable-dvbx) disable_dvbx="yes" + ;; + --enable-dvbx) disable_dvbx="no" + ;; + --disable-vobsub) disable_vobsub="yes" + ;; + --enable-vobsub) disable_vobsub="no" + ;; + --disable-ttxt) disable_ttxt="yes" + ;; + --enable-ttxt) disable_ttxt="no" + ;; + --disable-ttml) disable_ttml="yes" + ;; + --enable-vtt) disable_vtt="no" + ;; + --disable-vtt) disable_vtt="yes" + ;; + --enable-ttml) disable_ttml="no" + ;; + --disable-saf) disable_saf="yes" + ;; + --enable-saf) disable_saf="no" + ;; + --disable-smgr) disable_smgr="yes" + ;; + --enable-smgr) disable_smgr="no" + ;; + --disable-mpd) disable_mpd="yes" + ;; + --enable-mpd) disable_mpd="no" + ;; + --disable-dash) disable_dash="yes" + ;; + --enable-dash) disable_dash="no" + ;; + --disable-core) disable_core_tools="yes" + ;; + --enable-core) disable_core_tools="no" + ;; + --disable-hevc) disable_hevc="yes" + ;; + --enable-hevc) disable_hevc="no" + ;; + --disable-route) disable_route="yes" + ;; + --enable-route) disable_route="no" + ;; + --disable-qjs) enable_qjs="no" + ;; + --enable-qjs) enable_qjs="yes" + ;; + --disable-qjs-libc) enable_qjs_libc="no" + ;; + --enable-qjs-libc) enable_qjs_libc="yes" + ;; + --disable-log) disable_log="yes" + ;; + --enable-log) disable_log="no" + ;; + --disable-nvdec) disable_nvdec="yes" + ;; + --disable-codecs) disable_codecs="yes" + ;; + esac +done + +#always force static modules when static build is used, we cannot have one exe with libgpac_static using a module with libgpac dynamic, this would cause bugs with gf_sys_init() +if test "$static_build" = "yes"; then +static_modules="yes" +fi + + +if test "$disable_core_tools" = "no"; then + if test "$has_zlib" = "no" ; then + echo "error: zlib not found on system or in local libs" + exit 1 + fi + if test "$has_zlib" = "force-no" ; then + # can't have libpng without zlib + has_png="no" + # can't have freetype without libpng + has_ft="no" + fi + if test "$dlopen" = "no"; then + if test "$win32" = "no"; then + echo "error: dlopen not found on system" + exit 1 + fi + fi +else +GPAC_SH_FLAGS="" +has_ssl="no" +fi + +#look for OpenGL support or for TinyGL support +LINK3D="" +INCL3D="" +DarwinGL="no" + +if test "$darwin" = "yes" ; then + cat > $TMPC << EOF +#include +#include +int main( void ) { glEnable(GL_NORMALIZE); return 0; } +EOF + +else + cat > $TMPC << EOF +#include +#include +int main( void ) { glEnable(GL_NORMALIZE); return 0; } +EOF + +fi + +if test "$disable_3d" = "no" ; then + if test "$win32" = "yes" ; then + if test "$cygwin" = "yes" ; then + LINK3D="-lw32api/opengl32 -lw32api/glu32" + else + LINK3D="-lopengl32 -lglu32" + fi + elif test "$darwin" = "yes" ; then + LINK3D="-framework OpenGL -framework GLUT" + DarwinGL="yes" + else + LINK3D="-lGL -lGLU -lX11" + fi + if docc $LINK3D $LDFLAGS ; then + has_opengl="yes" + elif docc -I$X11_PATH/include -L$X11_PATH/lib $LDFLAGS ; then + has_opengl="yes" + INCL3D="-I$X11_PATH/include" + LINK3D="-L$X11_PATH/lib $LINK3D" + fi + if test "$has_opengl" = "no" ; then + LINK3D="" + fi +fi + +cat > $TMPC << EOF +#include +int main( void ) { int a ; a = TINYGL ; return 0;} +EOF + +if test "$enable_tinygl" = "yes" ;then + if docc $LDFLAGS -lTinyGL ; then + has_tinygl="yes" + has_opengl="yes" + LINK3D="-lTinyGL" + fi +fi + + +#look for joystick support +cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF +if test "$enable_joystick" = "yes" ;then + if docc $LDFLAGS ; then + has_joystick="yes" + fi +fi + + + +#look for DX support +dx_path="system" +has_mingw_directx="no" +if test "$win32" = "yes" ; then + + cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + + if docc $LDFLAGS ; then + has_mingw_directx="yes" + else + dx_path="$dxsdk_path" + if docc -I$dxsdk_path/include -L$dxsdk_path/lib -lddraw ; then + has_mingw_directx="yes" + fi + fi + +fi + +#look for SDL support +sdl_too_old=no +has_sdl=no + +sdl_config="sdl2-config" +if test "$sdl_local" = "yes"; then + sdl_config="$sdl_path/sdl2-config" + sdl_static="yes" +fi + + +#if test "$cross_prefix" = "" ; then + if type $sdl_config >/dev/null 2>>$logs; then + + cat > $TMPC << EOF +#include +#undef main +int main( void ) { return SDL_Init (SDL_INIT_VIDEO); } +EOF + + if test "$sdl_static" = "yes"; then + sdl_lib_flags=`$sdl_config --static-libs` + else + sdl_lib_flags=`$sdl_config --libs` + fi + sdl_cflags=`$sdl_config --cflags` + + if docc $sdl_cflags $LDFLAGS $sdl_lib_flags ; then + _sdlversion=`$sdl_config --version | sed 's/[^0-9]//g'` + if test "$_sdlversion" -lt 121 ; then + sdl_too_old=yes + else + has_sdl=yes + fi + fi + fi +#fi + +if test "$has_sdl" = "no" ; then + + sdl_config="sdl-config" + if test "$sdl_local" = "yes"; then + sdl_config="$sdl_path/sdl-config" + sdl_static="yes" + fi + + if test "$cross_prefix" = "" ; then + if type $sdl_config >/dev/null 2>>$logs; then + + cat > $TMPC << EOF +#include +#undef main +int main( void ) { return SDL_Init (SDL_INIT_VIDEO); } +EOF + + if test "$sdl_static" = "yes"; then + sdl_lib_flags=`$sdl_config --static-libs` + else + sdl_lib_flags=`$sdl_config --libs` + fi + sdl_cflags=`$sdl_config --cflags` + + if docc $sdl_cflags $LDFLAGS $sdl_lib_flags ; then + _sdlversion=`$sdl_config --version | sed 's/[^0-9]//g'` + if test "$_sdlversion" -lt 121 ; then + sdl_too_old=yes + else + has_sdl=yes + fi + fi + fi + fi +fi +#end SDL check + +#look at endianess +if test -z "$cross_prefix" ; then + +# big/little endian test +cat > $TMPC << EOF +#include +int main(int argc, char ** argv){ +volatile uint32_t i=0x01234567; +return (*((uint8_t*)(&i))) == 0x67; +} +EOF + + if docc $LDFLAGS 2>>$logs ; then + $TMPO && bigendian="yes" + else + echo big/little endian test failed + fi + +else + + # if cross compiling, cannot launch a program, so make a static guess + if test "$cpu" = "powerpc" -o "$cpu" = "mips" ; then + bigendian="yes" + fi + +fi + +if test "$debuginfo" = "no"; then + CFLAGS="$CFLAGS -DNDEBUG" +fi + +#man dir +if test x"$mandir" = x""; then + mandir="share/man" +fi + + +if test "$disable_codecs" = "yes"; then + has_jpeg="no" + has_png="no" + has_mad="no" + has_a52="no" + has_mad="no" + has_faad="no" + has_xvid="no" + has_opensvc="no" + has_openjpeg="no" + has_ffmpeg="no" + has_ogg="no" + has_theora="no" + has_vorbis="no" + has_openhevc="no" +fi + +if test "$static_bin" = "yes"; then + static_modules="yes" + static_build="yes" + #we cannot use openssl vanilla since it is usually built with DSO and requires dlopen + has_ssl="no" + #we cannot use ipv6 due to getaddrinfo not being present + has_ipv6="no" + +# has_js="no" +# has_ft="no" +# has_jpeg="no" +# has_png="no" +# has_mad="no" +# has_a52="no" +# has_mad="no" +# has_faad="no" +# has_xvid="no" +# has_opensvc="no" + + #we cannot use openjpeg to produce a static bin, no static lib on most distributions + has_openjpeg="no" + + #we cannot use FFMPEG to produce a static bin, no static lib on most distributions. This could be done by building a local statoc only ffmpeg +if test "$has_ffmpeg" = "system" ; then + has_ffmpeg="no" +fi + + #we cannot use ogg/theora/vorbis to produce a static bin, no static lib on most distributions, issues with lm acosf_finite, log_finite, ... + has_ogg="no" + has_theora="no" + has_vorbis="no" + #we cannot use openhevc to produce a static bin, dependencies to avcodec/avformat. We need a static ffmpeg build with openhevc support + has_openhevc="no" + has_freenect="no" + has_platinum="no" + has_lzma="no" + has_vtb="no" + +#we cannot use openGL nor audio/video output to produce a static bin + has_opengl="no" + has_sdl="no" + has_oss_audio="no" + has_alsa="no" + has_jack="no" + has_pulseaudio="no" + has_directfb="no" + has_x11="no" + has_dvb4linux="no" + has_mingw_directx="no" + +fi + +if test "$cpu" = "sh4"; then + viren_dir="`ls \"$source_path/modules\" | grep viren_out`" + if test "$viren_dir" = "viren_out"; then + enable_depth_compositor="yes" + fi +fi + +if test "$has_lzma" = "yes"; then + GPAC_SH_FLAGS="$GPAC_SH_FLAGS -llzma" + if test "$win32" = "yes"; then + extralibs="$extralibs -llzma" + fi +fi + + +if test "$disable_player" = "yes" ; then + disable_scenegraph="yes" +fi + +if test "$disable_scenegraph" = "yes" ; then + disable_3d="yes" + disable_svg="yes" + disable_vrml="yes" + disable_x3d="yes" + disable_bifs="yes" + disable_bifs_enc="yes" + disable_laser="yes" + disable_seng="yes" + disable_qtvr="yes" + disable_swf="yes" + disable_scene_stats="yes" + disable_scene_dump="yes" + disable_scene_encode="yes" + disable_loader_isoff="yes" + disable_loader_bt="yes" + disable_loader_xmt="yes" + disable_streaming="yes" + disable_player="yes" + disable_smgr="yes" + disable_nvdec="yes" + has_js="no" +fi + +if test "$disable_mpd" = "yes"; then + disable_dash="yes" +fi + +if test "$disable_od_parse" = "yes" ; then + disable_loader_isoff="yes" + disable_loader_bt="yes" + disable_loader_xmt="yes" +fi + +if test "$disable_parsers" = "yes" ; then + has_jpeg="no" + has_png="no" +fi + +#prepare for config.h writing +TMPH="${TMPDIR1}/gpac-conf-${RANDOM}-$$-${RANDOM}.h" +echo "/* Automatically generated by configure */" > $TMPH +echo "#ifndef GF_CONFIG_H" >> $TMPH +echo "#define GF_CONFIG_H" >> $TMPH +echo "#define GPAC_CONFIGURATION \"$GPAC_CONFIGURATION\"" >> $TMPH + +version="`grep '#define GPAC_VERSION ' \"$source_path/include/gpac/version.h\" | cut -d '"' -f 2`" +version_major=`grep '#define GPAC_VERSION_MAJOR ' $source_path/include/gpac/version.h | sed 's/[^0-9]*//g'` +version_minor=`grep '#define GPAC_VERSION_MINOR ' $source_path/include/gpac/version.h | sed 's/[^0-9]*//g'` +version_micro=`grep '#define GPAC_VERSION_MICRO ' $source_path/include/gpac/version.h | sed 's/[^0-9]*//g'` +soname_version="${version_major}.${version_minor}.${version_micro}" + +#check revision.h +cd $source_path +. ./check_revision.sh +cd $build_path + +echo "" +echo "** System Configuration" +echo "Install prefix: $prefix" +echo "Source path: $source_path" +echo "C compiler: $cc_naked" +echo "C++ compiler: $cxx_naked" +echo "make: $make" +echo "CPU: $cpu" +echo "Big Endian: $bigendian" +if test $cpu = "mips"; then + echo "MMI enabled: $mmi" +fi +echo "" +echo "** GPAC $version rev$revision Core Configuration **" +if test "$static_bin" = "yes" ; then + echo "Static binaries enabled" + echo "#define GPAC_STATIC_BUILD" >> $TMPH +elif test "$static_build" = "yes" ; then + echo "Static build enabled" + echo "#define GPAC_STATIC_BUILD" >> $TMPH +else + echo "Static Modules: $static_modules" +fi +echo "debug version: $debuginfo" +echo "GProf enabled: $gprof_build" +echo "Memory tracking enabled: $use_memory_tracking" +echo "Sanitizer enabled: $enable_sanitizer" +echo "Fixed-Point Version: $use_fixed_point" +echo "IPV6 Support: $has_ipv6" +echo "QuickJS Support: $enable_qjs (qjslibc $enable_qjs_libc)" + +if test "$disable_player" = "yes" ; then + echo "Player disabled" + echo "#define GPAC_DISABLE_PLAYER" >> $TMPH + disable_laser="yes" +fi + +if test "$has_st_nsec" = "yes" ; then + echo "#define GPAC_HAS_MTIM_NSEC" >> $TMPH +fi + + +if test "$disable_smgr" = "yes" ; then +disable_seng="yes" +disable_qtvr="yes" +disable_swf="yes" +disable_scene_stats="yes" +disable_scene_dump="yes" +disable_scene_encode="yes" +disable_loader_isoff="yes" +disable_loader_bt="yes" +disable_loader_xmt="yes" +disable_svg="yes" + echo "Scene Manager disabled" + echo "#define GPAC_DISABLE_SMGR" >> $TMPH +fi +if test "$disable_core_tools" = "yes" ; then + echo "Core tools disabled" + echo "#define GPAC_DISABLE_CORE_TOOLS" >> $TMPH +fi +if test "$disable_svg" = "yes" ; then + echo "SVG disabled" + echo "#define GPAC_DISABLE_SVG" >> $TMPH + disable_laser="yes" +fi +if test "$disable_vrml" = "yes" ; then + echo "MPEG-4/VRML/X3D disabled" + echo "#define GPAC_DISABLE_VRML" >> $TMPH +fi +if test "$disable_x3d" = "yes" ; then + echo "X3D disabled" + echo "#define GPAC_DISABLE_X3D" >> $TMPH +fi +if test "$disable_od" = "yes" ; then + echo "OD Full support disabled" + echo "#define GPAC_MINIMAL_ODF" >> $TMPH +fi +if test "$disable_od_parse" = "yes" ; then + echo "OD Parsing disabled" + echo "#define GPAC_DISABLE_OD_PARSE" >> $TMPH +fi +if test "$disable_bifs" = "yes" ; then + echo "BIFS coder disabled" + echo "#define GPAC_DISABLE_BIFS" >> $TMPH +fi +if test "$disable_bifs_enc" = "yes" ; then + echo "BIFS encoder disabled" + echo "#define GPAC_DISABLE_BIFS_ENC" >> $TMPH +fi +if test "$disable_laser" = "yes" ; then + echo "LASeR coder disabled" + echo "#define GPAC_DISABLE_LASER" >> $TMPH +fi +if test "$disable_saf" = "yes" ; then + echo "SAF container disabled" + echo "#define GPAC_DISABLE_SAF" >> $TMPH +fi +if test "$disable_seng" = "yes" ; then + echo "Scene encoder engine disabled" + echo "#define GPAC_DISABLE_SENG" >> $TMPH +fi +if test "$disable_qtvr" = "yes" ; then + echo "Cubic QTVR import disabled" + echo "#define GPAC_DISABLE_QTVR" >> $TMPH +fi +if test "$disable_avi" = "yes" ; then + echo "AVI disabled" + echo "#define GPAC_DISABLE_AVILIB" >> $TMPH +fi +if test "$disable_ogg" = "yes" ; then + echo "OGG disabled" + echo "#define GPAC_DISABLE_OGG" >> $TMPH +fi +if test "$disable_m2ps" = "yes" ; then + echo "MPEG-2 PS disabled" + echo "#define GPAC_DISABLE_MPEG2PS" >> $TMPH +fi +if test "$disable_m2ts" = "yes" ; then + echo "MPEG-2 TS disabled" + echo "#define GPAC_DISABLE_MPEG2TS" >> $TMPH +fi +if test "$disable_m2ts_mux" = "yes" ; then + echo "MPEG-2 TS Multiplexer disabled" + echo "#define GPAC_DISABLE_MPEG2TS_MUX" >> $TMPH +fi +if test "$disable_parsers" = "yes" ; then + echo "AV Parsers disabled" + echo "#define GPAC_DISABLE_AV_PARSERS" >> $TMPH +fi +if test "$disable_import" = "yes" ; then + echo "Media importers disabled" + echo "#define GPAC_DISABLE_MEDIA_IMPORT" >> $TMPH +fi +if test "$disable_export" = "yes" ; then + echo "Media exporters disabled" + echo "#define GPAC_DISABLE_MEDIA_EXPORT" >> $TMPH +fi +if test "$disable_swf" = "yes" ; then + echo "SWF import disabled" + echo "#define GPAC_DISABLE_SWF_IMPORT" >> $TMPH +fi +if test "$disable_scenegraph" = "yes" ; then + echo "Scene Graph disabled" + echo "#define GPAC_DISABLE_SCENEGRAPH" >> $TMPH +fi +if test "$disable_scene_stats" = "yes" ; then + echo "Scene statistics disabled" + echo "#define GPAC_DISABLE_SCENE_STATS" >> $TMPH +fi +if test "$disable_scene_dump" = "yes" ; then + echo "Scene dump disabled" + echo "#define GPAC_DISABLE_SCENE_DUMP" >> $TMPH +fi +if test "$disable_scene_encode" = "yes" ; then + echo "Scene encoder to ISO FF disabled" + echo "#define GPAC_DISABLE_SCENE_ENCODER" >> $TMPH +fi +if test "$disable_loader_isoff" = "yes" ; then + echo "Scene loader from ISO FF disabled" + echo "#define GPAC_DISABLE_LOADER_ISOM" >> $TMPH +fi +if test "$disable_loader_bt" = "yes" ; then + echo "BT/WRL Scene loader disabled" + echo "#define GPAC_DISABLE_LOADER_BT" >> $TMPH +fi +if test "$disable_loader_xmt" = "yes" ; then + echo "XMT/X3D Scene loader disabled" + echo "#define GPAC_DISABLE_LOADER_XMT" >> $TMPH +fi +if test "$disable_od_dump" = "yes" ; then + echo "OD dump disabled" + echo "#define GPAC_DISABLE_OD_DUMP" >> $TMPH +fi +if test "$disable_isom_dump" = "yes" ; then + echo "ISOM dump disabled" + echo "#define GPAC_DISABLE_ISOM_DUMP" >> $TMPH +fi +if test "$disable_crypto" = "yes" ; then + echo "Crypto tools disabled" + echo "#define GPAC_DISABLE_CRYPTO" >> $TMPH +fi +if test "$disable_isoff" = "yes" ; then + echo "ISO File Format disabled" + echo "#define GPAC_DISABLE_ISOM" >> $TMPH +fi +if test "$disable_isoff_write" = "yes" ; then + echo "ISO File Format write disabled" + echo "#define GPAC_DISABLE_ISOM_WRITE" >> $TMPH +fi +if test "$disable_isoff_hint" = "yes" ; then + echo "ISO File Format hinting disabled" + echo "#define GPAC_DISABLE_ISOM_HINTING" >> $TMPH +fi +if test "$disable_isoff_frag" = "yes" ; then + echo "ISO File Format fragments disabled" + echo "#define GPAC_DISABLE_ISOM_FRAGMENTS" >> $TMPH +fi +if test "$disable_isoff_hds" = "yes" ; then + echo "ISO File Format Adobe HDS disabled" + echo "#define GPAC_DISABLE_ISOM_ADOBE" >> $TMPH +fi + +if test "$disable_streaming" = "yes" ; then + echo "RTP/RTSP/SDP streaming disabled" + echo "#define GPAC_DISABLE_STREAMING" >> $TMPH +fi +if test "$disable_dvbx" = "no" ; then + echo "DVB MPE and DSM-CC disabled" + echo "#define GPAC_ENABLE_MPE" >> $TMPH + echo "#define GPAC_ENABLE_DSMCC" >> $TMPH +fi +if test "$disable_vobsub" = "yes" ; then + echo "VobSub disabled" + echo "#define GPAC_DISABLE_VOBSUB" >> $TMPH +fi +if test "$disable_ttxt" = "yes" ; then + echo "3GPP/Apple TimedText disabled" + echo "#define GPAC_DISABLE_TTXT" >> $TMPH +fi + +if test "$disable_ttml" = "yes" ; then + echo "TTML TimedText disabled" + echo "#define GPAC_DISABLE_TTML" >> $TMPH +fi + +if test "$disable_vtt" = "yes" ; then + echo "WebVTT disabled" + echo "#define GPAC_DISABLE_VTT" >> $TMPH +fi + +if test "$enable_depth_compositor" = "yes" ; then + echo "Depth Compositor enabled" + echo "#define GF_SR_USE_DEPTH" >> $TMPH +fi + +if test "$enable_qjs_stack_check" = "yes" ; then + echo "#define GPAC_QJS_STACK_CHECK" >> $TMPH +fi + +if test "$disable_mpd" = "yes" ; then + echo "HLS and DASH Manifest Disabled" + echo "#define GPAC_DISABLE_MPD" >> $TMPH +fi + +if test "$disable_dash" = "yes" ; then + echo "Adaptive HTTP Streaming Client disabled" + echo "#define GPAC_DISABLE_DASH_CLIENT" >> $TMPH +fi + +if test "$disable_hevc" = "yes" ; then + echo "HEVC Support disabled" + echo "#define GPAC_DISABLE_HEVC" >> $TMPH +fi + +if test "$disable_route" = "yes" ; then + echo "ROUTE Support disabled" + echo "#define GPAC_DISABLE_ROUTE" >> $TMPH +fi + +if test "$disable_crypto" = "yes" ; then + echo "CRYPTO Support disabled" + echo "#define GPAC_DISABLE_CRYPTO" >> $TMPH +fi + +if test "$disable_log" = "yes" ; then + echo "Logging disabled" + echo "#define GPAC_DISABLE_LOG" >> $TMPH +fi + +echo "" + +echo "** Detected libraries **" +echo "zlib: $has_zlib" +echo "OpenGL support: $has_opengl" +echo "TinyGL support: $has_tinygl" +echo "OpenSSL support: $has_ssl" + +if test "$win32" != "yes" ; then + echo "OSS Audio: $has_oss_audio" + echo "ALSA Audio: $has_alsa" + echo "Jack Audio: $has_jack" + echo "Pulse Audio: $has_pulseaudio" + echo "DirectFB: $has_directfb" + + if test "$has_x11" != "no" ; then + echo "X11 Shared Memory support: $has_x11_shm (path: $X11_PATH)" + echo "X11 XVideo support: $has_x11_xv" + fi +fi +echo "SDL: $has_sdl" +if test "$sdl_too_old" = "yes" ; then + echo "SDL Version too old - please upgrade for SDL support" +fi + +if test "$win32" = "yes" ; then + echo "DirectX: $has_mingw_directx" +fi +if test "$linux" = "yes" ; then + echo "DVB for Linux: $has_dvb4linux" +fi + +echo "FreeType: $has_ft" +echo "JPEG: $has_jpeg" +echo "OpenJPEG: $has_openjpeg" +echo "PNG: $has_png" +echo "MAD: $has_mad" +echo "FAAD: $has_faad" +echo "XVID: $has_xvid" +echo "FFMPEG: $has_ffmpeg" +if test "$has_ffmpeg" != "no" ; then +if test "$ffmpeg_vvc" = "yes" ; then +echo "FFMPG VVC Support: $ffmpeg_vvc" +fi +fi +echo "LZMA: $has_lzma" +echo "Xiph OGG: $has_ogg" +echo "Platinum UPnP: $has_platinum" + +if test "$has_ogg" = "no"; then + has_ogg="no" +else + echo "Xiph Vorbis: $has_vorbis" + echo "Xiph Theora: $has_theora" +fi +echo "A52 (AC3): $has_a52" +echo "OpenSVCDecoder: $has_opensvc" +echo "OpenHEVCDecoder: $has_openhevc" +echo "Freenect: $has_freenect" +echo "nghttp2: $has_nghttp2" + + +if test "$has_amr_nb_fixed" = "yes" ; then + echo "" + echo "*** AMR NB FIXED-POINT NOTICE ***" + echo "Make sure you have downloaded TS26.073 from:" + echo "http://www.3gpp.org/ftp/Specs/archive/26_series/26.073/26073-*.zip" + echo "or through gpac_extra_libs and extracted src to modules/amr_dec/amr_nb" + echo "without overwriting typedefs.h file" + echo "" +fi + +if test "$has_amr_nb" = "yes" ; then + echo "" + echo "*** AMR NB NOTICE ***" + echo "Make sure you have downloaded TS26.104 from:" + echo "http://www.3gpp.org/ftp/Specs/archive/26_series/26.104/26104-*.zip" + echo "or through gpac_extra_libs and extracted src to modules/amr_float_dec/amr_nb_ft" + echo "without overwriting typedefs.h file" + echo "" +fi + + +if test "$has_amr_wb" = "yes" ; then + echo "" + echo "*** AMR WB NOTICE ***" + echo "Make sure you have downloaded TS26.204 from:" + echo "http://www.3gpp.org/ftp/Specs/archive/26_series/26.204/26204-*.zip" + echo "or through gpac_extra_libs and extracted src to modules/amr_float_dec/amr_wb_ft" + echo "without overwriting typedefs.h file" + echo "" +fi + +echo "" + +#needs gmon for win32 gprof +if test "$gprof_build" = "yes"; then + if test "$win32" = "yes"; then + extralibs="$extralibs -lgmon" + fi +fi + +#we add no deprecate by default on osx (due to opengl ...) +if test "$darwin" = "yes" ; then + CFLAGS="$CFLAGS_DIR $CFLAGS -Wno-deprecated -Wno-deprecated-declarations" +else + CFLAGS="$CFLAGS -Wno-deprecated -Wno-deprecated-declarations -Wno-int-in-bool-context" +fi + + +if test "$enable_sanitizer" = "yes" ; then + CFLAGS="$CFLAGS -fsanitize=address,undefined -fno-sanitize-recover -g -fno-omit-frame-pointer -DASAN_ENABLED" + LDFLAGS="$LDFLAGS -fsanitize=address,undefined -ldl" +fi + + +ldir=`pwd` +CFLAGS="$CFLAGS -DGPAC_HAVE_CONFIG_H -I\"$ldir\"" +if test "$win32" = "no" ; then + CFLAGS="$CFLAGS -fvisibility=\"hidden\"" +fi +CXXFLAGS="$CXXFLAGS" + + +echo "Creating config.mak" +echo "# Automatically generated by configure - do not modify" > config.mak + +echo "GPAC_CONFIGURATION=$GPAC_CONFIGURATION" >> config.mak + +echo "prefix=$prefix" >> config.mak +echo "DESTDIR=$DESTDIR" >> config.mak +echo "moddir=gpac" >> config.mak +echo "tinygl_target_bin_dir=$target_bin_dir" >> config.mak +echo "MAKE=$make" >> config.mak + +if test "$verbose" = "yes" ; then +echo "CC=$cc_naked" >> config.mak +echo "AR=$ar" >> config.mak +echo "RANLIB=$ranlib" >> config.mak +echo "STRIP=$strip" >> config.mak +echo "WINDRES=$windres" >> config.mak +else +echo "CC=@$cc_naked" >> config.mak +echo "AR=@$ar" >> config.mak +echo "RANLIB=@$ranlib" >> config.mak +echo "STRIP=@$strip" >> config.mak +echo "WINDRES=$windres" >> config.mak +fi +echo "INSTALL=$install" >> config.mak +echo "LIBTOOL=@libtool" >> config.mak + +echo "INSTFLAGS=$instflags" >> config.mak +echo "OPTFLAGS=$CFLAGS" >> config.mak +echo "CXXFLAGS=$CXXFLAGS" >> config.mak +echo "LDFLAGS=$LDFLAGS" >> config.mak +echo "SHFLAGS=$SHFLAGS" >> config.mak + +pf="$prefix/" +lib_dir=${libdir#"$pf"} +echo "lib_dir=$lib_dir" >> config.mak + +pf="$prefix/" +man_dir=${mandir#"$pf"} +echo "man_dir=$man_dir" >> config.mak + + +echo "STATIC_MODULES=$static_modules" >> config.mak + +#for cross-compilation +if test "$cross_prefix" != "" ; then + echo "CROSS_COMPILING=yes" >> config.mak +fi + +if test "$cpu" = "x86" ; then + echo "TARGET_ARCH_X86=yes" >> config.mak +elif test "$cpu" = "armv4l" ; then + echo "TARGET_ARCH_ARMV4L=yes" >> config.mak +elif test "$cpu" = "alpha" ; then + echo "TARGET_ARCH_ALPHA=yes" >> config.mak +elif test "$cpu" = "sparc64" ; then + echo "TARGET_ARCH_SPARC64=yes" >> config.mak +elif test "$cpu" = "powerpc" ; then + echo "TARGET_ARCH_POWERPC=yes" >> config.mak +elif test "$cpu" = "mips" ; then + echo "TARGET_ARCH_MIPS=yes" >> config.mak +fi + + +if test "$bigendian" = "yes" ; then + echo "IS_BIGENDIAN=yes" >> config.mak + echo "#define GPAC_BIG_ENDIAN" >> $TMPH +fi +echo "EXTRALIBS=$extralibs" >> config.mak +echo "VERSION=$version" >>config.mak +echo "VERSION_MAJOR=$version_major" >>config.mak +echo "VERSION_SONAME=$soname_version" >>config.mak + +if test "$use_fixed_point" = "yes"; then + echo "#define GPAC_FIXED_POINT" >> $TMPH +fi + +if test "$use_memory_tracking" = "yes"; then + echo "#define GPAC_MEMORY_TRACKING" >> $TMPH + if test "$cygwin" = "yes" ; then + echo "#define GPAC_MEMORY_TRACKING_DISABLE_STACKTRACE" >> $TMPH + fi +fi + + +if test "$win32" = "yes" ; then + echo "CONFIG_WIN32=yes" >> config.mak + echo "CONFIG_OS=CONFIG_WIN32" >> config.mak + echo "#define GPAC_CONFIG_WIN32" >> $TMPH + if test "$cygwin" = "yes" ; then + echo "#define ftello64 ftell" >> $TMPH + echo "#define fseeko64 fseek" >> $TMPH + fi + if test "$mingw32" = "yes" ; then + +# -municode test +cat > $TMPC << EOF +#include +int wmain(int argc, char ** argv){ +return 0; +} +EOF + + if docc -municode ; then + echo "UNICODEFLAGS=-municode" >> config.mak + echo "HAS_WMAIN=yes" >> config.mak + else + echo "UNICODEFLAGS=" >> config.mak + if docc ; then + echo "HAS_WMAIN=yes" >> config.mak + else + echo "HAS_WMAIN=no" >> config.mak + fi + fi + fi +elif test "$linux" = "yes" ; then + echo "CONFIG_LINUX=yes" >> config.mak + echo "CONFIG_OS=CONFIG_LINUX" >> config.mak + echo "#define GPAC_CONFIG_LINUX" >> $TMPH +elif test "$freebsd" = "yes" ; then + echo "CONFIG_FREEBSD=yes" >> config.mak + echo "CONFIG_OS=CONFIG_FREEBSD" >> config.mak + echo "#define GPAC_CONFIG_FREEBSD" >> $TMPH +elif test "$darwin" = "yes" ; then + echo "CONFIG_DARWIN=yes" >> config.mak + echo "CONFIG_OS=CONFIG_DARWIN" >> config.mak + echo "#define GPAC_CONFIG_DARWIN" >> $TMPH + if test "$DarwinGL" = "yes" ; then + echo "#define CONFIG_DARWIN_GL" >> $TMPH + fi + echo "mac_apps=$Mac_Applications" >> config.mak +elif test "$sunos" = "yes" ; then + echo "CONFIG_SUNOS=yes" >> config.mak + echo "CONFIG_OS=CONFIG_SUNOS" >> config.mak + echo "#define GPAC_CONFIG_SUNOS" >> $TMPH +else + echo "CONFIG_OS=CONFIG_GEN" >> config.mak + echo "#define GPAC_CONFIG_GENERIC" >> $TMPH +fi + +if test "$win32" = "no" ; then + echo "GPAC_SH_FLAGS=$GPAC_SH_FLAGS" >> config.mak + echo "EXE_SUFFIX=" >> config.mak + echo "DYN_LIB_SUFFIX=$DYN_LIB_SUFFIX" >> config.mak +else + echo "EXE_SUFFIX=.exe" >> config.mak + echo "DYN_LIB_SUFFIX=.dll" >> config.mak +fi + + +echo "INSTFLAGS=$INSTFLAGS" >> config.mak + +echo "CONFIG_JS=$enable_qjs" >> config.mak +if test "$enable_qjs" = "yes" ; then +echo "#define GPAC_HAS_QJS" >> $TMPH +if test "$enable_qjs_libc" = "no" ; then +echo "#define GPAC_DISABLE_QJS_LIBC" >> $TMPH +fi + +fi + +if test "$has_zlib" = "no" -o "$has_zlib" = "force-no" ; then + echo "#define GPAC_DISABLE_ZLIB" >> $TMPH + echo "CONFIG_ZLIB=no" >> config.mak +else + echo "CONFIG_ZLIB=$has_zlib" >> config.mak +fi +echo "CONFIG_FT=$has_ft" >> config.mak + +echo "CONFIG_JPEG=$has_jpeg" >> config.mak +if test "$has_jpeg" != "no" ; then + echo "#define GPAC_HAS_JPEG" >> $TMPH + echo "jpeg_cflags=$jpeg_cflags" >> config.mak + echo "jpeg_lflags=$jpeg_lflags" >> config.mak +fi + +echo "CONFIG_PNG=$has_png" >> config.mak +if test "$has_png" != "no" ; then + echo "#define GPAC_HAS_PNG" >> $TMPH +fi + +echo "CONFIG_VTB=$has_vtb" >> config.mak +if test "$has_vtb" != "no" ; then + echo "#define GPAC_HAS_VTB" >> $TMPH + echo "vtb_ldflags=$vtb_ldflags" >> config.mak +fi + +echo "CONFIG_STRLCPY=$has_strlcpy" >> config.mak +if test "$has_strlcpy" != "no" ; then + echo "#define GPAC_HAS_STRLCPY" >> $TMPH +fi + +if test "$has_sock_un" != "no" ; then + echo "#define GPAC_HAS_SOCK_UN" >> $TMPH +fi + +echo "CONFIG_LZMA=$has_lzma" >> config.mak +if test "$has_lzma" = "yes"; then +echo "#define GPAC_HAS_LZMA" >> $TMPH +fi + +echo "CONFIG_JP2=$has_openjpeg" >> config.mak +if test "$has_openjpeg" != "no" ; then + echo "JP2_CFLAGS=$openjpeg_cflags" >> config.mak + echo "JP2_LDFLAGS=$openjpeg_ldflags" >> config.mak + echo "#define GPAC_HAS_JP2" >> $TMPH +fi +echo "CONFIG_FAAD=$has_faad" >> config.mak +if test "$has_faad" != "no" ; then + echo "#define GPAC_HAS_FAAD" >> $TMPH +fi +echo "CONFIG_MAD=$has_mad" >> config.mak +if test "$has_mad" != "no" ; then + echo "#define GPAC_HAS_MAD" >> $TMPH +fi +echo "CONFIG_XVID=$has_xvid" >> config.mak +if test "$has_xvid" != "no" ; then + echo "#define GPAC_HAS_XVID" >> $TMPH +fi +echo "CONFIG_OGG=$has_ogg" >> config.mak +echo "CONFIG_VORBIS=$has_vorbis" >> config.mak +if test "$has_vorbis" != "no" ; then + echo "#define GPAC_HAS_VORBIS" >> $TMPH +fi +echo "CONFIG_THEORA=$has_theora" >> config.mak +if test "$has_theora" != "no" ; then + echo "#define GPAC_HAS_THEORA" >> $TMPH +fi +echo "CONFIG_FFMPEG=$has_ffmpeg" >> config.mak +if test "$has_ffmpeg" = "no"; then +echo "DISABLE_DASHCAST=yes" >> config.mak +else + echo "ffmpeg_cflags=$ffmpeg_cflags" >> config.mak + echo "ffmpeg_lflags=$ffmpeg_lflags" >> config.mak + echo "CONFIG_LIBAV=$is_libav" >> config.mak + echo "CONFIG_LIBSWRESAMPLE=$has_libswresample" >> config.mak + echo "#define GPAC_HAS_FFMPEG" >> $TMPH + if test "$ffmpeg_vvc" = "yes"; then + echo "#define FFMPEG_ENABLE_VVC" >> $TMPH + fi +fi +echo "CONFIG_FFMPEG_OLD=$old_ffmpeg_inc" >> config.mak + +echo "CONFIG_OSS_AUDIO=$has_oss_audio" >> config.mak +echo "CONFIG_ALSA=$has_alsa" >> config.mak +echo "CONFIG_JACK=$has_jack" >> config.mak +echo "CONFIG_A52=$has_a52" >> config.mak +if test "$has_a52" != "no" ; then + echo "#define GPAC_HAS_LIBA52" >> $TMPH +fi +echo "CONFIG_PULSEAUDIO=$has_pulseaudio" >> config.mak +echo "CONFIG_FREENECT=$has_freenect" >> config.mak +if test "$has_freenect" != "no" +then + echo "FREENECT_CFLAGS=$freenect_flags" >> config.mak + echo "FREENECT_LDLAGS=$freenect_ld" >> config.mak +fi + +if test "$want_gcov" = "yes" ; then + echo "#define GPAC_ENABLE_COVERAGE" >> $TMPH +fi + +echo "CONFIG_NGHTTP2=$has_nghttp2" >> config.mak +if test "$has_nghttp2" != "no" ; then + echo "NGHTTP2_CFLAGS=$nghttp2_cflags" >> config.mak + echo "NGHTTP2_LDFLAGS=$nghttp2_ldflags" >> config.mak + echo "#define GPAC_HAS_HTTP2" >> $TMPH +fi + + +echo "DISABLE_PLAYER=$disable_player" >> config.mak +echo "DISABLE_STREAMING=$disable_streaming" >> config.mak +echo "DISABLE_SVG=$disable_svg" >> config.mak +echo "DISABLE_LASER=$disable_laser" >> config.mak +echo "DISABLE_SAF=$disable_saf" >> config.mak +echo "DISABLE_BIFS=$disable_bifs" >> config.mak +echo "DISABLE_SENG=$disable_seng" >> config.mak +echo "DISABLE_LOADER_ISOFF=$disable_loader_isoff" >> config.mak +echo "DISABLE_LOADER_BT=$disable_loader_bt" >> config.mak +echo "DISABLE_LOADER_XMT=$disable_loader_xmt" >> config.mak +echo "DISABLE_LOADER_QTVR=$disable_qtvr" >> config.mak +echo "DISABLE_LOADER_SWF=$disable_swf" >> config.mak +echo "DISABLE_SCENE_STATS=$disable_scene_stats" >> config.mak +echo "DISABLE_SCENE_DUMP=$disable_scene_dump" >> config.mak +echo "DISABLE_SCENE_ENCODE=$disable_scene_encode" >> config.mak +echo "DISABLE_SCENEGRAPH=$disable_scenegraph" >> config.mak +echo "DISABLE_CRYPTO=$disable_crypto" >> config.mak +echo "DISABLE_DVBX=$disable_dvbx" >> config.mak +echo "DISABLE_AVILIB=$disable_avi" >> config.mak +echo "DISABLE_M2PS=$disable_m2ps" >> config.mak +echo "DISABLE_OGG=$disable_ogg" >> config.mak +echo "DISABLE_ISOFF=$disable_isoff" >> config.mak +echo "DISABLE_ISOFF_HINT=$disable_isoff_hint" >> config.mak +echo "DISABLE_VOBSUB=$disable_vobsub" >> config.mak +echo "DISABLE_TTXT=$disable_ttxt" >> config.mak +echo "DISABLE_TTML=$disable_ttml" >> config.mak +echo "DISABLE_SMGR=$disable_smgr" >> config.mak +echo "DISABLE_AV_PARSERS=$disable_parsers" >> config.mak +echo "DISABLE_MEDIA_IMPORT=$disable_import" >> config.mak +echo "DISABLE_MEDIA_EXPORT=$disable_export" >> config.mak +echo "DISABLE_MPD=$disable_mpd" >> config.mak +echo "DISABLE_DASH_CLIENT=$disable_dash" >> config.mak +echo "DISABLE_CORE_TOOLS=$disable_core_tools" >> config.mak +echo "DISABLE_OD_DUMP=$disable_od_dump" >> config.mak +echo "DISABLE_OD_PARSE=$disable_od_parse" >> config.mak +echo "MINIMAL_OD=$disable_od" >> config.mak +echo "DISABLE_ISOM_ADOBE=$disable_isoff_hds" >> config.mak +echo "DISABLE_VRML=$disable_vrml" >> config.mak +echo "DISABLE_ROUTE=$disable_route" >> config.mak +echo "DISABLE_CRYPTO=$disable_crypto" >> config.mak + +if test "$disable_parsers" = "yes" ; then + disable_m2ts_mux="yes" +fi +echo "DISABLE_M2TS_MUX=$disable_m2ts_mux" >> config.mak +echo "DISABLE_M2TS=$disable_m2ts" >> config.mak + + +echo "GPAC_USE_TINYGL=$has_tinygl" >> config.mak +echo "OGL_INCLS=$INCL3D" >> config.mak + +echo "HAS_OPENGL=$has_opengl" >> config.mak + +if test "$has_opengl" = "yes" ; then + echo "OGL_LIBS=$LINK3D" >> config.mak +else + echo "#define GPAC_DISABLE_3D" >> $TMPH +fi + +if test "$has_tinygl" = "yes" ; then + echo "#define GPAC_USE_TINYGL" >> $TMPH +fi + +if test "$disable_nvdec" = "yes" ; then + echo "#define GPAC_DISABLE_NVDEC" >> $TMPH +fi + +echo "ENABLE_JOYSTICK=$has_joystick" >> config.mak + +echo "HAS_OPENSSL=$has_ssl" >> config.mak +if test "$has_ssl" = "yes" ; then + echo "SSL_LIBS=$LINK_SSL" >> config.mak + echo "#define GPAC_HAS_SSL" >> $TMPH +fi + +echo "CONFIG_SDL=$has_sdl" >> config.mak +if test "$has_sdl" = "yes" ; then + echo "SDL_CFLAGS=$sdl_cflags" >> config.mak + echo "SDL_LIBS=$sdl_lib_flags" >> config.mak +fi +if test "$has_ft" = "no" ; then + has_ft="no" +else + echo "FT_CFLAGS=$ft_cflags" >> config.mak + echo "FT_LIBS=$ft_lflags" >> config.mak +fi +echo "CONFIG_AMR_NB=$has_amr_nb_fixed" >> config.mak +echo "CONFIG_AMR_NB_FT=$has_amr_nb" >> config.mak +echo "CONFIG_AMR_WB_FT=$has_amr_wb" >> config.mak +echo "DEBUGBUILD=$debuginfo" >> config.mak +echo "GPROFBUILD=$gprof_build" >> config.mak +echo "STATIC_BINARY=$static_bin" >> config.mak +echo "STATICBUILD=$static_build" >> config.mak + +echo "CONFIG_IPV6=$has_ipv6" >> config.mak +if test "$has_ipv6" = "yes" ; then + echo "#define GPAC_HAS_IPV6" >> $TMPH +fi + +if test "$is_64" = "yes" ; then + echo "#define GPAC_64_BITS" >> $TMPH +fi + +if test "$win32" = "yes" ; then + echo "CONFIG_DIRECTX=$has_mingw_directx" >> config.mak + if test "$has_mingw_directx" = "yes" ; then + echo "DX_PATH=$dx_path" >> config.mak + fi +fi + +echo "CONFIG_PLATINUM=$has_platinum" >> config.mak + +echo "CONFIG_OPENSVC=$has_opensvc" >> config.mak +if test "$has_opensvc" = "yes" ; then + echo "OSVC_CFLAGS=$osvc_cflags" >> config.mak + echo "OSVC_LDFLAGS=$osvc_ldflags" >> config.mak + echo "#define GPAC_HAS_OPENSVC" >> $TMPH +fi + +echo "CONFIG_OPENHEVC=$has_openhevc" >> config.mak +if test "$has_openhevc" = "yes" ; then + echo "OHEVC_CFLAGS=$ohevc_cflags" >> config.mak + echo "OHEVC_LDFLAGS=$ohevc_ldflags" >> config.mak + echo "#define GPAC_HAS_OPENHEVC" >> $TMPH + + +if test "$static_modules" = "yes" ; then + echo "#define GPAC_OPENHEVC_STATIC" >> $TMPH +fi + +fi + +echo "MOZILLA_DIR=$moz_path" >> config.mak + +echo "LINUX_DVB=$has_dvb4linux" >> config.mak +if test "$has_dvb4linux" = "yes"; then + echo "#define GPAC_HAS_LINUX_DVB" >> $TMPH +fi + +if test "$has_oss_audio" != "no"; then + echo "OSS_INC_TYPE=$has_oss_audio" >> config.mak + echo "OSS_CFLAGS=$OSS_CFLAGS" >> config.mak + echo "OSS_LDFLAGS=$OSS_LDFLAGS" >> config.mak +fi + +echo "CONFIG_DIRECTFB=$has_directfb" >> config.mak +echo "DIRECTFB_INC_PATH=$directfb_inc" >> config.mak +echo "DIRECTFB_LIB=$directfb_lib" >> config.mak + +echo "CONFIG_X11=$has_x11" >> config.mak + +if test "$has_x11_shm" = "yes"; then + echo "USE_X11_SHM=$has_x11_shm" >> config.mak +fi +if test "$has_x11_xv" = "yes"; then + echo "USE_X11_XV=$has_x11_xv" >> config.mak +fi + +echo "CONFIG_HID=$has_hid" >> config.mak +echo "HID_LDFLAGS=$hid_lib" >> config.mak + + + +if test "$is_64" = "yes"; then +#not on OSX ... +if test "$darwin" = "yes"; then + echo "X11_LIB_PATH=$X11_PATH/lib" >> config.mak +else + echo "X11_LIB_PATH=$X11_PATH/lib64" >> config.mak +fi +else + echo "X11_LIB_PATH=$X11_PATH/lib" >> config.mak +fi +echo "X11_INC_PATH=$X11_PATH/include" >> config.mak + +GPAC_ENST_INC=no +GPAC_ENST=no +enst_dir="`ls \"$source_path/src/\" | grep enst`" +if test "$enst_dir" = "enst"; then + echo "GPAC Proprietary Extensions enabled" + GPAC_ENST_INC=yes + #we need libiconv for eit & co + cat > $TMPC << EOF +#include +int main( void ) { +return 0; +} +EOF + + if docc -L$local_lib -liconv ; then + GPAC_ENST=yes + echo "LIBGPAC_ENST=`cd src; ls enst/*.c | sed -e 's/\.c/.o/' | tr -s '\r\n' ' ' ; cd ..`" >> config.mak + else + echo "Couldn't find libiconv - disabling GPAC ENST extensions" + GPAC_ENST="no" + fi +fi +echo "GPAC_ENST=$GPAC_ENST" >> config.mak +echo "GPAC_ENST_INC=$GPAC_ENST" >> 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" + SRC_DIRS="src src/utils src/isomedia src/ietf src/odf src/bifs src/scenegraph src/filter_core src/filters src/terminal src/crypto src/media_tools src/scene_manager src/compositor src/laser src/evg src/quickjs src/jsmods" + + APP_DIRS="applications/gpac applications/mp4box applications/mp4client" + + 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 + + mkdir -p applications + ln -sf "$source_path/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" + done + + + cur_dir="`pwd`" + cd "$source_path/" + MOD_DIRS="`ls -d modules/*/`" + cd "$cur_dir" + + mkdir -p modules + ln -sf "$source_path/modules/Makefile" modules/Makefile + + for dir in $MOD_DIRS ; do + if [ -f "$source_path/$dir/Makefile" ]; then + mkdir -p "$dir" + ln -sf "$source_path/$dir/Makefile" "$dir/Makefile" + fi + done + if test "$has_mingw_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 + fi + + cd "$cur_dir" + + echo "SRC_LOCAL_PATH=no" >> config.mak +else + echo "SRC_LOCAL_PATH=yes" >> config.mak +fi + +echo "SRC_PATH=$source_path" >> config.mak +echo "BUILD_PATH=$build_path" >> config.mak +echo "LOCAL_INC_PATH=$local_inc" >> config.mak + + +echo "#endif" >> $TMPH + + +#do not overwrite config.h if unchanged to avoid superfluous rebuilds. +if ! cmp -s $TMPH config.h ; then + rm -f config.h + mv -f $TMPH config.h +else + echo "config.h is unchanged" +fi + +echo "Check config.log for detection failures" + +rm -f $TMPO $TMPC $TMPE $TMPS $TMPCXX $TMPH + + +if [ ! -d "./bin" ] ; then + mkdir ./bin +fi +if [ ! -d "./bin/gcc" ] ; then + mkdir ./bin/gcc +fi +if [ ! -d "./bin/gcc/temp" ] ; then + mkdir ./bin/gcc/temp +fi + + +echo '%.opic : %.c' >> config.mak +if test "$verbose" = "no" ; then +echo ' @echo " CC $<"' >> config.mak +fi +echo ' $(CC) $(CFLAGS) $(PIC_CFLAGS) -c $< -o $@' >> config.mak + +echo '%.o : %.c' >> config.mak +if test "$verbose" = "no" ; then +echo ' @echo " CC $<"' >> config.mak +fi +echo ' $(CC) $(CFLAGS) -c -o $@ $<' >> config.mak + +echo '%.o: %.cpp' >> config.mak +if test "$verbose" = "no" ; then +echo ' @echo " CC $<"' >> config.mak +fi +echo ' $(CXX) $(CFLAGS) -c -o $@ $<' >> config.mak + +echo '%.o: %.rc' >> config.mak +if test "$verbose" = "no" ; then +echo ' @echo " RC $<"' >> config.mak +fi +echo ' $(WINDRES) $< -o $@ ' >> config.mak + + +#pkg-config +generate_pkgconfig () { + echo "prefix=$prefix" + echo "exec_prefix=\${prefix}" + echo "libdir=\${exec_prefix}/$libdir" + echo "includedir=\${exec_prefix}/include" + echo "" + echo "Name: gpac" + echo "Description: GPAC Multimedia Framework" + echo "URL: http://gpac.io" + echo "Version:$version" + echo "Cflags: -I\${prefix}/include" + echo "Libs: -L\${libdir} -lgpac" +} + +generate_pkgconfig > gpac.pc + + +if test "$static_bin" = "yes"; then + if test "$darwin" = "yes" ; then + echo "\nWarning: static binaries on OSX cannot remove all dependendencies to system libraries\n" + elif test "$win32" = "yes" ; then + echo "\nWarning: static binaries on Win32 cannot remove all dependendencies to system libraries\n" + fi +fi + + + +echo "Done - type 'make help' for make info, 'make' to build" diff --git a/generate_installer.bat b/generate_installer.bat new file mode 100644 index 0000000..1f008ca --- /dev/null +++ b/generate_installer.bat @@ -0,0 +1,107 @@ +@echo off +set OLDDIR=%CD% +cd /d %~dp0 +REM ============================================ +echo *** Generating a Windows GPAC installer *** +REM ============================================ + + +:begin +IF "%1"=="win32" GOTO next +IF "%1"=="x64" GOTO next +echo You must specified target architecture : win32 or x64 +GOTO Abort + +:next +echo: +REM ============================================ +echo Check NSIS is in your PATH +REM ============================================ + + +if "%PROCESSOR_ARCHITECTURE%" == "AMD64" ( + setlocal enabledelayedexpansion + SET PRGROOT=!programfiles(x86^)! + setlocal disabledelayedexpansion +) +if "%PROCESSOR_ARCHITECTURE%" == "x86" ( + setlocal enabledelayedexpansion + SET PRGROOT=!programfiles! + setlocal disabledelayedexpansion +) + +set NSIS_EXEC="%PRGROOT%\NSIS\makensis.exe" +if not exist "%PRGROOT%\NSIS\makensis.exe" echo NSIS couldn't be found at default location %NSIS_EXEC% +if not exist "%PRGROOT%\NSIS\makensis.exe" goto Abort +echo Found NSIS at default location %NSIS_EXEC% + + +echo: +REM ============================================ +echo Retrieving version/revision information +REM ============================================ +if not exist include/gpac/revision.h echo version couldn't be found - check include/gpac/revision.h exists +if not exist include/gpac/revision.h goto Abort + +REM check if found a local commit which has not been pushed +for /f "delims=" %%a in ('git diff FETCH_HEAD') do @set diff=%%a +echo diff = %diff% +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 rev-parse --abbrev-ref HEAD') do @set BRANCH=%%a +REM remove anotated tag from VERSION +setlocal enabledelayedexpansion +call set VERSION=%%VERSION:!TAG!=%% +setlocal disabledelayedexpansion +SET VarRevisionGIT=%VERSION%-%BRANCH% +for /f "delims=" %%i in ('type include\gpac\revision.h ^| findstr /i /r "%VarRevisionGIT%"') do Set VarRevisionBuild=%%i +echo VarRevisionBuild = %VarRevisionBuild% +echo VarRevisionGIT = %VarRevisionGIT% +if !"%VarRevisionBuild%"==!"%VarRevisionGIT%" echo local revision and last build revision are not congruent - please consider rebuilding before generating an installer +if !"%VarRevisionBuild%"==!"%VarRevisionGIT%" goto Abort + +move packagers\win32_64\nsis\default.out packagers\win32_64\nsis\default.out_ +echo Name "GPAC Framework ${GPAC_VERSION} for %1 revision %VarRevisionGIT%" > packagers\win32_64\nsis\default.out +echo OutFile "gpac-${GPAC_VERSION}-rev%VarRevisionGIT%-%1.exe" >> packagers\win32_64\nsis\default.out +IF "%1"=="x64" echo !define IS_WIN64 >> packagers\win32_64\nsis\default.out + +echo: +REM ============================================ +echo Executing NSIS +REM ============================================ +call %NSIS_EXEC% packagers\win32_64\nsis\gpac_installer.nsi +IF %ERRORLEVEL% NEQ 0 GOTO Abort + + +echo: +REM ============================================ +echo Removing temporary files +REM ============================================ +move packagers\win32_64\nsis\default.out_ packagers\win32_64\nsis\default.out + + +echo: +REM ============================================ +echo Windows GPAC installer generated - goodbye! +REM ============================================ +REM LeaveBatchSuccess +set VarRevisionGIT= +set VarRevisionBuild= +cd /d %OLDDIR% +exit/b 0 + +:RevisionAbort +echo Local revision and remote revision are not congruent; you may have local commit +echo Please consider pushing your commit before generating an installer + +:Abort +echo: +echo *** ABORTING: CHECK ERROR MESSAGES ABOVE *** + +REM LeaveBatchError +set VarRevisionBuild= +cd /d %OLDDIR% +exit/b 1 diff --git a/gpac.spec b/gpac.spec new file mode 100644 index 0000000..d3e1091 --- /dev/null +++ b/gpac.spec @@ -0,0 +1,86 @@ +# $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.0 +Release: 2.0 +License: LGPL +Group: Applications/Multimedia +Source0: gpac-2.0.tar.gz +URL: http://gpac.io/ +BuildRoot: %{_tmppath}/%{name}-root +Requires: SDL +%{!?_without_freetype:Requires: freetype} +%{!?_without_faad:Requires: faad2} +%{!?_without_jpeg:Requires: libjpeg-6b} +%{!?_without_png:Requires: libpng} +%{!?_without_mad:Requires: libmad} +%{!?_without_xvid:Requires: xvidcore} +%{!?_without_ffmpeg:Requires: ffmpeg} +%{!?_without_jack:Requires: libjack} +BuildRequires: SDL-devel +%{!?_without_freetype:BuildRequires: freetype-devel} +%{!?_without_faad:BuildRequires: faad2-devel} +%{!?_without_jpeg:BuildRequires: libjpeg-devel} +%{!?_without_png:BuildRequires: libpng-devel} +%{!?_without_mad:BuildRequires: libmad-devel} +%{!?_without_xvid:BuildRequires: xvidcore-devel} +%{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} +%{!?_without_jack:BuildRequires: jack-audio-connection-kit} + +%global debug_package %{nil} + +%description + +GPAC is a framework for production, encoding, delivery and interactive playback of multimedia content. + +GPAC supports many AV codecs, multimedia containers (MP4,fMP4, TS, avi, mov, mpg, mkv ...), complex presentation formats (MPEG-4 Systems, SVG Tiny 1.2, VRML/X3D) and subtitles (SRT, WebVTT, TTXT/TX3G, TTML). + +Supported inputs and outputs are pipes, UDP/TCP/UN sockets, local files, HTTP, DASH/HLS, RTP/RTSP, MPEG-2 TS, ATSC 3.0 ROUTE sessions, desktop grabbing, camera/microphone inputs and any input format supported by FFmpeg. + +GPAC features a highly configurable media processing pipeline extensible through JavaScript, and can be embedded in Python or NodeJS applications. + +GPAC is licensed under the GNU Lesser General Public License. + + +Available rpmbuild rebuild options : +--without : freetype faad a52 jpeg png mad xvid ffmpeg jack + + +%prep +%setup -q -n gpac + +%build +%configure --enable-oss-audio %{?_without_freetype: --disable-ft} %{?_without_faad: --disable-faad} %{?_without_jpeg: --disable-jpeg} %{?_without_png: --disable-png} %{?_without_mad: --disable-mad} %{?_without_xvid: --disable-xvid} %{?_without_ffmpeg: --disable-ffmpeg} %{?_without_jack: --disable-jack} +make + +%install +rm -rf %{buildroot} +%makeinstall + +%clean +rm -rf %{buildroot} + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + + +%files +%defattr(-, root, root) +# %doc Changelog COPYING README.md +%{_bindir}/* +%{_libdir}/* +%{_includedir}/* +%{_datadir}/* + +%changelog +* Fri Sep 4 2020 Jean Le Feuvre +- GPAC 1.0 release +* Fri Jul 3 2015 Jean Le Feuvre +- Changed to README.md +* Wed Feb 13 2008 Pierre Souchay +- Added libjack +* Wed Jul 13 2005 Jean Le Feuvre +- Updated for GPAC LGPL release +* Mon Aug 09 2004 Sverker Abrahamsson +- Initial RPM release diff --git a/include/gpac/00_doxy.h b/include/gpac/00_doxy.h new file mode 100644 index 0000000..db10567 --- /dev/null +++ b/include/gpac/00_doxy.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) TELECOM ParisTech 2019 + do not include in gpac, only here to create doxygen group for doc ordering + */ + + +//define doxygen groups for their order +/*! +\defgroup utils_grp Core Tools + \defgroup setup_grp Base data types + \ingroup utils_grp + \defgroup libsys_grp Library configuration + \ingroup utils_grp + \defgroup mem_grp Memory Management + \ingroup utils_grp + \defgroup errors_grp Error codes + \ingroup utils_grp + \defgroup cst_grp Constants + \ingroup utils_grp + \defgroup log_grp Logging tools + \ingroup utils_grp + \defgroup bs_grp Bitstream IO + \ingroup utils_grp + \defgroup list_grp Generic List object + \ingroup utils_grp + \defgroup time_grp Local and Network time + \ingroup utils_grp + + \defgroup net_grp Network + \ingroup utils_grp + \defgroup thr_grp Process and Threads + \ingroup utils_grp + \defgroup math_grp Math + \ingroup utils_grp + \defgroup download_grp Downloader + \ingroup net_grp + \defgroup cache_grp DownloaderCache + \ingroup download_grp + \defgroup osfile_grp File System + \ingroup utils_grp + \defgroup bascod_grp base64 encoding + \ingroup utils_grp + \defgroup color_grp Color + \ingroup utils_grp + \defgroup cfg_grp Configuration File + \ingroup utils_grp + \defgroup cst_grp Constants + \ingroup utils_grp + \defgroup lang_grp Languages + \ingroup utils_grp + \defgroup tok_grp Tokenizer + \ingroup utils_grp + \defgroup hash_grp Hash and Compression + \ingroup utils_grp + \defgroup utf_grp Unicode and UTF + \ingroup utils_grp + \addtogroup xml_grp XML + \ingroup utils_grp + \defgroup miscsys_grp Misc tools + \ingroup utils_grp + \defgroup sysmain_grp Main tools + \ingroup utils_grp + + + +\defgroup media_grp Media Tools + +\defgroup iso_grp ISO Base Media File + +\defgroup filters_grp Filter Management + +\defgroup evg_grp Vector Graphics Rendering + +\defgroup scene_grp Scene Graph + +\defgroup mpeg4sys_grp MPEG-4 Systems + +\defgroup playback_grp Media Player + +\defgroup jsapi_grp JavaScript APIs +\brief JavaScript API available in GPAC + +Parts of the GPAC code can be modified at run-time using JavaScript. This part of the documentation describes the various APIs used in GPAC. + +For SVG and DOM scenegraph API, see https://www.w3.org/TR/SVGTiny12/svgudom.html. + +For BIFS and VRML scenegraph, see https://www.web3d.org/documents/specifications/14772/V2.0/part1/javascript.html + +SVG and BIFS APIs are automatically loaded when loading an SVG or BIFS scene with script nodes. They cannot be loaded in another way. + + +The JSFilter API is loaded through a dedicated filter, see `gpac -h jsf`. + +All other APIs shall be loaded by the parent script (scene or JSFilter) as ECMAScript modules using the `import` directive. + +GPAC uses the amazing QuickJS engine for JavaScript support: https://bellard.org/quickjs/quickjs.html. Although not JIT-enabled, it is small, fast and cross-platform which ensures the same behaviour on all devices. + +The module loader is the same as the QuickJS libc module loader (https://bellard.org/quickjs/quickjs.html#Modules), except that it checks for .so, .dll or .dylib extensions for C modules. + +This means that JS C modules as defined in QuickJS could also be used (https://bellard.org/quickjs/quickjs.html#C-Modules) +\warning support for C modules is still not fully tested + +There is currently no support for non-local modules (http, https ...). + +The default GPAC compilation includes the following modules from QuickJS: +- 'std': documentation https://bellard.org/quickjs/quickjs.html#std-module +- 'os': documentation https://bellard.org/quickjs/quickjs.html#os-module (see below) + +GPAC constants used in the API (error code, property types, specific flags for functions) are exported: +- using the same name as native code, e.g. GF_STATS_LOCAL, GF_FILTER_SAP_1, etc... +- in the global object of the javascript context + +Unless indicated otherwise, all errors are handled through exceptions. An exception object contains a code (integer) attribute and optionnally a message (string) attribute. + + +Types and interfaces are described using WebIDL, see https://heycam.github.io/webidl/, with some slight modifications. +\warning These IDL files are only intended to document the APIs, and are likely useless for other purposes. + + +Notes on QuickJS 'os' module support: +- Workers are supported on OSX, Windows, iOS and Linux; final dereference of the worker object will end the worker thread. +- On Windows, the `exec` function retuns an array rather than an int when run in asynchronous mode. The `waitpid` function will return an array where the first item is this pid object when the process is over, to keep the same behavious as under non Windows systems. +- On Windows, the `exec` function ignores `uid`, `gid` and user-specified stdin/stderr/stdout. + + +\defgroup pyapi_grp Python APIs +\brief Python API for using libgpac + +\defgroup nodejs_grp NodeJS APIs +\brief API for using libgpac in NodeJS + +*/ + diff --git a/include/gpac/Remotery.h b/include/gpac/Remotery.h new file mode 100644 index 0000000..67a4f2a --- /dev/null +++ b/include/gpac/Remotery.h @@ -0,0 +1,679 @@ + + +/* +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_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_BeginSample with its call to rmt_EndSample 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_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 diff --git a/include/gpac/ait.h b/include/gpac/ait.h new file mode 100644 index 0000000..06f8e7a --- /dev/null +++ b/include/gpac/ait.h @@ -0,0 +1,269 @@ +/* + * Copyright (c) TELECOM ParisTech 2011 + */ + +#ifndef _GF_AIT_H_ +#define _GF_AIT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Specific extensions for handling AIT in MPEG-2 TS. + */ + +#include +#include +#include +#include + + +#ifndef GPAC_DISABLE_MPEG2TS + + +#define AIT_SECTION_LENGTH_MAX 1021 +#define APPLICATION_TYPE_HTTP_APPLICATION 16 +#define DSMCC_SECTION_LENGTH_MAX 4093 + +/*! AIT descriptor tags*/ +typedef enum { + APPLICATION_DESCRIPTOR = 0x00, + APPLICATION_NAME_DESCRIPTOR = 0x01, + TRANSPORT_PROTOCOL_DESCRIPTOR = 0x02, + SIMPLE_APPLICATION_LOCATION_DESCRIPTOR = 0x15, + APPLICATION_USAGE_DESCRIPTOR = 0x16, + APPLICATION_BOUNDARY_DESCRIPTOR = 0x17, +} DESCRIPTOR_TAG; + +/*! AIT Application Control Code*/ +enum ApplicationControlCode { + AUTOSTART = 0x01, + PRESENT = 0x02, + DESTROY = 0x03, + KILL = 0x04, + PREFETCH = 0x05, + REMOTE = 0x06, + DISABLED = 0x07, + PLAYBACK_AUTOSTART = 0x08 +}; + +/*! AIT Transport Type*/ +enum TransportType { + BROADCAST = 0x01, + BROADBAND = 0x03 +}; + +/*! AIT table*/ +typedef struct +{ + u32 pid; + u32 service_id; + u8 table_id; + Bool section_syntax_indicator; + u16 section_length; + Bool test_application_flag; + u16 application_type; + u8 version_number; + Bool current_next_indicator; + u8 section_number; + u8 last_section_number; + u16 common_descriptors_length; + GF_List * common_descriptors; + u16 application_loop_length; + GF_List * application_decoded; + u32 CRC_32; + +} GF_M2TS_AIT; + +/*! AIT elementary stream / section filter*/ +typedef struct +{ + ABSTRACT_ES + GF_M2TS_SectionFilter *sec; + +} GF_M2TS_AIT_CARRY; + + +/*! AIT single application descriptor*/ +typedef struct +{ + u32 organisation_id; + u16 application_id; + u8 application_control_code; + u16 application_descriptors_loop_length; + GF_List * application_descriptors; + u8 application_descriptors_id[50]; + u8 index_app_desc_id; + +} GF_M2TS_AIT_APPLICATION_DECODE; + + +/*! AIT protocol identifier*/ +typedef enum { + FUTURE_USE = 0x00, + CAROUSEL = 0x01, + RESERVED = 0x02, + TRANSPORT_HTTP = 0x03, + DVB_USE = 0x04, + TO_REGISTER = 0x100, +} PROTOCOL_ID; + +/*! Application descriptor*/ +typedef struct +{ + u8 descriptor_tag; + u8 descriptor_length; + u8 application_profiles_length; + u16 application_profile; + u8 version_major; + u8 version_minor; + u8 version_micro; + Bool service_bound_flag; + u8 visibility; + u8 application_priority; + u8 transport_protocol_label[5]; + +} GF_M2TS_APPLICATION_DESCRIPTOR; + +/*! Application usage descriptor*/ +typedef struct +{ + u8 descriptor_tag; + u8 descriptor_length; + u8 usage_type; + +} GF_M2TS_APPLICATION_USAGE; + +/*! Application location descriptor*/ +typedef struct +{ + u8 descriptor_tag; + u8 descriptor_length; + char* initial_path_bytes; + +} GF_M2TS_SIMPLE_APPLICATION_LOCATION; + +/*! Application object carousel selector*/ +typedef struct +{ + Bool remote_connection; + u16 original_network_id; + u16 transport_stream_id; + u16 service_id; + u8 component_tag; + +} GF_M2TS_OBJECT_CAROUSEL_SELECTOR_BYTE; + +/*! Application HTTP transport descriptor*/ +typedef struct { + u8 URL_extension_length; + char* URL_extension_byte; + +} GF_M2TS_TRANSPORT_HTTP_URL_EXTENTION; + +/*! Application HTTP selector*/ +typedef struct +{ + u8 URL_base_length; + char* URL_base_byte; + u8 URL_extension_count; + GF_M2TS_TRANSPORT_HTTP_URL_EXTENTION* URL_extentions; + +} GF_M2TS_TRANSPORT_HTTP_SELECTOR_BYTE; + +/*! Transport Protocol descriptor*/ +typedef struct +{ + u8 descriptor_tag; + u8 descriptor_length; + u16 protocol_id; + u8 transport_protocol_label; + void* selector_byte; + +} GF_M2TS_TRANSPORT_PROTOCOL_DESCRIPTOR; + +/*! Application Name descriptor*/ +typedef struct +{ + u8 descriptor_tag; + u8 descriptor_length; + u32 ISO_639_language_code; + u8 application_name_length; + char* application_name_char; + +} GF_M2TS_APPLICATION_NAME_DESCRIPTOR; + +/*! Application boundary extension info */ +typedef struct +{ + u8 boundary_extension_length; + char* boundary_extension_byte; + +} GF_M2TS_APPLICATION_BOUNDARY_EXTENSION_INFO; + +/*! Application boundary descriptor*/ +typedef struct +{ + u8 descriptor_tag; + u8 descriptor_length; + u8 boundary_extension_count; + GF_M2TS_APPLICATION_BOUNDARY_EXTENSION_INFO* boundary_extension_info; + +} GF_M2TS_APPLICATION_BOUNDARY_DESCRIPTOR; + +/*! AIT implementation object*/ +typedef struct +{ + u32 application_id; + u8 application_control_code; + + u8 priority; + u16 application_profile; + + /* Transport mode - 1 Broadcast - 3 Broadband */ + Bool broadcast; + Bool broadband; + char* http_url; + char* carousel_url; + Bool url_received; + + /* Carousel */ + u32 carousel_pid; + u32 component_tag; + + + char* appli_name; + +} GF_M2TS_AIT_APPLICATION; + +/*! AIT Channel Info object*/ +typedef struct +{ + u32 service_id; + u32 version_number; + u32 ait_pid; + u32 nb_application; + GF_List *Application; + +} GF_M2TS_CHANNEL_APPLICATION_INFO; + +/*! AIT section callback used by MPEG-2 TS parser*/ +void on_ait_section(GF_M2TS_Demuxer *ts, u32 evt_type, void *par); +/*! creates a new AIT section*/ +GF_M2TS_ES *gf_ait_section_new(u32 service_id); +/*! gets the application info for the given service ID*/ +GF_M2TS_CHANNEL_APPLICATION_INFO* gf_m2ts_get_channel_application_info(GF_List* ChannelAppList, u32 ait_service_id); +/*! frees the given application info*/ +void gf_m2ts_delete_channel_application_info(GF_M2TS_CHANNEL_APPLICATION_INFO* ChannelApp); + +#endif /*GPAC_DISABLE_MPEG2TS*/ + + +#ifdef __cplusplus +} +#endif + +#endif //_GF_AIT_H_ + diff --git a/include/gpac/avparse.h b/include/gpac/avparse.h new file mode 100644 index 0000000..d44866a --- /dev/null +++ b/include/gpac/avparse.h @@ -0,0 +1,857 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Authoring 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_PARSERS_AV_H_ +#define _GF_PARSERS_AV_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Utility tools for audio and video raw media parsing. +*/ + +/*! +\addtogroup media_grp + +You will find in this module the documentation of all media tools in GPAC. +*/ + + +/*! +\addtogroup avp_grp AV Parsing +\ingroup media_grp +\brief Utility tools for audio and video raw media parsing. + +This section documents the audio and video parsing functions of the GPAC framework. +@{ +*/ + + +#include +#include + + + +/*! + Reduces input width/height to common aspect ration num/denum values +\param width width of the aspect ratio +\param height height of the aspect ratio + */ +void gf_media_reduce_aspect_ratio(u32 *width, u32 *height); + +/*! + Reduces input FPS to a more compact value (eg 25000/1000 -> 25/1) +\param timescale timescale of the aspect ratio +\param sample_dur sample duration of the aspect ratio in the given timescale + */ +void gf_media_get_reduced_frame_rate(u32 *timescale, u32 *sample_dur); + +/*! inserts emulation prevention bytes from buffer_src into buffer_dst +\param buffer_src source buffer (NAL without EPB) +\param buffer_dst destination buffer (NAL with EPB) +\param nal_size source buffer size +\return size of buffer after adding emulation prevention bytes +*/ +u32 gf_media_nalu_add_emulation_bytes(const u8 *buffer_src, u8 *buffer_dst, u32 nal_size); + +/*! gets the number of emulation prevention bytes to add to a non emulated buffer +\param buffer source buffer (NAL without EPB) +\param nal_size source buffer size +\return the number of emulation prevention bytes to add +*/ +u32 gf_media_nalu_emulation_bytes_add_count(u8 *buffer, u32 nal_size); + +/*! MPEG-1, MPEG-1, MPEG-4 part 2 decoder specific info (only serialized for MPEG-4) */ +typedef struct +{ + /*! video PL*/ + u8 VideoPL; + /*! set if stream is RAP only*/ + u8 RAP_stream; + /*! MPEG-4 part 2 video object type*/ + u8 objectType; + /*! set if object has shape coding*/ + u8 has_shape; + /*! set if object is an enhancement layer*/ + u8 enh_layer; + /*! video horizontal size*/ + u16 width; + /*! video vertical size*/ + u16 height; + /*! pixel aspect ratio numerator*/ + u8 par_num; + /*! pixel aspect ratio denominator*/ + u8 par_den; + + /*! video clock rate - frames are spaced by time_increment/clock_rate seconds*/ + u16 clock_rate; + /*! number of bits to code the time increment (internal use only)*/ + u8 NumBitsTimeIncrement; + /*! time increment between frames*/ + u32 time_increment; + /*! framerate, for MPEG 1/2*/ + Double fps; + /*! position of next object in the bitstream*/ + u32 next_object_start; + + /*! progressive video sequence */ + Bool progresive; + /*! chroma format */ + u8 chroma_fmt; +} GF_M4VDecSpecInfo; + + +/*! MPEG (1,2,4) visual object parser (DSI extraction and timing/framing)*/ +typedef struct __tag_m4v_parser GF_M4VParser; + +#ifndef GPAC_DISABLE_AV_PARSERS +/*! Creates a new MPEG-1/2/4 video parser +\param data buffer to parse +\param data_size size of buffer to parse +\param mpeg12video if set, parses as MPEG-1 or MPEG-2 +\return the created parser +*/ +GF_M4VParser *gf_m4v_parser_new(u8 *data, u64 data_size, Bool mpeg12video); +/*! Creates a new MPEG-1/2/4 video parser from a bitstream object +\param bs the bitstream object to use for parsing +\param mpeg12video if set, parses as MPEG-1 or MPEG-2 +\return the created parser +*/ +GF_M4VParser *gf_m4v_parser_bs_new(GF_BitStream *bs, Bool mpeg12video); +/*! Deletes a MPEG-1/2/4 video parser +\param m4v the mpeg video parser +*/ +void gf_m4v_parser_del(GF_M4VParser *m4v); +/*! Deletes a MPEG-1/2/4 video parser without destroying associated bitstream +\param m4v the mpeg video parser +*/ +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 +\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); +/*! resets the parser +\param m4v the mpeg video parser +\param obj_type if not 0, skip next start code and use (obj_type-1) for next obj parsing +*/ +void gf_m4v_parser_reset(GF_M4VParser *m4v, u8 obj_type); + +/*! parses a frame. The parser ALWAYS resync on the next object in the bitstream +thus you can seek the bitstream to copy the payload without re-seeking it +\param m4v the mpeg video parser +\param dsi pointer to the decoder specific info parsed +\param frame_type set to the frame type (I:0, P:1, B:2) +\param time_inc set to the time increment since last frame +\param size set to the size of the compressed frame +\param start set to the position of the first byte in the buffer/bitstream +\param is_coded set to 1 if frame is coded, 0 if skip frame, untouched if no frame found +\return error if any +*/ +GF_Err gf_m4v_parse_frame(GF_M4VParser *m4v, GF_M4VDecSpecInfo *dsi, u8 *frame_type, u32 *time_inc, u64 *size, u64 *start, Bool *is_coded); +/*! returns current object start in bitstream +\param m4v the mpeg video parser +\return the the position in the buffer/bitstream +*/ +u64 gf_m4v_get_object_start(GF_M4VParser *m4v); +/*! 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 +\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 +\return error if any +*/ +GF_Err gf_mpegv12_get_config(u8 *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); + +/*! rewrites Profile and Level indicator in MPEG-4 DSI +\param io_dsi encoded MPEG-4 decoder config +\param io_dsi_len size of encoded MPEG-4 decoder config +\param PL the new Profile/level to set +*/ +void gf_m4v_rewrite_pl(u8 **io_dsi, u32 *io_dsi_len, u8 PL); +/*! rewrites PAR code in DSI. Negative values will remove the par +\param io_dsi encoded MPEG-4 decoder config +\param io_dsi_len size of encoded MPEG-4 decoder config +\param par_n the numerator of the new aspect ratio to write +\param par_d the denominator of the new aspect ratio to write +\return error if any +*/ +GF_Err gf_m4v_rewrite_par(u8 **io_dsi, u32 *io_dsi_len, s32 par_n, s32 par_d); + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +/*! returns readable description of profile +\param video_pl the Profile/level +\return name of the profile (never NULL) +*/ +const char *gf_m4v_get_profile_name(u8 video_pl); + +#ifndef GPAC_DISABLE_AV_PARSERS +/*! returns next start code in mpeg 1/2 video buffer +\param pbuffer the video buffer +\param buflen size of the video buffer +\param optr set to the byte offset in the buffer +\param scode set to the start code value if found +\return 0 if found, -1 otherwise +*/ +s32 gf_mv12_next_start_code(u8 *pbuffer, u32 buflen, u32 *optr, u32 *scode); +/*! returns next slice start in mpeg 1/2 video buffer +\param pbuffer the video buffer +\param startoffset the offset in the buffer at which analysis shall begin +\param buflen size of the video buffer +\param slice_offset set to the byte offset of the slice start +\return 0 if found, -1 otherwise +*/ +s32 gf_mv12_next_slice_start(u8 *pbuffer, u32 startoffset, u32 buflen, u32 *slice_offset); + +#endif /* GPAC_DISABLE_AV_PARSERS*/ + +#ifndef GPAC_DISABLE_AV_PARSERS + +/*MP3 tools*/ +/*! gets the number of channels in an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the number of channels +*/ +u8 gf_mp3_num_channels(u32 hdr); +/*! gets the sampling rate of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the sampling rate +*/ +u16 gf_mp3_sampling_rate(u32 hdr); +/*! gets the window size (number of samples) in an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the window size +*/ +u16 gf_mp3_window_size(u32 hdr); +/*! gets the bitrate of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the bit rate +*/ +u32 gf_mp3_bit_rate(u32 hdr); +/*! gets the MPEG-4 object type indication of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the object type indication +*/ +u8 gf_mp3_object_type_indication(u32 hdr); +/*! gets the layer of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the layer +*/ +u8 gf_mp3_layer(u32 hdr); +/*! gets the frame size of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the size in bytes (includes the 4 bytes header size) +*/ +u16 gf_mp3_frame_size(u32 hdr); +/*! locates the next frame start in an MPEG-1/2/3 audio stream file +\param fin file to search +\return the frame header value, or 0 if not found +*/ +u32 gf_mp3_get_next_header(FILE* fin); +/*! locates the next frame start in an MPEG-1/2/3 audio buffer +\param buffer buffer to search +\param size size of buffer to search +\param pos set to the start position of the frame header in the buffer +\return the frame header value, or 0 if not found +*/ +u32 gf_mp3_get_next_header_mem(const u8 *buffer, u32 size, u32 *pos); + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +/*! gets the version size of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the version +*/ +u8 gf_mp3_version(u32 hdr); +/*! gets the version name of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the version name +*/ +const char *gf_mp3_version_name(u32 hdr); + + +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined (GPAC_DISABLE_OGG) + +/*! OGG audio codec descriptor*/ +typedef struct ogg_audio_codec_desc_t +{ + /*! name of the codec*/ + const char* codec_name; + /*! private*/ + void *parserPrivateState; + /*! number of channels*/ + int channels; + /*! samplerate*/ + int sample_rate; + + /*! process the data and returns the sample block_size (i.e. duration)*/ + GF_Err (*process)(struct ogg_audio_codec_desc_t *parserState, u8 *data, u32 data_length, void *importer, Bool *destroy_esd, u32 *track, u32 *di, u64 *duration, int *block_size); + /*! release private state*/ + void (*release)(struct ogg_audio_codec_desc_t *parserState); +} ogg_audio_codec_desc; + +/*! OGG Vorbis parser*/ +typedef struct +{ + u32 version, num_headers; + u32 min_block, max_block; + u32 max_r, avg_r, low_r; + + /*do not touch, parser private*/ + u32 modebits; + Bool mode_flag[64]; + u32 nb_init; + GF_BitStream *vbs; + + u32 sample_rate, channels; +} GF_VorbisParser; + +/*! parses vorbis header packets + initializes the parser on success, leave it to NULL otherwise +\param vp pointer to a vorbis parser to use +\param data source buffer +\param data_len size of buffer +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_vorbis_parse_header(GF_VorbisParser *vp, u8 *data, u32 data_len); + +/*! checks vorbis frame +\param vp the vorbis parser to use +\param data source buffer +\param data_len size of buffer +\return 0 if init error or not a vorbis frame, otherwise returns the number of audio samples in this frame +*/ +u32 gf_vorbis_check_frame(GF_VorbisParser *vp, u8 *data, u32 data_len); + +/*! OPUS parser*/ +typedef struct +{ + u32 version; + + u32 sample_rate, channels; + u8 OutputChannelCount; + u16 PreSkip; + u32 InputSampleRate; + u16 OutputGain; + + u8 ChannelMappingFamily, StreamCount, CoupledCount; + u8 ChannelMapping[255]; +} GF_OpusParser; + +/*! parses opus header packets - initializes the parser on success, leave it to NULL otherwise +\param op pointer to a vorbis parser to use +\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_OpusParser *op, u8 *data, u32 data_len); + +/*! checks if an opus frame is valid +\param op the vorbis parser to use +\param data source buffer +\param data_len size of buffer +\return 0 if init error or not a vorbis frame, otherwise returns the number of audio samples in this frame*/ +u32 gf_opus_check_frame(GF_OpusParser *op, u8 *data, u32 data_len); + +#endif /*!defined(GPAC_DISABLE_AV_PARSERS) && !defined (GPAC_DISABLE_OGG)*/ + +/*! reads escaped value according to usac/mpegh +\param bs bitstream object +\param nBits1 first number of bits +\param nBits2 second number of bits +\param nBits3 third number set of bits +\return value read +*/ +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 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 +*/ +s32 gf_mpegh_get_mhas_pl(u8 *ptr, u32 size, u64 *chan_layout); + +/*! reads a 32 bit sync safe integer of id3v2 from a bitstream object +\param bs the bitstream object to use - has to be positioned on the start if an id3v2 size field +\return the id3v2 size field read +*/ +u32 gf_id3_read_size(GF_BitStream *bs); + +/*! MPEG-4 audio object types*/ +enum +{ + /*! AAC main*/ + GF_M4A_AAC_MAIN = 1, + /*! AAC LC*/ + GF_M4A_AAC_LC = 2, + /*! AAC SSR*/ + GF_M4A_AAC_SSR = 3, + /*! AAC LTP*/ + GF_M4A_AAC_LTP = 4, + /*! AAC SBR*/ + GF_M4A_AAC_SBR = 5, + /*! AAC scalable*/ + GF_M4A_AAC_SCALABLE = 6, + /*! TwinVQ*/ + GF_M4A_TWINVQ = 7, + /*! CELP*/ + GF_M4A_CELP = 8, + /*! HVXC*/ + GF_M4A_HVXC = 9, + /*! TTSI*/ + GF_M4A_TTSI = 12, + /*! Main Synthetic*/ + GF_M4A_MAIN_SYNTHETIC = 13, + /*! Wavetable synthesis*/ + GF_M4A_WAVETABLE_SYNTHESIS = 14, + /*! General Midi*/ + GF_M4A_GENERAL_MIDI = 15, + /*! AudioFX synthesis*/ + GF_M4A_ALGO_SYNTH_AUDIO_FX = 16, + /*! Error Resilient AAC LC*/ + GF_M4A_ER_AAC_LC = 17, + /*! Error Resilient AAC LTP*/ + GF_M4A_ER_AAC_LTP = 19, + /*! Error Resilient AAC Scalable*/ + GF_M4A_ER_AAC_SCALABLE = 20, + /*! Error Resilient TwinVQ*/ + GF_M4A_ER_TWINVQ = 21, + /*! Error Resilient BSAC*/ + GF_M4A_ER_BSAC = 22, + /*! Error Resilient AAC LD*/ + GF_M4A_ER_AAC_LD = 23, + /*! Error Resilient CELP*/ + GF_M4A_ER_CELP = 24, + /*! Error Resilient HVXC*/ + GF_M4A_ER_HVXC = 25, + /*! Error Resilient HILN*/ + GF_M4A_ER_HILN = 26, + /*! Error Resilient Parametric*/ + GF_M4A_ER_PARAMETRIC = 27, + /*! SSC*/ + GF_M4A_SSC = 28, + /*! AAC PS*/ + GF_M4A_AAC_PS = 29, + /*! MPEG-1/2 layer 1*/ + GF_M4A_LAYER1 = 32, + /*! MPEG-1/2 layer 2*/ + GF_M4A_LAYER2 = 33, + /*! MPEG-1/2 layer 3*/ + GF_M4A_LAYER3 = 34, + /*! DST*/ + GF_M4A_DST = 35, + /*! ALS*/ + GF_M4A_ALS = 36, + /*! USAC*/ + GF_M4A_USAC = 42, +}; + +/*! AAC sample rates*/ +static const u32 GF_M4ASampleRates[] = +{ + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +/*! AAC channel configurations*/ +static const u32 GF_M4ANumChannels[] = +{ + 1, 2, 3, 4, 5, 6, 8, 0, 0, 0, 7, 8, 0, 8, 0 +}; + +#ifndef GPAC_DISABLE_AV_PARSERS + +/*! returns channel config value (as written in AAC DSI) for the given number of channels +\param nb_chan the number of channels +\return the MPEG-4 AAC channel config value +*/ +u32 gf_m4a_get_channel_cfg(u32 nb_chan); + +/*! MPEG-4 Audio decoder specific info*/ +typedef struct +{ + /*Number of channels (NOT AAC channel confgiuration), 0 if unknown in which case program_config_element must be set*/ + u32 nb_chan; + /*base audio object type*/ + u32 base_object_type; + /*base sample rate*/ + u32 base_sr; + /*index of base sample rate*/ + u32 base_sr_index; + /*set if SBR (Spectral Band Replication) is present*/ + Bool has_sbr; + /*sbr audio object type*/ + u32 sbr_object_type; + /*SBR sample rate*/ + u32 sbr_sr; + /*SBR sample index*/ + u32 sbr_sr_index; + /*set if PS (Parametric Stereo) is present*/ + Bool has_ps; + /*audio Profile level indication*/ + u8 audioPL; + /*channel configuration, only set when parsing (when writing, recomputed from nb_chan)*/ + u32 chan_cfg; + + /*set if program config element is present - members until end of struct are ignored/invalid if this is not set*/ + Bool program_config_element_present; + /*set if mono mixdown is present*/ + Bool mono_mixdown_present; + /*set if stereo mixdown is present*/ + Bool stereo_mixdown_present; + /*set if matrix mixdown is present*/ + Bool matrix_mixdown_idx_present; + /*set if pseudo surround is present*/ + Bool pseudo_surround_enable; + /*element instance*/ + u8 element_instance_tag; + /*object type*/ + u8 object_type; + /*samplerate index*/ + u8 sampling_frequency_index; + /*number of front channels*/ + u8 num_front_channel_elements; + /*number of side channels*/ + u8 num_side_channel_elements; + /*number of back channels*/ + u8 num_back_channel_elements; + /*number of LFE channels*/ + u8 num_lfe_channel_elements; + /*number of associated data channels*/ + u8 num_assoc_data_elements; + /*number of valid CC elements*/ + u8 num_valid_cc_elements; + /*id of the mono mixdown element*/ + u8 mono_mixdown_element_number; + /*id of the stereo mixdown element*/ + u8 stereo_mixdown_element_number; + /*id of the matrix mixdown element*/ + u8 matrix_mixdown_idx; + /*front channels cpe flags*/ + u8 front_element_is_cpe[15]; + /*front channels select flags*/ + u8 front_element_tag_select[15]; + /*side channels cpe flags*/ + u8 side_element_is_cpe[15]; + /*side channels select flags*/ + u8 side_element_tag_select[15]; + /*back channels cpe flags*/ + u8 back_element_is_cpe[15]; + /*back channels select flags*/ + u8 back_element_tag_select[15]; + /*LFE channels select flags*/ + u8 lfe_element_tag_select[15]; + /*associated data select flags*/ + u8 assoc_data_element_tag_select[15]; + /*CC elements indep flags*/ + u8 cc_element_is_ind_sw[15]; + /*CC elements select flags*/ + u8 valid_cc_element_tag_select[15]; + /*size of comment field*/ + u8 comment_field_bytes; + /*comment field*/ + u8 comments[255]; + //set after parsing program_config_element + u32 cpe_channels; +} GF_M4ADecSpecInfo; + +/*! parses MPEG-4 audio dsi +\param dsi the buffer containing the decoder config +\param dsi_size size of the buffer containing the decoder config +\param cfg will be filled with the parsed value +\return error code if any +*/ +GF_Err gf_m4a_get_config(u8 *dsi, u32 dsi_size, GF_M4ADecSpecInfo *cfg); +/*! gets audio profile and level for a given configuration +\param cfg the parsed MPEG-4 audio configuration +\return audio PL +*/ +u32 gf_m4a_get_profile(GF_M4ADecSpecInfo *cfg); + +/*! writes MPEG-4 audio dsi in a byte buffer - backward-compatible signaling extensions are not written +\param cfg the configuration to write +\param dsi set to the encoded buffer (to be freed by caller) +\param dsi_size set to the size of the encoded buffer +\return error code if any +*/ +GF_Err gf_m4a_write_config(GF_M4ADecSpecInfo *cfg, u8 **dsi, u32 *dsi_size); +/*! writes MPEG-4 audio dsi in a bitstream object - backward-compatible signaling extensions are not written +\param bs the bitstream object to write to +\param cfg the configuration to write +\return error code if any +*/ +GF_Err gf_m4a_write_config_bs(GF_BitStream *bs, GF_M4ADecSpecInfo *cfg); +/*! parses MPEG-4 audio dsi from bitstream +\param bs the bitstream object to use (shall start in the beginning of the dsi) +\param cfg will be filled with the parsed value +\param size_known set to GF_TRUE if the bitstream contains the complete DSI (and only it), to parse backward-compatible extensions +\return error code if any +*/ +GF_Err gf_m4a_parse_config(GF_BitStream *bs, GF_M4ADecSpecInfo *cfg, Bool size_known); + + +/*! reads program config element of MPEG-4 audio dsi +\param bs the bitstream object to use (shall start in the beginning of the dsi) +\param cfg the config to fill +\return error code if any +*/ +GF_Err gf_m4a_parse_program_config_element(GF_BitStream *bs, GF_M4ADecSpecInfo *cfg); + +/*! writes program config element of MPEG-4 audio dsi +\param bs the bitstream object to use (shall start in the beginning of the dsi) +\param cfg the config to write +\return error code if any +*/ +GF_Err gf_m4a_write_program_config_element_bs(GF_BitStream *bs, GF_M4ADecSpecInfo *cfg); + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +/*! gets the name of a given MPEG-4 audio object type +\param objectType the object type +\return the MPEG-4 audio object type name +*/ +const char *gf_m4a_object_type_name(u32 objectType); +/*! gets the name of the the given MPEG-4 audio profile +\param audio_pl the profile/level +\return the MPEG-4 audio profile name +*/ +const char *gf_m4a_get_profile_name(u8 audio_pl); + +#ifndef GPAC_DISABLE_AV_PARSERS + +//! \cond old name +typedef struct __ac3_config GF_AC3Header; +//! \endcond + +/*! parses an AC-3 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 AC3 header parsed +\param out_hdr will be filled by parser +\param full_parse if GF_TRUE, complete parsing of the header will be done +\return GF_TRUE if success +*/ +Bool gf_ac3_parser(u8 *buffer, u32 buffer_size, u32 *pos, GF_AC3Config *out_hdr, Bool full_parse); +/*! parses an AC-3 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 will be done +\return GF_TRUE if success +*/ +Bool gf_ac3_parser_bs(GF_BitStream *bs, GF_AC3Config *hdr, Bool full_parse); +/*! parses an EAC-3 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 will be done +\return GF_TRUE if success +*/ +Bool gf_eac3_parser_bs(GF_BitStream *bs, GF_AC3Config *hdr, Bool full_parse); +/*! gets the number of channels in an AC3 frame +\param acmod acmod of the associated frame header +\return number of channels +*/ +u32 gf_ac3_get_channels(u32 acmod); +/*! gets the bitrate of an AC3 frame +\param brcode brcode of the associated frame header +\return bitrate +*/ +u32 gf_ac3_get_bitrate(u32 brcode); + +/*! gets basic information from an AVC Sequence Parameter Set +\param sps SPS NAL buffer +\param sps_size size of buffer +\param sps_id set to the ID +\param width set to the width +\param height set to the height +\param par_n set to the pixel aspect ratio numerator +\param par_d set to the pixel aspect ratio denominator +\return error code if any +*/ +GF_Err gf_avc_get_sps_info(u8 *sps, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); +/*! gets basic information from an AVC Picture Parameter Set +\param pps PPS NAL buffer +\param pps_size size of buffer +\param pps_id set to the ID of the PPS +\param sps_id set to the SPS ID of the PPS +\return error code if any +*/ +GF_Err gf_avc_get_pps_info(u8 *pps, u32 pps_size, u32 *pps_id, u32 *sps_id); + +/*! gets basic information from an HEVC Sequence Parameter Set +\param sps_data SPS NAL buffer +\param sps_size size of buffer +\param sps_id set to the ID +\param width set to the width +\param height set to the height +\param par_n set to the pixel aspect ratio numerator +\param par_d set to the pixel aspect ratio denominator +\return error code if any +*/ +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); + +/*! gets basic information from a VVC Sequence Parameter Set +\param sps_data SPS NAL buffer +\param sps_size size of buffer +\param sps_id set to the ID +\param width set to the width +\param height set to the height +\param par_n set to the pixel aspect ratio numerator +\param par_d set to the pixel aspect ratio denominator +\return error code if any +*/ +GF_Err gf_vvc_get_sps_info(u8 *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +const char *gf_vvc_get_profile_name(u8 video_prof); + +/*! gets chroma format name from MPEG chroma format +\param chroma_format the chroma format to query (1: 420, 2: 422, 3: 444) +\return the name of the format +*/ +const char * gf_avc_hevc_get_chroma_format_name(u8 chroma_format); +/*! gets AVC profile name from profile indication +\param profile_idc the PL indication +\return the name of the profile +*/ +const char *gf_avc_get_profile_name(u8 profile_idc); + +/*! checks if avcc extensions are used for this profile + param profile_idc the PL indication +\return GF_TRUE if extensions must be written in avcc for the given profile +*/ +Bool gf_avcc_use_extensions(u8 profile_idc); + +/*! gets HEVC profile name from profile indication +\param profile_idc the PL indication +\return the name of the profile +*/ +const char *gf_hevc_get_profile_name(u8 profile_idc); + + +/*! parses an image from a bitstream oject. The bitstream must contain the whole image. If a thumbnail is included in the image, the indicated resolution is the one of the main image. +\param bs the source bitstream +\param codecid set to the codec ID of the image +\param width set to the width of the image +\param height set to the height of the image +\param dsi set to a buffer containing the decoder config of the image if any (in whihc case this buffer shall be freed by the caller) +\param dsi_len set to the allocated buffer size +*/ +void gf_img_parse(GF_BitStream *bs, u32 *codecid, u32 *width, u32 *height, u8 **dsi, u32 *dsi_len); + +/*! decodes a JPEG image in a preallocated buffer +\param jpg the JPEG buffer +\param jpg_size size of the JPEG buffer +\param width set to width of the image +\param height set to height of the image +\param pixel_format set to pixel format of the image +\param dst buffer to hold the decoded pixels (may be NULL) +\param dst_size size in bytes of the buffer to hold the decoded pixels (may be 0) +\param dst_nb_comp number of components in destination buffer +\return GF_BUFFER_TOO_SMALL if destination buffer is too small or error if any +*/ +GF_Err gf_img_jpeg_dec(u8 *jpg, u32 jpg_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size, u32 dst_nb_comp); + +/*! decodes a PNG image in a preallocated buffer +\param png the PNG buffer +\param png_size size of the PNG buffer +\param width set to width of the image +\param height set to height of the image +\param pixel_format set to pixel format of the image +\param dst buffer to hold the decoded pixels (may be NULL) +\param dst_size size in bytes of the buffer to hold the decoded pixels (may be 0) +\return GF_BUFFER_TOO_SMALL if destination buffer is too small or error if any +*/ +GF_Err gf_img_png_dec(u8 *png, u32 png_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size); +/*! encodes a raw image into a PNG image +\param data the pixel data +\param width the pixel width +\param height the pixel height +\param stride the pixel horizontal stride +\param pixel_format pixel format of the image +\param dst buffer to hold the decoded pixels (may be NULL) +\param dst_size set to the size in bytes of the buffer to hold the decoded pixels (may be 0) +\return GF_BUFFER_TOO_SMALL if destination buffer is too small or error if any +*/ +GF_Err gf_img_png_enc(u8 *data, u32 width, u32 height, s32 stride, u32 pixel_format, u8 *dst, u32 *dst_size); + + +/*!\brief OBU types*/ +typedef enum { + OBU_RESERVED_0 = 0, + OBU_SEQUENCE_HEADER = 1, + OBU_TEMPORAL_DELIMITER = 2, + OBU_FRAME_HEADER = 3, + OBU_TILE_GROUP = 4, + OBU_METADATA = 5, + OBU_FRAME = 6, + OBU_REDUNDANT_FRAME_HEADER = 7, + OBU_TILE_LIST = 8, + OBU_RESERVED_9 = 9, + OBU_RESERVED_10 = 10, + OBU_RESERVED_11 = 11, + OBU_RESERVED_12 = 12, + OBU_RESERVED_13 = 13, + OBU_RESERVED_14 = 14, + OBU_PADDING = 15, +} ObuType; + +/*!\brief OBU metadata types*/ +typedef enum { + OBU_METADATA_TYPE_HDR_CLL = 1, + OBU_METADATA_TYPE_HDR_MDCV = 2, + OBU_METADATA_TYPE_SCALABILITY = 3, + OBU_METADATA_TYPE_ITUT_T35 = 4, + OBU_METADATA_TYPE_TIMECODE = 5 +} ObuMetadataType; + +/*! gets the name of a given OBU type +\param obu_type the OBU type +\return the OBU name +*/ +const char *gf_av1_get_obu_name(ObuType obu_type); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_PARSERS_AV_H_*/ + diff --git a/include/gpac/base_coding.h b/include/gpac/base_coding.h new file mode 100644 index 0000000..6dd73ab --- /dev/null +++ b/include/gpac/base_coding.h @@ -0,0 +1,108 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_BASE_CODING_H_ +#define _GF_BASE_CODING_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Base 16 and 64 coding. +*/ + +/*! +\addtogroup bascod_grp +\brief Base 16 and 64 coding + +This section documents the base encoding and decoding functions of the GPAC framework. + +@{ + */ + +#include + +#ifndef GPAC_DISABLE_CORE_TOOLS + +/*! +\brief base64 encoder + +Encodes a data buffer to Base64 +\param in_buffer input data buffer +\param in_buffer_size input data buffer size +\param out_buffer output Base64 buffer location +\param out_buffer_size output Base64 buffer allocated size +\return size of the encoded Base64 buffer +\note the encoded data buffer is not NULL-terminated. + */ +u32 gf_base64_encode(const u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); +/*! +\brief base64 decoder + +Decodes a Base64 buffer to data +\param in_buffer input Base64 buffer +\param in_buffer_size input Base64 buffer size +\param out_buffer output data buffer location +\param out_buffer_size output data buffer allocated size +\return size of the decoded buffer + */ +u32 gf_base64_decode(u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); + +/*! +\brief base16 encoder + +Encodes a data buffer to Base16 +\param in_buffer input data buffer +\param in_buffer_size input data buffer size +\param out_buffer output Base16 buffer location +\param out_buffer_size output Base16 buffer allocated size +\return size of the encoded Base16 buffer +\note the encoded data buffer is not NULL-terminated. + */ +u32 gf_base16_encode(u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); + +/*! +\brief base16 decoder + +Decodes a Base16 buffer to data +\param in_buffer input Base16 buffer +\param in_buffer_size input Base16 buffer size +\param out_buffer output data buffer location +\param out_buffer_size output data buffer allocated size +\return size of the decoded buffer + */ +u32 gf_base16_decode(u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*GPAC_DISABLE_CORE_TOOLS*/ + +#endif /*_GF_BASE_CODING_H_*/ diff --git a/include/gpac/bifs.h b/include/gpac/bifs.h new file mode 100644 index 0000000..313c3a8 --- /dev/null +++ b/include/gpac/bifs.h @@ -0,0 +1,178 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / BIFS codec 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_BIFS_H_ +#define _GF_BIFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief MPEG-4 BIFS encoding and decoding. +*/ + +/*! +\addtogroup bifs_grp MPEG-4 BIFS +\ingroup mpeg4sys_grp +\brief MPEG-4 BIFS encoding and decoding + +This section documents the BIFS encoding and decoding of the GPAC framework. For scene graph documentation, check scenegraph.h +@{ + */ + + +#include +/*for BIFSConfig*/ +#include + +#ifndef GPAC_DISABLE_BIFS + +/*! BIFS decoder*/ +typedef struct __tag_bifs_dec GF_BifsDecoder; + +/*! creates a new BIFS decoder +\param scenegraph the scene graph on which the decoder operates +\param command_dec if set, the decoder will only work in memory mode (creating commands for the graph) + otherwise the decoder will always apply commands while decoding them +\return a new BIFS decoder, NULL if error +*/ +GF_BifsDecoder *gf_bifs_decoder_new(GF_SceneGraph *scenegraph, Bool command_dec); +/*! destroys a BIFS decoder +\param codec the decoder to destroy +*/ +void gf_bifs_decoder_del(GF_BifsDecoder *codec); + +/*! sets up a new BIFS stream +\param codec the BIFS decoder to use +\param ESID the ESID of the stream +\param DecoderSpecificInfo the decoder config of the stream +\param DecoderSpecificInfoLength the size of the decoder config of the stream +\param objectTypeIndication the object type indication for this stream +\return error if any +*/ +GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder *codec, u16 ESID, u8 *DecoderSpecificInfo, u32 DecoderSpecificInfoLength, u32 objectTypeIndication); + +/*! decodes a BIFS access unit and applies it to the graph (non-memory mode only) +\param codec the BIFS decoder to use +\param ESID the ESID of the stream +\param data the compressed BIFS access unit +\param data_length the size of the compressed BIFS access unit +\param ts_offset the time in seconds of the access unit since the start of the scene +\return error if any +*/ +GF_Err gf_bifs_decode_au(GF_BifsDecoder *codec, u16 ESID, const u8 *data, u32 data_length, Double ts_offset); + +/*! decodes a BIFS access unit in memory - cf scenegraph_vrml.h for commands usage +\param codec the BIFS decoder to use +\param ESID the ESID of the stream +\param data the compressed BIFS access unit +\param data_length the size of the compressed BIFS access unit +\param com_list list object to retrieve the list of decoded commands +\return error if any +*/ +GF_Err gf_bifs_decode_command_list(GF_BifsDecoder *codec, u16 ESID, u8 *data, u32 data_length, GF_List *com_list); + +/*! checks if conditionnals have been defined for the decoder +\param codec the BIFS decoder to check +\return GF_TRUE if the decoder has conditionnal nodes attached +*/ +Bool gf_bifs_decode_has_conditionnals(GF_BifsDecoder *codec); + +#ifndef GPAC_DISABLE_BIFS_ENC +/*! BIFS encoder*/ +typedef struct __tag_bifs_enc GF_BifsEncoder; + +/*! creates a new BIFS encoder +\param graph the scene graph being encoded +\return a new BIFS encoder, NULL if error +*/ +GF_BifsEncoder *gf_bifs_encoder_new(GF_SceneGraph *graph); +/*! destroys a BIFS encoder +\param codec the BIFS encoder +*/ +void gf_bifs_encoder_del(GF_BifsEncoder *codec); +/*! configures a new destination stream +\param codec the BIFS encoder to use +\param ESID the ESID of the created stream +\param cfg object describing the configuration of the stream +\param encodeNames if set, names of nodes with IDs will be encoded (as strings) +\param has_predictive set to GF_TRUE if the stream uses predictive field coding +\return error if any +*/ +GF_Err gf_bifs_encoder_new_stream(GF_BifsEncoder *codec, u16 ESID, GF_BIFSConfig *cfg, Bool encodeNames, Bool has_predictive); +/*! encodes a list of commands for the given stream in the output buffer - data is dynamically allocated for output +the scenegraph used is the one described in SceneReplace command, hence scalable streams shall be encoded in time order +\param codec the BIFS encoder to use +\param ESID the ESID of the stream owning the command list +\param command_list the list of commands to encode +\param out_data set to the allocated output buffer, to be freed by the caller +\param out_data_length set to the size of the allocated output buffer +\return error if any +*/ +GF_Err gf_bifs_encode_au(GF_BifsEncoder *codec, u16 ESID, GF_List *command_list, u8 **out_data, u32 *out_data_length); +/*! returns the encoded decoder configuration of a given stream +\param codec the BIFS encoder to use +\param ESID the ESID of the stream +\param out_data set to the allocated output buffer, to be freed by the caller +\param out_data_length set to the size of the allocated output buffer +\return error if any +*/ +GF_Err gf_bifs_encoder_get_config(GF_BifsEncoder *codec, u16 ESID, u8 **out_data, u32 *out_data_length); +/*! returns the BIFS version used by the encoder for a given stream +\param codec the BIFS encoder to use +\param ESID the ESID of the stream +\return BIFS version used +*/ +u8 gf_bifs_encoder_get_version(GF_BifsEncoder *codec, u16 ESID); + +/*! encodes current graph as a scene replacement command +\param codec the BIFS encoder to use +\param out_data set to the allocated output buffer, to be freed by the caller +\param out_data_length set to the size of the allocated output buffer +\return error if any +*/ +GF_Err gf_bifs_encoder_get_rap(GF_BifsEncoder *codec, u8 **out_data, u32 *out_data_length); +/*! sets the URL of the source content being encoded, used for opening relative path files in some nodes attributes +\param codec the BIFS encoder to use +\param src_url URL (parent directory or source file) of content being encoded +\return error if any +*/ +GF_Err gf_bifs_encoder_set_source_url(GF_BifsEncoder *codec, const char *src_url); + +#endif /*GPAC_DISABLE_BIFS_ENC*/ + +#endif /*GPAC_DISABLE_BIFS*/ + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_BIFS_H_*/ + diff --git a/include/gpac/bitstream.h b/include/gpac/bitstream.h new file mode 100644 index 0000000..ee7436b --- /dev/null +++ b/include/gpac/bitstream.h @@ -0,0 +1,655 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_BITSTREAM_H_ +#define _GF_BITSTREAM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file + \brief bitstream reading and writing. +*/ + +/*! +\addtogroup bs_grp +\brief Bitstream Reading and Writing + +This section documents the bitstream object of the GPAC framework. +\note Unless specified, all functions assume Big-Endian ordering of data in the bitstream. + +@{ + */ + +#include + + +/*! bitstream creation modes*/ +enum +{ + /*! read-only mode*/ + GF_BITSTREAM_READ = 0, + /*! read-write mode*/ + GF_BITSTREAM_WRITE, + /*! allows reallocating the buffer passed in WRITE mode*/ + GF_BITSTREAM_WRITE_DYN +}; + +/*! bitstream object*/ +typedef struct __tag_bitstream GF_BitStream; + +/*! +\brief bitstream constructor + +Constructs a bitstream from a buffer (read or write mode) +\param buffer buffer to read or write. In WRITE mode, this can be NULL to let the bitstream object dynamically allocate memory, in which case the size param is ignored. +\param size size of the buffer given. +\param mode operation mode for this bitstream: GF_BITSTREAM_READ for read, GF_BITSTREAM_WRITE for write. +\return new bitstream object +\note In write mode on an existing data buffer, data overflow is never signaled but simply ignored, it is the caller responsability to ensure it + * does not write more than possible. + */ +GF_BitStream *gf_bs_new(const u8 *buffer, u64 size, u32 mode); + +/*! +\brief bitstream reassignment + +Reassigns a bitstream in GF_BITSTREAM_READ mode to a new buffer +\param bs the bitstream to reassign +\param buffer buffer to read +\param size size of the buffer given. +\return error code if any + */ +GF_Err gf_bs_reassign_buffer(GF_BitStream *bs, const u8 *buffer, u64 size); + +/*! +\brief bitstream constructor from file handle + +Creates a bitstream from a file handle. +\param f handle of the file to use. This handle must be created with binary mode. +\param mode operation mode for this bitstream: GF_BITSTREAM_READ for read, GF_BITSTREAM_WRITE for write. +\return new bitstream object +\note - You have to open your file in the appropriated mode:\n + - GF_BITSTREAM_READ: bitstream is constructed for reading\n + - GF_BITSTREAM_WRITE: bitstream is constructed for writing\n +\note - you may use any of these modes for a file with read/write access. +\warning RESULTS ARE UNEXPECTED IF YOU TOUCH THE FILE WHILE USING THE BITSTREAM. + */ +GF_BitStream *gf_bs_from_file(FILE *f, u32 mode); +/*! +\brief bitstream destructor from file handle + +Deletes the bitstream object. If the buffer was created by the bitstream, it is deleted if still present. +\warning If the bitstream was constructed from a FILE object in write mode, the FILE object MUST be closed after destructing the bitstream +\param bs the target bitstream + */ +void gf_bs_del(GF_BitStream *bs); + +/*! +\brief bitstream constructor from callback output + +Creates a bitstream from in write mode suing output callback. +\param on_block_out callback function used to write blocks. +\param usr_data user data for callback. +\param block_size internal buffer size used while dispatching bytes. If 0, defaults to 40k. +\return new bitstream object + */ +GF_BitStream *gf_bs_new_cbk(GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size), void *usr_data, u32 block_size); + +/*! +\brief bitstream constructor from callback output and preallocated buffer + +Creates a bitstream from in write mode suing output callback. +\param on_block_out callback function used to write blocks. +\param usr_data user data for callback. +\param buffer internal buffer to use while dispatching bytes. If NULL, buffer is allocated internally using buffer_size. +\param buffer_size internal buffer size used while dispatching bytes. If 0, defaults to 40k. +\return new bitstream object + */ +GF_BitStream *gf_bs_new_cbk_buffer(GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size), void *usr_data, u8 *buffer, u32 buffer_size); + +/*! +\brief prevents block dispatch +Prevents byte dispatching in callback mode. This is used when seek operations are used. +\warning Block dispatch prevention acts in a counter mode: you must call as many time the function with prevent_dispatch = GF_FALSE as you called the function with prevent_dispatch = GF_TRUE +\param bs the target bitstream +\param prevent_dispatch activates temporary internal storage if set + */ +void gf_bs_prevent_dispatch(GF_BitStream *bs, Bool prevent_dispatch); + + +/*! +\brief integer reading + +Reads an integer coded on a number of bit. +\param bs the target bitstream +\param nBits the number of bits to read +\return the integer value read. + */ +u32 gf_bs_read_int(GF_BitStream *bs, u32 nBits); +/*! +\brief large integer reading + +Reads a large integer coded on a number of bit bigger than 32. +\param bs the target bitstream +\param nBits the number of bits to read +\return the large integer value read. + */ +u64 gf_bs_read_long_int(GF_BitStream *bs, u32 nBits); +/*! +\brief float reading + +Reads a float coded as IEEE 32 bit format. +\param bs the target bitstream +\return the float value read. + */ +Float gf_bs_read_float(GF_BitStream *bs); +/*! +\brief double reading + +Reads a double coded as IEEE 64 bit format. +\param bs the target bitstream +\return the double value read. + */ +Double gf_bs_read_double(GF_BitStream *bs); +/*! +\brief data reading + +Reads a data buffer. Emultation prevention byte removal is NOT applied here +\param bs the target bitstream +\param data the data buffer to be filled +\param nbBytes the amount of bytes to read +\return the number of bytes actually read. +\warning the data buffer passed must be large enough to hold the desired amount of bytes. + */ +u32 gf_bs_read_data(GF_BitStream *bs, u8 *data, u32 nbBytes); + +/*! +\brief align char reading + +Reads an integer coded on 8 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the char value read. + */ +u32 gf_bs_read_u8(GF_BitStream *bs); + +/*! +\brief align short reading + +Reads an integer coded on 16 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the short value read. + */ +u32 gf_bs_read_u16(GF_BitStream *bs); +/*! +\brief align 24-bit integer reading + +Reads an integer coded on 24 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the integer value read. + */ +u32 gf_bs_read_u24(GF_BitStream *bs); +/*! +\brief align integer reading + +Reads an integer coded on 32 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the integer value read. + */ +u32 gf_bs_read_u32(GF_BitStream *bs); +/*! +\brief align large integer reading + +Reads an integer coded on 64 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the large integer value read. + */ +u64 gf_bs_read_u64(GF_BitStream *bs); + +/*! +\brief little endian integer reading + +Reads an integer coded on 64 bits in little-endian order. +\param bs the target bitstream +\return the large integer value read. + */ +u64 gf_bs_read_u64_le(GF_BitStream *bs); +/*! +\brief little endian integer reading + +Reads an integer coded on 32 bits in little-endian order. +\param bs the target bitstream +\return the integer value read. + */ +u32 gf_bs_read_u32_le(GF_BitStream *bs); +/*! +\brief little endian integer reading + +Reads an integer coded on 16 bits in little-endian order. +\param bs the target bitstream +\return the integer value read. + */ +u16 gf_bs_read_u16_le(GF_BitStream *bs); + + +/*! +\brief variable length integer reading + +Reads an integer coded on a variable number of 4-bits chunks. The number of chunks is given by the number of non-0 bits at the beginning. +\param bs the target bitstream +\return the integer value read. + */ +u32 gf_bs_read_vluimsbf5(GF_BitStream *bs); + +/*! +\brief bit position + +Returns current bit position in the bitstream - only works in memory mode. +\param bs the target bitstream +\return the integer value read. + */ +u32 gf_bs_get_bit_offset(GF_BitStream *bs); + +/*! +\brief current bit position + +Returns bit position in the current byte of the bitstream - only works in memory mode. +\param bs the target bitstream +\return the integer value read. + */ +u32 gf_bs_get_bit_position(GF_BitStream *bs); + + +/*! +\brief integer writing + +Writes an integer on a given number of bits. +\param bs the target bitstream +\param value the integer to write +\param nBits number of bits used to code the integer + */ +void gf_bs_write_int(GF_BitStream *bs, s32 value, s32 nBits); +/*! +\brief large integer writing + +Writes an integer on a given number of bits greater than 32. +\param bs the target bitstream +\param value the large integer to write +\param nBits number of bits used to code the integer + */ +void gf_bs_write_long_int(GF_BitStream *bs, s64 value, s32 nBits); +/*! +\brief float writing + +Writes a float in IEEE 32 bits format. +\param bs the target bitstream +\param value the float to write + */ +void gf_bs_write_float(GF_BitStream *bs, Float value); +/*! +\brief double writing + +Writes a double in IEEE 64 bits format. +\param bs the target bitstream +\param value the double to write + */ +void gf_bs_write_double(GF_BitStream *bs, Double value); +/*! +\brief data writing + +Writes a data buffer. +\param bs the target bitstream +\param data the data to write +\param nbBytes number of data bytes to write +\return the number of written bytes + */ +u32 gf_bs_write_data(GF_BitStream *bs, const u8 *data, u32 nbBytes); + +/*! +\brief align char writing + +Writes an integer on 8 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the char value to write + */ +void gf_bs_write_u8(GF_BitStream *bs, u32 value); +/*! +\brief align short writing + +Writes an integer on 16 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the short value to write + */ +void gf_bs_write_u16(GF_BitStream *bs, u32 value); +/*! +\brief align 24-bits integer writing + +Writes an integer on 24 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the integer value to write + */ +void gf_bs_write_u24(GF_BitStream *bs, u32 value); +/*! +\brief align integer writing + +Writes an integer on 32 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the integer value to write + */ +void gf_bs_write_u32(GF_BitStream *bs, u32 value); + +/*! +\brief align large integer writing + +Writes an integer on 64 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the large integer value to write + */ +void gf_bs_write_u64(GF_BitStream *bs, u64 value); + + + +/*! +\brief little endian integer writing + +Writes an integer on 32 bits in little-endian order. +\param bs the target bitstream +\param value the integer value to write + */ +void gf_bs_write_u32_le(GF_BitStream *bs, u32 value); + +/*! +\brief little endian large integer writing + +Writes an integer on 64 bits in little-endian order. +\param bs the target bitstream +\param value the integer value to write + */ +void gf_bs_write_u64_le(GF_BitStream *bs, u64 value); + +/*! +\brief little endian short writing + +Writes an integer on 16 bits in little-endian order. +\param bs the target bitstream +\param value the short value to write + */ +void gf_bs_write_u16_le(GF_BitStream *bs, u32 value); + +/*! +\brief write byte multiple times + +Writes a give byte multiple times. +\param bs the target bitstream +\param byte the byte value to write +\param count the number of times the byte should be written +\return the number of bytes written + */ +u32 gf_bs_write_byte(GF_BitStream *bs, u8 byte, u32 count); + +/*! +\brief end of bitstream management + +Assigns a notification callback function for end of stream signaling in read mode +\param bs the target bitstream +\param EndOfStream the notification function to use +\param par opaque user data passed to the bitstream + */ +void gf_bs_set_eos_callback(GF_BitStream *bs, void (*EndOfStream)(void *par), void *par); + +/*! +\brief bitstream alignment checking + +Checks if bitstream position is aligned to a byte boundary. +\param bs the target bitstream +\return GF_TRUE if aligned with regard to the read/write mode, GF_FALSE otherwise + */ +Bool gf_bs_is_align(GF_BitStream *bs); + +/*! +\brief bitstream alignment + +Aligns bitstream to next byte boundary. In write mode, this will write 0 bit values until alignment. +\param bs the target bitstream +\return the number of bits read/written until alignment + */ +u8 gf_bs_align(GF_BitStream *bs); +/*! +\brief capacity query + +Returns the number of bytes still available in the bitstream in read mode. +\param bs the target bitstream +\return the number of bytes still available in read mode, -1 in write modes. + */ +u64 gf_bs_available(GF_BitStream *bs); +/*! +\brief buffer fetching + +Fetches the internal bitstream buffer in write mode. If a buffer was given at the bitstream construction, or if the bitstream is in read mode, this does nothing. +\param bs the target bitstream +\param output address of the memory block allocated by the bitstream. +\param outSize size of the allocated memory block. +\note + - It is the user responsability to destroy the allocated buffer + - Once this function has been called, the internal bitstream buffer is reseted. + */ +void gf_bs_get_content(GF_BitStream *bs, u8 **output, u32 *outSize); + +/*! +\brief buffer fetching + +Fetches the internal bitstream buffer in write mode. If a buffer was given at the bitstream construction, or if the bitstream is in read mode, this does nothing. Retrieves both the allocated buffer size and the written size +\param bs the target bitstream +\param output address of the memory block allocated by the bitstream. +\param outSize number of bytes written in the allocated memory block. +\param allocSize size of the allocated memory block. +\note + - It is the user responsability to destroy the allocated buffer + - Once this function has been called, the internal bitstream buffer is reseted. + */ +void gf_bs_get_content_no_truncate(GF_BitStream *bs, u8 **output, u32 *outSize, u32 *allocSize); + +/*! +\brief byte skipping + +Skips bytes in the bitstream. In Write mode, this will write the 0 integer value for memory-based bitstreams or seek the stream + for file-based bitstream. In read mode, emultation prevention byte is applied if enabled +\param bs the target bitstream +\param nbBytes the number of bytes to skip + */ +void gf_bs_skip_bytes(GF_BitStream *bs, u64 nbBytes); + +/*! +\brief bitstream seeking + +Seeks the bitstream to a given offset after the beginning of the stream. This will perform alignment of the bitstream in all modes. +\warning Results are unpredictable if seeking beyond the bitstream end is performed. +\param bs the target bitstream +\param offset buffer/file offset to seek to +\return error if any + */ +GF_Err gf_bs_seek(GF_BitStream *bs, u64 offset); + +/*! +\brief bitstream truncation + +Truncates the bitstream at the current position +\param bs the target bitstream + */ +void gf_bs_truncate(GF_BitStream *bs); + +/*! +\brief bit peeking + +Peeks a given number of bits (read without moving the position indicator) for read modes only. +\param bs the target bitstream +\param numBits the number of bits to peek +\param byte_offset + * if set, bitstream is aligned and moved from byte_offset before peeking (byte-aligned picking) + * otherwise, bitstream is not aligned and bits are peeked from current state +\return the integer value read +*/ +u32 gf_bs_peek_bits(GF_BitStream *bs, u32 numBits, u64 byte_offset); + +/*! +\brief bit reservoir query + +Queries the number of bits available in read mode. +\param bs the target bitstream +\return number of available bits if position is in the last byte of the buffer/stream, 8 otherwise + */ +u8 gf_bs_bits_available(GF_BitStream *bs); + +/*! +\brief position query + +Returns the reading/writting position in the buffer/file. +\param bs the target bitstream +\return the read/write position of the bitstream + */ +u64 gf_bs_get_position(GF_BitStream *bs); + +/*! +\brief size query + +Returns the size of the associated buffer/file. +\param bs the target bitstream +\return the size of the bitstream + */ +u64 gf_bs_get_size(GF_BitStream *bs); +/*! +\brief file-based size query + +Returns the size of a file-based bitstream and force a seek to end of file. This is used in case the file handle describes a file being constructed on disk while being read? +\param bs the target bitstream +\return the disk size of the associated file + */ +u64 gf_bs_get_refreshed_size(GF_BitStream *bs); + + +/*! +\brief transfer content from source bitstream to destination bitstream + +Returns the size of the associated buffer/file. +\param dst the target bitstream +\param src the source bitstream. +\param keep_src If not set, the source bitstream is empty after calling the function +\return error if any + */ +GF_Err gf_bs_transfer(GF_BitStream *dst, GF_BitStream *src, Bool keep_src); + + +/*! +\brief Flushes bitstream content to disk + +Flushes bitstream contet to disk +\param bs the target bitstream + */ +void gf_bs_flush(GF_BitStream *bs); + +/*! +\brief AVC&HEVC Annex B mode, only used for read mode + +Enables or disable emulation byte prevention for AVC and HEVC annex B formats in read mode. This does NOT apply to \ref gf_bs_read_data nor \ref gf_bs_skip_bytes +\param bs the target bitstream +\param do_remove if true, emulation prevention bytes will be removed + */ +void gf_bs_enable_emulation_byte_removal(GF_BitStream *bs, Bool do_remove); + +/*! +\brief Inserts a data block, moving bytes to the end + +Inserts a data block at a given position, pushing all bytes after the insertion point to the end of the stream. + This does NOT work if \ref gf_bs_enable_emulation_byte_removal or \ref gf_bs_new_cbk where used. + The position after the call will be the same as before the call. If the position is not the end of the bitstream + all bytes after the position will be lost. +\param bs the target bitstream +\param data block to insert +\param size size of the block to insert +\param offset insertion offset from bitstream start +\return error code if any + */ +GF_Err gf_bs_insert_data(GF_BitStream *bs, u8 *data, u32 size, u64 offset); + +/*! +\brief Sets cookie + +Sets a 64 bit cookie (integer, pointer) on the bitstream, returning the current cookie value +\param bs the target bitstream +\param cookie the new cookie to assign +\return the cookie value before re-assign + */ +u64 gf_bs_set_cookie(GF_BitStream *bs, u64 cookie); + +/*! +\brief Gets cookie + +Gets the current cookie on the bitstream +\param bs the target bitstream +\return the current cookie value + */ +u64 gf_bs_get_cookie(GF_BitStream *bs); + + +/*! +\brief Marks overflow access + +Marks the bitstream as overflown (reading outside of buffer range). Marking is done automatically when reading but can be forced using this function. + +\param bs the target bitstream +\param reset if GF_TRUE, reset overflown state, otherwise mark as overflown + */ +void gf_bs_mark_overflow(GF_BitStream *bs, Bool reset); + +/*! +\brief Gets overflow state + +Gets overflow state of the bitstream +\param bs the target bitstream +\return 2 if an overflow was marked by user using \ref gf_bs_mark_overflow, 1 if an overflow occured, 0 otherwise + */ +u32 gf_bs_is_overflow(GF_BitStream *bs); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_BITSTREAM_H_*/ + diff --git a/include/gpac/cache.h b/include/gpac/cache.h new file mode 100644 index 0000000..5041657 --- /dev/null +++ b/include/gpac/cache.h @@ -0,0 +1,283 @@ +/* + * 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 +\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 + +/** + * 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_ */ diff --git a/include/gpac/color.h b/include/gpac/color.h new file mode 100644 index 0000000..5f72d34 --- /dev/null +++ b/include/gpac/color.h @@ -0,0 +1,296 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / common tools interfaces + * + * 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_COLOR_H_ +#define _GF_COLOR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + + +/*! +\file +\brief Color conversion. +*/ + +/*! +\addtogroup color_grp +\brief Color tools + +This section documents color tools for image processing and color conversion +@{ +*/ + + +/*!\brief Video framebuffer object + +The video framebuffer object represents uncompressed color data like images in a variety of formats. Data in the video framebuffer MUST be continuous. +*/ +typedef struct +{ + /*!Width of the video framebuffer */ + u32 width; + /*!Height of the video framebuffer */ + u32 height; + /*!Horizontal pitch of the video framebuffer (number of bytes to skip to go to next (right) pixel in the buffer). May be + negative for some framebuffers (embedded devices). 0 means linear frame buffer (pitch_x==bytes per pixel)*/ + s32 pitch_x; + /*!Vertical pitch of the video framebuffer (number of bytes to skip to go down one line in the buffer). May be + negative for some framebuffers (embedded devices)*/ + s32 pitch_y; + /*!Pixel format of the video framebuffer*/ + u32 pixel_format; + /*!pointer to the beginning of the video memory (top-left corner)*/ + u8 *video_buffer; + /*!indicates that the video data reside on systems memory or video card one*/ + Bool is_hardware_memory; + /*!indicates U and V (and optional alpha) buffers in case of planar video with separated component. If not set, all components are in the video_buffer pointer*/ + u8 *u_ptr, *v_ptr, *a_ptr; + /*! alpha value for this surface*/ + u8 global_alpha; +} GF_VideoSurface; + +/*!\brief Video Window object + +The video window object represents a rectangle in framebuffer coordinate system +*/ +typedef struct +{ + /*!left-most coordinate of the rectangle*/ + u32 x; + /*!top-most coordinate of the rectangle*/ + u32 y; + /*!width of the rectangle*/ + u32 w; + /*!height of the rectangle*/ + u32 h; +} GF_Window; + + +/*!\brief color matrix object + +The Color transformation matrix object allows complete color space transformation (shift, rotate, skew, add).\n + The matrix coefs are in rgba order, hence the color RGBA is transformed to: + \code + R' m0 m1 m2 m3 m4 R + G' m5 m6 m7 m8 m9 G + B' = m10 m11 m12 m13 m14 x B + A' m15 m16 m17 m18 m19 A + 0 0 0 0 0 1 0 + \endcode + Coeficients are in intensity scale, ranging from 0 to \ref FIX_ONE. +*/ +typedef struct +{ + /*!color matrix coefficient*/ + Fixed m[20]; + /*!internal flag to speed up things when matrix is identity. This is a read only flag, do not modify it*/ + u32 identity; +} GF_ColorMatrix; + +/*!\brief ARGB color object + +The color type used in the GPAC framework represents colors in the form 0xAARRGGBB, with each component ranging from 0 to 255 +*/ +typedef u32 GF_Color; +/*!\hideinitializer color formatting macro from alpha, red, green and blue components expressed as integers ranging from 0 to 255*/ +#define GF_COL_ARGB(a, r, g, b) (((u32) a)<<24 | ((u32) r)<<16 | ((u32) g)<<8 | ((u32) b)) + +/*!\hideinitializer color formatting macro from alpha, red, green and blue components expressed as fixed numbers ranging from 0 to \ref FIX_ONE*/ +#define GF_COL_ARGB_FIXED(_a, _r, _g, _b) GF_COL_ARGB(FIX2INT(255*(_a)), FIX2INT(255*(_r)), FIX2INT(255*(_g)), FIX2INT(255*(_b))) +/*!\hideinitializer gets alpha component of a color*/ +#define GF_COL_A(c) (u8) ((c)>>24) +/*!\hideinitializer gets red component of a color*/ +#define GF_COL_R(c) (u8) ( ((c)>>16) & 0xFF) +/*!\hideinitializer gets green component of a color*/ +#define GF_COL_G(c) (u8) ( ((c)>>8) & 0xFF) +/*!\hideinitializer gets blue component of a color*/ +#define GF_COL_B(c) (u8) ( (c) & 0xFF) +/*!\hideinitializer 16-bits color formatting macro from red, green and blue components*/ +#define GF_COL_565(r, g, b) (u16) (((r & 248)<<8) + ((g & 252)<<3) + (b>>3)) +/*!\hideinitializer 15-bits color formatting macro from red, green and blue components*/ +#define GF_COL_555(r, g, b) (u16) (((r & 248)<<7) + ((g & 248)<<2) + (b>>3)) + +/*!\hideinitializer gets alpha component of a wide color*/ +#define GF_COLW_A(c) (u16) ((c)>>48) +/*!\hideinitializer gets red component of a wide color*/ +#define GF_COLW_R(c) (u16) ( ((c)>>32) & 0xFFFF) +/*!\hideinitializer gets green component of a wide color*/ +#define GF_COLW_G(c) (u16) ( ((c)>>16) & 0xFFFF) +/*!\hideinitializer gets blue component of a wide color*/ +#define GF_COLW_B(c) (u16) ( (c) & 0xFFFF) + +/*!Parses color from HTML name or hexa representation +\param name name of the color to parse +\return GF_Color value with alpha set to 0xFF if successfull, 0 otherwise +*/ +GF_Color gf_color_parse(const char *name); + +/*! Gets color from HTML name or hexa representation +\param col color to identify +\return name of the color if successfull, NULL otherwise +*/ +const char *gf_color_get_name(GF_Color col); + +/*! Enumerates built-in colors +\param idx index of color to query, incremented by one on success +\param color set to color value, can be NULL +\param color_name set to color name, can be NULL +\return GF_TRUE if sucess, GF_FALSE otherwise +*/ +Bool gf_color_enum(u32 *idx, GF_Color *color, const char **color_name); + +/*! Inits a color matrix to identity +\param _this the target color matrix to initialize*/ +void gf_cmx_init(GF_ColorMatrix *_this); + +/*! Inits all coefficients of a color matrix +\param _this color matrix to initialize +\param mrr red-to-red multiplication factor +\param mrg red-to-green multiplication factor +\param mrb red-to-blue multiplication factor +\param mra red-to-alpha multiplication factor +\param tr red translation factor +\param mgr green-to-red multiplication factor +\param mgg green-to-green multiplication factor +\param mgb green-to-blue multiplication factor +\param mga green-to-alpha multiplication factor +\param tg green translation factor +\param mbr blue-to-red multiplication factor +\param mbg blue-to-green multiplication factor +\param mbb blue-to-blue multiplication factor +\param mba blue-to-alpha multiplication factor +\param tb blue translation factor +\param mar alpha-to-red multiplication factor +\param mag alpha-to-green multiplication factor +\param mab alpha-to-blue multiplication factor +\param maa alpha-to-alpha multiplication factor +\param ta alpha translation factor +*/ +void gf_cmx_set(GF_ColorMatrix *_this, + Fixed mrr, Fixed mrg, Fixed mrb, Fixed mra, Fixed tr, + Fixed mgr, Fixed mgg, Fixed mgb, Fixed mga, Fixed tg, + Fixed mbr, Fixed mbg, Fixed mbb, Fixed mba, Fixed tb, + Fixed mar, Fixed mag, Fixed mab, Fixed maa, Fixed ta); +/*! Inits a matrix from another matrix +\param _this color matrix to initialize +\param from color matrix to copy from +*/ +void gf_cmx_copy(GF_ColorMatrix *_this, GF_ColorMatrix *from); +/*!\brief color matrix multiplication + +Multiplies a color matrix by another one. Result is _this*with +\param _this color matrix to transform. Once the function called, _this will contain the resulting color matrix +\param with color matrix to add +*/ +void gf_cmx_multiply(GF_ColorMatrix *_this, GF_ColorMatrix *with); +/*!\brief color matrix transform + +Transforms a color with a given color matrix +\param _this color matrix to use. +\param col color to transform +\return transformed color +*/ +GF_Color gf_cmx_apply(GF_ColorMatrix *_this, GF_Color col); + +/*!\brief color matrix transform + +Transforms a color with a given color matrix +\param _this color matrix to use. +\param a alpha to transform (in/out) +\param r red to transform (in/out) +\param g green to transform (in/out) +\param b blue to transform (in/out) +*/ +void gf_cmx_apply_argb(GF_ColorMatrix *_this, u8 *a, u8 *r, u8 *g, u8 *b); + +/*!\brief color matrix transform on wide pixel (16 bit per component) + +Transforms a color with a given color matrix +\param _this color matrix to use. +\param col color to transform +\return transformed color +*/ +u64 gf_cmx_apply_wide(GF_ColorMatrix *_this, u64 col); + +/*!\brief color components matrix transform + +Transforms color components with a given color matrix +\param _this color matrix to use. +\param a pointer to alpha component. Once the function is called, a contains the transformed alpha component +\param r pointer to red component. Once the function is called, r contains the transformed red component +\param g pointer to green component. Once the function is called, g contains the transformed green component +\param b pointer to blue component. Once the function is called, b contains the transformed blue component +*/ +void gf_cmx_apply_fixed(GF_ColorMatrix *_this, Fixed *a, Fixed *r, Fixed *g, Fixed *b); + + +/*!\brief Color Key descriptor + +The ColorKey object represents a ColorKey with low and high threshold keying +*/ +typedef struct +{ + /*!color key R, G, and B components*/ + u8 r, g, b; + /*!Alpha value for opaque (non-keyed) pixels*/ + u8 alpha; + /*!low variance threshold*/ + u8 low; + /*!high variance threshold*/ + u8 high; +} GF_ColorKey; + +/*!\brief stretches two video surfaces + +Software stretch of source surface onto destination surface. +\param dst destination surface +\param src source surface +\param dst_wnd destination rectangle. If null the entire destination surface is used +\param src_wnd source rectangle. If null the entire source surface is used +\param alpha blend factor of source over alpha +\param flip flips the source +\param colorKey makes source pixel matching the color key transparent +\param cmat applies color matrix to the source +\return error code if any + */ +GF_Err gf_stretch_bits(GF_VideoSurface *dst, GF_VideoSurface *src, GF_Window *dst_wnd, GF_Window *src_wnd, u8 alpha, Bool flip, GF_ColorKey *colorKey, GF_ColorMatrix * cmat); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_COLOR_H_*/ + diff --git a/include/gpac/compositor.h b/include/gpac/compositor.h new file mode 100644 index 0000000..552e7c4 --- /dev/null +++ b/include/gpac/compositor.h @@ -0,0 +1,283 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Scene Compositor 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_COMPOSITOR_H_ +#define _GF_COMPOSITOR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief GPAC A/V/2D/3D compositor/rendering. +*/ + +/*! +\addtogroup compose_grp Compositor +\ingroup playback_grp +\brief GPAC A/V/2D/3D compositor/rendering. + +This section documents the compositor of GPAC in charge of assembling audio, images, video, text, 2D and 3D graphics + in a timed fashion. The compositor can only be run as a filter starting from GPAC 0.9.0, as it requires the filters API + to fetch media data. + The compositor can work in real-time mode (player) or as a regular filter. See `gpac -h compositor`for more information. +@{ + */ + + +/*include scene graph API*/ +#include +/*GF_User and GF_Terminal */ +#include +/*frame buffer definition*/ +#include + +/*! Compositor object*/ +typedef struct __tag_compositor GF_Compositor; + +/*! loads a compositor object +\param compositor a preallocated structure for the compositor to initialize +\return error if any +*/ +GF_Err gf_sc_load(GF_Compositor *compositor); +/*! unloads compositor +\param compositor the compositor object to unload. The structure memory is not freed +*/ +void gf_sc_unload(GF_Compositor *compositor); + +/*! sets simulation frame rate. The compositor framerate impacts the frequency at which time nodes and animations are updated, +but does not impact the video objects frame rates. +\param compositor the target compositor +\param fps the desired frame rate +*/ +void gf_sc_set_fps(GF_Compositor *compositor, GF_Fraction fps); + +/*! sets the root scene graph of the compositor. +\param compositor the target compositor +\param scene_graph the scene graph to attach. If NULL, removes current scene and resets simulation time +\return error if any +*/ +GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph); + +/*! draws a single frame. If the frame is drawn, a packet is sent on the compositor vout output pi +\param compositor the target compositor +\param no_video_flush disables video frame flushing to graphics card. Ignored in non-player mode +\param ms_till_next set to the number of milliseconds until next expected frame +\return GF_TRUE if there are pending tasks (frame late, fonts pending, etc) or GF_FALSE if everything was ready while drawing the frame +*/ +Bool gf_sc_draw_frame(GF_Compositor *compositor, Bool no_video_flush, s32 *ms_till_next); + +/*! notify the given node has been modified. The compositor filters object to decide whether the scene graph has to be +traversed or not. +\param compositor the target compositor +\param node the node to invalidate. If NULL, this means complete traversing of the graph is requested +*/ +void gf_sc_invalidate(GF_Compositor *compositor, GF_Node *node); + +/*! returns the compositor time. The compositor time is the time every time line is synchronized to +\param compositor the target compositor +\return compositor time in milliseconds +*/ +u32 gf_sc_get_clock(GF_Compositor *compositor); + +/*! signals the node or scenegraph is about to be destroyed. This should be called after the node destructor if any. +This function cleans up any pending events on the target node or graph. +\param compositor the target compositor +\param node the target node. If NULL, sg shall be set to the scenegraph about to be destroyed +\param sg the target scenegraph +*/ +void gf_sc_node_destroy(GF_Compositor *compositor, GF_Node *node, GF_SceneGraph *sg); + +/*! locks/unlocks the visual scene rendering. Modifications of the scene tree shall only happen when scene compositor is locked +\param compositor the target compositor +\param do_lock indicates if the compositor should be locked (GF_TRUE) or released (GF_FALSE) +*/ +void gf_sc_lock(GF_Compositor *compositor, Bool do_lock); + +/*! notify user input +\param compositor the target compositor +\param event the target event to notify +\return GF_FALSE if event hasn't been handled by the compositor, GF_TRUE otherwise*/ +Bool gf_sc_user_event(GF_Compositor *compositor, GF_Event *event); + +/*! maps screen coordinates to bifs 2D coordinates for the current zoom/pan settings. The input coordinate X and Y are + point coordinates in the display expressed in BIFS-like fashion (0,0) at center of display and Y increasing from bottom to top + +\param compositor the target compositor +\param X horizontal point coordinate +\param Y vertical point coordinate +\param bifsX set to the scene horizontal coordinate of the point +\param bifsY set to the scene vertical coordinate of the point +*/ +void gf_sc_map_point(GF_Compositor *compositor, s32 X, s32 Y, Fixed *bifsX, Fixed *bifsY); + +/*! sets user options. Options are as defined in user.h +\param compositor the target compositor +\param type the target option type +\param value the target option value +\return error if any +*/ +GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value); +/*! gets user options. Options are as defined in user.h +\param compositor the target compositor +\param type the target option type +\return the option value +*/ +u32 gf_sc_get_option(GF_Compositor *compositor, u32 type); + +/*! returns current FPS +\param compositor the target compositor +\param absoluteFPS if set to GF_TRUE, the return value is the absolute framerate, eg NbFrameCount/NbTimeSpent regardless of +whether a frame has been drawn or not, which means the FPS returned can be much greater than the compositor FPS. If set to GF_FALSE, the return value is the FPS taking into account not drawn frames (eg, less than or equal to compositor FPS) +\return the current frame rate +*/ +Double gf_sc_get_fps(GF_Compositor *compositor, Bool absoluteFPS); + +/*! checks if a text selection is in progress +\param compositor the target compositor +\return GF_TRUE is some text is selected, GF_FALSE otherwise +*/ +Bool gf_sc_has_text_selection(GF_Compositor *compositor); + +/*! gets text selection +\param compositor the target compositor +\return the selected text as UTF8 string +*/ +const char *gf_sc_get_selected_text(GF_Compositor *compositor); + +/*! replace text selection content +\param compositor the target compositor +\param text the text to paste as UTF8 string +\return error if any +*/ +GF_Err gf_sc_paste_text(GF_Compositor *compositor, const char *text); + +/*! gets screen buffer. This locks the scene graph too until \ref gf_sc_get_offscreen_buffer is called +\param compositor the target compositor +\param framebuffer will be set to the grabbed framebuffer. The pixel data is owned by the compositor and shall not be freed +\param depth_grab_mode mode for depth grabbing in 3D +\return error if any +*/ +GF_Err gf_sc_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, GF_CompositorGrabMode depth_grab_mode); + +/*! gets offscreen buffer in autostereo rendering modes. This locks the scene graph too until \ref gf_sc_get_offscreen_buffer is called +\param compositor the target compositor +\param framebuffer will be set to the grabbed framebuffer. The pixel data is owned by the compositor and shall not be freed +\param view_idx indicates the 0-based index of the view to grab +\param depth_grab_mode mode for depth grabbing in 3D +\return error if any +*/ +GF_Err gf_sc_get_offscreen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, u32 view_idx, GF_CompositorGrabMode depth_grab_mode); + +/*! releases screen buffer and unlocks graph +\param compositor the target compositor +\param framebuffer used during grab call +\return error if any +*/ +GF_Err gf_sc_release_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer); + +/*! forces full graphics reset (deletes GL textures, FBO, 2D offscreen caches, etc ...). +\param compositor the target compositor +*/ +void gf_sc_reset_graphics(GF_Compositor *compositor); + +/*! gets viewpoints/viewports for main scene - idx is 1-based, and if greater than number of viewpoints return GF_EOS +\param compositor the target compositor +\param viewpoint_idx the index of the requested viewport. This is a 1-based index, and if greater than number of viewpoints the function will return GF_EOS +\param out_name set to the viewport name. May be NULL +\param is_bound set to GF_TRUE if the viewport is bound. May be NULL +\return GF_EOS if no more viewpoint, or error if any +*/ +GF_Err gf_sc_get_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const char **out_name, Bool *is_bound); + +/*! sets viewpoints/viewports for main scene given its name - idx is 1-based, or 0 to retrieve by viewpoint name +if only one viewpoint is present in the scene, this will bind/unbind it +\param compositor the target compositor +\param viewpoint_idx the index of the viewport to bind. This is a 1-based index, and if greater than number of viewpoints the function will return GF_EOS. Use 0 to bind the viewpoint with the given name. +\param viewpoint_name name of the viewpoint to bind if viewpoint_idx is 0 +\return error if any +*/ +GF_Err gf_sc_set_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const char *viewpoint_name); + +/*! renders subscene root node. rs is the current traverse stack +\param compositor the target compositor +\param inline_parent the parent node of the subscene (VRML Inline, SVG animation, ...) +\param subscene the subscene tree to render +\param rs the rendering state at the parent level. This is needed to handle graph metrics changes between scenes +*/ +void gf_sc_traverse_subscene(GF_Compositor *compositor, GF_Node *inline_parent, GF_SceneGraph *subscene, void *rs); + +/*! sets output (display) size +\param compositor the target compositor +\param new_width the desired new output width +\param new_height the desired new output height +\return error if any +*/ +GF_Err gf_sc_set_size(GF_Compositor *compositor, u32 new_width, u32 new_height); + +/*! adds or removes extra scene from compositor. Extra scenes are on-screen displays, text tracks or any other scene graphs +not directly loaded by the main scene +\param compositor the target compositor +\param extra_scene the scene graph to add/remove +\param do_remove if set to GF_TRUE, the given scene graph will be unregistered; otherwise, the given scene graph will be registered +*/ +void gf_sc_register_extra_graph(GF_Compositor *compositor, GF_SceneGraph *extra_scene, Bool do_remove); + +/*! retrieves the compositor object associated with a node. Currently GPAC only handles one possible compositor per node. +\param node the target node +\return the compositor used by the node +*/ +GF_Compositor *gf_sc_get_compositor(GF_Node *node); + +/*! executes a script action +\param compositor the target compositor +\param type the script action type +\param node the optional node on which the action takes place +\param param the parameter for this action +\return GF_TRUE if success +*/ +Bool gf_sc_script_action(GF_Compositor *compositor, GF_JSAPIActionType type, GF_Node *node, GF_JSAPIParam *param); + +/*! checks if the given URI matches a built-in GPAC VRML Prototype node +\param compositor the target compositor +\param uri the URI to test +\return GF_TRUE if the URI indicates a built-in prototype, GF_FALSE otherwise +*/ +Bool gf_sc_uri_is_hardcoded_proto(GF_Compositor *compositor, const char *uri); + +/*! reloads the compositor configuration from the GPAC config file +\param compositor the target compositor +*/ +void gf_sc_reload_config(GF_Compositor *compositor); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_COMPOSITOR_H_*/ + diff --git a/include/gpac/config_file.h b/include/gpac/config_file.h new file mode 100644 index 0000000..1f4c8c0 --- /dev/null +++ b/include/gpac/config_file.h @@ -0,0 +1,202 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_CONFIG_FILE_H_ +#define _GF_CONFIG_FILE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief configuration file. + */ + +/*! +\addtogroup cfg_grp +\brief Configuration File object + +This section documents the configuration file objects of the GPAC framework. +A specific global configuration file is used for libgpac, see \ref libsys_grp. +Such objects may be used by third-party applications to store data, and are used by the player scripting API to store +various GUI data. +A configuration file is formatted as the INI file mode of WIN32 in sections and keys. +\code +[Section1] +Key1=foo +Key2=bar + +[Section2] +Key1=@multiline +for +some +reason@ +Key3=2 + +\endcode +@{ +*/ + +#include + +/*! configuration file object*/ +typedef struct __tag_config GF_Config; + +/*! +\brief configuration file constructor + +Constructs a configuration file. +\param filePath directory the file is located in +\param fileName name of the configuration file +\return the configuration file object, NULL if the file does not exist + */ +GF_Config *gf_cfg_new(const char *filePath, const char *fileName); +/*! +\brief alternative configuration file constructor + +Constructs a configuration file. If file does not exist, configuration will be still created +\param filePath directory the file is located in +\param fileName name of the configuration file +\return the configuration file object, never NULL, even if file does not exist + */ +GF_Config *gf_cfg_force_new(const char *filePath, const char *fileName); +/*! +\brief configuration file destructor + +Destroys the configuration file and saves it if needed. +\param cfgFile the target configuration file + */ +void gf_cfg_del(GF_Config *cfgFile); +/*! +\brief configuration file destructor + +Destroys the configuration file and removes the file from disk. +\param cfgFile the target configuration file + */ +void gf_cfg_remove(GF_Config *cfgFile); +/*! +\brief configuration saving + +Saves the configuration file if modified. +\param cfgFile the target configuration file +\return error if any + */ +GF_Err gf_cfg_save(GF_Config *cfgFile); +/*! +\brief key value query + +Gets a key value from its section and name. +\param cfgFile the target configuration file +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, NULL otherwise. + */ +const char *gf_cfg_get_key(GF_Config *cfgFile, const char *secName, const char *keyName); + +/*! +\brief key value update + +Sets a key value from its section and name. +\param cfgFile the target configuration file +\param secName the desired key parent section name +\param keyName the desired key name +\param keyValue the desired key value +\note this will also create both section and key if they are not found in the configuration file +\return error if any + */ +GF_Err gf_cfg_set_key(GF_Config *cfgFile, const char *secName, const char *keyName, const char *keyValue); +/*! +\brief section count query + +Gets the number of sections in the configuration file +\param cfgFile the target configuration file +\return the number of sections + */ +u32 gf_cfg_get_section_count(GF_Config *cfgFile); +/*! +\brief section name query + + Gets a section name based on its index +\param cfgFile the target configuration file +\param secIndex 0-based index of the section to query +\return the section name if found, NULL otherwise + */ +const char *gf_cfg_get_section_name(GF_Config *cfgFile, u32 secIndex); +/*! +\brief key count query + +Gets the number of keys in a section of the configuration file +\param cfgFile the target configuration file +\param secName the target section +\return the number of keys in the section + */ +u32 gf_cfg_get_key_count(GF_Config *cfgFile, const char *secName); +/*! +\brief key count query + +Gets the number of keys in a section of the configuration file +\param cfgFile the target configuration file +\param secName the target section +\param keyIndex 0-based index of the key in the section +\return the key name if found, NULL otherwise + */ +const char *gf_cfg_get_key_name(GF_Config *cfgFile, const char *secName, u32 keyIndex); + +/*! +\brief section destrouction + +Removes all entries in the given section +\param cfgFile the target configuration file +\param secName the target section + */ +void gf_cfg_del_section(GF_Config *cfgFile, const char *secName); + +/*! + +Get the full filename associated with this config file +\param iniFile The Configuration +\return the associated filename + */ +const char * gf_cfg_get_filename(GF_Config *iniFile); + + +/*! + +Do not save results to file +\param iniFile The Configuration +\return error code + */ +GF_Err gf_cfg_discard_changes(GF_Config *iniFile); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_CONFIG_FILE_H_*/ + diff --git a/include/gpac/configuration.h b/include/gpac/configuration.h new file mode 100644 index 0000000..40fc2c6 --- /dev/null +++ b/include/gpac/configuration.h @@ -0,0 +1,345 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2008-2012 + * All rights reserved + * + * This file is part of GPAC + * + * 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_CONFIG_H_ +#define _GF_CONFIG_H_ + +/*! +\addtogroup setup_grp +\brief Base data types + +This section documents the base data types of GPAC. + +@{ +*/ + +#define GPAC_CONFIGURATION "(static configuration file)" + + + +/*this file defines all common macros for libgpac compilation + except for symbian32 which uses .mmp directives ... */ + +/*Configuration for visual studio, 32/64 bits */ +#if defined(_WIN32) && !defined(_WIN32_WCE) + +#ifndef GPAC_MP4BOX_MINI + +#define GPAC_HAS_SSL + +#define GPAC_HAS_QJS +//codecs +#define GPAC_HAS_JPEG +#define GPAC_HAS_PNG +#define GPAC_HAS_LIBA52 +#define GPAC_HAS_FAAD +#define GPAC_HAS_JP2 +#define GPAC_HAS_MAD +#define GPAC_HAS_OPENHEVC +#define GPAC_HAS_OPENSVC +#define GPAC_HAS_THEORA +#define GPAC_HAS_VORBIS +#define GPAC_HAS_XVID +#define GPAC_HAS_FFMPEG +#define GPAC_HAS_DTAPI +#define GPAC_HAS_HTTP2 + +/*IPv6 enabled - for win32, this is evaluated at compile time, !! do not uncomment !!*/ + +#define GPAC_MEMORY_TRACKING + +/*Win32 IPv6 is evaluated at compile time, !! do not uncomment !!*/ +//#define GPAC_HAS_IPV6 + +#define GPAC_HAS_GLU + +#ifndef GPAC_CONFIG_WIN32 +#define GPAC_CONFIG_WIN32 +#endif //GPAC_CONFIG_WIN32 + + +#endif /*GPAC_MP4BOX_MINI*/ + +/*Configuration for WindowsCE 32 bits */ +#elif defined(_WIN32_WCE) + +#ifndef GPAC_FIXED_POINT +#define GPAC_FIXED_POINT +#endif + +/*use intel fixed-point*/ +//#define GPAC_USE_IGPP +/*use intel fixed-point with high precision*/ +//#define GPAC_USE_IGPP_HP + +#if defined(GPAC_USE_IGPP) && defined(GPAC_USE_IGPP_HP) +#error "Only one of GPAC_USE_IGPP and GPAC_USE_IGPP_HP can be defined" +#endif + +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) +#define GPAC_USE_GLES1X +#endif + + +#define GPAC_HAS_QJS +#define GPAC_HAS_JPEG +#define GPAC_HAS_PNG + +/*comment this line if you don't have a GLU32 version for Windows Mobile*/ +//#define GPAC_HAS_GLU + + +/*Configuration for Android */ +#elif defined(GPAC_CONFIG_ANDROID) + +#define GPAC_HAS_IPV6 +#define GPAC_USE_GLES1X +/*don't use fixed-point version on Android, not needed*/ +//#define GPAC_FIXED_POINT + +#define GPAC_HAS_QJS +#define GPAC_HAS_JPEG +#define GPAC_HAS_PNG +#define GPAC_HAS_HTTP2 + +/*Configuration for XCode OSX (not iOS) */ +#elif defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_CONFIG_IOS) + +#define GPAC_HAS_IPV6 +#define GPAC_HAS_SSL +#define GPAC_HAS_SOCK_UN + +//64-bits OSX +#ifdef __LP64__ +/*! macro defined for 64-bits platforms*/ +#define GPAC_64_BITS +#endif + +#define GPAC_HAS_QJS +//#define GPAC_DISABLE_QJS_LIBC +#define GPAC_HAS_JPEG +#define GPAC_HAS_PNG +#define GPAC_HAS_GLU +#define GPAC_HAS_VTB +#define GPAC_HAS_HTTP2 + +#define GPAC_MEMORY_TRACKING + +/*Configuration for XCode iOS*/ +#elif defined(GPAC_CONFIG_DARWIN) && defined(GPAC_CONFIG_IOS) + +//64-bits iOS +#ifdef __LP64__ +/*! macro defined for 64-bits platforms*/ +#define GPAC_64_BITS +#endif + +#define GPAC_HAS_QJS +#define GPAC_HAS_JPEG +#define GPAC_HAS_PNG +#define GPAC_HAS_SOCK_UN + +/*don't use fixed-point version on iOS, not needed*/ +//#define GPAC_FIXED_POINT + +//#define GPAC_USE_GLES1X +#define GPAC_USE_GLES2 + +// glu port available in gpac extra libs +#define GPAC_HAS_GLU + +/*extra libs supported on iOS*/ +#define GPAC_HAS_FAAD +#define GPAC_HAS_MAD +#define GPAC_HAS_FFMPEG +#define GPAC_HAS_SDL +#define GPAC_HAS_FREETYPE + +#define GPAC_HAS_IPV6 +#define GPAC_HAS_SSL +#define GPAC_DISABLE_OGG +#define GPAC_HAS_STRLCPY +#define GPAC_HAS_VTB +#define GPAC_HAS_HTTP2 + +/*Configuration for Symbian*/ +#elif defined(__SYMBIAN32__) + +#ifndef GPAC_FIXED_POINT +#define GPAC_FIXED_POINT +#endif + +#define GPAC_HAS_QJS +#define GPAC_HAS_JPEG +#define GPAC_HAS_PNG + +#else +#error "Unknown target platform used with static configuration file" +#endif + + +/*disables player */ +//#define GPAC_DISABLE_PLAYER + +/*disables scene manager */ +//#define GPAC_DISABLE_SMGR + +/*disables core tools */ +//#define GPAC_DISABLE_CORE_TOOLS + +/*disables zlib */ +#ifndef GPAC_MP4BOX_MINI +//#define GPAC_DISABLE_ZLIB +#else +#define GPAC_DISABLE_ZLIB +#endif + +/*disables SVG scene graph*/ +//#define GPAC_DISABLE_SVG + +/*disables VRML/BIFS scene graphs*/ +//#define GPAC_DISABLE_VRML + +/*disables X3D scene graphs*/ +//#define GPAC_DISABLE_X3D + +/*disables MPEG-4 OD Framework - this only minimalize the set of OD features used, however all cannot be removed +this macro is currently defined in setup.h */ +//#define GPAC_MINIMAL_ODF + +/*disables BIFS coding*/ +//#define GPAC_DISABLE_BIFS + +/*disables LASeR coder*/ +//#define GPAC_DISABLE_LASER +//#define GPAC_DISABLE_SAF + +/*disables BIFS Engine support*/ +//#define GPAC_DISABLE_SENG + +/*disables Cubic QTVR importing*/ +//#define GPAC_DISABLE_QTVR + +/*disables AVILib support*/ +//#define GPAC_DISABLE_AVILIB + +/*disables OGG support*/ +//#define GPAC_DISABLE_OGG + +/*disables MPEG2 PS support*/ +//#define GPAC_DISABLE_MPEG2PS + +/*disables MPEG2 TS demux support*/ +//#define GPAC_DISABLE_MPEG2TS + +/*disables MPEG2 TS Mux support*/ +//#define GPAC_DISABLE_MPEG2TS_MUX + +/*disables all media import functions*/ +//#define GPAC_DISABLE_MEDIA_IMPORT + +/*disable all AV parsing functions*/ +//#define GPAC_DISABLE_AV_PARSERS + +/*disables all media export functions*/ +//#define GPAC_DISABLE_MEDIA_EXPORT + +/*disables SWF importer*/ +//#define GPAC_DISABLE_SWF_IMPORT + +/*disables all media export functions*/ +//#define GPAC_DISABLE_SCENE_STATS + +/*disables scene -> MP4 encoder*/ +//#define GPAC_DISABLE_SCENE_ENCODER + +/*disables ISOM -> scene decoder*/ +//#define GPAC_DISABLE_LOADER_ISOM + +/*disables BT/WRL/X3DV -> scene decoder*/ +//#define GPAC_DISABLE_LOADER_BT + +/*disables XMTA/X3D -> scene decoder*/ +//#define GPAC_DISABLE_LOADER_XMT + +/*disables crypto tools*/ +//#define GPAC_DISABLE_CRYPTO + +/*disables all ISO FF*/ +//#define GPAC_DISABLE_ISOM + +/*disables ISO FF hint tracks*/ +//#define GPAC_DISABLE_ISOM_HINTING + +/*disables ISO FF writing*/ +//#define GPAC_DISABLE_ISOM_WRITE + +/*disables ISO FF fragments*/ +//#define GPAC_DISABLE_ISOM_FRAGMENTS + +/*disables scene graph */ +//#define GPAC_DISABLE_SCENEGRAPH + +/*disables scene graph textual dump*/ +//#define GPAC_DISABLE_SCENE_DUMP + +/*disables OD graph textual dump*/ +//#define GPAC_DISABLE_OD_DUMP + +/*disables OD graph textual dump*/ +//#define GPAC_DISABLE_ISOM_DUMP + +/*disables IETF RTP/SDP/RTSP*/ +//#define GPAC_DISABLE_STREAMING + +/*disables dashclient */ +//#define GPAC_DISABLE_DASH_CLIENT + +/*disables Timed Text support */ +//#define GPAC_DISABLE_TTXT + +/*disables TTML */ +//#define GPAC_DISABLE_TTML + +/*disables WebVTT */ +//#define GPAC_DISABLE_VTT + +/*disables DASH MPD */ +//#define GPAC_DISABLE_MPD + +/*disables HEVC */ +//#define GPAC_DISABLE_HEVC + +/*disables AOM AV1 */ +//#define GPAC_DISABLE_AV1 + +/*disables VOBSUB */ +//#define GPAC_DISABLE_VOBSUB + + +/*! @} */ + +#endif /*_GF_CONFIG_H_*/ diff --git a/include/gpac/constants.h b/include/gpac/constants.h new file mode 100644 index 0000000..48cb1e5 --- /dev/null +++ b/include/gpac/constants.h @@ -0,0 +1,1655 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / exported constants + * + * 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_CONSTANTS_H_ +#define _GF_CONSTANTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/*! +\file +\brief Most constants defined in GPAC are in this file. +\addtogroup cst_grp +\brief Constants + +This section documents some constants used in the GPAC framework which are not related to any specific sub-project. + +@{ +*/ + + +/*! +\brief Supported media stream types +\hideinitializer + +Supported media stream types for media objects. +*/ +enum +{ + /*!Unknown stream type*/ + GF_STREAM_UNKNOWN = 0, + /*!MPEG-4 Object Descriptor Stream*/ + GF_STREAM_OD = 0x01, + /*!MPEG-4 Object Clock Reference Stream*/ + GF_STREAM_OCR = 0x02, + /*!MPEG-4 Scene Description Stream*/ + GF_STREAM_SCENE = 0x03, + /*!Visual Stream (Video, Image or MPEG-4 SNHC Tools)*/ + GF_STREAM_VISUAL = 0x04, + /*!Audio Stream (Audio, MPEG-4 Structured-Audio Tools)*/ + GF_STREAM_AUDIO = 0x05, + /*!MPEG-7 Description Stream*/ + GF_STREAM_MPEG7 = 0x06, + /*!MPEG-4 Intellectual Property Management and Protection Stream*/ + GF_STREAM_IPMP = 0x07, + /*!MPEG-4 Object Content Information Stream*/ + GF_STREAM_OCI = 0x08, + /*!MPEG-4 MPEGlet Stream*/ + GF_STREAM_MPEGJ = 0x09, + /*!MPEG-4 User Interaction Stream*/ + GF_STREAM_INTERACT = 0x0A, + /*!MPEG-4 IPMP Tool Stream*/ + GF_STREAM_IPMP_TOOL = 0x0B, + /*!MPEG-4 Font Data Stream*/ + GF_STREAM_FONT = 0x0C, + /*!MPEG-4 Streaming Text Stream*/ + GF_STREAM_TEXT = 0x0D, + + /* From 0x20 to Ox3F, this is the user private range */ + + /*!Nero Digital Subpicture Stream*/ + GF_STREAM_ND_SUBPIC = 0x38, + + /*GPAC internal stream types*/ + + + /*!GPAC Private Scene streams\n + \n + \note + This stream type (MPEG-4 user-private) is reserved for streams used to create a scene decoder + handling the scene without input streams, as is the case for file readers (BT/VRML/XML..).\n + */ + GF_STREAM_PRIVATE_SCENE = 0x20, + + GF_STREAM_METADATA = 0x21, + + GF_STREAM_ENCRYPTED = 0xE0, + /*stream carries files, each file being a complete AU*/ + GF_STREAM_FILE = 0xE1, + + //other stream types may be declared using their handler 4CC as defined in ISOBMFF +}; + +/*! Gets the stream type name based on stream type +\param streamType stream type GF_STREAM_XXX as defined in constants.h +\return NULL if unknown, otherwise value + */ +const char *gf_stream_type_name(u32 streamType); + +/*! Gets the stream type short name based on stream type (usually the lower case value of the stream name) +\param streamType stream type GF_STREAM_XXX as defined in constants.h +\return NULL if unknown, otherwise value + */ +const char *gf_stream_type_short_name(u32 streamType); + +/*! Gets the stream type by name +\param name name of the stream type to query +\return GF_STREAM_UNKNOWN if unknown, otherwise GF_STREAM_XXX value + */ +u32 gf_stream_type_by_name(const char *name); + +/*! Enumerates defined stream types +\param idx index of the stream type, 0-based +\param name name of the stream type (used when parsing stream type from textual definition) +\param desc description of the stream type +\return stream type, or GF_STREAM_UNKNOWN if no more stream types + */ +u32 gf_stream_types_enum(u32 *idx, const char **name, const char **desc); + +#ifndef GF_4CC +/*! macro for 4CC*/ +#define GF_4CC(a,b,c,d) ((((u32)a)<<24)|(((u32)b)<<16)|(((u32)c)<<8)|(d)) +#endif + +/*! +\brief Pixel Formats + +Supported pixel formats for everything using video +*/ +typedef enum +{ + /*!8 bit GREY */ + GF_PIXEL_GREYSCALE = GF_4CC('G','R','E','Y'), + /*!16 bit greyscale, first alpha, then grey*/ + 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'), + /*!12 bit RGB on 16 bits (4096 colors)*/ + GF_PIXEL_RGB_444 = GF_4CC('R','4','4','4'), + /*!15 bit RGB*/ + GF_PIXEL_RGB_555 = GF_4CC('R','5','5','5'), + /*!16 bit RGB*/ + GF_PIXEL_RGB_565 = GF_4CC('R','5','6','5'), + /*!24 bit RGB*/ + GF_PIXEL_RGB = GF_4CC('R','G','B','3'), + /*!24 bit BGR*/ + GF_PIXEL_BGR = GF_4CC('B','G','R','3'), + /*!32 bit RGB. Component ordering in bytes is R-G-B-X.*/ + GF_PIXEL_RGBX = GF_4CC('R','G','B','4'), + /*!32 bit BGR. Component ordering in bytes is B-G-R-X.*/ + GF_PIXEL_BGRX = GF_4CC('B','G','R','4'), + /*!32 bit RGB. Component ordering in bytes is X-R-G-B.*/ + GF_PIXEL_XRGB = GF_4CC('R','G','B','X'), + /*!32 bit BGR. Component ordering in bytes is X-B-G-R.*/ + GF_PIXEL_XBGR = GF_4CC('B','G','R','X'), + + /*!32 bit ARGB. Component ordering in bytes is A-R-G-B.*/ + GF_PIXEL_ARGB = GF_4CC('A','R','G','B'), + /*!32 bit RGBA (OpenGL like). Component ordering in bytes is R-G-B-A.*/ + GF_PIXEL_RGBA = GF_4CC('R','G','B', 'A'), + /*!32 bit BGRA. Component ordering in bytes is B-G-R-A.*/ + GF_PIXEL_BGRA = GF_4CC('B','G','R','A'), + /*!32 bit ABGR. Component ordering in bytes is A-B-G-R.*/ + GF_PIXEL_ABGR = GF_4CC('A','B','G','R'), + + /*!RGB24 + depth plane. Component ordering in bytes is R-G-B-D.*/ + GF_PIXEL_RGBD = GF_4CC('R', 'G', 'B', 'D'), + /*!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'), + /*!Stereo RGB24 */ + GF_PIXEL_RGBS = GF_4CC('R', 'G', 'B', 'S'), + /*!Stereo RGBA. Component ordering in bytes is R-G-B-A. */ + GF_PIXEL_RGBAS = GF_4CC('R', 'G', 'A', 'S'), + + /*internal format for OpenGL using pachek RGB 24 bit plus planar depth plane at the end of the image*/ + GF_PIXEL_RGB_DEPTH = GF_4CC('R', 'G', 'B', 'd'), + + /*!YUV packed 422 format*/ + GF_PIXEL_YUYV = GF_4CC('Y','U','Y','V'), + /*!YUV packed 422 format*/ + GF_PIXEL_YVYU = GF_4CC('Y','V','Y','U'), + /*!YUV packed 422 format*/ + GF_PIXEL_UYVY = GF_4CC('U','Y','V','Y'), + /*!YUV packed 422 format*/ + GF_PIXEL_VYUY = GF_4CC('V','Y','U','Y'), + + /*!YUV packed 422 format 10 bits, little endian*/ + GF_PIXEL_YUYV_10 = GF_4CC('Y','U','Y','L'), + /*!YUV packed 422 format 10 bits, little endian*/ + GF_PIXEL_YVYU_10 = GF_4CC('Y','V','Y','L'), + /*!YUV packed 422 format 10 bits, little endian*/ + GF_PIXEL_UYVY_10 = GF_4CC('U','Y','V','L'), + /*!YUV packed 422 format 10 bits, little endian*/ + GF_PIXEL_VYUY_10 = GF_4CC('V','Y','U','L'), + + /*!YUV planar format*/ + GF_PIXEL_YUV = GF_4CC('Y','U','1','2'), + /*!YVU planar format*/ + GF_PIXEL_YVU = GF_4CC('Y','V','1','2'), + /*!YUV420p in 10 bits mode, little endian*/ + GF_PIXEL_YUV_10 = GF_4CC('Y','0','1','0'), + /*!YUV420p + Alpha plane*/ + GF_PIXEL_YUVA = GF_4CC('Y', 'U', 'V', 'A'), + /*!YUV420p + Depth plane*/ + GF_PIXEL_YUVD = GF_4CC('Y', 'U', 'V', 'D'), + /*!420 Y planar UV interleaved*/ + GF_PIXEL_NV21 = GF_4CC('N','V','2','1'), + /*!420 Y planar UV interleaved, 10 bits, little endian */ + GF_PIXEL_NV21_10 = GF_4CC('N','2','1','0'), + /*!420 Y planar VU interleaved (U and V swapped) */ + GF_PIXEL_NV12 = GF_4CC('N','V','1','2'), + /*!420 Y planar VU interleaved (U and V swapped), 10 bits, little endian */ + GF_PIXEL_NV12_10 = GF_4CC('N','1','2','0'), + /*!422 YUV*/ + GF_PIXEL_YUV422 = GF_4CC('Y','4','4','2'), + /*!422 YUV, 10 bits, little endian*/ + GF_PIXEL_YUV422_10 = GF_4CC('Y','2','1','0'), + /*!444 YUV+Alpha*/ + GF_PIXEL_YUVA444 = GF_4CC('Y','A','4','4'), + /*!444 YUV*/ + GF_PIXEL_YUV444 = GF_4CC('Y','4','4','4'), + /*!444 YUV, 10 bits, little endian*/ + GF_PIXEL_YUV444_10 = GF_4CC('Y','4','1','0'), + /*!444 YUV packed*/ + GF_PIXEL_YUV444_PACK = GF_4CC('Y','4','4','p'), + /*!444 YUV+Alpha packed*/ + GF_PIXEL_YUVA444_PACK = GF_4CC('Y','A','4','p'), + /*!444 YUV 10 bit packed*/ + GF_PIXEL_YUV444_10_PACK = GF_4CC('Y','4','1','p'), + + /*!Unknown format exposed a single OpenGL texture to be consumed using samplerExternalOES*/ + GF_PIXEL_GL_EXTERNAL = GF_4CC('E','X','G','L') +} GF_PixelFormat; + + +/*! enumerates GPAC pixel formats +\param pf_name name of the pixel format +\return pixel format code +*/ +GF_PixelFormat gf_pixel_fmt_parse(const char *pf_name); + +/*! gets name of pixel formats +\param pfmt pixel format code +\return pixel format name +*/ +const char *gf_pixel_fmt_name(GF_PixelFormat pfmt); + +/*! checks if pixel format is known, does not throw error message +\param pf_4cc pixel format code or 0 +\param pf_name pixel format name or short name or NULL +\return GF_TRUE is format is known, GF_FALSE otherwise +*/ +Bool gf_pixel_fmt_probe(GF_PixelFormat pf_4cc, const char *pf_name); + +/*! gets short name of pixel formats, as used for file extensions +\param pfmt pixel format code +\return pixel format short name, "unknown" if not found +*/ +const char *gf_pixel_fmt_sname(GF_PixelFormat pfmt); + +/*! enumerates pixel formats +\param idx index of the pixel format, 0-based +\param name name of the pixel format +\param fileext file extension of the pixel format +\param description description of the pixel format +\return pixel format code, 0 if no more pixel formats are available +*/ +GF_PixelFormat gf_pixel_fmt_enum(u32 *idx, const char **name, const char **fileext, const char **description); + +/*! gets the list of all supported pixel format names +\return list of supported pixel format names +*/ +const char *gf_pixel_fmt_all_names(); + +/*! gets the list of all supported pixel format names +\return list of supported pixel format short names +*/ +const char *gf_pixel_fmt_all_shortnames(); + +/*! returns size and stride characteristics for the pixel format. If the stride or stride_uv value are not 0, they are used to compute the size. Otherwise no padding at end of line is assumed. +\param pixfmt pixfmt format code +\param width target frame width +\param height target frame height +\param[out] out_size output frame size +\param[in,out] out_stride output frame stride for single plane or plane 0 +\param[in,out] out_stride_uv output frame stride for UV planes +\param[out] out_planes output frame plane numbers +\param[out] out_plane_uv_height height of UV planes +\return error code if any +*/ +Bool gf_pixel_get_size_info(GF_PixelFormat pixfmt, u32 width, u32 height, u32 *out_size, u32 *out_stride, u32 *out_stride_uv, u32 *out_planes, u32 *out_plane_uv_height); + +/*! Gets the number of bytes per pixel on first plane +\param pixfmt pixel format code +\return number of bytes per pixel +*/ +u32 gf_pixel_get_bytes_per_pixel(GF_PixelFormat pixfmt); + +/*! Gets the number of bits per component +\param pixfmt pixel format code +\return number of bits per component +*/ +u32 gf_pixel_is_wide_depth(GF_PixelFormat pixfmt); + +/*! Gets the number of component per pixel +\param pixfmt pixel format code +\return number of bytes per pixel +*/ +u32 gf_pixel_get_nb_comp(GF_PixelFormat pixfmt); + +/*! Checks if pixel format is transparent +\param pixfmt pixel format code +\return GF_TRUE if alpha channel is present, GF_FALSE otherwise +*/ +Bool gf_pixel_fmt_is_transparent(GF_PixelFormat pixfmt); + +/*! Checks if format is YUV +\param pixfmt pixel format code +\return GF_TRUE is YUV format, GF_FALSE otherwise (greyscale or RGB) +*/ +Bool gf_pixel_fmt_is_yuv(GF_PixelFormat pixfmt); + +/*! gets pixel format associated with a given uncompressed video QT code +\param qt_code the desired QT/ISOBMFF uncompressed video code +\return the corresponding pixel format, or 0 if unknown code +*/ +GF_PixelFormat gf_pixel_fmt_from_qt_type(u32 qt_code); +/*! gets QY code associated with a given pixel format +\param pixfmt the desired pixel format +\return the corresponding QT code, or 0 if no asociation +*/ +u32 gf_pixel_fmt_to_qt_type(GF_PixelFormat pixfmt); + +/*! +\brief Codec IDs + +Codec ID identifies the stream coding type. The enum is divided into values less than 255, which are equivalent to MPEG-4 systems ObjectTypeIndication. Other values are 4CCs, usually matching ISOMEDIA sample entry types*/ +typedef enum +{ + /*!Never used by PID declarations, but used by filters caps*/ + GF_CODECID_NONE = 0, + /*! codecid for BIFS v1*/ + GF_CODECID_BIFS = 0x01, + /*! codecid for OD v1*/ + GF_CODECID_OD_V1 = 0x01, + /*! codecid for BIFS v2*/ + GF_CODECID_BIFS_V2 = 0x02, + /*! codecid for OD v2*/ + GF_CODECID_OD_V2 = 0x02, + /*! codecid for BIFS InputSensor streams*/ + GF_CODECID_INTERACT = 0x03, + /*! codecid for streams with extended BIFS config*/ + GF_CODECID_BIFS_EXTENDED = 0x04, + /*! codecid for AFX streams with AFXConfig*/ + GF_CODECID_AFX = 0x05, + /*! codecid for Font data streams */ + GF_CODECID_FONT = 0x06, + /*! codecid for synthesized texture streams */ + GF_CODECID_SYNTHESIZED_TEXTURE = 0x07, + /*! codecid for streaming text streams */ + GF_CODECID_TEXT_MPEG4 = 0x08, + /*! codecid for LASeR streams*/ + GF_CODECID_LASER = 0x09, + /*! codecid for SAF streams when stored in MP4 ...*/ + GF_CODECID_SAF = 0x0A, + + /*! codecid for MPEG-4 Video Part 2 streams*/ + GF_CODECID_MPEG4_PART2 = 0x20, + /*! codecid for MPEG-4 Video Part 10 (H.264 | AVC ) streams*/ + GF_CODECID_AVC = 0x21, + /*! codecid for AVC Parameter sets streams*/ + GF_CODECID_AVC_PS = 0x22, + /*! codecid for HEVC video */ + GF_CODECID_HEVC = 0x23, + /*! codecid for H264-SVC streams*/ + GF_CODECID_SVC = 0x24, + /*! codecid for HEVC layered streams*/ + GF_CODECID_LHVC = 0x25, + /*! codecid for H264-SVC streams*/ + GF_CODECID_MVC = 0x29, + /*! codecid for MPEG-4 AAC streams*/ + GF_CODECID_AAC_MPEG4 = 0x40, + /*! codecid for MPEG-2 Visual Simple Profile streams*/ + GF_CODECID_MPEG2_SIMPLE = 0x60, + /*! codecid for MPEG-2 Visual Main Profile streams*/ + GF_CODECID_MPEG2_MAIN = 0x61, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_SNR = 0x62, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_SPATIAL = 0x63, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_HIGH = 0x64, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_422 = 0x65, + /*! codecid for MPEG-2 AAC Main Profile streams*/ + GF_CODECID_AAC_MPEG2_MP = 0x66, + /*! codecid for MPEG-2 AAC Low Complexity Profile streams*/ + GF_CODECID_AAC_MPEG2_LCP = 0x67, + /*! codecid for MPEG-2 AAC Scalable Sampling Rate Profile streams*/ + GF_CODECID_AAC_MPEG2_SSRP = 0x68, + /*! codecid for MPEG-2 Audio Part 3 streams*/ + GF_CODECID_MPEG2_PART3 = 0x69, + /*! codecid for MPEG-1 Video streams*/ + GF_CODECID_MPEG1 = 0x6A, + /*! codecid for MPEG-1 Audio streams, layer 3*/ + GF_CODECID_MPEG_AUDIO = 0x6B, + /*! codecid for JPEG streams*/ + GF_CODECID_JPEG = 0x6C, + /*! codecid for PNG streams*/ + GF_CODECID_PNG = 0x6D, + + GF_CODECID_LAST_MPEG4_MAPPING = 0xFF, + + /*! codecid for JPEG-2000 streams*/ + GF_CODECID_J2K = GF_4CC('j','p','2','k'), + + /*!H263 visual streams*/ + GF_CODECID_S263 = GF_4CC('s','2','6','3'), + GF_CODECID_H263 = GF_4CC('h','2','6','3'), + + /*! codecid for HEVC tiles */ + GF_CODECID_HEVC_TILES = GF_4CC( 'h', 'v', 't', '1' ), + + /*! codecid for EVRC Voice streams*/ + GF_CODECID_EVRC = GF_4CC('s','e','v','c'), + /*! codecid for SMV Voice streams*/ + GF_CODECID_SMV = GF_4CC('s','s','m','v'), + /*! codecid for 13K Voice / QCELP audio streams*/ + GF_CODECID_QCELP = GF_4CC('s','q','c','p'), + /*! codecid for AMR*/ + GF_CODECID_AMR = GF_4CC('s','a','m','r'), + /*! codecid for AMR-WB*/ + GF_CODECID_AMR_WB = GF_4CC('s','a','w','b'), + /*! codecid for EVRC, PacketVideo MUX*/ + GF_CODECID_EVRC_PV = GF_4CC('p','e','v','c'), + + /*! codecid for SMPTE VC-1 Video streams*/ + GF_CODECID_SMPTE_VC1 = GF_4CC('v','c','-','1'), + /*! codecid for Dirac Video streams*/ + GF_CODECID_DIRAC = GF_4CC('d','r','a','c'), + /*! codecid for AC-3 audio streams*/ + 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 Dolby TrueHS audio streams*/ + GF_CODECID_TRUEHD = GF_4CC('m','l','p','a'), + /*! codecid for DRA audio streams*/ + GF_CODECID_DRA = GF_4CC('d','r','a','1'), + /*! codecid for ITU G719 audio streams*/ + GF_CODECID_G719 = GF_4CC('g','7','1','9'), + /*! codecid for DTS Express low bit rate audio*/ + GF_CODECID_DTS_LBR = GF_4CC('d','t','s','e'), + /*! codecid for DTS Coherent Acoustics audio streams*/ + GF_CODECID_DTS_CA = GF_4CC('d','t','s','c'), + /*! codecid for DTS-HD High Resolution audio streams*/ + GF_CODECID_DTS_HD_HR = GF_4CC('d','t','s','h'), + /*! codecid for DTS-HD Master audio streams*/ + GF_CODECID_DTS_HD_MASTER = GF_4CC('d','t','s','l'), + /*! codecid for DTS-X Master audio streams*/ + GF_CODECID_DTS_X = GF_4CC('d','t','s','x'), + + /*! codecid for DVB EPG*/ + GF_CODECID_DVB_EIT = GF_4CC('e','i','t',' '), + + /*! codecid for streaming SVG*/ + GF_CODECID_SVG = GF_4CC('s','g','g',' '), + /*! codecid for streaming SVG + gz*/ + GF_CODECID_SVG_GZ = GF_4CC('s','v','g','z'), + /*! codecid for DIMS (dsi = 3GPP DIMS configuration)*/ + GF_CODECID_DIMS = GF_4CC('d','i','m','s'), + /*! codecid for streaming VTT*/ + GF_CODECID_WEBVTT = GF_4CC('w','v','t','t'), + /*! codecid for streaming simple text*/ + GF_CODECID_SIMPLE_TEXT = GF_4CC('s','t','x','t'), + /*! codecid for meta data streams in text format*/ + GF_CODECID_META_TEXT = GF_4CC('m','e','t','t'), + /*! codecid for meta data streams in XML format*/ + GF_CODECID_META_XML = GF_4CC('m','e','t','x'), + /*! codecid for subtitle streams in text format*/ + GF_CODECID_SUBS_TEXT = GF_4CC('s','b','t','t'), + /*! codecid for subtitle streams in xml format*/ + GF_CODECID_SUBS_XML = GF_4CC('s','t','p','p'), + + /*! codecid for subtitle/text streams in tx3g / apple text format*/ + GF_CODECID_TX3G = GF_4CC( 't', 'x', '3', 'g' ), + + /*! + \brief OGG DecoderConfig + + The DecoderConfig for theora, vorbis, flac and opus contains all intitialization ogg packets for the codec + and is formatted as follows:\n + \code + while (dsi_size) { + bit(16) packet_size; + char packet[packet_size]; + dsi_size -= packet_size; + } + \endcode + */ + /*! codecid for theora video streams*/ + GF_CODECID_THEORA = GF_4CC('t','h','e','u'), + /*! codecid for vorbis audio streams*/ + GF_CODECID_VORBIS = GF_4CC('v','o','r','b'), + /*! codecid for flac audio streams*/ + GF_CODECID_FLAC = GF_4CC('f','l','a','c'), + /*! codecid for speex audio streams*/ + GF_CODECID_SPEEX = GF_4CC('s','p','e','x'), + /*! codecid for opus audio streams*/ + GF_CODECID_OPUS = GF_4CC('O','p','u','s'), + /*! codecid for subpic DVD subtittles - the associated stream type is text*/ + GF_CODECID_SUBPIC = GF_4CC('s','u','b','p'), + /*! codecid for ADPCM audio, as used in AVI*/ + GF_CODECID_ADPCM = GF_4CC('A','P','C','M'), + /*! codecid for IBM CVSD audio, as used in AVI*/ + GF_CODECID_IBM_CVSD = GF_4CC('C','S','V','D'), + /*! codecid for ALAW audio, as used in AVI*/ + GF_CODECID_ALAW = GF_4CC('A','L','A','W'), + /*! codecid for MULAW audio, as used in AVI*/ + GF_CODECID_MULAW = GF_4CC('M','L','A','W'), + /*! codecid for OKI ADPCM audio, as used in AVI*/ + GF_CODECID_OKI_ADPCM = GF_4CC('O','P','C','M'), + /*! codecid for DVI ADPCM audio, as used in AVI*/ + GF_CODECID_DVI_ADPCM = GF_4CC('D','P','C','M'), + /*! codecid for DIGISTD audio, as used in AVI*/ + GF_CODECID_DIGISTD = GF_4CC('D','S','T','D'), + /*! codecid for Yamaha ADPCM audio, as used in AVI*/ + GF_CODECID_YAMAHA_ADPCM = GF_4CC('Y','P','C','M'), + /*! codecid for TrueSpeech audio, as used in AVI*/ + GF_CODECID_DSP_TRUESPEECH = GF_4CC('T','S','P','E'), + /*! codecid for GSM 610 audio, as used in AVI*/ + GF_CODECID_GSM610 = GF_4CC('G','6','1','0'), + /*! codecid for IBM MULAW audio, as used in AVI*/ + GF_CODECID_IBM_MULAW = GF_4CC('I','U','L','W'), + /*! codecid for IBM ALAW audio, as used in AVI*/ + GF_CODECID_IBM_ALAW = GF_4CC('I','A','L','W'), + /*! codecid for IBM ADPCM audio, as used in AVI*/ + GF_CODECID_IBM_ADPCM = GF_4CC('I','P','C','M'), + /*! codecid for Flash/ShockWave streams*/ + GF_CODECID_FLASH = GF_4CC( 'f', 'l', 's', 'h' ), + /*! codecid for RAW media streams. No decoder config associated (config through PID properties)*/ + GF_CODECID_RAW = GF_4CC('R','A','W','M'), + + GF_CODECID_AV1 = GF_4CC('A','V','1',' '), + + GF_CODECID_VP8 = GF_4CC('V','P','0','8'), + GF_CODECID_VP9 = GF_4CC('V','P','0','9'), + GF_CODECID_VP10 = GF_4CC('V','P','1','0'), + + /*MPEG-H audio*/ + GF_CODECID_MPHA = GF_4CC('m','p','h','a'), + /*MPEG-H mux audio*/ + GF_CODECID_MHAS = GF_4CC('m','h','a','s'), + + /*QT ProRes*/ + GF_CODECID_APCH = GF_4CC( 'a', 'p', 'c', 'h' ), + GF_CODECID_APCO = GF_4CC( 'a', 'p', 'c', 'o' ), + GF_CODECID_APCN = GF_4CC( 'a', 'p', 'c', 'n' ), + GF_CODECID_APCS = GF_4CC( 'a', 'p', 'c', 's' ), + GF_CODECID_AP4X = GF_4CC( 'a', 'p', '4', 'x' ), + GF_CODECID_AP4H = GF_4CC( 'a', 'p', '4', 'h' ), + + GF_CODECID_TMCD = GF_4CC('t','m','c','d'), + + /*! codecid for FFV1*/ + GF_CODECID_FFV1 = GF_4CC('f','f','v','1'), + + GF_CODECID_FFMPEG = GF_4CC('F','F','I','D'), + + /*! codecid for VVC video */ + GF_CODECID_VVC = GF_4CC('v','v','c',' '), + GF_CODECID_VVC_SUBPIC = GF_4CC('v','v','c','s'), + + /*! codecid for USAC / xHE-AACv2 audio */ + GF_CODECID_USAC = GF_4CC('u','s','a','c'), + + GF_CODECID_V210 = GF_4CC('v','2','1','0'), + + + /*! codecid for MPEG-1 Audio streams, layer 1*/ + GF_CODECID_MPEG_AUDIO_L1 = GF_4CC('m','p','a','1'), + + //fake codec IDs for RTP + GF_CODECID_FAKE_MP2T = GF_4CC('M','P','2','T') +} GF_CodecID; + +/*! Gets a textual description for the given codecID +\param codecid target codec ID +\return textual description of the stream +*/ +const char *gf_codecid_name(GF_CodecID codecid); + +/*! Enumerates supported codec format +\param idx 0-based index, to incremented at each call +\param short_name pointer for codec name +\param long_name pointer for codec description +\return codec ID +*/ +GF_CodecID gf_codecid_enum(u32 idx, const char **short_name, const char **long_name); + +/*! Gets the associated streamtype for the given codecID +\param codecid target codec ID +\return stream type if known, GF_STREAM_UNKNOWN otherwise +*/ +u32 gf_codecid_type(GF_CodecID codecid); + +/*! Gets alternate ID of codec if any +\param codecid target codec ID +\return alternate codec ID if known, GF_CODECID_NONE otherwise +*/ +GF_CodecID gf_codecid_alt(GF_CodecID codecid); + +/*! Gets the associated ObjectTypeIndication if any for the given codecID +\param codecid target codec ID +\return ObjectTypeIndication if defined, 0 otherwise +*/ +u8 gf_codecid_oti(GF_CodecID codecid); + +/*! Gets the codecID from a given ObjectTypeIndication +\param stream_type stream type of the stream +\param oti ObjectTypeIndication of the stream +\return the codecID for this OTI +*/ +GF_CodecID gf_codecid_from_oti(u32 stream_type, u32 oti); + +/*! Gets the associated 4CC used by isomedia or RFC6381 +\param codecid target codec ID +\return RFC 4CC of codec, 0 if not mapped/known +*/ +u32 gf_codecid_4cc_type(GF_CodecID codecid); + +/*! Gets the codecid given the associated short name +\param cname target codec short name +\return codecid codec ID +*/ +GF_CodecID gf_codecid_parse(const char *cname); + +/*! Gets the raw file ext (one or more, | separated) for the given codecid +\param codecid codec ID +\return returns file extension +*/ +const char *gf_codecid_file_ext(GF_CodecID codecid); + +/*! Gets the raw file mime type for the given codecid +\param codecid codec ID +\return returns file mime type +*/ +const char *gf_codecid_mime(GF_CodecID codecid); + +/*! Gets the codecid from isomedia code point +\param isobmftype isomedia code point +\return codec ID, 0 if not mapped/known +*/ +GF_CodecID gf_codec_id_from_isobmf(u32 isobmftype); + +/*! +\brief AFX Object Code +*/ +enum +{ + /*!3D Mesh Compression*/ + GPAC_AFX_3DMC = 0x00, + /*!Wavelet Subdivision Surface*/ + GPAC_AFX_WAVELET_SUBDIVISION = 0x01, + /*!MeshGrid*/ + GPAC_AFX_MESHGRID = 0x02, + /*!Coordinate Interpolator*/ + GPAC_AFX_COORDINATE_INTERPOLATOR = 0x03, + /*!Orientation Interpolator*/ + GPAC_AFX_ORIENTATION_INTERPOLATOR = 0x04, + /*!Position Interpolator*/ + GPAC_AFX_POSITION_INTERPOLATOR = 0x05, + /*!Octree Image*/ + GPAC_AFX_OCTREE_IMAGE = 0x06, + /*!BBA*/ + GPAC_AFX_BBA = 0x07, + /*!PointTexture*/ + GPAC_AFX_POINT_TEXTURE = 0x08, + /*!3DMC Extension*/ + GPAC_AFX_3DMC_EXT = 0x09, + /*!FootPrint representation*/ + GPAC_AFX_FOOTPRINT = 0x0A, + /*!Animated Mesh Compression*/ + GPAC_AFX_ANIMATED_MESH = 0x0B, + /*!Scalable Complexity*/ + GPAC_AFX_SCALABLE_COMPLEXITY = 0x0C, +}; +/*! Gets a textual description of an AFX stream type +\param afx_code target stream type descriptor +\return textural description of the AFX stream +*/ +const char *gf_stream_type_afx_name(u8 afx_code); + + +/*channel cfg flags - DECODERS MUST OUTPUT STEREO/MULTICHANNEL IN THIS ORDER*/ + +/*! +\brief Audio Channel Configuration + +Audio channel flags for spatialization. + \note Decoders must output stereo/multichannel audio channels in this order in the decoded audio frame. +*/ +enum +{ + /*!Left Audio Channel*/ + GF_AUDIO_CH_FRONT_LEFT = (1), + /*!Right Audio Channel*/ + GF_AUDIO_CH_FRONT_RIGHT = (1<<1), + /*!Center Audio Channel - may also be used to signal monophonic audio*/ + GF_AUDIO_CH_FRONT_CENTER = (1<<2), + /*!LFE Audio Channel*/ + GF_AUDIO_CH_LFE = (1<<3), + /*!Back Left Audio Channel*/ + GF_AUDIO_CH_SURROUND_LEFT = (1 << 4), + /*!Back Right Audio Channel*/ + GF_AUDIO_CH_SURROUND_RIGHT = (1 << 5), + /*Between left and center in front Audio Channel*/ + GF_AUDIO_CH_FRONT_CENTER_LEFT = (1 << 6), + /*Between right and center in front Audio Channel*/ + GF_AUDIO_CH_FRONT_CENTER_RIGHT = (1 << 7), + /*!Side Left Audio Channel*/ + GF_AUDIO_CH_REAR_SURROUND_LEFT = (1<<8), + /*!Side Right Audio Channel*/ + GF_AUDIO_CH_REAR_SURROUND_RIGHT = (1<<9), + /*!Back Center Audio Channel*/ + GF_AUDIO_CH_REAR_CENTER = (1 << 10), + /*!Left surround direct Channel*/ + GF_AUDIO_CH_SURROUND_DIRECT_LEFT = (1 << 11), + /*!Right surround direct Channel*/ + GF_AUDIO_CH_SURROUND_DIRECT_RIGHT = (1 << 12), + /*!Left side surround Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_LEFT = (1 << 13), + /*!Right side surround Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_RIGHT = (1 << 14), + /*!Left wide front Channel*/ + GF_AUDIO_CH_WIDE_FRONT_LEFT = (1 << 15), + /*!Right wide front Channel*/ + GF_AUDIO_CH_WIDE_FRONT_RIGHT = (1 << 16), + /*!Left front top Channel*/ + GF_AUDIO_CH_FRONT_TOP_LEFT = (1 << 17), + /*!Right front top Channel*/ + GF_AUDIO_CH_FRONT_TOP_RIGHT = (1 << 18), + /*!Center front top Channel*/ + GF_AUDIO_CH_FRONT_TOP_CENTER = (1 << 19), + /*!Left surround top Channel*/ + GF_AUDIO_CH_SURROUND_TOP_LEFT = (1 << 20), + /*!Right surround top Channel*/ + GF_AUDIO_CH_SURROUND_TOP_RIGHT = (1 << 21), + /*!Center surround top Channel*/ + GF_AUDIO_CH_REAR_CENTER_TOP = (1 << 22), + /*!Left side surround top Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_TOP_LEFT = (1 << 23), + /*!Left side surround top Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_TOP_RIGHT = (1 << 24), + /*!Center surround top Channel*/ + GF_AUDIO_CH_CENTER_SURROUND_TOP = (1 << 25), + /*!LFE 2 Channel*/ + GF_AUDIO_CH_LFE2 = (1 << 26), + /*!Left front bottom Channel*/ + GF_AUDIO_CH_FRONT_BOTTOM_LEFT = (1 << 27), + /*!Right front bottom Channel*/ + GF_AUDIO_CH_FRONT_BOTTOM_RIGHT = (1 << 28), + /*!Center front bottom Channel*/ + GF_AUDIO_CH_FRONT_BOTTOM_CENTER = (1 << 29), + /*!Left surround bottom Channel*/ + GF_AUDIO_CH_SURROUND_BOTTOM_LEFT = (1 << 30), + /*!Right surround bottom Channel*/ + GF_AUDIO_CH_SURROUND_BOTTOM_RIGHT = 0x80000000 //(1 << 31) +}; +/*64 bit flags are defined as macro to avoid msvc compil warnings*/ +/*!Left edge of screen Channel*/ +#define GF_AUDIO_CH_SCREEN_EDGE_LEFT 0x2000000000ULL +/*!Right edge of screen Channel*/ +#define GF_AUDIO_CH_SCREEN_EDGE_RIGHT 0x4000000000ULL +/*!left back surround Channel*/ +#define GF_AUDIO_CH_BACK_SURROUND_LEFT 0x20000000000ULL +/*!right back surround Channel*/ +#define GF_AUDIO_CH_BACK_SURROUND_RIGHT 0x40000000000ULL + + +/*! +\brief Audio Sample format + + Audio sample bit format. +*/ +typedef enum +{ + /*! sample = unsigned byte, interleaved channels*/ + GF_AUDIO_FMT_U8 = 1, + /*! sample = signed short Little Endian, interleaved channels*/ + GF_AUDIO_FMT_S16, + /*! sample = signed short Big Endian, interleaved channels*/ + GF_AUDIO_FMT_S16_BE, + /*! sample = signed integer, interleaved channels*/ + GF_AUDIO_FMT_S32, + /*! sample = 1 float, interleaved channels*/ + GF_AUDIO_FMT_FLT, + /*! sample = 1 double, interleaved channels*/ + GF_AUDIO_FMT_DBL, + /*! sample = signed integer, interleaved channels*/ + GF_AUDIO_FMT_S24, + /*! not a format, indicates the value of last packed format*/ + GF_AUDIO_FMT_LAST_PACKED, + /*! sample = unsigned byte, planar channels*/ + GF_AUDIO_FMT_U8P, + /*! sample = signed short, planar channels*/ + GF_AUDIO_FMT_S16P, + /*! sample = signed integer, planar channels*/ + GF_AUDIO_FMT_S32P, + /*! sample = 1 float, planar channels*/ + GF_AUDIO_FMT_FLTP, + /*! sample = 1 double, planar channels*/ + GF_AUDIO_FMT_DBLP, + /*! sample = signed integer, planar channels*/ + GF_AUDIO_FMT_S24P, +} GF_AudioFormat; + + +/*! enumerates GPAC audio formats +\param af_name name of the audio format +\return audio format code +*/ +GF_AudioFormat gf_audio_fmt_parse(const char *af_name); + +/*! gets name of audio formats +\param afmt audio format code +\return audio format name +*/ +const char *gf_audio_fmt_name(GF_AudioFormat afmt); + +/*! gets short name of audio formats, as used for file extensions +\param afmt audio format code +\return audio format short name +*/ +const char *gf_audio_fmt_sname(GF_AudioFormat afmt); + + +/*! gets the list of all supported audio format names +\return list of supported audio format names +*/ +const char *gf_audio_fmt_all_names(); + +/*! gets the list of all supported audio format names +\return list of supported audio format short names +*/ +const char *gf_audio_fmt_all_shortnames(); + +/*! returns number of bots per sample for the given format +\param afmt desired audio format +\return bit depth of format +*/ +u32 gf_audio_fmt_bit_depth(GF_AudioFormat afmt); + +/*! Check if a given audio format is planar +\param afmt desired audio format +\return GF_TRUE if the format is planar, false otherwise +*/ +Bool gf_audio_fmt_is_planar(GF_AudioFormat afmt); + +/*! Returns audio format for raw audio ISOBMFF sample description type +\param msubtype ISOBMFF sample description type +\return the associated audio format or 0 if not known + */ +GF_AudioFormat gf_audio_fmt_from_isobmf(u32 msubtype); + +/*! Returns QTFF/ISOBMFF sample description 4CC of an audio format +\param afmt audio format to query +\return the associated 4CC or 0 if not known + */ +u32 gf_audio_fmt_to_isobmf(GF_AudioFormat afmt); + +/*! enumerates audio formats +\param idx index of the audio format, 0-based +\param name name of the audio format +\param fileext file extension of the pixel format +\param desc audio format description +\return audio format or 0 if no more audio formats are available +*/ +GF_AudioFormat gf_audio_fmt_enum(u32 *idx, const char **name, const char **fileext, const char **desc); + +/*! get CICP layout code point from audio configuration +\param nb_chan number of channels +\param nb_surr number of surround channels +\param nb_lfe number of LFE channels +\return CICP layout code point format or 0 if unknown +*/ +u32 gf_audio_fmt_get_cicp_layout(u32 nb_chan, u32 nb_surr, u32 nb_lfe); + +/*! get channel layout mask from CICP layout +\param cicp_layout channel layout CICP code point +\return layout mask or 0 if unknown +*/ +u64 gf_audio_fmt_get_layout_from_cicp(u32 cicp_layout); + +/*! get CICP layout name +\param cicp_layout channel layout CICP code point +\return name of layout of "unknown" if unknown +*/ +const char *gf_audio_fmt_get_layout_name_from_cicp(u32 cicp_layout); + +/*! get channel layout name +\param chan_layout channel layout mask +\return name of layout of "unknown" if unknown +*/ +const char *gf_audio_fmt_get_layout_name(u64 chan_layout); + + +/*! get channel layout from name +\param name channel layout name +\return channel layout mask +*/ +u64 gf_audio_fmt_get_layout_from_name(const char *name); + +/*! get CICP layout value from channel layout mask +\param chan_layout channel layout mask +\return CICP code point or 255 if unknown +*/ +u32 gf_audio_fmt_get_cicp_from_layout(u64 chan_layout); + +/*! get channel count from channel layout +\param chan_layout channel layout mask +\return number of channels in this layout +*/ +u32 gf_audio_fmt_get_num_channels_from_layout(u64 chan_layout); + +/*! get dloby chanmap value from cicp layout +\param cicp_layout channel CICP layout +\return dolby chanmap +*/ +u16 gf_audio_fmt_get_dolby_chanmap(u32 cicp_layout); + +/*! 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 +\param ch_mask set t o audio channel mask, as used in GPAC - may be NULL +\return CICP code point, or 0 if no more to enumerate*/ +u32 gf_audio_fmt_cicp_enum(u32 idx, const char **short_name, u64 *ch_mask); + +/*! Color primaries as defined by ISO/IEC 23001-8 / 23091-2 + */ +typedef enum +{ + GF_COLOR_PRIM_RESERVED0 = 0, + GF_COLOR_PRIM_BT709 = 1, + GF_COLOR_PRIM_UNSPECIFIED = 2, + GF_COLOR_PRIM_RESERVED = 3, + GF_COLOR_PRIM_BT470M = 4, + GF_COLOR_PRIM_BT470BG = 5, + GF_COLOR_PRIM_SMPTE170M = 6, + GF_COLOR_PRIM_SMPTE240M = 7, + GF_COLOR_PRIM_FILM = 8, + GF_COLOR_PRIM_BT2020 = 9, + GF_COLOR_PRIM_SMPTE428 = 10, + GF_COLOR_PRIM_SMPTE431 = 11, + GF_COLOR_PRIM_SMPTE432 = 12, + GF_COLOR_PRIM_EBU3213 = 22 +} GF_ColorPrimaries; + +/*! Color Transfer Characteristics as defined by ISO/IEC 23001-8 / 23091-2 +*/ +typedef enum +{ + GF_COLOR_TRC_RESERVED0 = 0, + GF_COLOR_TRC_BT709 = 1, + GF_COLOR_TRC_UNSPECIFIED = 2, + GF_COLOR_TRC_RESERVED = 3, + GF_COLOR_TRC_GAMMA22 = 4, + GF_COLOR_TRC_GAMMA28 = 5, + GF_COLOR_TRC_SMPTE170M = 6, + GF_COLOR_TRC_SMPTE240M = 7, + GF_COLOR_TRC_LINEAR = 8, + GF_COLOR_TRC_LOG = 9, + GF_COLOR_TRC_LOG_SQRT = 10, + GF_COLOR_TRC_IEC61966_2_4 = 11, + GF_COLOR_TRC_BT1361_ECG = 12, + GF_COLOR_TRC_IEC61966_2_1 = 13, + GF_COLOR_TRC_BT2020_10 = 14, + GF_COLOR_TRC_BT2020_12 = 15, + GF_COLOR_TRC_SMPTE2084 = 16, + GF_COLOR_TRC_SMPTE428 = 17, + GF_COLOR_TRC_ARIB_STD_B67 = 18 +} GF_ColorTransferCharacteristic; + +/*! MatrixCoefficients as defined by ISO/IEC 23001-8 / 23091-2 +*/ +typedef enum +{ + GF_COLOR_MX_RGB = 0, + GF_COLOR_MX_BT709 = 1, + GF_COLOR_MX_UNSPECIFIED = 2, + GF_COLOR_MX_RESERVED = 3, + GF_COLOR_MX_FCC47 = 4, + GF_COLOR_MX_BT470BG = 5, + GF_COLOR_MX_SMPTE170M = 6, + GF_COLOR_MX_SMPTE240M = 7, + GF_COLOR_MX_YCGCO = 8, + GF_COLOR_MX_BT2020_NCL = 9, + GF_COLOR_MX_BT2020_CL = 10, + GF_COLOR_MX_SMPTE2085 = 11, +} GF_ColorMatrixCoefficients; + +/*! Chroma location values, semantics from CoreVideo - direct match of values to FFmpeg*/ +typedef enum { + /*! Chroma location is not known*/ + GF_CHROMALOC_UNKNOWN=0, + /*! Chroma sample is horizontally co-sited with the left column of luma samples, but centered vertically (MPEG-2/4 4:2:0, H.264 default for 4:2:0)*/ + GF_CHROMALOC_LEFT, + /*! The chroma sample is fully centered ( MPEG-1 4:2:0, JPEG 4:2:0, H.263 4:2:0)*/ + GF_CHROMALOC_CENTER, + /*! The chroma sample is co-sited with the top-left luma sample (ITU-R 601, SMPTE 274M 296M S314M(DV 4:1:1), mpeg2 4:2:2)*/ + GF_CHROMALOC_TOPLEFT, + /*! The chroma sample is horizontally centered, but is co-sited with the top row of luma samples*/ + GF_CHROMALOC_TOP, + /*! The chroma sample is co-sited with the bottom-left luma sample*/ + GF_CHROMALOC_BOTTOMLEFT, + /*! The chroma sample is horizontally centered, but is co-sited with the bottom row of luma samples*/ + GF_CHROMALOC_BOTTOM, + /*! The Cr and Cb samples are alternatingly co-sited with the left luma samples of the same field */ + GF_CHROMALOC_DV420, +} GF_ChromaLocation; + + +/*DIMS unit flags */ +/*! +\brief DIMS Unit header flags + +DIMS Unit header flags as 3GPP TS 26.142. + */ +enum +{ + /*!S: is-Scene: DIMS unit contains a complete document (svg)*/ + GF_DIMS_UNIT_S = 1, + /*!M: is-RAP: DIMS unit is a random access point*/ + GF_DIMS_UNIT_M = 1<<1, + /*!I: is-Redundant: DIMS unit is made of redundant data*/ + GF_DIMS_UNIT_I = 1<<2, + /*!D: redundant-exit: DIMS unit is the end of redundant data*/ + GF_DIMS_UNIT_D = 1<<3, + /*!P: priority: DIMS unit is high priority*/ + GF_DIMS_UNIT_P = 1<<4, + /*!C: compressed: DIMS unit is compressed*/ + GF_DIMS_UNIT_C = 1<<5 +}; + + +/*! AVC NAL unit types */ +enum +{ + /*! Non IDR AVC slice*/ + GF_AVC_NALU_NON_IDR_SLICE = 1, + /*! DP_A AVC slice*/ + GF_AVC_NALU_DP_A_SLICE = 2, + /*! DP_B AVC slice*/ + GF_AVC_NALU_DP_B_SLICE = 3, + /*! DP_C AVC slice*/ + GF_AVC_NALU_DP_C_SLICE = 4, + /*! IDR AVC slice*/ + GF_AVC_NALU_IDR_SLICE = 5, + /*! SEI Message*/ + GF_AVC_NALU_SEI = 6, + /*! Sequence Parameter Set */ + GF_AVC_NALU_SEQ_PARAM = 7, + /*! Picture Parameter Set*/ + GF_AVC_NALU_PIC_PARAM = 8, + /*! Access Unit delimiter*/ + GF_AVC_NALU_ACCESS_UNIT = 9, + /*! End of Sequence*/ + GF_AVC_NALU_END_OF_SEQ = 10, + /*! End of stream*/ + GF_AVC_NALU_END_OF_STREAM = 11, + /*! Filler data*/ + GF_AVC_NALU_FILLER_DATA = 12, + /*! Sequence Parameter Set Extension*/ + GF_AVC_NALU_SEQ_PARAM_EXT = 13, + /*! SVC preffix*/ + GF_AVC_NALU_SVC_PREFIX_NALU = 14, + /*! SVC subsequence parameter set*/ + GF_AVC_NALU_SVC_SUBSEQ_PARAM = 15, + /*! Auxiliary slice*/ + GF_AVC_NALU_SLICE_AUX = 19, + /*! SVC slice*/ + GF_AVC_NALU_SVC_SLICE = 20, + /*! View and dependency representation delimiter */ + GF_AVC_NALU_VDRD = 24, + /*! Dolby Vision RPU */ + GF_AVC_NALU_DV_RPU = 28, + /*! Dolby Vision EL */ + GF_AVC_NALU_DV_EL = 30, + + /*! NALU-FF extractor */ + GF_AVC_NALU_FF_AGGREGATOR=30, + /*! NALU-FF aggregator */ + GF_AVC_NALU_FF_EXTRACTOR=31, +}; + + +/*! AVC slice types */ +enum +{ + /*! P slice*/ + GF_AVC_TYPE_P = 0, + /*! B slice*/ + GF_AVC_TYPE_B = 1, + /*! I slice*/ + GF_AVC_TYPE_I = 2, + /*! SP slice*/ + GF_AVC_TYPE_SP = 3, + /*! SI slice*/ + GF_AVC_TYPE_SI = 4, + /*! Type2 P slice*/ + GF_AVC_TYPE2_P = 5, + /*! Type2 B slice*/ + GF_AVC_TYPE2_B = 6, + /*! Type2 I slice*/ + GF_AVC_TYPE2_I = 7, + /*! Type2 SP slice*/ + GF_AVC_TYPE2_SP = 8, + /*! Type2 SI slice*/ + GF_AVC_TYPE2_SI = 9 +}; + +/*! Scheme Type only used internally to signal HLS sample AES in TS */ +#define GF_HLS_SAMPLE_AES_SCHEME GF_4CC('s','a','e','s') + + +/*! HEVC NAL unit types */ +enum +{ + /*! Trail N HEVC slice*/ + GF_HEVC_NALU_SLICE_TRAIL_N = 0, + /*! Trail R HEVC slice*/ + GF_HEVC_NALU_SLICE_TRAIL_R = 1, + /*! TSA N HEVC slice*/ + GF_HEVC_NALU_SLICE_TSA_N = 2, + /*! TSA R HEVC slice*/ + GF_HEVC_NALU_SLICE_TSA_R = 3, + /*! STSA N HEVC slice*/ + GF_HEVC_NALU_SLICE_STSA_N = 4, + /*! STSA R HEVC slice*/ + GF_HEVC_NALU_SLICE_STSA_R = 5, + /*! RADL N HEVC slice*/ + GF_HEVC_NALU_SLICE_RADL_N = 6, + /*! RADL R HEVC slice*/ + GF_HEVC_NALU_SLICE_RADL_R = 7, + /*! RASL N HEVC slice*/ + GF_HEVC_NALU_SLICE_RASL_N = 8, + /*! RASL R HEVC slice*/ + GF_HEVC_NALU_SLICE_RASL_R = 9, + /*! Reserved non-IRAP SLNR VCL NAL unit types*/ + GF_HEVC_NALU_SLICE_RSV_VCL_N10 = 10, + GF_HEVC_NALU_SLICE_RSV_VCL_N12 = 12, + GF_HEVC_NALU_SLICE_RSV_VCL_N14 = 14, + /*! Reserved non-IRAP sub-layer reference VCL NAL unit types*/ + GF_HEVC_NALU_SLICE_RSV_VCL_R11 = 11, + GF_HEVC_NALU_SLICE_RSV_VCL_R13 = 13, + GF_HEVC_NALU_SLICE_RSV_VCL_R15 = 15, + /*! BLA LP HEVC slice*/ + GF_HEVC_NALU_SLICE_BLA_W_LP = 16, + /*! BLA DLP HEVC slice*/ + GF_HEVC_NALU_SLICE_BLA_W_DLP = 17, + /*! BLA no LP HEVC slice*/ + GF_HEVC_NALU_SLICE_BLA_N_LP = 18, + /*! IDR DLP HEVC slice*/ + GF_HEVC_NALU_SLICE_IDR_W_DLP = 19, + /*! IDR HEVC slice*/ + GF_HEVC_NALU_SLICE_IDR_N_LP = 20, + /*! CRA HEVC slice*/ + GF_HEVC_NALU_SLICE_CRA = 21, + /*! Video Parameter Set*/ + GF_HEVC_NALU_VID_PARAM = 32, + /*! Sequence Parameter Set*/ + GF_HEVC_NALU_SEQ_PARAM = 33, + /*! Picture Parameter Set*/ + GF_HEVC_NALU_PIC_PARAM = 34, + /*! AU delimiter*/ + GF_HEVC_NALU_ACCESS_UNIT = 35, + /*! End of sequence*/ + GF_HEVC_NALU_END_OF_SEQ = 36, + /*! End of stream*/ + GF_HEVC_NALU_END_OF_STREAM = 37, + /*! Filler Data*/ + GF_HEVC_NALU_FILLER_DATA = 38, + /*! prefix SEI message*/ + GF_HEVC_NALU_SEI_PREFIX = 39, + /*! suffix SEI message*/ + GF_HEVC_NALU_SEI_SUFFIX = 40, + + /*! NALU-FF aggregator */ + GF_HEVC_NALU_FF_AGGREGATOR=48, + /*! NALU-FF extractor */ + GF_HEVC_NALU_FF_EXTRACTOR=49, + + /*! Dolby Vision RPU */ + GF_HEVC_NALU_DV_RPU = 62, + /*! Dolby Vision EL */ + GF_HEVC_NALU_DV_EL = 63 +}; + + + +/*! VVC NAL unit types - vtm10) */ +enum +{ + /*! Trail N VVC slice*/ + GF_VVC_NALU_SLICE_TRAIL = 0, + /*! STSA N VVC slice*/ + GF_VVC_NALU_SLICE_STSA = 1, + /*! STSA N VVC slice*/ + GF_VVC_NALU_SLICE_RADL = 2, + /*! STSA N VVC slice*/ + GF_VVC_NALU_SLICE_RASL = 3, + /*! IDR with RADL VVC slice*/ + GF_VVC_NALU_SLICE_IDR_W_RADL = 7, + /*! IDR DLP VVC slice*/ + GF_VVC_NALU_SLICE_IDR_N_LP = 8, + /*! CRA VVC slice*/ + GF_VVC_NALU_SLICE_CRA = 9, + /*! CRA VVC slice*/ + GF_VVC_NALU_SLICE_GDR = 10, + + /*! Operation Point Info */ + GF_VVC_NALU_OPI = 12, + /*! Decode Parameter Set*/ + GF_VVC_NALU_DEC_PARAM = 13, + /*! Video Parameter Set*/ + GF_VVC_NALU_VID_PARAM = 14, + /*! Sequence Parameter Set*/ + GF_VVC_NALU_SEQ_PARAM = 15, + /*! Picture Parameter Set*/ + GF_VVC_NALU_PIC_PARAM = 16, + /*! APS prefix */ + GF_VVC_NALU_APS_PREFIX = 17, + /*! APS suffix */ + GF_VVC_NALU_APS_SUFFIX = 18, + /*! Picture Header*/ + GF_VVC_NALU_PIC_HEADER = 19, + /*! AU delimiter*/ + GF_VVC_NALU_ACCESS_UNIT = 20, + /*! End of sequence*/ + GF_VVC_NALU_END_OF_SEQ = 21, + /*! End of stream*/ + GF_VVC_NALU_END_OF_STREAM = 22, + /*! prefix SEI message*/ + GF_VVC_NALU_SEI_PREFIX = 23, + /*! suffix SEI message*/ + GF_VVC_NALU_SEI_SUFFIX = 24, + /*! Filler Data*/ + GF_VVC_NALU_FILLER_DATA = 25, +}; +/*! Number of defined QCELP rate sizes*/ +static const unsigned int GF_QCELP_RATE_TO_SIZE_NB = 7; +/*! QCELP rate sizes - note that these sizes INCLUDE the rate_type header byte*/ +static const unsigned int GF_QCELP_RATE_TO_SIZE [] = {0, 1, 1, 4, 2, 8, 3, 17, 4, 35, 5, 8, 14, 1}; + +/*! Number of defined EVRC rate sizes*/ +static const unsigned int GF_SMV_EVRC_RATE_TO_SIZE_NB = 6; +/*! EVRC rate sizes - note that these sizes INCLUDE the rate_type header byte*/ +static const unsigned int GF_SMV_EVRC_RATE_TO_SIZE [] = {0, 1, 1, 3, 2, 6, 3, 11, 4, 23, 5, 1}; + +/*! AMR frame sizes*/ +static const unsigned int GF_AMR_FRAME_SIZE[16] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0 }; +/*! AMR WB frame sizes*/ +static const unsigned int GF_AMR_WB_FRAME_SIZE[16] = { 17, 23, 32, 36, 40, 46, 50, 58, 60, 5, 5, 0, 0, 0, 0, 0 }; + + +/*! out-of-band sample description index for 3GPP (128 and 255 reserved in RFC)*/ +#define GF_RTP_TX3G_SIDX_OFFSET 129 + + +/*! RFC6381 codec name max length*/ +#define RFC6381_CODEC_NAME_SIZE_MAX 100 + + +/*! ID3v2 tags*/ +typedef enum { + GF_ID3V2_FRAME_AENC = GF_4CC('A','E','N','C'), + GF_ID3V2_FRAME_APIC = GF_4CC('A','P','I','C'), + GF_ID3V2_FRAME_COMM = GF_4CC('C','O','M','M'), + GF_ID3V2_FRAME_COMR = GF_4CC('C','O','M','R'), + GF_ID3V2_FRAME_ENCR = GF_4CC('E','N','C','R'), + GF_ID3V2_FRAME_EQUA = GF_4CC('E','Q','U','A'), + GF_ID3V2_FRAME_ETCO = GF_4CC('E','T','C','O'), + GF_ID3V2_FRAME_GEOB = GF_4CC('G','E','O','B'), + GF_ID3V2_FRAME_GRID = GF_4CC('G','R','I','D'), + GF_ID3V2_FRAME_IPLS = GF_4CC('I','P','L','S'), + GF_ID3V2_FRAME_LINK = GF_4CC('L','I','N','K'), + GF_ID3V2_FRAME_MCDI = GF_4CC('M','C','D','I'), + GF_ID3V2_FRAME_MLLT = GF_4CC('M','L','L','T'), + GF_ID3V2_FRAME_OWNE = GF_4CC('O','W','N','E'), + GF_ID3V2_FRAME_PRIV = GF_4CC('P','R','I','V'), + GF_ID3V2_FRAME_PCNT = GF_4CC('P','C','N','T'), + GF_ID3V2_FRAME_POPM = GF_4CC('P','O','P','M'), + GF_ID3V2_FRAME_POSS = GF_4CC('P','O','S','S'), + GF_ID3V2_FRAME_RBUF = GF_4CC('R','B','U','F'), + GF_ID3V2_FRAME_RVAD = GF_4CC('R','V','A','D'), + GF_ID3V2_FRAME_RVRB = GF_4CC('R','V','R','B'), + GF_ID3V2_FRAME_SYLT = GF_4CC('S','Y','L','T'), + GF_ID3V2_FRAME_SYTC = GF_4CC('S','Y','T','C'), + GF_ID3V2_FRAME_TALB = GF_4CC('T','A','L','B'), + GF_ID3V2_FRAME_TBPM = GF_4CC('T','B','P','M'), + GF_ID3V2_FRAME_TCAT = GF_4CC('T','C','A','T'), + GF_ID3V2_FRAME_TCMP = GF_4CC('T','C','M','P'), + GF_ID3V2_FRAME_TCOM = GF_4CC('T','C','O','M'), + GF_ID3V2_FRAME_TCON = GF_4CC('T','C','O','N'), + GF_ID3V2_FRAME_TCOP = GF_4CC('T','C','O','P'), + GF_ID3V2_FRAME_TDAT = GF_4CC('T','D','A','T'), + GF_ID3V2_FRAME_TDES = GF_4CC('T','D','E','S'), + GF_ID3V2_FRAME_TDLY = GF_4CC('T','D','L','Y'), + GF_ID3V2_FRAME_TDRC = GF_4CC('T','D','R','C'), + GF_ID3V2_FRAME_TENC = GF_4CC('T','E','N','C'), + GF_ID3V2_FRAME_TEXT = GF_4CC('T','E','X','T'), + GF_ID3V2_FRAME_TFLT = GF_4CC('T','F','L','T'), + GF_ID3V2_FRAME_TIME = GF_4CC('T','I','M','E'), + GF_ID3V2_FRAME_TIT1 = GF_4CC('T','I','T','1'), + GF_ID3V2_FRAME_TIT2 = GF_4CC('T','I','T','2'), + GF_ID3V2_FRAME_TIT3 = GF_4CC('T','I','T','3'), + GF_ID3V2_FRAME_TKEY = GF_4CC('T','K','E','Y'), + GF_ID3V2_FRAME_TKWD = GF_4CC('T','K','W','D'), + GF_ID3V2_FRAME_TLAN = GF_4CC('T','L','A','N'), + GF_ID3V2_FRAME_TLEN = GF_4CC('T','L','E','N'), + GF_ID3V2_FRAME_TMED = GF_4CC('T','M','E','D'), + GF_ID3V2_FRAME_TOAL = GF_4CC('T','O','A','L'), + GF_ID3V2_FRAME_TOFN = GF_4CC('T','O','F','N'), + GF_ID3V2_FRAME_TOLY = GF_4CC('T','O','L','Y'), + GF_ID3V2_FRAME_TOPE = GF_4CC('T','O','P','E'), + GF_ID3V2_FRAME_TORY = GF_4CC('T','O','R','Y'), + GF_ID3V2_FRAME_TOWN = GF_4CC('T','O','W','N'), + GF_ID3V2_FRAME_TPE1 = GF_4CC('T','P','E','1'), + GF_ID3V2_FRAME_TPE2 = GF_4CC('T','P','E','2'), + GF_ID3V2_FRAME_TPE3 = GF_4CC('T','P','E','3'), + GF_ID3V2_FRAME_TPE4 = GF_4CC('T','P','E','4'), + GF_ID3V2_FRAME_TPOS = GF_4CC('T','P','E','5'), + GF_ID3V2_FRAME_TPUB = GF_4CC('T','P','U','B'), + GF_ID3V2_FRAME_TRCK = GF_4CC('T','R','C','K'), + GF_ID3V2_FRAME_TRDA = GF_4CC('T','R','D','A'), + GF_ID3V2_FRAME_TRSN = GF_4CC('T','R','S','N'), + GF_ID3V2_FRAME_TRSO = GF_4CC('T','R','S','O'), + GF_ID3V2_FRAME_TSIZ = GF_4CC('T','S','I','Z'), + GF_ID3V2_FRAME_TSO2 = GF_4CC('T','S','O','2'), + GF_ID3V2_FRAME_TSOA = GF_4CC('T','S','O','A'), + GF_ID3V2_FRAME_TSOC = GF_4CC('T','S','O','C'), + GF_ID3V2_FRAME_TSOT = GF_4CC('T','S','O','T'), + GF_ID3V2_FRAME_TSOP = GF_4CC('T','S','O','P'), + GF_ID3V2_FRAME_TSRC = GF_4CC('T','S','R','C'), + GF_ID3V2_FRAME_TSSE = GF_4CC('T','S','S','E'), + GF_ID3V2_FRAME_TYER = GF_4CC('T','Y','E','R'), + GF_ID3V2_FRAME_TXXX = GF_4CC('T','X','X','X'), + GF_ID3V2_FRAME_UFID = GF_4CC('U','F','I','D'), + GF_ID3V2_FRAME_USER = GF_4CC('U','S','E','R'), + GF_ID3V2_FRAME_USLT = GF_4CC('U','S','L','T'), + GF_ID3V2_FRAME_WCOM = GF_4CC('W','C','O','M'), + GF_ID3V2_FRAME_WCOP = GF_4CC('W','C','O','P'), + GF_ID3V2_FRAME_WOAF = GF_4CC('W','O','A','F'), + GF_ID3V2_FRAME_WOAR = GF_4CC('W','O','A','R'), + GF_ID3V2_FRAME_WOAS = GF_4CC('W','O','A','S'), + GF_ID3V2_FRAME_WORS = GF_4CC('W','O','R','S'), + GF_ID3V2_FRAME_WPAY = GF_4CC('W','P','A','Y'), + GF_ID3V2_FRAME_WPUB = GF_4CC('W','P','U','B'), + GF_ID3V2_FRAME_WXXX = GF_4CC('W','X','X','X'), +} GF_ID3v2FrameType; + +/*! tag base types*/ +enum +{ + /*! tag is a string*/ + GF_ITAG_STR=0, + /*! tag is an 8 bit int*/ + GF_ITAG_INT8, + /*! tag is a 16 bit int*/ + GF_ITAG_INT16, + /*! tag is a 32 bit int*/ + GF_ITAG_INT32, + /*! tag is an 64 bits int*/ + GF_ITAG_INT64, + /*! tag is a boolean (8bit) */ + GF_ITAG_BOOL, + /*! tag is ID3 genre tag, either 32 bit int or string*/ + GF_ITAG_ID3_GENRE, + /*! tag is an fraction on 6 bytes (first 2 unused)*/ + GF_ITAG_FRAC6, + /*! tag is an fraction on 8 bytes (first 2 and last 2 unused)*/ + GF_ITAG_FRAC8, + /*! tag is a file*/ + GF_ITAG_FILE, +}; +/*! finds a tag by its ID3 value + \param id3tag ID3 tag value + \return corresponding tag index, -1 if not found +*/ +s32 gf_itags_find_by_id3tag(u32 id3tag); + +/*! finds a tag by its itunes value + \param itag itunes tag value + \return corresponding tag index, -1 if not found +*/ +s32 gf_itags_find_by_itag(u32 itag); + +/*! finds a tag by its name + \param tag_name tag name + \return corresponding tag index, -1 if not found +*/ +s32 gf_itags_find_by_name(const char *tag_name); + +/*! gets tag associated type + \param tag_idx tag index + \return corresponding tag type, -1 if error +*/ +s32 gf_itags_get_type(u32 tag_idx); + +/*! gets tag associated name + \param tag_idx tag index + \return corresponding tag name, NULL if error +*/ +const char *gf_itags_get_name(u32 tag_idx); + +/*! gets tag associated alternative names + \param tag_idx tag index + \return corresponding tag name, NULL if none +*/ +const char *gf_itags_get_alt_name(u32 tag_idx); + +/*! gets tag associated itunes tag + \param tag_idx tag index + \return corresponding itunes tag, 0 if error +*/ +u32 gf_itags_get_itag(u32 tag_idx); + +/*! gets tag associated id3 tag + \param tag_idx tag index + \return corresponding id3 tag, 0 if error +*/ +u32 gf_itags_get_id3tag(u32 tag_idx); + +/*! enumerates tags + \param tag_idx tag index, increased at each call + \param itag set to itunes tag value - may be NULL + \param id3tag set to ID3 tag value - may be NULL + \param type set to tag type - may be NULL + \return tag name, NULL if error +*/ +const char *gf_itags_enum_tags(u32 *tag_idx, u32 *itag, u32 *id3tag, u32 *type); + +/*! gets genre name by genre tag +\param tag genre tag (from 1 to max number of ID3 genres) +\return genre name, NULL if not found*/ +const char *gf_id3_get_genre(u32 tag); + +/*! gets genre tag by genre name +\param name genre name +\return genre tag, 0 if not found*/ +u32 gf_id3_get_genre_tag(const char *name); + + +/*! meta types from box_code_meta.c - fileimport.c */ +enum { + + GF_META_ITEM_TYPE_MIME = GF_4CC('m', 'i', 'm', 'e'), + GF_META_ITEM_TYPE_URI = GF_4CC('u', 'r', 'i', ' '), + GF_META_ITEM_TYPE_PICT = GF_4CC('p', 'i', 'c', 't'), + + GF_META_TYPE_SVG = GF_4CC('s','v','g',' '), + GF_META_TYPE_SVGZ = GF_4CC('s','v','g','z'), + GF_META_TYPE_SMIL = GF_4CC('s','m','i','l'), + GF_META_TYPE_SMLZ = GF_4CC('s','m','l','z'), + GF_META_TYPE_X3D = GF_4CC('x','3','d',' '), + GF_META_TYPE_X3DZ = GF_4CC('x','3','d','z'), + GF_META_TYPE_XMTA = GF_4CC('x','m','t','a'), + GF_META_TYPE_XMTZ = GF_4CC('x','m','t','z'), + + GF_META_TYPE_RVCI = GF_4CC('r','v','c','i'), + +}; + + +/*! meta types from box_code_meta.c - fileimport.c */ +enum { + + GF_S4CC_MPEG4 = GF_4CC('m', 'p', '4', 's'), + GF_S4CC_LASER = GF_4CC('l', 's', 'r', '1'), +}; + + +/*! CICP code points for color primaries */ +enum +{ + GF_CICP_PRIM_RESERVED_0 = 0, + GF_CICP_PRIM_BT709, + GF_CICP_PRIM_UNSPECIFIED, + GF_CICP_PRIM_RESERVED_3, + GF_CICP_PRIM_BT470M, + GF_CICP_PRIM_BT470G, + GF_CICP_PRIM_SMPTE170, + GF_CICP_PRIM_SMPTE240, + GF_CICP_PRIM_FILM, + GF_CICP_PRIM_BT2020, + GF_CICP_PRIM_SMPTE428, + GF_CICP_PRIM_SMPTE431, + GF_CICP_PRIM_SMPTE432, + + GF_CICP_PRIM_EBU3213=22, + + GF_CICP_PRIM_LAST +}; + +/*! CICP code points for color transfer */ +enum +{ + GF_CICP_TRANSFER_RESERVED_0 = 0, + GF_CICP_TRANSFER_BT709, + GF_CICP_TRANSFER_UNSPECIFIED, + GF_CICP_TRANSFER_RESERVED_3, + GF_CICP_TRANSFER_BT470M, + GF_CICP_TRANSFER_BT470BG, + GF_CICP_TRANSFER_SMPTE170, + GF_CICP_TRANSFER_SMPTE240, + GF_CICP_TRANSFER_LINEAR, + GF_CICP_TRANSFER_LOG100, + GF_CICP_TRANSFER_LOG316, + GF_CICP_TRANSFER_IEC61966, + GF_CICP_TRANSFER_BT1361, + GF_CICP_TRANSFER_SRGB, + GF_CICP_TRANSFER_BT2020_10, + GF_CICP_TRANSFER_BT2020_12, + GF_CICP_TRANSFER_SMPTE2084, + GF_CICP_TRANSFER_SMPTE428, + GF_CICP_TRANSFER_STDB67, //prores only + + GF_CICP_TRANSFER_LAST +}; + + +/*! CICP code points for matrix coefficients */ +enum +{ + GF_CICP_MX_IDENTITY = 0, + GF_CICP_MX_BT709, + GF_CICP_MX_UNSPECIFIED, + GF_CICP_MX_RESERVED_3, + GF_CICP_MX_FCC47, + GF_CICP_MX_BT601_625, + GF_CICP_MX_SMPTE170, + GF_CICP_MX_SMPTE240, + GF_CICP_MX_YCgCo, + GF_CICP_MX_BT2020, + GF_CICP_MX_BT2020_CL, + GF_CICP_MX_YDzDx, + + GF_CICP_MX_LAST + //the rest is reserved +}; + +/*! parse CICP color primaries + \param val CICP color primaries name +\return 0xFFFFFFFF if error , value otherwise +*/ +u32 gf_cicp_parse_color_primaries(const char *val); + +/*! get CICP color primaries name +\param cicp_prim CICP color primaries code +\return name or "unknown"" if error +*/ +const char *gf_cicp_color_primaries_name(u32 cicp_prim); + +/*! get CICP color primaries names +\return coma-separated list of GPAC names for CICP color primaries +*/ +const char *gf_cicp_color_primaries_all_names(); + +/*! parse CICP color transfer + \param val CICP color transfer name +\return 0xFFFFFFFF if error , value otherwise +*/ +u32 gf_cicp_parse_color_transfer(const char *val); + +/*! get CICP color transfer name +\param cicp_trans CICP color transfer code +\return name or "unknown"" if error +*/ +const char *gf_cicp_color_transfer_name(u32 cicp_trans); + +/*! get CICP color transfer names +\return coma-separated list of GPAC names for CICP color transfer +*/ +const char *gf_cicp_color_transfer_all_names(); + +/*! parse CICP color matrix coefficients + \param val CICP color matrix coefficients name +\return 0xFFFFFFFF if error , value otherwise +*/ +u32 gf_cicp_parse_color_matrix(const char *val); + +/*! get CICP color matrix coefficients name +\param cicp_mx CICP color matrix coefficients code +\return name or "unknown"" if error +*/ +const char *gf_cicp_color_matrix_name(u32 cicp_mx); + +/*! get CICP color matrix names +\return coma-separated list of GPAC names for CICP color matrix +*/ +const char *gf_cicp_color_matrix_all_names(); + + +/*! stereo frame packing types */ +enum +{ + /*! monoscopic video*/ + GF_STEREO_NONE = 0, + /*! left eye in top half of video, right eye in bottom half of video*/ + GF_STEREO_TOP_BOTTOM, + /*! left eye in left half of video, right eye in right half of video*/ + GF_STEREO_LEFT_RIGHT, + /*! stereo mapped through mesh*/ + GF_STEREO_CUSTOM, + /*! left eye in right half of video, right eye in left half of video*/ + GF_STEREO_RIGHT_LEFT, + /*! left eye in bottom half of video, right eye in top half of video*/ + GF_STEREO_BOTTOM_TOP, +}; + +/*! 360 projection types */ +enum +{ + /*! flat video*/ + GF_PROJ360_NONE = 0, + /*! cube map projection video is upper half: right, left, up, lower half: down, front, back*/ + GF_PROJ360_CUBE_MAP, + /*! Equirectangular projection / video*/ + GF_PROJ360_EQR, + /*! Mesh projection (not supported yet)*/ + GF_PROJ360_MESH +}; + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_CONSTANTS_H_*/ diff --git a/include/gpac/crypt.h b/include/gpac/crypt.h new file mode 100644 index 0000000..439f6c5 --- /dev/null +++ b/include/gpac/crypt.h @@ -0,0 +1,154 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2000-2022 +* All rights reserved +* +* This file is part of GPAC / Crypto 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. +* +*/ + +/* +The GPAC crypto lib uses either openSSL 1.x or tiny-AES (https://github.com/kokke/tiny-AES-c) when not available +The crypto lib only supports AES 128 bits in CBC and CTR modes +*/ + + +#ifndef _GF_CRYPT_H_ +#define _GF_CRYPT_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Utility tools for encryption and decryption. +*/ + +/*! +\addtogroup crypt_grp Cryptography +\ingroup media_grp +\brief Utility tools for encryption and decryption. + +This section documents the encryption and decryption routines used by GPAC, mostly AES 128 in CBC or CTR modes. + +@{ +*/ + +#include + +/*! cryptographic context object*/ +typedef struct _gf_crypt_context GF_Crypt; + +/*! Key size in bytes for AES 128*/ +#define GF_AES_128_KEYSIZE 16 + +/*! Chaining mode of AES*/ +typedef enum { + /*! CBC chaining mode*/ + GF_CBC = 0, + /*! CTR chaining mode*/ + GF_CTR = 1, + /*! ECB (no chaining), payload must be a multiple of 16-bytes blocks*/ + GF_ECB = 2, +} GF_CRYPTO_MODE; + +/*! Algorithm mode to use*/ +typedef enum { + /*! AES 128 bit encryption*/ + GF_AES_128 = 0 +} GF_CRYPTO_ALGO; + + +/*! opens crypto context +\param algorithm the algorithm to use +\param mode the chaining mode of the algorithm +\return a new crypto context +*/ +GF_Crypt *gf_crypt_open(GF_CRYPTO_ALGO algorithm, GF_CRYPTO_MODE mode); +/*! destroys a crypto context +\param gfc the target crytpo context +*/ +void gf_crypt_close(GF_Crypt *gfc); + +/*! initializes all buffers for the specified context +After calling this function you can use the crypto context for encryption or decryption (not both). +\param gfc the target crytpo context +\param key the key to use. MUST be 16 bytes long +\param iv the seed/initialization vector to use. It needs to be random and unique (but not secret). The same IV must be used +for encryption/decryption. MUST be 16 bytes long +\return error if any +*/ +GF_Err gf_crypt_init(GF_Crypt *gfc, void *key, const void *iv); + +/*! changes key, does not touch IV. This is used for key rolling +\param gfc the target crytpo context +\param key the new key to use. MUST be 16 bytes long +\return error if any +*/ +GF_Err gf_crypt_set_key(GF_Crypt *gfc, void *key); + +/*! sets the IV for the algorithm. Used for ISMA and CENC CTR. +\param gfc the target crytpo context +\param iv the new eed/initialization vector to use +\param size the size of the IV. Must be 16 or 17 for AES. If 17 (AES CTR only), the first byte shall contain the counter position +\return error if any +*/ +GF_Err gf_crypt_set_IV(GF_Crypt *gfc, const void *iv, u32 size); + +/*! gets the IV of the algorithm. +The size will hold the size of the state and the state must have enough bytes to hold it (17 is enough for AES 128). +In CTR mode, the first byte will be set to the counter value (number of bytes consumed in last block), or 0 if all bytes were consumed +\param gfc the target crytpo context +\param iv filled with the current IV +\param size will be set to the IV size (16 for AES CBC? 17 for AES CTR) +\return error if any +*/ +GF_Err gf_crypt_get_IV(GF_Crypt *gfc, void *iv, u32 *size); + +/*! encrypts a payload. The encryption is done inplace (plaintext is replaced by the ciphertext). + The buffer size should be k*algorithms_block_size if used in a mode which operated in blocks (CBC) or whatever when used in CTR which operate in streams. + +\param gfc the target crytpo context +\param plaintext the clear buffer +\param size the size of the clear buffer +\return error if any +*/ +GF_Err gf_crypt_encrypt(GF_Crypt *gfc, void *plaintext, u32 size); + +/*! decrypts a payload. The decryption is done inplace (ciphertext is replaced by the plaintext). + The buffer size should be k*algorithms_block_size if used in a mode which operated in blocks (CBC) or whatever when used in CTR which operate in streams. + +\param gfc the target crytpo context +\param ciphertext the encrypted buffer +\param size the size of the encrypted buffer +\return error if any +*/ +GF_Err gf_crypt_decrypt(GF_Crypt *gfc, void *ciphertext, u32 size); + + +/*! @} */ + + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_CRYPT_H_*/ diff --git a/include/gpac/crypt_tools.h b/include/gpac/crypt_tools.h new file mode 100644 index 0000000..4a97192 --- /dev/null +++ b/include/gpac/crypt_tools.h @@ -0,0 +1,296 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * 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_CRYPT_TOOLS_H_ +#define _GF_CRYPT_TOOLS_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Utility tools for ISMA and Common Encryption. +*/ + +/*! +\addtogroup crypt_grp +\ingroup media_grp +\brief Utility tools for ISMA and Common Encryption. + +This section documents the helper tools (mostly crypt info file) used in ISMA and CENC encryption. + +@{ + */ + +#include + +/*! Supported encryption scheme types */ +enum +{ + /*! ISMA E&A encryption*/ + GF_CRYPT_TYPE_ISMA = GF_4CC( 'i', 'A', 'E', 'C' ), + /*! OMA DRM encryption*/ + GF_CRYPT_TYPE_OMA = GF_4CC( 'o', 'd', 'k', 'm' ), + /*! CENC CTR-128 encryption*/ + GF_CRYPT_TYPE_CENC = GF_4CC('c','e','n','c'), + /*! CENC CBC-128 encryption*/ + GF_CRYPT_TYPE_CBC1 = GF_4CC('c','b','c','1'), + /*! CENC CTR-128 pattern encryption*/ + GF_CRYPT_TYPE_CENS = GF_4CC('c','e','n','s'), + /*! CENC CBC-128 pattern encryption*/ + GF_CRYPT_TYPE_CBCS = GF_4CC('c','b','c','s'), + /*! Adobe CBC-128 encryption*/ + GF_CRYPT_TYPE_ADOBE = GF_4CC('a','d','k','m'), + /*! PIFF CTR-128 encryption*/ + GF_CRYPT_TYPE_PIFF = GF_4CC('p','i','f','f'), + /*! HLS Sample encryption*/ + GF_CRYPT_TYPE_SAES = GF_4CC('s','a','e','s'), + +}; + +/*! Selective encryption modes */ +enum +{ + /*! no selective encryption*/ + GF_CRYPT_SELENC_NONE = 0, + /*! only encrypts RAP samples*/ + GF_CRYPT_SELENC_RAP, + /*! only encrypts non-RAP samples*/ + GF_CRYPT_SELENC_NON_RAP, + /*! selective encryption of random samples*/ + GF_CRYPT_SELENC_RAND, + /*! selective encryption of a random sample in given range*/ + GF_CRYPT_SELENC_RAND_RANGE, + /*! selective encryption of first sample in given range*/ + GF_CRYPT_SELENC_RANGE, + /*! encryption of all samples but the preview range*/ + GF_CRYPT_SELENC_PREVIEW, + /*!no encryption of samples, signaled with sample group*/ + GF_CRYPT_SELENC_CLEAR, + /*!no encryption of samples, signaled without sample group but bytesOfEncrypted = 0*/ + GF_CRYPT_SELENC_CLEAR_FORCED, +}; + +/*! Key info structure, one per defined key in the DRM XML doc*/ +typedef struct +{ + //keep first 3 bin128 at the begining for data alignment + /*! KEY ID*/ + bin128 KID; + /*! key value*/ + bin128 key; + /*! constant IV or initial IV if not constant*/ + u8 IV[16]; + + /*! hls_info defined*/ + char *hls_info; + /*!IV size */ + u8 IV_size; + /*! constant IV size */ + u8 constant_IV_size; +} GF_CryptKeyInfo; + +/*! key roll modes*/ +typedef enum +{ + /*! change keys every keyRoll AUs*/ + GF_KEYROLL_SAMPLES = 0, + /*! roll keys at each SAP type 1 or 2 for streams with SAPs*/ + GF_KEYROLL_SAPS, + /*! change keys every keyRoll DASH segments*/ + GF_KEYROLL_SEGMENTS, + /*! change keys every keyRoll periods*/ + GF_KEYROLL_PERIODS, +} GF_KeyRollType; + +/*! Crypto information for one media stream*/ +typedef struct +{ + /*! scheme type used for encryptio of the track*/ + u32 scheme_type; + /*! ID of track / PID / ... to be encrypted*/ + u32 trackID; + /*! URI of key management system / rightsIssuerURL*/ + char *KMS_URI; + /*! Scheme URI or contentID for OMA*/ + char *Scheme_URI; + /*! selective encryption type*/ + u32 sel_enc_type; + /*! for OMA, sets preview range in samples. Otherwise sets encryption AU frequency (encrypts 1 AU every sel_enc_range ones)*/ + u32 sel_enc_range; + /*! IPMP signaling: 0: none, 1: IPMP, 2: IPMPX + when IPMP signaling is enabled, the OD stream will be updated with IPMP Update commands + THIS IS DEPRECATED IN GPAC + */ + u32 ipmp_type; + /*! if not set and IPMP enabled, defaults to TrackID + THIS IS DEPRECATED IN GPAC + */ + u32 ipmp_desc_id; + /*! type of box where sample auxiliary informations is saved, either "senc" or "PSEC" (PIFF)*/ + u32 sai_saved_box_type; + + /*! OMA encryption type: 0: none, 1: AES CBC, 2: AES CTR*/ + u8 encryption; + /*! OMA textual headers*/ + char *TextualHeaders; + /*! OMA transaction ID*/ + char TransactionID[17]; + + /*! CENC extensions - TODO, we could extend the support to allow per key patterns and selective encryption modes + and also add support for multiple keys in ISMA ?*/ + /*! default encryption state for samples*/ + u32 IsEncrypted; + /*! number of defined keys*/ + u32 nb_keys; + /*! keys defined*/ + GF_CryptKeyInfo *keys; + + /*! default key index to use*/ + u32 defaultKeyIdx; + /*! roll period of keys*/ + u32 keyRoll; + /*! roll type */ + GF_KeyRollType roll_type; + /*! number of bytes to leave in the clear for non NAL-based tracks. Only used in cbcs mode*/ + u32 clear_bytes; + + /*! CENS/CBCS pattern */ + u8 crypt_byte_block, skip_byte_block; + + /* ! for avc1 ctr CENC edition 1 */ + Bool allow_encrypted_slice_header; + /*! 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; + + /*0: same stsd for clear samples + 1: dedicated stsd entry for clear samples, placed before the crypted entry in stsd, + 2: dedicated stsd entry for clear samples, placed after the crypted entry in stsd, + */ + u32 force_clear_stsd_idx; + + /*! adobe metadata in base64*/ + char *metadata; + + /*! force using type set in XML rather than type indicated in file when decrypting*/ + Bool force_type; + + /*! generate random keys and key values*/ + Bool rand_keys; + + /*! randomly encrypts subsample if rand() % subs_rand is 0*/ + u32 subs_rand; + /*! list of VCL NAL/OBU indices to encrypt, 1-based*/ + char *subs_crypt; + /*! use multiple keys per sample*/ + Bool multi_key; + /*! roll key over subsamples. If 0, roll by 1 every encrypted sample. If 1 (-1==0) disable key roll*/ + u32 mkey_roll_plus_one; + /*!coma-separated list of indices of keys to use per subsample. Value 0 means keep clear. If less indices than subsamples, keep subsamples in clear*/ + char *mkey_subs; +} GF_TrackCryptInfo; + +/*! Crypto information*/ +typedef struct +{ + /*! list of track infos*/ + GF_List *tcis; + /*! global for all tracks unless overridden*/ + u32 def_crypt_type; + /*! indicates a common key is used*/ + Bool has_common_key; + /*! intern to parser*/ + Bool in_text_header; + /*! intern to parser*/ + GF_Err last_parse_error; +} GF_CryptInfo; + +/*! loads a given crypto configuration file. Full doc is available at https://gpac.wp.imt.fr/mp4box/encryption/common-encryption/ +\param file name of the crypt XML file +\param out_err set to return error +\return the crypt info +*/ +GF_CryptInfo *gf_crypt_info_load(const char *file, GF_Err *out_err); + +/*! deletes crypto configuration file. +\param info the target crypt info +*/ +void gf_crypt_info_del(GF_CryptInfo *info); + +#if !defined(GPAC_DISABLE_CRYPTO) && !defined(GPAC_DISABLE_ISOM_WRITE) + +/*! decrypts a file +\param infile source MP4 file to decrypt +\param drm_file location of crypto configuration file +\param outname location of destination file +\param interleave_time interleave time of the destination file - 0 means flat storage +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_decrypt_file(GF_ISOFile *infile, const char *drm_file, const char *outname, Double interleave_time, u32 fs_dump_flags); + +/*! decrypts a fragment +\param infile source MP4 file to decrypt +\param drm_file location of crypto configuration file +\param dst_file location of destination file +\param frag_name name of fragment to decrypt +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_decrypt_fragment(GF_ISOFile *infile, const char *drm_file, const char *dst_file, const char *frag_name, u32 fs_dump_flags); + +/*! encrypts a file +\param infile source MP4 file to encrypt +\param drm_file location of crypto configuration file +\param outname location of destination file +\param interleave_time interleave time of the destination file - 0 means flat storage +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_crypt_file(GF_ISOFile *infile, const char *drm_file, const char *outname, Double interleave_time, u32 fs_dump_flags); + +/*! encrypts a fragment +\param infile init segment of the MP4 file to encrypt - this SHALL NOT be encrypted +\param drm_file location of crypto configuration file +\param dst_file location of destination file +\param frag_name name of fragment to encrypt +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_crypt_fragment(GF_ISOFile *infile, const char *drm_file, const char *dst_file, const char *frag_name, u32 fs_dump_flags); + +#endif /*!defined(GPAC_DISABLE_CRYPTO) && !defined(GPAC_DISABLE_ISOM_WRITE)*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_CRYPT_TOOLS_H_*/ + diff --git a/include/gpac/dash.h b/include/gpac/dash.h new file mode 100644 index 0000000..dc93219 --- /dev/null +++ b/include/gpac/dash.h @@ -0,0 +1,1174 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2012-2022 + * All rights reserved + * + * This file is part of GPAC / Adaptive HTTP Streaming 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_DASH_H_ +#define _GF_DASH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief DASH Client API. The DASH client can be used without GPAC player but requires at least the base utils (threads, lists, NTP timing). The HTTP interface used can be either GPAC's one or any other downloader. +*/ + +/*! +\addtogroup dashc_grp DASH Client +\ingroup media_grp +\brief MPEG-DASH and HLS Client + +@{ +*/ + +#include +#include + +#ifndef GPAC_DISABLE_DASH_CLIENT + +/*! + * All the possible Mime-types for MPD files + */ +static const char * const GF_DASH_MPD_MIME_TYPES[] = { "application/dash+xml", "video/vnd.3gpp.mpd", "audio/vnd.3gpp.mpd", "video/vnd.mpeg.dash.mpd", "audio/vnd.mpeg.dash.mpd", NULL }; + +/*! + * All the possible Mime-types for M3U8 files + */ +static const char * const GF_DASH_M3U8_MIME_TYPES[] = { "video/x-mpegurl", "audio/x-mpegurl", "application/x-mpegURL", "application/vnd.apple.mpegURL", NULL}; + +/*! + * All the possible Mime-types for Smooth files + */ +static const char * const GF_DASH_SMOOTH_MIME_TYPES[] = { "application/vnd.ms-sstr+xml", NULL}; + +/*! DASH Event type. The DASH client communicates with the user through a callback mechanism using events*/ +typedef enum +{ + /*! event sent if an error occurs when setting up manifest*/ + GF_DASH_EVENT_MANIFEST_INIT_ERROR, + /*! event sent before groups first segment is fetched - user shall decide which group to select at this point*/ + GF_DASH_EVENT_SELECT_GROUPS, + /*! event sent if an error occurs during period setup - the download thread will exit at this point*/ + GF_DASH_EVENT_PERIOD_SETUP_ERROR, + /*! event sent once the first segment of each selected group is fetched - user should load playback chain(s) at this point*/ + GF_DASH_EVENT_CREATE_PLAYBACK, + /*! event sent when resetting groups at period switch or at exit - user should unload playback chain(s) at this point*/ + GF_DASH_EVENT_DESTROY_PLAYBACK, + /*! event sent once a new segment becomes available*/ + GF_DASH_EVENT_SEGMENT_AVAILABLE, + /*! event sent when quality has been switched for the given group*/ + GF_DASH_EVENT_QUALITY_SWITCH, + /*! position in timeshift buffer has changed (eg, paused)*/ + GF_DASH_EVENT_TIMESHIFT_UPDATE, + /*! event sent when timeshift buffer is overflown - the group_idx param contains the max number of dropped segments of all representations dropped by the client, or -1 if play pos is ahead of live */ + GF_DASH_EVENT_TIMESHIFT_OVERFLOW, + /*! event sent when we need the decoding statistics*/ + GF_DASH_EVENT_CODEC_STAT_QUERY, + /*! event sent when no threading to trigger segment download abort*/ + GF_DASH_EVENT_ABORT_DOWNLOAD, + /*! event sent whenever cache is full, to allow client to dispatch any segment*/ + GF_DASH_EVENT_CACHE_FULL, + /*! event sent when all groups are done in a period - if group_idx is 1, this announces a time discontinuity for next period*/ + GF_DASH_EVENT_END_OF_PERIOD, +} GF_DASHEventType; + +/*! DASH network I/O abstraction object*/ +typedef struct _gf_dash_io GF_DASHFileIO; +/*! structure used for all IO sessions for DASH*/ +typedef void *GF_DASHFileIOSession; + +/*! DASH network I/O abstraction object*/ +struct _gf_dash_io +{ + /*! user private data*/ + void *udta; + + /*! signals errors or specific actions to perform*/ + GF_Err (*on_dash_event)(GF_DASHFileIO *dashio, GF_DASHEventType evt, s32 group_idx, GF_Err setup_error); + + /*! used to check whether a representation is supported or not. Function returns 1 if supported, 0 otherwise + if this callback is not set, the representation is assumed to be supported*/ + Bool (*dash_codec_supported)(GF_DASHFileIO *dashio, const char *codec, u32 width, u32 height, Bool is_interlaced, u32 fps_num, u32 fps_denum, u32 sample_rate); + + /*! called whenever a file has to be deleted*/ + void (*delete_cache_file)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *cache_url); + + /*! create a file download session for the given resource - group_idx may be -1 if this is a global resource , otherwise it indicates the group/adaptationSet in which the download happens*/ + GF_DASHFileIOSession (*create)(GF_DASHFileIO *dashio, Bool persistent, const char *url, s32 group_idx); + /*! delete a file download session*/ + void (*del)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! aborts downloading in the given file session*/ + void (*abort)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! resetup the file session with a new resource to get - this allows persistent connection usage with HTTP servers*/ + GF_Err (*setup_from_url)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *url, s32 group_idx); + /*! set download range for the file session*/ + GF_Err (*set_range)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, u64 start_range, u64 end_range, Bool discontinue_cache); + /*! initialize the file session - all the headers shall be fetched before returning*/ + GF_Err (*init)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! download the content - synchronous call: all the file shall be fetched before returning*/ + GF_Err (*run)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + + /*! get URL of the file - it may be different from the original one if resource relocation happened*/ + const char *(*get_url)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! get the name of the cache file. If NULL is returned, the file cannot be cached and its associated URL will be used when + the client request file to play*/ + const char *(*get_cache_name)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! get the MIME type of the file*/ + const char *(*get_mime)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! get the given hedaer value in the last HTTP response. Function is optional*/ + const char *(*get_header_value)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *header_name); + /*! gets the UTC time at which reply has been received. Function is optional*/ + u64 (*get_utc_start_time)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! get the average download rate for the session. If no session is specified, gets the max download rate + for the client (used for bandwidth simulation in local files)*/ + u32 (*get_bytes_per_sec)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! get the total size on bytes for the session*/ + u32 (*get_total_size)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + /*! get the total size on bytes for the session*/ + u32 (*get_bytes_done)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); + + /*! callback when manifest (DASH, HLS) or sub-playlist (HLS) is updated*/ + void (*manifest_updated)(GF_DASHFileIO *dashio, const char *manifest_name, const char *local_path, s32 group_idx); + +}; + +/*! DASH client object*/ +typedef struct __dash_client GF_DashClient; + +/*! Quality selection mode of initial segments*/ +typedef enum +{ + /*! 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*/ + GF_DASH_SELECT_QUALITY_HIGHEST, + /*! selects the lowest bandwidth when starting - if one of the representation does not have video (HLS), it will NOT be selected*/ + GF_DASH_SELECT_BANDWIDTH_LOWEST, + /*! 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; + + +/*! create a new DASH client +\param dash_io DASH callbacks to the user +\param max_cache_duration maximum duration in milliseconds for the cached media. If less than \code mpd@minBufferTime \endcode , \code mpd@minBufferTime \endcode is used +\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 keep_files do not delete files from the cache +\param disable_switching turn off bandwidth switching algorithm +\param first_select_mode indicates which representation to select upon startup +\param initial_time_shift_value sets initial buffering: if between 0 and 100, this is a percentage of the time shift window of the session. If greater than 100, this is a time shift in milliseconds. +\return a new DASH client +*/ +GF_DashClient *gf_dash_new(GF_DASHFileIO *dash_io, + u32 max_cache_duration, + s32 auto_switch_count, + Bool keep_files, + Bool disable_switching, + GF_DASHInitialSelectionMode first_select_mode, + u32 initial_time_shift_value); + +/*! destroys a DASH client +\param dash the target dash clien +*/ +void gf_dash_del(GF_DashClient *dash); + +/*! opens the DASH client for the specific manifest file +\param dash the target dash client +\param manifest_url the URL of the manifest to open +\return error if any +*/ +GF_Err gf_dash_open(GF_DashClient *dash, const char *manifest_url); + +/*! closes the dash client +\param dash the target dash client +*/ +void gf_dash_close(GF_DashClient *dash); + +/*! for unthreaded session, executes DASH logic +\param dash the target dash client +\return error if any, GF_EOS on end of session*/ +GF_Err gf_dash_process(GF_DashClient *dash); + +/*! returns URL of the DASH manifest file +\param dash the target dash client +\return the currently loaded manifest +*/ +const char *gf_dash_get_url(GF_DashClient *dash); + +/*! tells whether we are playing some Apple HLS M3U8 +\param dash the target dash client +\return GF_TRUE if the manifest is M3U8 +*/ +Bool gf_dash_is_m3u8(GF_DashClient *dash); + +/*! tells whether we are playing some MS SmoothStreaming +\param dash the target dash client +\return GF_TRUE if the manifest is SmoothStreaming +*/ +Bool gf_dash_is_smooth_streaming(GF_DashClient *dash); + +/*! gets title and source for this MPD +\param dash the target dash client +\param title set to the title of the manifest (may be NULL) +\param source set to the source of the manifest (may be NULL) +*/ +void gf_dash_get_info(GF_DashClient *dash, const char **title, const char **source); + +/*! switches quality up or down +\param dash the target dash client +\param switch_up indicates if the quality should be increased (GF_TRUE) or decreased (GF_FALSE) +*/ +void gf_dash_switch_quality(GF_DashClient *dash, Bool switch_up); + +/*! indicates whether the DASH client is running or not +\param dash the target dash client +\return GF_TRUE if the session is still active, GF_FALSE otherwise +*/ +Bool gf_dash_is_running(GF_DashClient *dash); + +/*! indicates whether the DASH client is in setup stage (sloving periods, destroying or creating playback chain) or not +\param dash the target dash client +\return GF_TRUE if the session is in setup, GF_FALSE otherwise +*/ +Bool gf_dash_is_in_setup(GF_DashClient *dash); + +/*! gets duration of the presentation +\param dash the target dash client +\return the duration in second of the session +*/ +Double gf_dash_get_duration(GF_DashClient *dash); + +/*! sets timeshift for the presentation - this function does not trigger a seek, this has to be done by the caller +\param dash the target dash client +\param ms_in_timeshift if between 0 and 100, this is a percentage of the time shift window of the session. If greater than 100, this is a time shift in milliseconds +\return error if any +*/ +GF_Err gf_dash_set_timeshift(GF_DashClient *dash, u32 ms_in_timeshift); + +/*! returns the number of groups. A group is a set of media resources that are alternate of each other in terms of bandwidth/quality +\param dash the target dash client +\return the number of groups in the active session +*/ +u32 gf_dash_get_group_count(GF_DashClient *dash); + +/*! associates user data (or NULL) with a given group +\param dash the target dash client +\param group_index the 0-based index of the target group +\param udta the opaque data to associate to the group +\return error if any +*/ +GF_Err gf_dash_set_group_udta(GF_DashClient *dash, u32 group_index, void *udta); + +/*! returns the user data associated with this group +\param dash the target dash client +\param group_index the 0-based index of the target group +\return the opaque data to associate to the group +*/ +void *gf_dash_get_group_udta(GF_DashClient *dash, u32 group_index); + +/*! indicates whether a group is selected for playback or not. Currently groups cannot be selected during playback +\param dash the target dash client +\param group_index the 0-based index of the target group +\return GF_TRUE if the group is selected for playback/download +*/ +Bool gf_dash_is_group_selected(GF_DashClient *dash, u32 group_index); + +/*! indicates whether this group is dependent on another group (because representations are). If this is the case, all representations of this group will be made available through the base group (and has_next_segment flag) if the group is selected. +returns -1 if not dependent on another group, otherwise return dependent group index +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return -1 if the group does not depend on another group, otherwise returns the dependent group index +*/ +s32 gf_dash_group_has_dependent_group(GF_DashClient *dash, u32 group_idx); + +/*! gives the number of groups depending on this one for decoding +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the number of other groups depending on this group +*/ +u32 gf_dash_group_get_num_groups_depending_on(GF_DashClient *dash, u32 group_idx); + +/*! gets the index of the depending_on group with the specified group_depending_on_dep_idx (between 0 and \ref gf_dash_group_get_num_groups_depending_on -1) +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param group_depending_on_dep_idx the 0-based index of the queried dependent group +\return the index of the depending_on group +*/ +s32 gf_dash_get_dependent_group_index(GF_DashClient *dash, u32 group_idx, u32 group_depending_on_dep_idx); + +/*! indicates whether a group can be selected for playback or not. Some groups may have been disabled because of non supported features +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if the group can be selected for playback, GF_FALSE otherwise +*/ +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 +\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 +*/ +void gf_dash_group_select(GF_DashClient *dash, u32 group_idx, Bool select); + +/*! gets group ID (through the group attribute), -1 if undefined +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return ID of the group +*/ +s32 gf_dash_group_get_id(GF_DashClient *dash, u32 group_idx); + +/*! 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 +*/ +void gf_dash_enable_group_selection(GF_DashClient *dash, Bool enable); + +/*! checks if first segment (used to initialize) was an init segment or the first in a sequence (aka M2TS) +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if first segment was a media segment +*/ +Bool gf_dash_group_init_segment_is_media(GF_DashClient *dash, u32 group_idx); + +/*! performs selection of representations based on language code +\param dash the target dash client +\param lang_code_rfc_5646 the language code used by the default group selection +*/ +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 +\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); + +/*! 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 +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param crypto_type set to 0 if no encryption in segments, 1 if full segment encryption, 2 if CENC/per-sample encryption is used - may be NULL +\param key_IV set to the IV used for the first media segment (can be NULL) +\return the key URL of the first media segment, either a URN or a resolved URL +*/ +const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 group_idx, u32 *crypto_type, bin128 *key_IV); + +/*! returns the language of the group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the language of the group or NULL if no language associated +*/ +const char *gf_dash_group_get_language(GF_DashClient *dash, u32 group_idx); + +/*! returns the number of audio channelsof 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 +*/ +u32 gf_dash_group_get_audio_channels(GF_DashClient *dash, u32 group_idx); + +/*! gets time shift buffer depth of the group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return time shift buffer depth in ms of the group, -1 means infinity +*/ +u32 gf_dash_group_get_time_shift_buffer_depth(GF_DashClient *dash, u32 group_idx); + +/*! gets current time in time shift buffer +This functions retrieves the maximum value (further in the past) of all selected/active groups +\param dash the target dash client +\return current time in timeshift buffer in seconds - 0 means 'live point' +*/ +Double gf_dash_get_timeshift_buffer_pos(GF_DashClient *dash); + +/*! sets codec statistics of a group for playback rate adjustment +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param avg_dec_time average decode time in microseconds of a frame, 0 if unknown +\param max_dec_time maximum decode time in microseconds of a frame, 0 if unknown +\param irap_avg_dec_time average decode time of SAP 1/2/3 pictures in microseconds of a frame, 0 if unknown +\param irap_max_dec_time maximum decode time of SAP 1/2/3 pictures in microseconds of a frame, 0 if unknown +\param codec_reset indicates a codec reset is pending (pipeline flush is needed before reinit) +\param decode_only_rap indicates only SAP1/2/3 are currently being decoded. This may trigger a switch to a trick mode representation +*/ +void gf_dash_group_set_codec_stat(GF_DashClient *dash, u32 group_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); + +/*! sets buffer levels of a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param buffer_min_ms minimum buffer in milliseconds (pipeline would rebuffer below this level) +\param buffer_max_ms maximum buffer in milliseconds (pipeline would block above this level) +\param buffer_occupancy_ms current buffer occupancy in milliseconds +*/ +void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 group_idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms); + +/*! indicates the buffer time in ms after which the player resumes playback. This value is less or equal to the buffer_max_ms +indicated in \ref gf_dash_group_set_buffer_levels +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param max_target_buffer_ms buffer in milliseconds above which pipeline resumes after a rebuffering event +\return error if any +*/ +GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 group_idx, u32 max_target_buffer_ms); + +/*! DASH descriptor types*/ +typedef enum +{ + /*! Accessibility descriptor*/ + GF_MPD_DESC_ACCESSIBILITY, + /*! Audio config descriptor*/ + GF_MPD_DESC_AUDIOCONFIG, + /*! Content Protection descriptor*/ + GF_MPD_DESC_CONTENT_PROTECTION, + /*! Essential Property descriptor*/ + GF_MPD_DESC_ESSENTIAL_PROPERTIES, + /*! Supplemental Property descriptor*/ + GF_MPD_DESC_SUPPLEMENTAL_PROPERTIES, + /*! Frame packing descriptor*/ + GF_MPD_DESC_FRAME_PACKING, + /*! Role descriptor*/ + GF_MPD_DESC_ROLE, + /*! Rating descriptor*/ + GF_MPD_DESC_RATING, + /*! Viewpoint descriptor*/ + GF_MPD_DESC_VIEWPOINT +} GF_DashDescriptorType; + +/*! enumerates descriptors of the given type +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param desc_type type of descriptor being checked +\param role_idx index of the descriptor being checked for this type +\param desc_id set to the ID of the descriptor if found (optional, may be NULL) +\param desc_scheme set to the scheme of the descriptor if found (optional, may be NULL) +\param desc_value set to the desc_value of the descriptor if found (optional, may be NULL) +\return GF_TRUE if descriptor is found, GF_FALSE otherwise +*/ +Bool gf_dash_group_enum_descriptor(GF_DashClient *dash, u32 group_idx, GF_DashDescriptorType desc_type, u32 role_idx, const char **desc_id, const char **desc_scheme, const char **desc_value); + +/*! returns the URL and byte range of the next media resource to play in this group. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param dependent_representation_index index of the dependent representation to query, 0-based +\param url set to the URL of the next segment +\param start_range set to the start byte offset in the segment (optional, may be NULL) +\param end_range set to the end byte offset in the segment (optional, may be NULL) +\param switching_index set to the quality index of the segment (optional, may be NULL) +\param switching_url set to the URL of the switching segment if needed (optional, may be NULL) +\param switching_start_range set to start byte offset of the switching segment if needed (optional, may be NULL) +\param switching_end_range set to end byte offset of the switching segment if needed (optional, may be NULL) +\param original_url set to original URL value of the segment (optional, may be NULL) +\param has_next_segment set to GF_TRUE if next segment location is known (unthreaded mode) or next segment is downloaded (threaded mode) (optional, may be NULL) +\param key_url set to the key URL of the next segment for MPEG-2 TS full segment encryption (optional, may be NULL). The URL is either a URN or a resolved URL +\param key_IV set to the key initialization vector of the next segment for MPEG-2 TS full segment encryption (optional, may be NULL) +\return GF_BUFFER_TOO_SMALL if no segment found, GF_EOS if end of session, GF_URL_REMOVED if segment is disabled (but all output info is OK, this can be ignored and considered as GF_OK by the user) or error if any +*/ +GF_Err gf_dash_group_get_next_segment_location(GF_DashClient *dash, u32 group_idx, u32 dependent_representation_index, const char **url, u64 *start_range, u64 *end_range, + s32 *switching_index, const char **switching_url, u64 *switching_start_range, u64 *switching_end_range, + const char **original_url, Bool *has_next_segment, const char **key_url, bin128 *key_IV); + +/*! gets some info on the segment +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param dependent_representation_index index of the dependent representation to query, 0-based +\param seg_name set to the segment name, without base url - optional, may be NULL +\param seg_number set to the segment number for $Number$ addressing - optional, may be NULL +\param seg_time set to the segment start time - optional, may be NULL +\param seg_dur_ms set to the segment estimated duration in ms - optional, may be NULL +\param init_segment set to the init segment name, without base url - optional, may be NULL +\return error if any, GF_BUFFER_TOO_SMALL if no segments queued for download +*/ +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); + +/*! checks if loop was detected in playback. This is mostly used for broadcast (eMBMS, ROUTE) based on pcap replay. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if segment numbers loop was detected +*/ +Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 group_idx); + +/*! checks if group is using low latency delivery. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if low latency is used, GF_FALSE otherwise +*/ +Bool gf_dash_is_low_latency(GF_DashClient *dash, u32 group_idx); + +/*! gets average duration of segments for the current rep. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param duration set to average segment duration +\param timescale set to timescale used to exprss duration +\return error if any +*/ +GF_Err gf_dash_group_get_segment_duration(GF_DashClient *dash, u32 group_idx, u32 *duration, u32 *timescale); + +/*! gets ID of active representaion. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return ID of representation, NULL if error +*/ +const char *gf_dash_group_get_representation_id(GF_DashClient *dash, u32 group_idx); + +/*! returns number of seconds at which playback shall start for the group in the current period. +The first segment available for the period will be so that gf_dash_group_get_start_range is in this range after the caller +adjusts it with PTO (eg the returned time is in period timeline, not media timeline) +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return media playback start range in seconds*/ +Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 group_idx); + +/*! discards the first media resource in the queue of this group +\param dash the target dash client +\param group_idx the 0-based index of the target group +*/ +void gf_dash_group_discard_segment(GF_DashClient *dash, u32 group_idx); + +/*! indicates to the DASH engine that the group playback has been stopped by the user +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param done mark group as done if GF_TRUE, or not done if GF_FALSE +*/ +void gf_dash_set_group_done(GF_DashClient *dash, u32 group_idx, Bool done); + +/*! gets presentationTimeOffset and timescale for the active representation +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param presentation_time_offset set to the presentation time offset for the group +\param timescale set to the timescale used to represent the presentation time offset +\return error if any +*/ +GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 group_idx, u64 *presentation_time_offset, u32 *timescale); + +/*! checks if the session is in the last period +\param dash the target dash client +\param check_eos if GF_TRUE, return GF_TRUE only if the last period is known to be the last one (not an open period in live) +\return GF_TRUE if the playback position is in the last period of the presentation +*/ +Bool gf_dash_in_last_period(GF_DashClient *dash, Bool check_eos); + +/*! checks if the group is playing +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if group is done playing +*/ +Bool gf_dash_get_group_done(GF_DashClient *dash, u32 group_idx); + +/*! gets current period switching status for the session. +\param dash the target dash client +\return possible values: + 1 if the period switching has been requested (due to seeking), + 2 if the switching is in progress (all groups will soon be destroyed and plyback will be stopped and restarted) + 0 if no switching is requested +*/ +u32 gf_dash_get_period_switch_status(GF_DashClient *dash); + +/*! request period switch - this is typically called when the media engine signals that no more data is available for playback +\param dash the target dash client +*/ +void gf_dash_request_period_switch(GF_DashClient *dash); + +/*! checks if the client is in a period setup state +\param dash the target dash client +\return GF_TRUE if the DASH engine is currently setting up a period (creating groups and fetching initial segments) +*/ +Bool gf_dash_in_period_setup(GF_DashClient *dash); + +/*! seeks playback to the given time. If period changes, all playback is stopped and restarted +If the session is dynamic (live), the start_range is ignored and recomputed from current UTC clock to be at the live point. If timeshifting is desired, use \ref gf_dash_set_timeshift before seeking +\param dash the target dash client +\param start_range the desired seek time in seconds +*/ +void gf_dash_seek(GF_DashClient *dash, Double start_range); + +/*! checks if seeking time is in the previously playing segment +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if the seek request was in a different segment than the previously playing one +*/ +Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 group_idx); +/*! gets video info for this group if video +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param max_width set to the maximum width in the group +\param max_height set to the maximum height in the group +\return error if any +*/ +GF_Err gf_dash_group_get_video_info(GF_DashClient *dash, u32 group_idx, u32 *max_width, u32 *max_height); + +/*! seeks only a given group - results are undefined if the seek operation triggers a period switch +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param seek_to the desired seek time in seconds +*/ +void gf_dash_group_seek(GF_DashClient *dash, u32 group_idx, Double seek_to); + +/*! sets playback speed of the session. Speed is used in adaptation logic +\param dash the target dash client +\param speed current playback speed +*/ +void gf_dash_set_speed(GF_DashClient *dash, Double speed); + +/*! updates media bandwidth for the given group. Only allowed for groups without dependencies to other groups +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param bits_per_sec current download rate in bits per seconds +\param total_bytes total size of segment being downloaded +\param bytes_done number of bytes already downloaded in current segment +\param us_since_start time elapsed in microseconds since segment has been scheduled for download +\return error if any +*/ +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); + +/*! enables UTC drift computation using HTTP header "Server-UTC: UTC", where UTC is in ms +\param dash the target dash client +\param estimate_utc_drift if GF_TRUE, enables UTC drift compensation, otherwise disables it +*/ +void gf_dash_enable_utc_drift_compensation(GF_DashClient *dash, Bool estimate_utc_drift); + +/*! checks if session is dynamic offering (live) +\param dash the target dash client +\return GF_TRUE if MPD is dynamic, GF_FALSE otherwise +*/ +Bool gf_dash_is_dynamic_mpd(GF_DashClient *dash); + +/*! gets minimum buffer time of session indicated in MPD +\param dash the target dash client +\return minimum buffer time in ms +*/ +u32 gf_dash_get_min_buffer_time(GF_DashClient *dash); + +/*! gets the maximum segment duration in session +\param dash the target dash client +\return the maximum segment duration in ms +*/ +u32 gf_dash_get_max_segment_duration(GF_DashClient *dash); + +/*! gets the difference between the local UTC clock and the one reported by the server +\param dash the target dash client +\return difference in milliseconds +*/ +s32 gf_dash_get_utc_drift_estimate(GF_DashClient *dash); + +/*! shifts UTC clock of server by shift_utc_ms so that new UTC in MPD is old + shift_utc_ms +\param dash the target dash client +\param shift_utc_ms UTC clock shift in milliseconds. A positive value will move the clock in the future, a negative value in the past +*/ +void gf_dash_set_utc_shift(GF_DashClient *dash, s32 shift_utc_ms); + +/*! sets max video display capabilities +\param dash the target dash client +\param width the maximum width of the display +\param height the maximum height of the display +\param max_display_bpp the maximum bits per pixel of the display +\return error if any +*/ +GF_Err gf_dash_set_max_resolution(GF_DashClient *dash, u32 width, u32 height, u8 max_display_bpp); + +/*! sets min time in ms between a 404 and the next request on the same group. The default value is 500 ms. +\param dash the target dash client +\param min_timeout_between_404 minimum delay in milliseconds between retries +\return error if any +*/ +GF_Err gf_dash_set_min_timeout_between_404(GF_DashClient *dash, u32 min_timeout_between_404); + +/*! sets time in ms after which 404 request for a segment will indicate segment lost. The client always retries for segment availability time + segment duration. This allows extending slightly the probe time (used when segment durations varies, or for VBR broadcast). The default value is 100 ms. +\param dash the target dash client +\param expire_after_ms delay in milliseconds +\return error if any +*/ +GF_Err gf_dash_set_segment_expiration_threshold(GF_DashClient *dash, u32 expire_after_ms); + +/*! only enables the given groups - this shall be set before calling \ref gf_dash_open. If NULL, no groups will be disabled +\param dash the target dash client +\param groups_idx list of 0-based index of the target groups to enable, +\param nb_groups number of group indexes in list +*/ +void gf_dash_debug_groups(GF_DashClient *dash, const u32 *groups_idx, u32 nb_groups); + +/*! split all adatation sets so that they contain only one representation (quality) +\param dash the target dash client +*/ +void gf_dash_split_adaptation_sets(GF_DashClient *dash); + +/*! low latency mode of dash client*/ +typedef enum +{ + /*! 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 +\param low_lat_mode low latency mode +*/ +void gf_dash_set_low_latency_mode(GF_DashClient *dash, GF_DASHLowLatencyMode low_lat_mode); + +/*! indicates typical buffering used by the user app before playback starts. + This allows fetching data earlier in live mode, if the timeshiftbuffer allows for it +\param dash the target dash client +\param buffer_time_ms typical playout buffer in milliseconds +*/ +void gf_dash_set_user_buffer(GF_DashClient *dash, u32 buffer_time_ms); + +/*! indicates the number of segments to wait before switching up bandwidth. The default value is 1 (ie stay in current bandwidth or one more segment before switching up, event if download rate is enough). +Setting this to 0 means the switch will happen instantly, but this is more prone to quality changes due to network variations +\param dash the target dash client +\param switch_probe_count the number of probes before switching +*/ +void gf_dash_set_switching_probe_count(GF_DashClient *dash, u32 switch_probe_count); + +/*! enables agressive switching mode. If agressive switching is enabled, switching targets to the closest bandwidth fitting the available download rate. Otherwise, switching targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth). Default value is no. +\param dash the target dash client +\param enable_agressive_switch if GF_TRUE, enables agressive mode, otherwise disables it +*/ +void gf_dash_set_agressive_adaptation(GF_DashClient *dash, Bool enable_agressive_switch); + +/*! enables single-range requests for LL-HLS byterange, rather than issuing a request per PART. This assumes that: + - each URI in the different parts is the SAME + - byte ranges are contiguous in the URL + +Errors will be thrown if these are not met on future parts and merging will be disabled, however the scheduled buggy segment will NOT be disarded + +\param dash the target dash client +\param enable_single_range if GF_TRUE, enables single range, otherwise disables it +*/ +void gf_dash_enable_single_range_llhls(GF_DashClient *dash, Bool enable_single_range); + +/*! create a new DASH client +\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); + +/*! returns active period start +\param dash the target dash client +\return period start in milliseconds +*/ +u64 gf_dash_get_period_start(GF_DashClient *dash); + +/*! gets active period duration +\param dash the target dash client +\return active period duration in milliseconds +*/ +u64 gf_dash_get_period_duration(GF_DashClient *dash); + +/*! gets number of quality available for a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return number of quality available +*/ +u32 gf_dash_group_get_num_qualities(GF_DashClient *dash, u32 group_idx); + +/*! gets the number of components in a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return number of components +*/ +u32 gf_dash_group_get_num_components(GF_DashClient *dash, u32 group_idx); + +/*! disable speed adaptation +\param dash the target dash client +\param disable if GF_TRUE? speed adaptation is disabled, otherwise it is enabled +*/ +void gf_dash_disable_speed_adaptation(GF_DashClient *dash, Bool disable); + +/*! DASH/HLS quality information structure*/ +//UPDATE DASHQualityInfoNat in libgpac.py whenever modifying this structure !! +typedef struct +{ + /*! bandwidth in bits per second*/ + u32 bandwidth; + /*! ID*/ + const char *ID; + /*! mime type*/ + const char *mime; + /*! codec parameter of mime type*/ + const char *codec; + /*! video width*/ + u32 width; + /*! video width*/ + u32 height; + /*! video interlaced flag*/ + Bool interlaced; + /*! video framerate numerator*/ + u32 fps_num; + /*! video framerate denominator*/ + u32 fps_den; + /*! video sample aspect ratio numerator*/ + u32 par_num; + /*! video sample aspect ratio denominator*/ + u32 par_den; + /*! audio sample rate*/ + u32 sample_rate; + /*! audio channel count*/ + u32 nb_channels; + /*! disabled flag (not supported by DASH client)*/ + Bool disabled; + /*! selected flag*/ + Bool is_selected; + /*! AST offset in seconds, 0 if not low latency*/ + Double ast_offset; + /*! average segment duration, 0 if unknown*/ + Double average_duration; + /*! list of segmentURLs if known, NULL otherwise. Used for onDemand profile to get segment sizes*/ + const GF_List *seg_urls; +} GF_DASHQualityInfo; + +/*! gets information on a given quality +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param quality_idx the 0-based index of the quality +\param quality filled with information for the desired quality +\return error if any +*/ +GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 group_idx, u32 quality_idx, GF_DASHQualityInfo *quality); + +/*! gets segment template info used by group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param segment_timeline_timescale set to segment timeline timescale, or to 0 if no segment timeline +\param init_url set to initialization URL (template, timeline or base URL for VoD) as indicated in manifest (no resolution to base URL) +\return segment template, NULL if no templates used. Memory must be freed by caller +*/ +char *gf_dash_group_get_template(GF_DashClient *dash, u32 group_idx, u32 *segment_timeline_timescale, const char **init_url); + +/*! checks automatic switching mode +\param dash the target dash client +\return GF_TRUE if automatic quality switching is enabled +*/ +Bool gf_dash_get_automatic_switching(GF_DashClient *dash); + +/*! sets automatic quality switching mode. If automatic switching is off, switching can only happen based on caller inputs +\param dash the target dash client +\param enable_switching if GF_TRUE, automatic switching is enabled; otherwise it is disabled +\return error if any +*/ +GF_Err gf_dash_set_automatic_switching(GF_DashClient *dash, Bool enable_switching); + +/*! selects quality of a group, either by ID or by index if ID is null +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param ID the ID of the desired quality +\param q_idx the 0-based index of the desired quality +\return error if any +*/ +GF_Err gf_dash_group_select_quality(GF_DashClient *dash, u32 group_idx, const char *ID, u32 q_idx); + +/*! gets currently active quality of a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the current quality index for the given group*/ +s32 gf_dash_group_get_active_quality(GF_DashClient *dash, u32 group_idx); + +/*! forces NTP of the DASH client to be the given NTP +\param dash the target dash client +\param server_ntp NTP timestamp to set as server clock +*/ +void gf_dash_override_ntp(GF_DashClient *dash, u64 server_ntp); + +/*! Tile adaptation mode +This mode specifies how bitrate is allocated across tiles of the same video +*/ +typedef enum +{ + /*! 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*/ + GF_DASH_ADAPT_TILE_ROWS, + /*! bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row*/ + GF_DASH_ADAPT_TILE_ROWS_REVERSE, + /*! bitrate decreased for top and bottom rows only, same rate for each tile on the row*/ + GF_DASH_ADAPT_TILE_ROWS_MIDDLE, + /*! bitrate decreases for each column of tiles starting from the left, same rate for each tile on the column*/ + GF_DASH_ADAPT_TILE_COLUMNS, + /*! bitrate decreases for each column of tiles starting from the right, same rate for each tile on the column*/ + GF_DASH_ADAPT_TILE_COLUMNS_REVERSE, + /*! bitrate decreased for left and right columns only, same rate for each tile on the column*/ + GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE, + /*! bitrate decreased for all tiles on the edge of the picture*/ + 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 +\param mode the desired tile adaptation mode +\param tile_rate_decrease percentage (0->100) of global bandwidth to use at each level (recursive rate decrease for all level). If 0% or 100%, automatic rate allocation among tiles is performed (default mode) +*/ +void gf_dash_set_tile_adaptation_mode(GF_DashClient *dash, GF_DASHTileAdaptationMode mode, u32 tile_rate_decrease); + + +/*! consider tile with highest quality degradation hints (not visible ones or not gazed at) as lost, triggering a GF_URL_REMOVE upon \ref gf_dash_group_get_next_segment_location calls. Mostly used to debug tiling adaptation +\param dash the target dash client +\param disable_tiles if GF_TRUE, tiles with highest quality degradation hints will not be played. +*/ +void gf_dash_disable_low_quality_tiles(GF_DashClient *dash, Bool disable_tiles); + +/*! gets current tile adaptation mode +\param dash the target dash client +\return tile adaptation mode*/ +GF_DASHTileAdaptationMode gf_dash_get_tile_adaptation_mode(GF_DashClient *dash); + +/*! gets max width and height in pixels of the SRD (Spatial Relationship Descriptor) a given group belongs to, if any +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param max_width set to the maximum width of the SRD of this group +\param max_height set to the maximum height of the SRD of this group +\return GF_TRUE if the group has an SRD descriptor associated, GF_FALSE otherwise +*/ +Bool gf_dash_group_get_srd_max_size_info(GF_DashClient *dash, u32 group_idx, u32 *max_width, u32 *max_height); + +/*! gets SRD info, in SRD coordinate, of the SRD this group belongs to, if any +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param srd_id set to the id of the SRD of this group +\param srd_x set to the horizontal coordinate of the SRD of this group +\param srd_y set to the vertical coordinate of the SRD of this group +\param srd_w set to the width of the SRD of this group +\param srd_h set to the height of the SRD of this group +\param srd_width set to the reference width (usually max width) of the SRD of this group +\param srd_height set to the reference height (usually max height) of the SRD of this group +\return GF_TRUE if the group has an SRD descriptor associated, GF_FALSE otherwise +*/ +Bool gf_dash_group_get_srd_info(GF_DashClient *dash, u32 group_idx, u32 *srd_id, u32 *srd_x, u32 *srd_y, u32 *srd_w, u32 *srd_h, u32 *srd_width, u32 *srd_height); + +/*! sets quality hint for the given group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param quality_degradation_hint quality degradation from 0 (no degradation) to 100 (worse quality possible) +\return error if any +*/ +GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 group_idx, u32 quality_degradation_hint); + +/*! sets visible rectangle of a video object, may be used for adaptation. If min_x==max_x==min_y=max_y==0, disable adaptation +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param min_x horizontal coordinate of first visible column +\param max_x horizontal coordinate of last visible column +\param min_y horizontal coordinate of first visible row +\param max_y horizontal coordinate of last visible row +\param is_gaze if set, {min_x, min_y} indicate the position of the gaze (and {max_x, max_y} are ignored) +\return error if any +*/ +GF_Err gf_dash_group_set_visible_rect(GF_DashClient *dash, u32 group_idx, u32 min_x, u32 max_x, u32 min_y, u32 max_y, Bool is_gaze); + +/*! Ignores xlink on periods if some adaptation sets are specified in the period with xlink +\param dash the target dash client +\param ignore_xlink if GF_TRUE? xlinks will be ignored on periods containing both xlinks and adaptation sets +*/ +void gf_dash_ignore_xlink(GF_DashClient *dash, Bool ignore_xlink); + +/*! checks if all groups are done +\param dash the target dash client +\return GF_TRUE if all active groups in period are done +*/ +Bool gf_dash_all_groups_done(GF_DashClient *dash); + +/*! sets a query string to append to xlink on periods +\param dash the target dash client +\param query_string the query string to append to xlinks on periods +*/ +void gf_dash_set_period_xlink_query_string(GF_DashClient *dash, const char *query_string); + +/*! sets MPD chaining mode +\param dash the target dash client +\param chaining_mode if 0, no chaining. If 1, chain at end. If 2 chain on error or at end +*/ +void gf_dash_set_chaining_mode(GF_DashClient *dash, u32 chaining_mode); + + +/*! DASH client adaptation algorithm*/ +typedef enum { + /*! no adaptation*/ + GF_DASH_ALGO_NONE = 0, + /*! GPAC rate-based adaptation*/ + GF_DASH_ALGO_GPAC_LEGACY_RATE, + /*! GPAC buffer-based adaptation*/ + GF_DASH_ALGO_GPAC_LEGACY_BUFFER, + /*! BBA-0*/ + GF_DASH_ALGO_BBA0, + /*! BOLA finite*/ + GF_DASH_ALGO_BOLA_FINITE, + /*! BOLA basic*/ + GF_DASH_ALGO_BOLA_BASIC, + /*! BOLA-U*/ + GF_DASH_ALGO_BOLA_U, + /*! BOLA-O*/ + GF_DASH_ALGO_BOLA_O, + /*! Custom*/ + GF_DASH_ALGO_CUSTOM +} GF_DASHAdaptationAlgorithm; + +/*! sets dash adaptation algorithm. Cannot be called on an active session +\param dash the target dash client +\param algo the algorithm to use +*/ +void gf_dash_set_algo(GF_DashClient *dash, GF_DASHAdaptationAlgorithm algo); + +/*! sets group download status of the last downloaded segment for non threaded modes +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param dep_rep_idx the 0-based index of the current dependent rep +\param err error status of the download, GF_OK if no error +*/ +void gf_dash_set_group_download_state(GF_DashClient *dash, u32 group_idx, u32 dep_rep_idx, GF_Err err); + +/*! sets group download statistics of the last downloaded segment for non threaded modes +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param dep_rep_idx the 0-based index of the dependent rep +\param bytes_per_sec transfer rates in bytes per seconds +\param file_size segment size in bytes +\param is_broadcast set to GF_TRUE if the file is received over a multicast/broadcast link such as eMBMS or ROUTE (i.e. file was pushed to cache) +\param us_since_start time in microseconds since start of the download +*/ +void gf_dash_group_store_stats(GF_DashClient *dash, u32 group_idx, u32 dep_rep_idx, u32 bytes_per_sec, u64 file_size, Bool is_broadcast, u64 us_since_start); + +/*! 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. +\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 +*/ +void gf_dash_set_route_ast_shift(GF_DashClient *dash, u32 ast_shift); + +/*! gets the minimum wait time before calling \ref gf_dash_process again for unthreaded mode +\param dash the target dash client +\return minimum wait time in ms +*/ +u32 gf_dash_get_min_wait_ms(GF_DashClient *dash); + +/*! gets the adaptation set ID of a given group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the adaptation set ID, -1 if not set +*/ +s32 gf_dash_group_get_as_id(GF_DashClient *dash, u32 group_idx); + +/*! check if the group has an init segment associated +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if init segment is present, GF_FALSE otherwise +*/ +Bool gf_dash_group_has_init_segment(GF_DashClient *dash, u32 group_idx); + +//any change to the structure below MUST be reflected in libgpac.py !! + +/*! Information passed to DASH custom algorithm*/ +typedef struct +{ + /*! last segment download rate in bits per second */ + u32 download_rate; + /*! size of last downloaded segment*/ + u32 file_size; + /*! current playback speed*/ + Double speed; + /*! max supported playback speed according to associated decoder stats*/ + Double max_available_speed; + /*! display width of the video in pixels, 0 if audio stream*/ + u32 display_width; + /*! display height of the video in pixels, 0 if audio stream*/ + u32 display_height; + /*! index of currently selected quality*/ + u32 active_quality_idx; + /*! minimum buffer level in milliseconds below witch rebuffer will happen*/ + u32 buffer_min_ms; + /*! maximum buffer level allowed in milliseconds. Packets won't get dropped if overflow, but the algorithm should try not to overflow this buffer*/ + u32 buffer_max_ms; + /*! current buffer level in milliseconds*/ + u32 buffer_occupancy_ms; + /*! degradation hint, 0 means no degradation, 100 means tile completely hidden*/ + u32 quality_degradation_hint; + /*! cumulated download rate of all active groups - 0 means all files are local*/ + u32 total_rate; +} GF_DASHCustomAlgoInfo; + +/*! Callback function for custom rate adaptation +\param udta user data +\param group_idx index of group to adapt +\param base_group_idx index of associated base group if group is a dependent group +\param force_lower_complexity set to true if the dash client would like a lower complexity +\param stats statistics for last downloaded segment +\return value can be: +- the index of the new quality to select (as listed in group.reps[]) +- `-1` to not take decision now and postpone it until dependent groups are done +- `-2` to disable quality +- any other negative value means error + */ +typedef s32 (*gf_dash_rate_adaptation)(void *udta, u32 group_idx, u32 base_group_idx, Bool force_lower_complexity, GF_DASHCustomAlgoInfo *stats); + +/*! Callback function for custom rate monitor, not final yet +\param udta user data +\param group_idx index of group to adapt +\param bits_per_sec estimated download rate (not premultiplied by playback speed) +\param total_bytes size of segment being downloaded, 0 if unknown +\param bytes_done bytes received for segment +\param us_since_start microseconds ellapse since segment was sheduled for download +\param buffer_dur_ms current buffer duration in milliseconds +\param current_seg_dur duration of segment being downloaded, 0 if unknown +\return quality index (>=0) to switch to after abort, -1 to do nothing (no abort), -2 for internal algorithms having already setup the desired quality and requesting only abort + */ +typedef s32 (*gf_dash_download_monitor)(void *udta, u32 group_idx, u32 bits_per_sec, u64 total_bytes, u64 bytes_done, u64 us_since_start, u32 buffer_dur_ms, u32 current_seg_dur); + + +/*! sets custom rate adaptation logic +\param dash the target dash client +\param udta user data to pass back to callback functions +\param algo_custom rate adaptation custom logic +\param download_monitor_custom download monitor custom logic + */ +void gf_dash_set_algo_custom(GF_DashClient *dash, void *udta, + gf_dash_rate_adaptation algo_custom, + gf_dash_download_monitor download_monitor_custom); + + +#endif //GPAC_DISABLE_DASH_CLIENT + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_DASH_H_*/ + diff --git a/include/gpac/download.h b/include/gpac/download.h new file mode 100644 index 0000000..907e649 --- /dev/null +++ b/include/gpac/download.h @@ -0,0 +1,568 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2021 + * 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_DOWNLOAD_H_ +#define _GF_DOWNLOAD_H_ + +/*! +\file +\brief HTTP(S) Downloader. + */ + +/*! + +\addtogroup download_grp +\brief HTTP Downloader + +This section documents the file downloading tools the GPAC framework. Currently HTTP and HTTPS are supported. HTTPS may not be supported depending on GPAC compilation options (HTTPS in GPAC needs OpenSSL installed on the system). + +@{ + + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/*!the download manager object. This is usually not used by GPAC modules*/ +typedef struct __gf_download_manager GF_DownloadManager; +/*!the download manager session.*/ +typedef struct __gf_download_session GF_DownloadSession; +/*!the optional filter session.*/ +typedef struct __gf_filter_session GF_DownloadFilterSession; + +/*! URL information object*/ +typedef struct GF_URL_Info_Struct { + const char * protocol; + char * server_name; + char * remotePath; + char * canonicalRepresentation; + char * userName; + char * password; + u16 port; +} GF_URL_Info; + +/*! +Extracts the information from an URL. A call to gf_dm_url_info_init() must have been issue before calling this method. +\param url The URL to fill +\param info This structure will be initialized properly and filled with the data +\param baseURL The baseURL to use if any (can be null) +\return GF_OK if URL is well formed and supported by GPAC + */ +GF_Err gf_dm_get_url_info(const char * url, GF_URL_Info * info, const char * baseURL); + +/** +Inits the GF_URL_Info structure before it can be used +\param info The structure to initialize + */ +void gf_dm_url_info_init(GF_URL_Info * info); + +/*! +Frees the inner structures of a GF_URL_Info_Struct +\param info The info to free + */ +void gf_dm_url_info_del(GF_URL_Info * info); + +/*! +\brief download manager constructor + +Creates a new download manager object. +\param fsess optional filter session. If not NULL, the filter session will be used for async downloads. Otherwise, threads will be created +\return the download manager object +*/ +GF_DownloadManager *gf_dm_new(GF_DownloadFilterSession *fsess); +/*! +\brief download manager destructor + +Deletes the download manager. All running sessions are aborted +\param dm the download manager object + */ +void gf_dm_del(GF_DownloadManager *dm); + +/*! +\brief callback function for authentication + +The gf_dm_get_usr_pass type is the type for the callback of the \ref gf_dm_set_auth_callback function used for password retrieval +\param usr_cbk opaque user data +\param site_url url of the site the user and password are requested for +\param usr_name the user name for this site. The allocated space for this buffer is 50 bytes. \note this varaibale may already be formatted. +\param password the password for this site and user. The allocated space for this buffer is 50 bytes. +\return 0 if user didn't fill in the information which will result in an authentication failure, 1 otherwise. +*/ +typedef Bool (*gf_dm_get_usr_pass)(void *usr_cbk, const char *site_url, char *usr_name, char *password); + +/*! +\brief password retrieval assignment + +Assigns the callback function used for user password retrieval. If no such function is assigned to the download manager, all downloads requiring authentication will fail. +\param dm the download manager object +\param get_pass specifies \ref gf_dm_get_usr_pass callback function for user and password retrieval. +\param usr_cbk opaque user data passed to callback function + */ +void gf_dm_set_auth_callback(GF_DownloadManager *dm, gf_dm_get_usr_pass get_pass, void *usr_cbk); + +/*!downloader session message types*/ +typedef enum +{ + /*!signal that session is setup and waiting for connection request*/ + GF_NETIO_SETUP = 0, + /*!signal that session connection is done*/ + GF_NETIO_CONNECTED, + /*!request a protocol method from the user. Default value is "GET" for HTTP*/ + GF_NETIO_GET_METHOD, + /*!request a header from the user. */ + GF_NETIO_GET_HEADER, + /*!requesting content from the user, if any. Content is appended to the request*/ + 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*/ + GF_NETIO_DATA_EXCHANGE, + /*!all data has been transfered*/ + GF_NETIO_DATA_TRANSFERED, + /*!signal that the session has been deconnected*/ + GF_NETIO_DISCONNECTED, + /*!downloader session failed (error code set) or done/destroyed (no error code)*/ + GF_NETIO_STATE_ERROR, + /*!signal that a new session is being requested on that same connection (h2, h3) + This is only used for server sessions*/ + GF_NETIO_REQUEST_SESSION, + /*! stream has been canceled by remote peer*/ + GF_NETIO_CANCEL_STREAM, +} GF_NetIOStatus; + +/*!session download flags*/ +typedef enum +{ + /*!session is not threaded, the user must explicitly fetch the data , either with the function gf_dm_sess_fetch_data + or the function gf_dm_sess_process- if the session is threaded, the user must call gf_dm_sess_process to start the session*/ + GF_NETIO_SESSION_NOT_THREADED = 1, + /*! session data is cached or not */ + GF_NETIO_SESSION_NOT_CACHED = 1<<1, + /*! forces data notification even when session is threaded*/ + GF_NETIO_SESSION_NOTIFY_DATA = 1<<2, + /*indicates that the connection to the server should be kept once the download is successfully completed*/ + GF_NETIO_SESSION_PERSISTENT = 1<<3, + /*file is stored in memory, and the cache name is set to gpac://%u@%p, where %d is the size in bytes and %d is the the pointer to the memory. + Memory cached files are destroyed upon downloader destruction*/ + GF_NETIO_SESSION_MEMORY_CACHE = 1<<4, + /*! do not delete files after download*/ + GF_NETIO_SESSION_KEEP_CACHE = 1<<5, + /*! do not delete files after download of first resource (used for init segments)*/ + GF_NETIO_SESSION_KEEP_FIRST_CACHE = 1<<6, +} GF_NetIOFlags; + + +/*!protocol I/O parameter*/ +typedef struct +{ + /*!parameter message type*/ + 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*/ + const u8 *data; + /*!size of associated data. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE messages*/ + u32 size; + /*protocol header. Only valid for GF_NETIO_GET_HEADER, GF_NETIO_PARSE_HEADER and GF_NETIO_GET_METHOD*/ + 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 + */ + u32 reply; + /*download session for which the message is being sent*/ + GF_DownloadSession *sess; +} GF_NETIO_Parameter; + +/*! +\brief callback function for data reception and state signaling + +The gf_dm_user_io type is the type for the data callback function of a download session +\param usr_cbk opaque user data +\param parameter the input/output parameter structure +*/ +typedef void (*gf_dm_user_io)(void *usr_cbk, GF_NETIO_Parameter *parameter); + + + +/*! +\brief download session constructor + +Creates a new download session +\param dm the download manager object +\param url file to retrieve (no PUT/POST yet, only downloading is supported) +\param dl_flags combination of session download flags +\param user_io specifies \ref gf_dm_user_io callback function for data reception and service messages +\param usr_cbk opaque user data passed to callback function +\param error error for failure cases +\return the session object or NULL if error. If no error is indicated and a NULL session is returned, this means the file is local + */ +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 *error); + +/*! +\brief download session simple constructor + +Creates a new download session +\param dm The download manager used to create the download session +\param url file to retrieve (no PUT/POST yet, only downloading is supported) +\param dl_flags combination of session download flags +\param user_io specifies \ref gf_dm_user_io callback function for data reception and service messages +\param usr_cbk opaque user data passed to callback function +\param e error for failure cases +\return the session object or NULL if error. If no error is indicated and a NULL session is returned, this means the file is local + */ +GF_DownloadSession *gf_dm_sess_new_simple(GF_DownloadManager * dm, const char *url, u32 dl_flags, + gf_dm_user_io user_io, + void *usr_cbk, + GF_Err *e); + +/*! + \brief downloader session destructor + +Deletes the download session, cleaning the cache if indicated in the configuration file of the download manager (section "Downloader", key "CleanCache") +\param sess the download session +*/ +void gf_dm_sess_del(GF_DownloadSession * sess); + +/*! +\brief aborts downloading + +Aborts all operations in the session, regardless of its state. The session cannot be reused once this is called. +\param sess the download session + */ +void gf_dm_sess_abort(GF_DownloadSession * sess); + +/*! +\brief gets last session error + +Gets the last error that occured in the session +\param sess the download session +\return the last error + */ +GF_Err gf_dm_sess_last_error(GF_DownloadSession *sess); + + +/*! +\brief fetches data on session + +Fetches data from the server. This will also performs connections and all needed exchange with server. +\param sess the download session +\param buffer destination buffer +\param buffer_size destination buffer allocated size +\param read_size amount of data actually fetched +\note this can only be used when the session is not threaded +\return error if any + */ +GF_Err gf_dm_sess_fetch_data(GF_DownloadSession * sess, char *buffer, u32 buffer_size, u32 *read_size); + +/*! +\brief get mime type as lower case + +Fetches the mime type of the URL this session is fetching, value will be returned lower case, so application/x-mpegURL will be returned as application/x-mpegurl +\param sess the download session +\return the mime type of the URL, or NULL if error. You should get the error with \ref gf_dm_sess_last_error + */ +const char *gf_dm_sess_mime_type(GF_DownloadSession * sess); + +/*! +\brief sets session range + +Sets the session byte range. This shll be called before processing the session. +\param sess the download session +\param start_range HTTP download start range in byte +\param end_range HTTP download end range in byte +\param discontinue_cache If set, forces a new cache entry if byte range are not continuous. Otherwise a single cache entry is used to reconstruct the file +\note this can only be used when the session is not threaded +\return error if any + */ +GF_Err gf_dm_sess_set_range(GF_DownloadSession *sess, u64 start_range, u64 end_range, Bool discontinue_cache); +/*! +\brief get cache file name + +Gets the cache file name for the session. +\param sess the download session +\return the absolute path of the cache file, or NULL if the session is not cached*/ +const char *gf_dm_sess_get_cache_name(GF_DownloadSession * sess); + +/*! +\brief Marks the cache file to be deleted once the file is not used anymore by any session +\param dm the download manager +\param url The URL associate to the cache entry to be deleted + */ +void gf_dm_delete_cached_file_entry(const GF_DownloadManager * dm, const char * url); + +/*! +Marks the cache file for this session to be deleted once the file is not used anymore by any session +\param sess the download session +\param url The URL associate to the cache entry to be deleted + */ +void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url); + +/*! +\brief get statistics + +Gets download statistics for the session. All output parameters are optional and may be set to NULL. +\param sess the download session +\param server the remote server address +\param path the path on the remote server +\param total_size the total size in bytes the file fetched, 0 if unknown. +\param bytes_done the amount of bytes received from the server +\param bytes_per_sec the average data rate in bytes per seconds +\param net_status the session status +\return error if any + */ +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); + +/*! +\brief get start time + +Gets session start time in UTC. If chunk-transfer is used, the start time is reset at each chunk start +\param sess the download session +\return UTC start time + */ +u64 gf_dm_sess_get_utc_start(GF_DownloadSession *sess); + + +/*! +\brief fetch session object + +Fetches the session object (process all headers and data transfer). This is only usable if the session is not threaded +\param sess the download session +\return the last error in the session or 0 if none*/ +GF_Err gf_dm_sess_process(GF_DownloadSession *sess); + +/*! +\brief fetch session object headers + +Fetch the session object headers and stops after that. This is only usable if the session is not threaded +\param sess the download session +\return the last error in the session or 0 if none*/ +GF_Err gf_dm_sess_process_headers(GF_DownloadSession * sess); + +/*! +\brief Get session resource url + +Returns the original resource URL associated with the session +\param sess the download session +\return the associated URL + */ +const char *gf_dm_sess_get_resource_name(GF_DownloadSession *sess); + +#ifndef GPAC_DISABLE_CORE_TOOLS +/*! +Downloads a file over the network using a download manager +\param dm The download manager to use, function will use all associated cache resources +\param url The url to download +\param filename The filename to download +\param start_range start position of a byte range +\param end_range end position of a byte range +\param redirected_url If not NULL, \p redirected_url will be allocated and filled with the URL after redirection. Caller takes ownership +\return GF_OK if everything went fine, an error otherwise + */ +GF_Err gf_dm_wget_with_cache(GF_DownloadManager * dm, const char *url, const char *filename, u64 start_range, u64 end_range, char **redirected_url); + +/*! +\brief Same as gf_dm_wget_with_cache, but initializes the GF_DownloadManager by itself. + +This function is deprecated, please use gf_dm_wget_with_cache instead +\param url The url to download +\param filename The filename to download +\param start_range start position of a byte range +\param end_range end position of a byte range +\param redirected_url If not NULL, \p redirected_url will be allocated and filled with the URL after redirection. Caller takes ownership +\return GF_OK if everything went fine, an error otherwise + */ +GF_Err gf_dm_wget(const char *url, const char *filename, u64 start_range, u64 end_range, char **redirected_url); + +#endif /* GPAC_DISABLE_CORE_TOOLS */ + +/*! +Re-setup an existing, completed session to download a new URL. If same server/port/protocol is used, the same socket will be reused if the session has the GF_NETIO_SESSION_PERSISTENT flag set. This is only possible if the session is not threaded. +\param sess The session +\param url The new url for the session +\param allow_direct_reuse Allow reuse of cache entry without checking cache directives +\return GF_OK or error + */ +GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse); + +/*! +\brief retrieves the HTTP header value for the given name + +Retrieves the HTTP header value for the given header name. +\param sess the current session +\param name the target header name +\return header value or NULL if not found + */ +const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name); + +/*! +\brief enumerates the HTTP headers for a session + +Retrieves the HTTP header name and value for the given header index. +\param sess the current session +\param idx index for the enumeration, must be initialized to 0 for the first call +\param hdr_name where name of header is stored - do not free +\param hdr_val where value of header is stored - do not free +\return error code, GF_OK or GF_EOS if no more headers + */ +GF_Err gf_dm_sess_enum_headers(GF_DownloadSession *sess, u32 *idx, const char **hdr_name, const char **hdr_val); + +/*! +\brief sets download manager max rate per session + +Sets the maximum rate (per session only at the current time). +\param dm the download manager 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_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec); + +/*! +\brief gets download manager max rate per session + +Sets the maximum rate (per session only at the current time). +\param dm the download manager object +\return the rate in bits per sec. If 0, HTTP rate is not limited + */ +u32 gf_dm_get_data_rate(GF_DownloadManager *dm); + + +/*! +\brief gets cumultaed download rate for all sessions + +Gets the cumultated bitrate in of all active sessions. +\param dm the download manager object +\return the cumulated rate in bits per sec. + */ +u32 gf_dm_get_global_rate(GF_DownloadManager *dm); + + +/*! +\brief Get header sizes and times stats for the session + +Get header sizes and times stats for the session +\param sess the current session +\param req_hdr_size request header size in bytes. May be NULL. +\param rsp_hdr_size response header size in bytes. May be NULL. +\param connect_time connection time in micro seconds. May be NULL. +\param reply_time elapsed time between request sent and response header received, in micro seconds. May be NULL. +\param download_time download time since request sent, in micro seconds. May be NULL. +\return error code if any + */ +GF_Err gf_dm_sess_get_header_sizes_and_times(GF_DownloadSession *sess, u32 *req_hdr_size, u32 *rsp_hdr_size, u32 *connect_time, u32 *reply_time, u32 *download_time); + +/*! +\brief Forces session to use memory storage + +Forces session to use memory storage for future downloads +\param sess the current session +\param force_cache_type if 1, cache will be kept even if session is reassigned. If 2, cache will ne kept for next resource downloaded, then no caching for subsequent resources (used for init segments) + */ +void gf_dm_sess_force_memory_mode(GF_DownloadSession *sess, u32 force_cache_type); + +/*! +Registers a local cache provider (bypassing the http session), used when populating cache from input data (ROUTE for example) + +\param dm the download manager +\param local_cache_url_provider_cbk callback function to the cache provider. The callback function shall return GF_TRUE if the requested URL is provided by this local cache +\param lc_udta opaque pointer passed to the callback function +\return error code if any + */ +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); + +/*! +Adds a local entry in the cache + +\param dm the download manager +\param szURL the URL this resource is caching +\param blob blob object holding the data of the resource +\param start_range start range of the data in the resource +\param end_range start range of the data in the resource. If both start_range and end_range are 0, the data is the complete resource +\param mime associated MIME type if any +\param clone_memory indicates that the data shall be cloned in the cache because the caller will discard it +\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); + +/*! +Forces HTTP headers for a given cache entry + +\param dm the download manager +\param entry the cache entry to update +\param headers the header string, including CRLF delimiters, to force +\return error code if any +*/ +GF_Err gf_dm_force_headers(GF_DownloadManager *dm, const DownloadedCacheEntry entry, const char *headers); + +/*! HTTP methods*/ +enum +{ + /*! unsupported*/ + GF_HTTP_UNKNOWN = 0, + /*! GET*/ + GF_HTTP_GET, + /*! HEAD*/ + GF_HTTP_HEAD, + /*! OPTIONS*/ + GF_HTTP_OPTIONS, + /*! CONNECT*/ + GF_HTTP_CONNECT, + /*! TRACE*/ + GF_HTTP_TRACE, + /*! PUT*/ + GF_HTTP_PUT, + /*! POST*/ + GF_HTTP_POST, + /*! DELETE*/ + GF_HTTP_DELETE +}; + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_DOWNLOAD_H_*/ + diff --git a/include/gpac/dsmcc.h b/include/gpac/dsmcc.h new file mode 100644 index 0000000..a32ec1a --- /dev/null +++ b/include/gpac/dsmcc.h @@ -0,0 +1,665 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jonathan Sillan + * Copyright (c) Telecom ParisTech 2011-2012 + * 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_DSMCC_H_ +#define _GF_DSMCC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + + +#ifndef GPAC_DISABLE_MPEG2TS + + +#define DSMCC_SECTION_LENGTH_MAX 4093 + +typedef enum { + DOWNLOAD_INFO_REQUEST = 0x1001, + DOWNLOAD_INFO_REPONSE_INDICATION = 0x1002, + DOWNLOAD_DATA_BLOCK = 0x1003, + DOWNLOAD_DATA_REQUEST = 0x1004, + DOWNLOAD_DATA_CANCEL = 0x1005, + DOWNLOAD_SERVER_INITIATE = 0x1006 +} DSMCC_DOWNLOAD_MESSAGE_ID; + +typedef enum { + TAG_BIOP = 0x49534F06, + TAG_LITE_OPTIONS = 0x49534F05 +} DSMCC_DOWNLOAD_PROFILE_ID_TAG; + +typedef enum { + CACHING_PRIORITY_DESCRIPTOR = 0x71, + CONTENT_TYPE_DESCRIPTOR = 0x72, + COMPRESSED_MODULE_DESCRIPTOR = 0x09 +} DSMCC_BIOP_DESCRIPTOR; + +typedef struct { + u8 descriptor_tag; + u8 descriptor_length; + u32 carousel_id; + u8 FormatID; + u8 *private_data_byte; + u8 ModuleVersion; + u8 ModuleId; + u16 BlockSize; + u32 ModuleSize; + u8 CompressionMethod; + u32 OriginalSize; + u8 TimeOut; + u8 ObjectKeyLength; + u8* ObjectKeyData; +} GF_M2TS_CAROUSEL_INDENTIFIER_DESCRIPTOR; + +typedef struct +{ + u32 moduleId; + u32 downloadId; + u32 version_number; + Bool done; +} GF_M2TS_DSMCC_PROCESSED; + +typedef struct +{ + /* Module identifier */ + u32 moduleId; + /* Version number of the module */ + u32 version_number; + /* size in byte of the module */ + u32 size; + /* Download identifier */ + u32 downloadId; + /* buffer of data */ + char* buffer; + /* byte shifting in the buffer of data */ + u32 byte_sift; + /* last section number processed */ + u16 section_number; + /* the last section number of the module */ + u16 last_section_number; + /* size in byte of each block in the module */ + u32 block_size; + /* Checks if the module has been processed */ + /* 1 if yes */ + /* 0 otherwise */ + Bool processed; + /* Checks if the module's data are zipped */ + /* 1 if yes */ + /* 0 otherwise */ + Bool Gzip; + /* Size of the module's data after uncompression */ + u32 original_size; +} GF_M2TS_DSMCC_MODULE; + +typedef struct +{ + /* table id : identifier for dsmcc message type */ + u8 table_id; + /* indicates the presence of CRC 32 */ + u8 section_syntax_indicator; + u8 private_indicator; + /* length in byte of the dsmcc section */ + u16 dsmcc_section_length; + /* linked with the moduleId if carried by the section */ + u16 table_id_extension; + /* version number linked with the Data block if carried by the section */ + u8 version_number; + u8 current_next_indicator; + /* section number of the data block if carried by the section */ + u8 section_number; + /* last section number of the data block if carried by the section */ + u8 last_section_number; + void* DSMCC_Extension; + u32 checksum; + u32 CRC_32; +} GF_M2TS_DSMCC_SECTION; + +typedef struct +{ + u8 adaptationType; + char* adaptationDataByte; +} +GF_M2TS_DSMCC_ADAPTATION_HEADER; + +typedef struct +{ + u8 protocolDiscriminator; + u8 dsmccType; + u16 messageId; + /* dsmccMessageHeader mode */ + u32 transactionId; + /* dsmccDownloadDataHeader */ + u32 downloadId; + u8 reserved; + u8 adaptationLength; + u16 messageLength; + /* added not in the spec */ + u8 header_length; + GF_M2TS_DSMCC_ADAPTATION_HEADER* DsmccAdaptationHeader; + +} GF_M2TS_DSMCC_MESSAGE_DATA_HEADER; + +/* DOWNLOAD_DATA_MESSAGE */ +typedef struct +{ + u8 protocolDiscriminator; + u8 dsmccType; + u16 messageId; + u32 downloadId; + u8 reserved; + u8 adaptationLength; + u16 messageLength; + GF_M2TS_DSMCC_ADAPTATION_HEADER* DsmccAdaptationHeader; + +} GF_M2TS_DSMCC_DOWNLOAD_DATA_HEADER; + +typedef struct +{ + u8 subDescriptorType; + u8 subDescriptorLength; + u8 *additionalInformation; + +} GF_M2TS_DSMCC_SUBDESCRIPTOR; + +typedef struct +{ + u8 descriptorType; + u8 descriptorLength; + u8 specifierType; + u32 specifierData; + u16 model; + u16 version; + u8 subDescriptorCount; + GF_M2TS_DSMCC_SUBDESCRIPTOR* SubDescriptor; + +} GF_M2TS_DSMCC_DESCRIPTOR; + +typedef struct +{ + u16 compatibilityDescriptorLength; + u16 descriptorCount; + GF_M2TS_DSMCC_DESCRIPTOR* Descriptor; +} GF_M2TS_DSMCC_COMPATIBILITY_DESCRIPTOR; + +typedef struct +{ + u32 bufferSize; + u16 maximumBlockSize; + GF_M2TS_DSMCC_COMPATIBILITY_DESCRIPTOR CompatibilityDescr; + u16 privateDataLength; + char* privateDataByte; +} GF_M2TS_DSMCC_DOWNLOAD_INFO_REQUEST; + +typedef struct +{ + u16 moduleId; + u32 moduleSize; + u8 moduleVersion; + u8 moduleInfoLength; + char* moduleInfoByte; +} GF_M2TS_DSMCC_INFO_MODULES; + +typedef struct +{ + u32 downloadId; + u16 blockSize; + u8 windowSize; + u8 ackPeriod; + u32 tCDownloadWindow; + u32 tCDownloadScenario; + GF_M2TS_DSMCC_COMPATIBILITY_DESCRIPTOR CompatibilityDescr; + u16 numberOfModules; + GF_M2TS_DSMCC_INFO_MODULES Modules; + u16 privateDataLength; + char* privateDataByte; +} GF_M2TS_DSMCC_DOWNLOAD_INFO_RESP_INDIC; + +typedef struct +{ + u8 moduleId; + u8 moduleVersion; + u8 reserved; + u8 blockNumber; + char* blockDataByte; + /*added not in the spec */ + u32 dataBlocksize; +} GF_M2TS_DSMCC_DOWNLOAD_DATA_BLOCK; + +typedef struct +{ + u16 moduleId; + u16 blockNumber; + u8 downloadReason; +} GF_M2TS_DSMCC_DOWNLOAD_DATA_REQUEST_MESSAGE; + +typedef struct +{ + u32 downloadId; + u16 moduleId; + u16 blockNumber; + u8 downloadCancelReason; + u8 reserved; + u16 privateDataLength; + char* privateDataByte; +} GF_M2TS_DSMCC_DOWNLOAD_CANCEL; + +typedef struct +{ + u32 GroupId; + u32 GroupSize; + GF_M2TS_DSMCC_COMPATIBILITY_DESCRIPTOR CompatibilityDescr; + u16 GroupInfoLength; + char* groupInfoByte; +} GF_M2TS_DSMCC_INFO_GROUP; + +typedef struct +{ + u16 NumberOfGroups; + GF_M2TS_DSMCC_INFO_GROUP* InfoGroup; + u16 PrivateDataLength; + char* privateDataByte; + +} GF_M2TS_DSMCC_GROUP_INFO_INDICATION; + +typedef struct +{ + u8 serverId[20]; + GF_M2TS_DSMCC_COMPATIBILITY_DESCRIPTOR CompatibilityDescr; + u16 privateDataLength; + char* privateDataByte; + GF_M2TS_DSMCC_GROUP_INFO_INDICATION* GroupInfoIndic; +} GF_M2TS_DSMCC_DOWNLOAD_SERVER_INIT; + +typedef struct +{ + GF_M2TS_DSMCC_MESSAGE_DATA_HEADER DownloadDataHeader; + void* dataMessagePayload; +} GF_M2TS_DSMCC_DOWNLOAD_DATA_MESSAGE; + +/* DESCRIPTOR LIST */ + +typedef struct +{ + u8 descriptorTag; + u8 descriptorLength; + u8 postDiscontinuityIndicator; + u8 contentId; + u8 STC_Reference[5]; + u8 NPT_Reference[5]; + u16 scaleNumerator; + u16 scaleDenominator; +} GF_M2TS_DSMCC_NPT_REFERENCE_DESCRIPTOR; + + +typedef struct +{ + u8 descriptorTag; + u8 descriptorLength; + void* descriptor; +} GF_M2TS_DSMCC_STREAM_DESCRIPTOR; + +/* OBJECT CAROUSEL */ +typedef struct { + u16 id; + u16 use; + u16 assocTag; + u8 selector_length; + char* selector_data; + u16 selector_type; + u32 transactionId; + u32 timeout; +} GF_M2TS_DSMCC_BIOP_TAPS; + +typedef struct { + u8 AFI; + u8 type; + u32 carouselId; + u8 specifierType; + u32 specifierData; + u16 transport_stream_id; + u16 original_network_id; + u16 service_id; + u32 reserved; +} GF_M2TS_DSMCC_SERVICE_DOMAIN; + +typedef struct { + u32 componentId_tag; + u8 component_data_length; + u32 carouselId; + u16 moduleId; + u8 version_major; + u8 version_minor; + u8 objectKey_length; + u32 objectKey_data; +} GF_M2TS_DSMCC_BIOP_OBJECT_LOCATION; + +typedef struct { + u32 componentId_tag; + u8 component_data_length; + u8 taps_count; + GF_M2TS_DSMCC_BIOP_TAPS* Taps; + char* additional_tap_byte; +} GF_M2TS_DSMCC_BIOP_CONN_BINDER; + +typedef struct { + GF_M2TS_DSMCC_BIOP_OBJECT_LOCATION ObjectLocation; + GF_M2TS_DSMCC_BIOP_CONN_BINDER ConnBinder; +} GF_M2TS_DSMCC_BIOP_PROFILE_BODY; + +typedef struct { + u32 id_length; + char* id_data; + u32 kind_length; + char* kind_data; +} GF_M2TS_DSMCC_BIOP_NAME_COMPONENT; + +typedef struct { + u32 componentId_tag; + u8 component_data_length; + u8 serviceDomain_length; + GF_M2TS_DSMCC_SERVICE_DOMAIN serviceDomain_data; + u32 nameComponents_count; + GF_M2TS_DSMCC_BIOP_NAME_COMPONENT* NameComponent; + u32 initialContext_length; + char* InitialContext_data_byte; +} GF_M2TS_DSMCC_BIOP_SERVICE_LOCATION; + +typedef struct { + u32 componentId_tag; + u8 component_data_length; + char* component_data_byte; +} GF_M2TS_DSMCC_BIOP_LITE_COMPONENT; + +typedef struct { + u32 profileId_tag; + u32 profile_data_length; + u8 profile_data_byte_order; + u8 lite_component_count; + + GF_M2TS_DSMCC_BIOP_PROFILE_BODY* BIOPProfileBody; + GF_M2TS_DSMCC_BIOP_SERVICE_LOCATION* ServiceLocation; + GF_M2TS_DSMCC_BIOP_LITE_COMPONENT* LiteComponent; + +} GF_M2TS_DSMCC_BIOP_TAGGED_PROFILE; + +typedef struct { + u32 type_id_length; + char* type_id_byte; + u32 taggedProfiles_count; + GF_List* taggedProfile; +} GF_M2TS_DSMCC_IOR; + +typedef struct { + u32 moduleTimeOut; + u32 blockTimeOut; + u32 minBlockTime; + u8 taps_count; + GF_M2TS_DSMCC_BIOP_TAPS* Taps; + u8 userInfoLength; + u8* userInfo_data; + GF_List* descriptor; + + u8 compression_method; + u8 transparency_level; +} GF_M2TS_DSMCC_BIOP_MODULE_INFO; + +typedef struct { + u32 context_id; + u16 context_data_length; + char* context_data_byte; +} GF_M2TS_DSMCC_SERVICE_CONTEXT; + +typedef struct { + GF_M2TS_DSMCC_IOR IOR; + u8 downloadTaps_count; + GF_M2TS_DSMCC_BIOP_TAPS* Taps; + u8 serviceContextList_count; + GF_M2TS_DSMCC_SERVICE_CONTEXT* ServiceContext; + u16 userInfoLength; + char* userInfo_data; +} GF_M2TS_DSMCC_SERVICE_GATEWAY_INFO; + + +/* DESCRIPTORS */ +typedef struct { + u8 descriptor_tag; + u8 descriptor_length; + u8 priority_value; + u8 transparency_level; +} GF_M2TS_DSMCC_BIOP_CACHING_PRIORITY_DESCRIPTOR; + +typedef struct { + u8 descriptor_tag; + u8 descriptor_length; + u8 compression_method; + u32 original_size; +} GF_M2TS_DSMCC_BIOP_COMPRESSED_MODULE_DESCRIPTOR; + +typedef struct { + u8 descriptor_tag; + u8 descriptor_length; + char* content_type_data_byte; +} GF_M2TS_DSMCC_BIOP_CONTENT_TYPE_DESRIPTOR; + + +typedef struct { + /* "BIOP" */ + u32 magic; + u8 biop_version_major; + u8 biop_version_minor; + u8 byte_order; + u8 message_type; + /* size in byte of the whole object carousel */ + u32 message_size; + u8 objectKey_length; + /* witness the kind of object carousel the item is */ + /* fil for a file */ + /* dir for a directory */ + /* srg for the ServiceGateway */ + /* str for Stream Message */ + /* ste for Stream Event Message */ + u32 objectKey_data; + u32 objectKind_length; + /* The number that identifies the object in the module */ + char* objectKind_data; + u16 objectInfo_length; +} GF_M2TS_DSMCC_BIOP_HEADER; + +typedef struct { + GF_M2TS_DSMCC_BIOP_HEADER* Header; + u64 ContentSize; + GF_List* descriptor; + u8 serviceContextList_count; + GF_M2TS_DSMCC_SERVICE_CONTEXT* ServiceContext; + u32 messageBody_length; + /* size in byte of the data of the file */ + u32 content_length; + /* data a the file */ + char* content_byte; +} GF_M2TS_DSMCC_BIOP_FILE; + +typedef struct { + /* Name */ + u8 nameComponents_count; + /* There is must be only one nameComponent */ + u8 id_length; + /* the name of the item */ + char * id_data; + u8 kind_length; + /* the kind of the item */ + /* fil for a file */ + /* dir for a directory */ + /* srg for the ServiceGateway */ + /* str for Stream Message */ + /* ste for Stream Event Message */ + char* kind_data; + /* 1 if the item a file or a stream */ + /* 0 if the item si a directory */ + u8 BindingType; + GF_M2TS_DSMCC_IOR IOR; + u16 objectInfo_length; + u64 ContentSize; + GF_List* descriptor; +} GF_M2TS_DSMCC_BIOP_NAME; + +typedef struct { + GF_M2TS_DSMCC_BIOP_HEADER* Header; + char* objectInfo_data; + u8 serviceContextList_count; + GF_M2TS_DSMCC_SERVICE_CONTEXT* ServiceContext; + /* Length is byte of the message */ + u32 messageBody_length; + /* Number of the item */ + u16 bindings_count; + /* List of the item in the directory */ + GF_M2TS_DSMCC_BIOP_NAME* Name; +} GF_M2TS_DSMCC_BIOP_DIRECTORY; + +typedef struct { + u8 aDescription_length; + char* aDescription_bytes; + u32 duration_aSeconds; + u32 duration_aMicroseconds; + u8 audio; + u8 video; + u8 data; +} GF_M2TS_DSMCC_STREAM_INFO; + +typedef struct { + GF_M2TS_DSMCC_BIOP_HEADER* Header; + GF_M2TS_DSMCC_STREAM_INFO Info; + char* objectInfo_byte; + u8 serviceContextList_count; + GF_M2TS_DSMCC_SERVICE_CONTEXT* ServiceContext; + u32 messageBody_length; + u8 taps_count; + GF_M2TS_DSMCC_BIOP_TAPS* Taps; +} GF_M2TS_DSMCC_BIOP_STREAM_MESSAGE; + +typedef struct { + u8 eventName_length; + char* eventName_data_byte; +} GF_M2TS_DSMCC_BIOP_EVENT_LIST; + +typedef struct { + GF_M2TS_DSMCC_BIOP_HEADER* Header; + GF_M2TS_DSMCC_STREAM_INFO Info; + u16 eventNames_count; + GF_M2TS_DSMCC_BIOP_EVENT_LIST* EventList; + char* objectInfo_byte; + u8 serviceContextList_count; + GF_M2TS_DSMCC_SERVICE_CONTEXT* ServiceContext; + u32 messageBody_length; + u8 taps_count; + GF_M2TS_DSMCC_BIOP_TAPS* Taps; + u8 eventIds_count; + u16* eventId; +} GF_M2TS_DSMCC_BIOP_STREAM_EVENT; + +/*Define the base element for extracted dsmcc element*/ +#define GF_M2TS_DSMCC_ELEMENT \ + u32 moduleId; \ + u32 downloadId; \ + u32 version_number; \ + u32 objectKey_data; \ + char* name; \ + void* parent; + +typedef struct +{ + GF_M2TS_DSMCC_ELEMENT + /*Path to the file */ + char* Path; +} GF_M2TS_DSMCC_FILE; + +typedef struct +{ GF_M2TS_DSMCC_ELEMENT + /* List of files in the directory*/ + GF_List* File; + /* List of directories of the directory*/ + GF_List* Dir; + /*Path to the directory */ + char* Path; +} GF_M2TS_DSMCC_DIR; + +typedef struct +{ GF_M2TS_DSMCC_ELEMENT + /* Number of process directories */ + u8 nb_processed_dir; + /* Service Id of the data carousel*/ + u32 service_id; + /* List of files of the root of the file system*/ + GF_List* File; + /* List of directories of the root of the file system*/ + GF_List* Dir; +} GF_M2TS_DSMCC_SERVICE_GATEWAY; + +typedef struct +{ + /* List that carries the modules to process */ + GF_List* dsmcc_modules; + /* List of processed module */ + GF_M2TS_DSMCC_PROCESSED processed[512]; + /*Check if the ServiceGateway has been recovered*/ + /* 1 ServiceGateway received */ + /* 0 otherwise */ + Bool Got_ServiceGateway; + /* ServiceGateway Structure */ + GF_M2TS_DSMCC_SERVICE_GATEWAY* ServiceGateway; + /* u32 transactionId for DownloadInfoIndicator versioning */ + u32 transactionId; + /* List that carries modules that have been received before inti - TO DO */ + GF_List* Unprocessed_module; + /* Service ID that carries this carousel */ + u32 service_id; + /* Path of the root directory of the file system */ + char* root_dir; + /*Check if the index.html (root of the application) has been recovered*/ + /* 1 index.html received received */ + /* 0 otherwise */ + Bool get_index; + /* Number of the application that uses the carousel*/ + u32 application_id; +} GF_M2TS_DSMCC_OVERLORD; + +void on_dsmcc_section(GF_M2TS_Demuxer *ts, u32 evt_type, void *par); +GF_Err gf_m2ts_process_dsmcc(GF_M2TS_DSMCC_OVERLORD* dsmcc_overlord,GF_M2TS_DSMCC_SECTION *dsmcc, char *data, u32 data_size, u32 table_id); +GF_M2TS_DSMCC_OVERLORD* gf_m2ts_init_dsmcc_overlord(u32 service_id); +GF_M2TS_DSMCC_OVERLORD* gf_m2ts_get_dmscc_overlord(GF_List* Dsmcc_controller,u32 service_id); +void gf_m2ts_delete_dsmcc_overlord(GF_M2TS_DSMCC_OVERLORD* dsmcc_overlord); + +#endif /*GPAC_DISABLE_MPEG2TS*/ + +#ifdef __cplusplus +} +#endif + +#endif //_GF_CAROUSSEL_H_ + diff --git a/include/gpac/dvb_mpe.h b/include/gpac/dvb_mpe.h new file mode 100644 index 0000000..55f749b --- /dev/null +++ b/include/gpac/dvb_mpe.h @@ -0,0 +1,46 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / MPEG2-TS 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 gf_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 gf_free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef _GF_DVB_MPE_H_ +#define _GF_DVB_MPE_H_ + +#include +#include + +#ifndef GPAC_DISABLE_MPEG2TS + +typedef struct tag_m2ts_section_mpe GF_M2TS_SECTION_MPE; +typedef struct _sock_entry GF_SOCK_ENTRY; + +void gf_dvb_mpe_init(GF_M2TS_Demuxer *ts); +void gf_dvb_mpe_shutdown(GF_M2TS_Demuxer *ts); +GF_M2TS_ES *gf_dvb_mpe_section_new(); +void gf_dvb_mpe_section_del(GF_M2TS_ES *es); +void gf_m2ts_print_mpe_info(GF_M2TS_Demuxer *ts); + +#endif //GPAC_DISABLE_MPEG2TS + +#endif //_GF_DVB_MPE_H_ diff --git a/include/gpac/events.h b/include/gpac/events.h new file mode 100644 index 0000000..3bf16c7 --- /dev/null +++ b/include/gpac/events.h @@ -0,0 +1,425 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / Events management + * + * 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_EVENTS_H_ +#define _GF_EVENTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Event system used by GPAC playback. + */ + +/*! +\addtogroup evt_grp Event System +\ingroup playback_grp +\brief Event system used by GPAC playback. + +This section documents the event structures used by the terminal, the compositor, input modules and output rendering modules for communication. +@{ + */ + +#include +#include +#include + + +/*! mouse button modifiers*/ +enum +{ + GF_MOUSE_LEFT = 0, + GF_MOUSE_MIDDLE, + GF_MOUSE_RIGHT +}; + +/*! Mouse event structure + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_MOUSEMOVE, GF_EVENT_MOUSEWHEEL, GF_EVENT_MOUSEDOWN, GF_EVENT_MOUSEUP*/ + u8 type; + /*mouse location in output window, 2D-like: top-left (0,0), increasing y towards bottom*/ + s32 x, y; + /*wheel position (wheel current delta / wheel absolute delta) for GF_EVENT_MouseWheel*/ + Fixed wheel_pos; + /*0: left - 1: middle, 2- right*/ + u32 button; + /*key modifier*/ + u32 key_states; +} GF_EventMouse; + +/*! Mouse event structure + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_MULTITOUCH*/ + u8 type; + /*normalized center of multitouch event*/ + Fixed x, y; + /*finger rotation*/ + Fixed rotation; + /*finger pinch*/ + Fixed pinch; + /*number of fingers detected*/ + u32 num_fingers; +} GF_EventMultiTouch; + +/*! Keyboard key event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_KEYDOWN and GF_EVENT_KEYUP*/ + u8 type; + /*above GPAC/DOM key code*/ + u32 key_code; + /* hadrware key value (matching ASCI) */ + u32 hw_code; + /*key modifier*/ + u32 flags; +} GF_EventKey; + +/*! Keyboard character event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_TEXTINPUT*/ + u8 type; + /*above virtual key code*/ + u32 unicode_char; +} GF_EventChar; + +/*! Window resize event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_SIZE*/ + u8 type; + /*width and height*/ + u32 width, height; +} GF_EventSize; + +/*! Video setup (2D or 3D) event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_VIDEO_SETUP*/ + u8 type; + /*width and height of visual surface to allocate*/ + u32 width, height; + /*indicates whether double buffering is desired - when sent from plugin to player, indicates the backbuffer has been destroyed*/ + Bool back_buffer; + /*indicates whether system memory for the backbuffer is desired (no video blitting)*/ + Bool system_memory; + /*indicates whether opengl context shall be created. Values are: + GF_FALSE: no opengl context shall be created + GF_TRUE: opengl context shall be created for the main window and set as the current one + */ + Bool use_opengl; + + Bool disable_vsync; + + // resources must be resetup before next render step (this is mainly due to discard all OpenGL textures and cached objects) - inly used when sent from plugin to term + Bool hw_reset; +} GF_EventVideoSetup; + +/*! Windows show event + event proc return value: ignored + this event may be triggered by the compositor if owning window or if shortcut fullscreen is detected +*/ +typedef struct +{ + /*GF_EVENT_SHOWHIDE*/ + u8 type; + /*0: hidden - 1: visible - 2: fullscreen*/ + u32 show_type; +} GF_EventShow; + +/*! Mouse cursor event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_SET_CURSOR*/ + u8 type; + /*set if is visible*/ + u32 cursor_type; +} GF_EventCursor; + +/*! Window caption event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_SET_CAPTION*/ + u8 type; + const char *caption; +} GF_EventCaption; + +/*! Window move event + event proc: never posted +*/ +typedef struct +{ + /*GF_EVENT_MOVE*/ + u8 type; + s32 x, y; + /*0: absolute positionning, 1: relative move, 2: use alignment constraints*/ + u32 relative; + /*0: left/top, 1: middle, 2: right/bottom*/ + u8 align_x, align_y; +} GF_EventMove; + +/*! Media duration event + duration may be signaled several times: it may change when setting up streams + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_DURATION*/ + u8 type; + /*duration in seconds*/ + Double duration; + /*is seeking supported for service*/ + Bool can_seek; +} GF_EventDuration; + +/*! Hyperlink navigation event + event proc return value: 0 if URL not supported, 1 if accepted (it is the user responsability to load the url) + YOU SHALL NOT DIRECTLY OPEN THE NEW URL IN THE EVENT PROC, THIS WOULD DEADLOCK THE TERMINAL +*/ +typedef struct +{ + /*GF_EVENT_NAVIGATE and GF_EVENT_NAVIGATE_INFO*/ + u8 type; + /*new url to open / data to handle*/ + const char *to_url; + /*parameters (cf vrml spec) - UNUSED for GF_EVENT_NAVIGATE_INFO*/ + u32 param_count; + const char **parameters; +} GF_EventNavigate; + + +/*! Service message event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_MESSAGE*/ + u8 type; + /*name of service issuing the message*/ + const char *service; + /*message*/ + const char *message; + /*error if any*/ + GF_Err error; +} GF_EventMessage; + +/*! Progress event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_PROGRESS*/ + u8 type; + /*name of service issuing the progress notif*/ + const char *service; + /*progress type: 0: buffering, 1: downloading, 2: importing (BT/VRML/...), 3: filter status update*/ + u32 progress_type; + /*amount done and total amount of operation. + For buffer events, expresses current and total desired stream buffer in scene in milliseconds + For download events, expresses current and total size of download in bytes + For import events, no units defined (depends on importers) + */ + u32 done, total; + union { + /* for download events */ + u32 bytes_per_seconds; + /* for filter status event, index of filter in session */ + u32 filter_idx; + }; + +} GF_EventProgress; + +/*! Service connection event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_CONNECT*/ + u8 type; + /*sent upon connection/deconnection completion. if any error, it is signaled through message event*/ + Bool is_connected; +} GF_EventConnect; + +/*! Addon connection notification event + event proc return value: 1 to indicate the terminal should attempt a default layout for this addon, 0: nothing will be done +*/ +typedef struct +{ + /*GF_EVENT_ADDON_DETECTED*/ + u8 type; + const char *addon_url; + const char *mime_type; +} GF_EventAddonConnect; + +/*! Authentication event + event proc return value: 1 if info has been completed, 0 otherwise (and operation this request was for will then fail) +*/ +typedef struct +{ + /*GF_EVENT_AUTHORIZATION*/ + u8 type; + /*the URL the auth request is for*/ + const char *site_url; + /*user name (provided buffer can hold 50 bytes). It may already be formatted, or an empty ("") string*/ + char *user; + /*password (provided buffer can hold 50 bytes)*/ + char *password; +} GF_EventAuthorize; + + +/*! System desktop colors and window decoration event + event proc return value: 1 if info has been completed, 0 otherwise +*/ +typedef struct +{ + /*GF_EVENT_SYS_COLORS*/ + u8 type; + /*ARGB colors, in order: + ActiveBorder, ActiveCaption, AppWorkspace, Background, ButtonFace, ButtonHighlight, ButtonShadow, + ButtonText, CaptionText, GrayText, Highlight, HighlightText, InactiveBorder, InactiveCaption, + InactiveCaptionText, InfoBackground, InfoText, Menu, MenuText, Scrollbar, ThreeDDarkShadow, + ThreeDFace, ThreeDHighlight, ThreeDLightShadow, ThreeDShadow, Window, WindowFrame, WindowText + */ + u32 sys_colors[28]; +} GF_EventSysColors; + +/*! DOM mutation event*/ +typedef struct { + /* GF_EVENT_TREE_MODIFIED, GF_EVENT_NODE_INSERTED, GF_EVENT_NODE_REMOVED, GF_EVENT_NODE_INSERTED_DOC, GF_EVENT_NODE_REMOVED_DOC, GF_EVENT_ATTR_MODIFIED, GF_EVENT_CHAR_DATA_MODIFIED */ + u8 type; + void *relatedNode; + void *prevValue; + void *newValue; + void *attrName; + u8 attrChange; +} GF_EventMutation; + +/*! Open file notification event*/ +typedef struct { + /* GF_EVENT_DROPFILE*/ + u8 type; + u32 nb_files; + char **files; +} GF_EventOpenFile; + +/*! Orientation sensor change event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_SENSOR_ORIENTATION*/ + u8 type; + /*device orientation as quaternion if w is not 0, or as radians otherwise*/ + Float x, y, z, w; +} GF_EventSensor; + + +/*! Orientation sensor activation event + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_SENSOR_REQUEST*/ + u8 type; + /*device evt type to activate (eg GF_EVENT_SENSOR_ORIENTATION)*/ + u32 sensor_type; + Bool activate; +} GF_EventSensorRequest; + + +/*! Clipboard + event proc return value: true if text has been set for COPY, ignored otherwise +*/ +typedef struct +{ + /*GF_EVENT_MESSAGE*/ + u8 type; + /* + - const char * for PASTE_TEXT + - char * for COPY_TEXT, must be freed by caller + */ + char *text; +} GF_EventClipboard; + + +/*! Event object*/ +typedef union +{ + u8 type; + GF_EventMouse mouse; + GF_EventMultiTouch mtouch; + GF_EventKey key; + GF_EventChar character; + GF_EventSensor sensor; + GF_EventSize size; + GF_EventShow show; + GF_EventDuration duration; + GF_EventNavigate navigate; + GF_EventMessage message; + GF_EventProgress progress; + GF_EventConnect connect; + GF_EventCaption caption; + GF_EventCursor cursor; + GF_EventAuthorize auth; + GF_EventSysColors sys_cols; + GF_EventMove move; + GF_EventVideoSetup setup; + GF_EventMutation mutation; + GF_EventOpenFile open_file; + GF_EventAddonConnect addon_connect; + GF_EventSensorRequest activate_sensor; + GF_EventClipboard clipboard; +} GF_Event; + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_EVENTS_H_*/ + diff --git a/include/gpac/events_constants.h b/include/gpac/events_constants.h new file mode 100644 index 0000000..0b0e1e3 --- /dev/null +++ b/include/gpac/events_constants.h @@ -0,0 +1,528 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Events management + * + * 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_EVENTS_CONSTANTS_H_ +#define _GF_EVENTS_CONSTANTS_H_ + +/*! +\file +\brief Constants for event system used by GPAC playback. + */ + +/*! +\addtogroup evt_grp +\ingroup playback_grp +@{ + */ + + +/* + minimal event system + + DO NOT CHANGE THEIR POSITION IN THE LIST, USED TO SPEED UP FILTERING OF USER INPUT EVENTS +*/ + +/*! Event types*/ +typedef enum { + + /****************************************************** + + Events used for both GPAC internals and DOM Events + + *******************************************************/ + /*MouseEvents*/ + GF_EVENT_CLICK, + GF_EVENT_MOUSEUP, + GF_EVENT_MOUSEDOWN, + GF_EVENT_MOUSEOVER, + GF_EVENT_MOUSEOUT, + /*!! ALL MOUSE EVENTS SHALL BE DECLARED BEFORE MOUSEMOVE !! */ + GF_EVENT_MOUSEMOVE, + /*mouse wheel event*/ + GF_EVENT_MOUSEWHEEL, + + /*Key Events*/ + GF_EVENT_KEYUP, + GF_EVENT_KEYDOWN, /* covers KeyDown, KeyPress and AccessKey */ + GF_EVENT_LONGKEYPRESS, + /*character input*/ + GF_EVENT_TEXTINPUT, + + GF_EVENT_MULTITOUCH, + + /****************************************************** + + Events used for DOM Events only + + *******************************************************/ + GF_EVENT_TEXTSELECT, + + /*DOM UIEvents*/ + GF_EVENT_FOCUSIN, + GF_EVENT_FOCUSOUT, + GF_EVENT_ACTIVATE, + GF_EVENT_CHANGE, + GF_EVENT_FOCUS, + GF_EVENT_BLUR, + /*SVG (HTML) Events*/ + GF_EVENT_LOAD, + GF_EVENT_UNLOAD, + GF_EVENT_ABORT, + GF_EVENT_ERROR, + GF_EVENT_RESIZE, + GF_EVENT_SCROLL, + GF_EVENT_ZOOM, + GF_EVENT_BEGIN, /*this is a fake event, it is NEVER fired, only used in SMIL begin*/ + GF_EVENT_BEGIN_EVENT, + GF_EVENT_END, /*this is a fake event, it is NEVER fired, only used in SMIL end*/ + GF_EVENT_END_EVENT, + GF_EVENT_REPEAT, /*this is a fake event, it is NEVER fired, only used in SMIL repeat*/ + GF_EVENT_REPEAT_EVENT, + + /*DOM MutationEvents - NOT SUPPORTED YET*/ + GF_EVENT_TREE_MODIFIED, + GF_EVENT_NODE_INSERTED, + GF_EVENT_NODE_REMOVED, + GF_EVENT_NODE_INSERTED_DOC, + GF_EVENT_NODE_REMOVED_DOC, + GF_EVENT_ATTR_MODIFIED, + GF_EVENT_CHAR_DATA_MODIFIED, + GF_EVENT_NODE_NAME_CHANGED, + GF_EVENT_ATTR_NAME_CHANGED, + + GF_EVENT_DCCI_PROP_CHANGE, + + /*LASeR events*/ + GF_EVENT_ACTIVATED, + GF_EVENT_DEACTIVATED, + GF_EVENT_PAUSE, + GF_EVENT_PAUSED_EVENT, + GF_EVENT_PLAY, + GF_EVENT_REPEAT_KEY, + GF_EVENT_RESUME_EVENT, + GF_EVENT_SHORT_ACCESSKEY, + /*pseudo-event, only used in LASeR coding*/ + GF_EVENT_EXECUTION_TIME, + + /*HTML5 media events*/ + GF_EVENT_MEDIA_SETUP_BEGIN, /*not HTML5 but should be :)*/ + GF_EVENT_MEDIA_SETUP_DONE, /*not HTML5 but should be :)*/ + GF_EVENT_MEDIA_LOAD_START, + GF_EVENT_MEDIA_LOAD_DONE, /*not HTML5 but should be :)*/ + GF_EVENT_MEDIA_PROGRESS, + GF_EVENT_MEDIA_SUSPEND, + GF_EVENT_MEDIA_EMPTIED, + GF_EVENT_MEDIA_STALLED, + GF_EVENT_MEDIA_LOADED_METADATA, + GF_EVENT_MEDIA_LODADED_DATA, + GF_EVENT_MEDIA_CANPLAY, + GF_EVENT_MEDIA_CANPLAYTHROUGH, + GF_EVENT_MEDIA_PLAYING, + GF_EVENT_MEDIA_WAITING, + GF_EVENT_MEDIA_SEEKING, + GF_EVENT_MEDIA_SEEKED, + GF_EVENT_MEDIA_ENDED, + GF_EVENT_MEDIA_DURATION_CHANGED, + GF_EVENT_MEDIA_TIME_UPDATE, + GF_EVENT_MEDIA_RATECHANGE, + GF_EVENT_MEDIA_VOLUME_CHANGED, + + GF_EVENT_HTML_MSE_SOURCE_OPEN, + GF_EVENT_HTML_MSE_SOURCE_ENDED, + GF_EVENT_HTML_MSE_SOURCE_CLOSE, + GF_EVENT_HTML_MSE_UPDATE_START, + GF_EVENT_HTML_MSE_UPDATE, + GF_EVENT_HTML_MSE_UPDATE_END, + GF_EVENT_HTML_MSE_UPDATE_ERROR, + GF_EVENT_HTML_MSE_UPDATE_ABORT, + GF_EVENT_HTML_MSE_ADD_SOURCE_BUFFER, + GF_EVENT_HTML_MSE_REMOVE_SOURCE_BUFFER, + + GF_EVENT_BATTERY, + GF_EVENT_CPU, + GF_EVENT_UNKNOWN, + + + /****************************************************** + + Events used for GPAC internals only + + *******************************************************/ + + /*same as mousedown, generated internally by GPAC*/ + GF_EVENT_DBLCLICK, + + /*scene attached event, dispatched when the root node of a scene is loaded and + attached to the window or parent object (animation, inline, ...)*/ + GF_EVENT_SCENE_ATTACHED, + + /*VP resize attached event, dispatched when viewport of a scene is being modified + attached to the window or parent object (animation, inline, ...)*/ + GF_EVENT_VP_RESIZE, + + /*window events*/ + /*size has changed - indicate new w & h in .x end .y fields of event. + When sent from gpac to a video plugin, indicates the output size should be changed. This is only sent when the plugin + manages the output video himself + When sent from a video plugin to gpac, indicates the output size has been changed. This is only sent when the plugin + manages the output video himself + */ + GF_EVENT_SIZE, + /*signals the scene size (if indicated in scene) upon connection (sent to the user event proc only) + if scene size hasn't changed (seeking or other) this event is not sent + */ + GF_EVENT_SCENE_SIZE, + GF_EVENT_SHOWHIDE, /*window show/hide (minimized or other). */ + GF_EVENT_SET_CURSOR, /*set mouse cursor*/ + GF_EVENT_SET_CAPTION, /*set window caption*/ + GF_EVENT_MOVE, /*move window*/ + GF_EVENT_REFRESH, /*window needs repaint (whenever needed, eg restore, hide->show, background refresh, paint)*/ + GF_EVENT_QUIT, /*app is being closed - associated structure is evt.message to carry any potential reason for quiting*/ + /*video hw setup message: + - when sent from gpac to plugin, indicates that the plugin should re-setup hardware context due to a window resize: + * 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). Depending on windowing systems + and implementations, it could be possible to resize a window without destroying the GL context. + + - when sent from plugin to gpac, indicates that hardware has been setup. + - when sent from gpac to user, indicate aspect ratio has been modified and video output is ready + */ + GF_EVENT_VIDEO_SETUP, + //set current GL context for the calling thread + GF_EVENT_SET_GL, + /*queries the list of system colors - only exchanged between compositor and video output*/ + GF_EVENT_SYS_COLORS, + + /*indicates some text has been pasted - from video output to compositor only*/ + GF_EVENT_PASTE_TEXT, + /*queries for text to be copied - from video output to compositor only*/ + GF_EVENT_COPY_TEXT, + + /*terminal events*/ + GF_EVENT_CONNECT, /*signal URL is connected*/ + GF_EVENT_DURATION, /*signal duration of presentation*/ + GF_EVENT_EOS, /*signal End of scene playback*/ + GF_EVENT_AUTHORIZATION, /*indicates a user and pass is queried*/ + GF_EVENT_NAVIGATE, /*indicates the user app should load or jump to the given URL.*/ + GF_EVENT_NAVIGATE_INFO, /*indicates the link or its description under the mouse pointer*/ + GF_EVENT_MESSAGE, /*message from the MPEG-4 terminal*/ + GF_EVENT_PROGRESS, /*progress message from the MPEG-4 terminal*/ + GF_EVENT_VIEWPOINTS, /*indicates viewpoint list has changed - no struct associated*/ + GF_EVENT_STREAMLIST, /*indicates stream list has changed - no struct associated - only used when no scene info is present*/ + GF_EVENT_METADATA, /*indicates a change in associated metadata*/ + GF_EVENT_MIGRATE, /*indicates a session migration request*/ + GF_EVENT_DISCONNECT, /*indicates the current url should be disconnected*/ + GF_EVENT_RESOLUTION, /*indicates the screen resolution has changed*/ + GF_EVENT_DROPFILE, + /* Events for Keyboad */ + GF_EVENT_TEXT_EDITING_START, + GF_EVENT_TEXT_EDITING_END, + + //fire when quality change is detected by compositor + GF_EVENT_QUALITY_SWITCHED, + //fire when timeshift depth changes + GF_EVENT_TIMESHIFT_DEPTH, + //fire when position in timeshift buffer changes + GF_EVENT_TIMESHIFT_UPDATE, + //fire when position overflows the timeshift buffer + GF_EVENT_TIMESHIFT_OVERFLOW, + //fire when position underruns the timeshift buffer (eg fast forward / seek in the future) + GF_EVENT_TIMESHIFT_UNDERRUN, + GF_EVENT_MAIN_ADDON_STATE, + + GF_EVENT_ADDON_DETECTED, + + GF_EVENT_SENSOR_ORIENTATION, + GF_EVENT_SENSOR_REQUEST, +} GF_EventType; + +/*! GPAC/DOM3 key codes*/ +typedef enum { + GF_KEY_UNIDENTIFIED = 0, + GF_KEY_ACCEPT = 1, /* "Accept" The Accept (Commit) key.*/ + GF_KEY_AGAIN, /* "Again" The Again key.*/ + GF_KEY_ALLCANDIDATES, /* "AllCandidates" The All Candidates key.*/ + GF_KEY_ALPHANUM, /*"Alphanumeric" The Alphanumeric key.*/ + GF_KEY_ALT, /*"Alt" The Alt (Menu) key.*/ + GF_KEY_ALTGRAPH, /*"AltGraph" The Alt-Graph key.*/ + GF_KEY_APPS, /*"Apps" The Application key.*/ + GF_KEY_ATTN, /*"Attn" The ATTN key.*/ + GF_KEY_BROWSERBACK, /*"BrowserBack" The Browser Back key.*/ + GF_KEY_BROWSERFAVORITES, /*"BrowserFavorites" The Browser Favorites key.*/ + GF_KEY_BROWSERFORWARD, /*"BrowserForward" The Browser Forward key.*/ + GF_KEY_BROWSERHOME, /*"BrowserHome" The Browser Home key.*/ + GF_KEY_BROWSERREFRESH, /*"BrowserRefresh" The Browser Refresh key.*/ + GF_KEY_BROWSERSEARCH, /*"BrowserSearch" The Browser Search key.*/ + GF_KEY_BROWSERSTOP, /*"BrowserStop" The Browser Stop key.*/ + GF_KEY_CAPSLOCK, /*"CapsLock" The Caps Lock (Capital) key.*/ + GF_KEY_CLEAR, /*"Clear" The Clear key.*/ + GF_KEY_CODEINPUT, /*"CodeInput" The Code Input key.*/ + GF_KEY_COMPOSE, /*"Compose" The Compose key.*/ + GF_KEY_CONTROL, /*"Control" The Control (Ctrl) key.*/ + GF_KEY_CRSEL, /*"Crsel" The Crsel key.*/ + GF_KEY_CONVERT, /*"Convert" The Convert key.*/ + GF_KEY_COPY, /*"Copy" The Copy key.*/ + GF_KEY_CUT, /*"Cut" The Cut key.*/ + GF_KEY_DOWN, /*"Down" The Down Arrow key.*/ + GF_KEY_END, /*"End" The End key.*/ + GF_KEY_ENTER, /*"Enter" The Enter key. + Note: This key identifier is also used for the Return (Macintosh numpad) key.*/ + GF_KEY_ERASEEOF, /*"EraseEof" The Erase EOF key.*/ + GF_KEY_EXECUTE, /*"Execute" The Execute key.*/ + GF_KEY_EXSEL, /*"Exsel" The Exsel key.*/ + GF_KEY_F1, /*"F1" The F1 key.*/ + GF_KEY_F2, /*"F2" The F2 key.*/ + GF_KEY_F3, /*"F3" The F3 key.*/ + GF_KEY_F4, /*"F4" The F4 key.*/ + GF_KEY_F5, /*"F5" The F5 key.*/ + GF_KEY_F6, /*"F6" The F6 key.*/ + GF_KEY_F7, /*"F7" The F7 key.*/ + GF_KEY_F8, /*"F8" The F8 key.*/ + GF_KEY_F9, /*"F9" The F9 key.*/ + GF_KEY_F10, /*"F10" The F10 key.*/ + GF_KEY_F11, /*"F11" The F11 key.*/ + GF_KEY_F12, /*"F12" The F12 key.*/ + GF_KEY_F13, /*"F13" The F13 key.*/ + GF_KEY_F14, /*"F14" The F14 key.*/ + GF_KEY_F15, /*"F15" The F15 key.*/ + GF_KEY_F16, /*"F16" The F16 key.*/ + GF_KEY_F17, /*"F17" The F17 key.*/ + GF_KEY_F18, /*"F18" The F18 key.*/ + GF_KEY_F19, /*"F19" The F19 key.*/ + GF_KEY_F20, /*"F20" The F20 key.*/ + GF_KEY_F21, /*"F21" The F21 key.*/ + GF_KEY_F22, /*"F22" The F22 key.*/ + GF_KEY_F23, /*"F23" The F23 key.*/ + GF_KEY_F24, /*"F24" The F24 key.*/ + GF_KEY_FINALMODE, /*"FinalMode" The Final Mode (Final) key used on some asian keyboards.*/ + GF_KEY_FIND, /*"Find" The Find key.*/ + GF_KEY_FULLWIDTH, /*"FullWidth" The Full-Width Characters key.*/ + GF_KEY_HALFWIDTH, /*"HalfWidth" The Half-Width Characters key.*/ + GF_KEY_HANGULMODE, /*"HangulMode" The Hangul (Korean characters) Mode key.*/ + GF_KEY_HANJAMODE, /*"HanjaMode" The Hanja (Korean characters) Mode key.*/ + GF_KEY_HELP, /*"Help" The Help key.*/ + GF_KEY_HIRAGANA, /*"Hiragana" The Hiragana (Japanese Kana characters) key.*/ + GF_KEY_HOME, /*"Home" The Home key.*/ + GF_KEY_INSERT, /*"Insert" The Insert (Ins) key.*/ + GF_KEY_JAPANESEHIRAGANA, /*"JapaneseHiragana" The Japanese-Hiragana key.*/ + GF_KEY_JAPANESEKATAKANA, /*"JapaneseKatakana" The Japanese-Katakana key.*/ + GF_KEY_JAPANESEROMAJI, /*"JapaneseRomaji" The Japanese-Romaji key.*/ + GF_KEY_JUNJAMODE, /*"JunjaMode" The Junja Mode key.*/ + GF_KEY_KANAMODE, /*"KanaMode" The Kana Mode (Kana Lock) key.*/ + GF_KEY_KANJIMODE, /*"KanjiMode" The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key.*/ + GF_KEY_KATAKANA, /*"Katakana" The Katakana (Japanese Kana characters) key.*/ + GF_KEY_LAUNCHAPPLICATION1, /*"LaunchApplication1" The Start Application One key.*/ + GF_KEY_LAUNCHAPPLICATION2, /*"LaunchApplication2" The Start Application Two key.*/ + GF_KEY_LAUNCHMAIL, /*"LaunchMail" The Start Mail key.*/ + GF_KEY_LEFT, /*"Left" The Left Arrow key.*/ + GF_KEY_META, /*"Meta" The Meta key.*/ + GF_KEY_MEDIANEXTTRACK, /*"MediaNextTrack" The Media Next Track key.*/ + GF_KEY_MEDIAPLAYPAUSE, /*"MediaPlayPause" The Media Play Pause key.*/ + GF_KEY_MEDIAPREVIOUSTRACK, /*"MediaPreviousTrack" The Media Previous Track key.*/ + GF_KEY_MEDIASTOP, /*"MediaStop" The Media Stok key.*/ + GF_KEY_MODECHANGE, /*"ModeChange" The Mode Change key.*/ + GF_KEY_NONCONVERT, /*"Nonconvert" The Nonconvert (Don't Convert) key.*/ + GF_KEY_NUMLOCK, /*"NumLock" The Num Lock key.*/ + GF_KEY_PAGEDOWN, /*"PageDown" The Page Down (Next) key.*/ + GF_KEY_PAGEUP, /*"PageUp" The Page Up key.*/ + GF_KEY_PASTE, /*"Paste" The Paste key.*/ + GF_KEY_PAUSE, /*"Pause" The Pause key.*/ + GF_KEY_PLAY, /*"Play" The Play key.*/ + GF_KEY_PREVIOUSCANDIDATE, /*"PreviousCandidate" The Previous Candidate function key.*/ + GF_KEY_PRINTSCREEN, /*"PrintScreen" The Print Screen (PrintScrn, SnapShot) key.*/ + GF_KEY_PROCESS, /*"Process" The Process key.*/ + GF_KEY_PROPS, /*"Props" The Props key.*/ + GF_KEY_RIGHT, /*"Right" The Right Arrow key.*/ + GF_KEY_ROMANCHARACTERS, /*"RomanCharacters" The Roman Characters function key.*/ + GF_KEY_SCROLL, /*"Scroll" The Scroll Lock key.*/ + GF_KEY_SELECT, /*"Select" The Select key.*/ + GF_KEY_SELECTMEDIA, /*"SelectMedia" The Select Media key.*/ + GF_KEY_SHIFT, /*"Shift" The Shift key.*/ + GF_KEY_STOP, /*"Stop" The Stop key.*/ + GF_KEY_UP, /*"Up" The Up Arrow key.*/ + GF_KEY_UNDO, /*"Undo" The Undo key.*/ + GF_KEY_VOLUMEDOWN, /*"VolumeDown" The Volume Down key.*/ + GF_KEY_VOLUMEMUTE, /*"VolumeMute" The Volume Mute key.*/ + GF_KEY_VOLUMEUP, /*"VolumeUp" The Volume Up key.*/ + GF_KEY_WIN, /*"Win" The Windows Logo key.*/ + GF_KEY_ZOOM, /*"Zoom" The Zoom key.*/ + GF_KEY_BACKSPACE, /*"U+0008" The Backspace (Back) key.*/ + GF_KEY_TAB, /*"U+0009" The Horizontal Tabulation (Tab) key.*/ + GF_KEY_CANCEL, /*"U+0018" The Cancel key.*/ + GF_KEY_ESCAPE, /*"U+001B" The Escape (Esc) key.*/ + GF_KEY_SPACE, /*"U+0020" The Space (Spacebar) key.*/ + GF_KEY_EXCLAMATION, /*"U+0021" The Exclamation Mark (Factorial, Bang) key (!).*/ + GF_KEY_QUOTATION, /*"U+0022" The Quotation Mark (Quote Double) key (").*/ + GF_KEY_NUMBER, /*"U+0023" The Number Sign (Pound Sign, Hash, Crosshatch, Octothorpe) key (#).*/ + GF_KEY_DOLLAR, /*"U+0024" The Dollar Sign (milreis, escudo) key ($).*/ + GF_KEY_AMPERSAND, /*"U+0026" The Ampersand key (&).*/ + GF_KEY_APOSTROPHE, /*"U+0027" The Apostrophe (Apostrophe-Quote, APL Quote) key (').*/ + GF_KEY_LEFTPARENTHESIS, /*"U+0028" The Left Parenthesis (Opening Parenthesis) key (().*/ + GF_KEY_RIGHTPARENTHESIS, /*"U+0029" The Right Parenthesis (Closing Parenthesis) key ()).*/ + GF_KEY_STAR, /*"U+002A" The Asterix (Star) key (*).*/ + GF_KEY_PLUS, /*"U+002B" The Plus Sign (Plus) key (+).*/ + GF_KEY_COMMA, /*"U+002C" The Comma (decimal separator) sign key (,).*/ + GF_KEY_HYPHEN, /*"U+002D" The Hyphen-minus (hyphen or minus sign) key (-).*/ + GF_KEY_FULLSTOP, /*"U+002E" The Full Stop (period, dot, decimal point) key (.).*/ + GF_KEY_SLASH, /*"U+002F" The Solidus (slash, virgule, shilling) key (/).*/ + GF_KEY_0, /*"U+0030" The Digit Zero key (0).*/ + GF_KEY_1, /*"U+0031" The Digit One key (1).*/ + GF_KEY_2, /*"U+0032" The Digit Two key (2).*/ + GF_KEY_3, /*"U+0033" The Digit Three key (3).*/ + GF_KEY_4, /*"U+0034" The Digit Four key (4).*/ + GF_KEY_5, /*"U+0035" The Digit Five key (5).*/ + GF_KEY_6, /*"U+0036" The Digit Six key (6).*/ + GF_KEY_7, /*"U+0037" The Digit Seven key (7).*/ + GF_KEY_8, /*"U+0038" The Digit Eight key (8).*/ + GF_KEY_9, /*"U+0039" The Digit Nine key (9).*/ + GF_KEY_COLON, /*"U+003A" The Colon key (:).*/ + GF_KEY_SEMICOLON, /*"U+003B" The Semicolon key (;).*/ + GF_KEY_LESSTHAN, /*"U+003C" The Less-Than Sign key (<).*/ + GF_KEY_EQUALS, /*"U+003D" The Equals Sign key (=).*/ + GF_KEY_GREATERTHAN, /*"U+003E" The Greater-Than Sign key (>).*/ + GF_KEY_QUESTION, /*"U+003F" The Question Mark key (?).*/ + GF_KEY_AT, /*"U+0040" The Commercial At (@) key.*/ + GF_KEY_A, /*"U+0041" The Latin Capital Letter A key (A).*/ + GF_KEY_B, /*"U+0042" The Latin Capital Letter B key (B).*/ + GF_KEY_C, /*"U+0043" The Latin Capital Letter C key (C).*/ + GF_KEY_D, /*"U+0044" The Latin Capital Letter D key (D).*/ + GF_KEY_E, /*"U+0045" The Latin Capital Letter E key (E).*/ + GF_KEY_F, /*"U+0046" The Latin Capital Letter F key (F).*/ + GF_KEY_G, /*"U+0047" The Latin Capital Letter G key (G).*/ + GF_KEY_H, /*"U+0048" The Latin Capital Letter H key (H).*/ + GF_KEY_I, /*"U+0049" The Latin Capital Letter I key (I).*/ + GF_KEY_J, /*"U+004A" The Latin Capital Letter J key (J).*/ + GF_KEY_K, /*"U+004B" The Latin Capital Letter K key (K).*/ + GF_KEY_L, /*"U+004C" The Latin Capital Letter L key (L).*/ + GF_KEY_M, /*"U+004D" The Latin Capital Letter M key (M).*/ + GF_KEY_N, /*"U+004E" The Latin Capital Letter N key (N).*/ + GF_KEY_O, /*"U+004F" The Latin Capital Letter O key (O).*/ + GF_KEY_P, /*"U+0050" The Latin Capital Letter P key (P).*/ + GF_KEY_Q, /*"U+0051" The Latin Capital Letter Q key (Q).*/ + GF_KEY_R, /*"U+0052" The Latin Capital Letter R key (R).*/ + GF_KEY_S, /*"U+0053" The Latin Capital Letter S key (S).*/ + GF_KEY_T, /*"U+0054" The Latin Capital Letter T key (T).*/ + GF_KEY_U, /*"U+0055" The Latin Capital Letter U key (U).*/ + GF_KEY_V, /*"U+0056" The Latin Capital Letter V key (V).*/ + GF_KEY_W, /*"U+0057" The Latin Capital Letter W key (W).*/ + GF_KEY_X, /*"U+0058" The Latin Capital Letter X key (X).*/ + GF_KEY_Y, /*"U+0059" The Latin Capital Letter Y key (Y).*/ + GF_KEY_Z, /*"U+005A" The Latin Capital Letter Z key (Z).*/ + GF_KEY_LEFTSQUAREBRACKET, /*"U+005B" The Left Square Bracket (Opening Square Bracket) key ([).*/ + GF_KEY_BACKSLASH, /*"U+005C" The Reverse Solidus (Backslash) key (\).*/ + GF_KEY_RIGHTSQUAREBRACKET, /*"U+005D" The Right Square Bracket (Closing Square Bracket) key (]).*/ + GF_KEY_CIRCUM, /*"U+005E" The Circumflex Accent key (^).*/ + GF_KEY_UNDERSCORE, /*"U+005F" The Low Sign (Spacing Underscore, Underscore) key (_).*/ + GF_KEY_GRAVEACCENT, /*"U+0060" The Grave Accent (Back Quote) key (`).*/ + GF_KEY_LEFTCURLYBRACKET, /*"U+007B" The Left Curly Bracket (Opening Curly Bracket, Opening Brace, Brace Left) key ({).*/ + GF_KEY_PIPE, /*"U+007C" The Vertical Line (Vertical Bar, Pipe) key (|).*/ + GF_KEY_RIGHTCURLYBRACKET, /*"U+007D" The Right Curly Bracket (Closing Curly Bracket, Closing Brace, Brace Right) key (}).*/ + GF_KEY_DEL, /*"U+007F" The Delete (Del) Key.*/ + GF_KEY_INVERTEXCLAMATION, /*"U+00A1" The Inverted Exclamation Mark key (�).*/ + GF_KEY_DEADGRAVE, /*"U+0300" The Combining Grave Accent (Greek Varia, Dead Grave) key.*/ + GF_KEY_DEADEACUTE, /*"U+0301" The Combining Acute Accent (Stress Mark, Greek Oxia, Tonos, Dead Eacute) key.*/ + GF_KEY_DEADCIRCUM, /*"U+0302" The Combining Circumflex Accent (Hat, Dead Circumflex) key.*/ + GF_KEY_DEADTILDE, /*"U+0303" The Combining Tilde (Dead Tilde) key.*/ + GF_KEY_DEADMACRON, /*"U+0304" The Combining Macron (Long, Dead Macron) key.*/ + GF_KEY_DEADBREVE, /*"U+0306" The Combining Breve (Short, Dead Breve) key.*/ + GF_KEY_DEADABOVEDOT, /*"U+0307" The Combining Dot Above (Derivative, Dead Above Dot) key.*/ + GF_KEY_DEADDIARESIS, /*"U+0308" The Combining Diaeresis (Double Dot Abode, Umlaut, Greek Dialytika, Double Derivative, Dead Diaeresis) key.*/ + GF_KEY_DEADRINGABOVE, /*"U+030A" The Combining Ring Above (Dead Above Ring) key.*/ + GF_KEY_DEADDOUBLEACUTE, /*"U+030B" The Combining Double Acute Accent (Dead Doubleacute) key.*/ + GF_KEY_DEADCARON, /*"U+030C" The Combining Caron (Hacek, V Above, Dead Caron) key.*/ + GF_KEY_DEADCEDILLA, /*"U+0327" The Combining Cedilla (Dead Cedilla) key.*/ + GF_KEY_DEADOGONEK, /*"U+0328" The Combining Ogonek (Nasal Hook, Dead Ogonek) key.*/ + GF_KEY_DEADIOTA, /*"U+0345" The Combining Greek Ypogegrammeni (Greek Non-Spacing Iota Below, Iota Subscript, Dead Iota) key.*/ + GF_KEY_EURO, /*"U+20AC" The Euro Currency Sign key (�).*/ + GF_KEY_DEADVOICESOUND, /*"U+3099" The Combining Katakana-Hiragana Voiced Sound Mark (Dead Voiced Sound) key.*/ + GF_KEY_DEADSEMIVOICESOUND, /*"U+309A" The Combining Katakana-Hiragana Semi-Voiced Sound Mark (Dead Semivoiced Sound) key. */ + /* STB */ + GF_KEY_CHANNELUP, /*ChannelUp*/ + GF_KEY_CHANNELDOWN, /*ChannelDown*/ + GF_KEY_TEXT, /*Text*/ + GF_KEY_INFO, /*Info*/ + GF_KEY_EPG, /*EPG*/ + GF_KEY_RECORD, /*Record*/ + GF_KEY_BEGINPAGE, /*BeginPage*/ + /* end STB */ + + /*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; + + +/*! key modifiers state - set by terminal (not set by video driver)*/ +typedef enum +{ + GF_KEY_MOD_SHIFT = (1), + GF_KEY_MOD_CTRL = (1<<2), + GF_KEY_MOD_ALT = (1<<3), + + GF_KEY_EXT_NUMPAD = (1<<4), + GF_KEY_EXT_LEFT = (1<<5), + GF_KEY_EXT_RIGHT = (1<<6) +} GF_KeyModifier; + +/*! Sensor apperance signaling*/ +enum +{ + /*regular*/ + GF_CURSOR_NORMAL = 0x00, + /*hyperlink*/ + GF_CURSOR_ANCHOR, + /*touch/click*/ + GF_CURSOR_TOUCH, + /*discSensor, cylinderSensor, sphereSensor*/ + GF_CURSOR_ROTATE, + /*proximitySensor & proximitySensor2D*/ + GF_CURSOR_PROXIMITY, + /*planeSensor & planeSensor2D*/ + GF_CURSOR_PLANE, + /*collision*/ + GF_CURSOR_COLLIDE, + /*hide the cursor*/ + GF_CURSOR_HIDE, +}; + +#if 0 +/*! Mutation AttrChangeType Signaling*/ +enum +{ + /*! modification */ + GF_MUTATION_ATTRCHANGE_MODIFICATION = 0x01, + GF_MUTATION_ATTRCHANGE_ADDITION = 0x02, + GF_MUTATION_ATTRCHANGE_REMOVAL = 0x03, +}; +#endif + + +/*! @} */ + +#endif diff --git a/include/gpac/evg.h b/include/gpac/evg.h new file mode 100644 index 0000000..0cc2555 --- /dev/null +++ b/include/gpac/evg.h @@ -0,0 +1,1094 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / Embedded Vector Graphics engine + * + * 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_EVG_H_ +#define _GF_EVG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/*! +\file "gpac/evg.h" +\brief 2D vector graphics rasterizer + +This file contains all defined functions for 2D vector graphics of the GPAC framework. +*/ + +/*! +\addtogroup evg_grp +\brief Vector Graphics rendering of GPAC. + +GPAC uses a software rasterizer for vector graphics based on FreeType's "ftgray" anti-aliased renderer. + +The rasterizer supports +- drawing to RGB, YUV and grayscale surfaces. +- Texture mapping using RGB, YUV and grayscale textures +- Linear Gradients with opacity +- Radial Gradients with opacity +- Solid brush with opacity +- axis-aligned clipping + +The rasterizer is only capable of filling a given GF_Path object +- striking (outlining) of a path must be done by using \ref gf_path_get_outline to the the outline path +- text must be first converted to GF_Path + +The rasterizer also supports experimental drawing of 3D primitives (point, lines, triangles). This is obviously not intended to replace GPU rendering, but can +be useful to draw simple primitives over an existing video source. The following constraints apply in 3D mode: +- stencils are not used +- path can only be drawn using \ref gf_evg_surface_draw_path + +@{ +*/ + +/*! stencil type used for all stencils*/ +typedef struct _gf_evg_base_stencil GF_EVGStencil; +/*! surface type*/ +typedef struct _gf_evg_surface GF_EVGSurface; + + +/*! stencil types*/ +typedef enum +{ + /*! solid color stencil*/ + GF_STENCIL_SOLID = 0, + /*! linear color gradient stencil*/ + GF_STENCIL_LINEAR_GRADIENT, + /*! radial color gradient stencil*/ + GF_STENCIL_RADIAL_GRADIENT, + /*! texture stencil*/ + GF_STENCIL_VERTEX_GRADIENT, + /*! texture stencil*/ + GF_STENCIL_TEXTURE, +} GF_StencilType; + +/*! gradient filling modes*/ +typedef enum +{ + /*! edge colors are repeated until path is filled*/ + GF_GRADIENT_MODE_PAD, + /*! pattern is inversed each time it's repeated*/ + GF_GRADIENT_MODE_SPREAD, + /*! pattern is repeated to fill path*/ + GF_GRADIENT_MODE_REPEAT +} GF_GradientMode; + +/*! texture map flags*/ +typedef enum +{ + /*! texture is repeated in its horizontal direction*/ + GF_TEXTURE_REPEAT_S = (1<<1), + /*! texture is repeated in its horizontal direction*/ + GF_TEXTURE_REPEAT_T = (1<<2), + /*! texture is fliped horizontally*/ + GF_TEXTURE_FLIP_X = (1<<3), + /*! texture is fliped vertically*/ + GF_TEXTURE_FLIP_Y = (1<<4), +} GF_TextureMapFlags; + +/*! filter levels for texturing - up to the graphics engine but the following levels are used by +the client*/ +typedef enum +{ + /*! high speed mapping (ex, no filtering applied)*/ + GF_TEXTURE_FILTER_HIGH_SPEED, + /*! compromise between speed and quality (ex, filter to nearest pixel)*/ + GF_TEXTURE_FILTER_MID, + /*! high quality mapping (ex, bi-linear/bi-cubic interpolation)*/ + GF_TEXTURE_FILTER_HIGH_QUALITY +} GF_TextureFilter; + +/*! rasterizer antialiasing depending on the graphics engine*/ +typedef enum +{ + /*! raster shall use no antialiasing */ + GF_RASTER_HIGH_SPEED, + /*! raster should use fast mode and good quality if possible*/ + GF_RASTER_MID, + /*! raster should use full antialiasing - this is the default for all new surfaces*/ + GF_RASTER_HIGH_QUALITY +} GF_RasterQuality; + + +/*! common constructor for all stencil types +\param type the stencil type +\return a new stencil object*/ +GF_EVGStencil *gf_evg_stencil_new(GF_StencilType type); + +/*! common destructor for all stencils +\param stencil the target stencil +*/ +void gf_evg_stencil_delete(GF_EVGStencil *stencil); + +/*! sets stencil transformation matrix +\param stencil the target stencil +\param mat the 2D matrix to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_matrix(GF_EVGStencil *stencil, GF_Matrix2D *mat); + +/*! gets stencil transformation matrix +\param stencil the target stencil +\param mat the 2D matrix to fetch +\return TRUE if resulting matrix is fetched, false otherwise (matrix is identity or does not apply to this stencil class) +*/ +Bool gf_evg_stencil_get_matrix(GF_EVGStencil * stencil, GF_Matrix2D *mat); + +/*! sets stencil transformation matrix auto mode. + +When auto matrix mode is activated, the stencil matrix only describes texture mapping transform in uv space (normalized UV coordinates, OpenGL like), +and final transformation to raster coordinates is done internally. +When auto matrix mode is not activated,, the stencil matrix must describe the complete transformation from uv space to raster coordinates (consequently, the matrix usually includes the final path transformation). + +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 +\return error if any +*/ +GF_Err gf_evg_stencil_set_auto_matrix(GF_EVGStencil * stencil, Bool auto_on); + +/*! sets stencil transformation matrix auto mode. +\param stencil the target stencil +\return true if auto mode is enabled, false otherwise +*/ +Bool gf_evg_stencil_get_auto_matrix(GF_EVGStencil * stencil); + +/*! gets stencil type +\param stencil the target stencil +\return stencil type +*/ +GF_StencilType gf_evg_stencil_type(GF_EVGStencil *stencil); + +/*! sets color for solid brush stencil +\param stencil the target stencil +\param color the color to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_brush_color(GF_EVGStencil *stencil, GF_Color color); + +/*! gets color for solid brush stencil +\param stencil the target stencil +\return the stencil color +*/ +GF_Color gf_evg_stencil_get_brush_color(GF_EVGStencil * stencil); + +/*! sets gradient repeat mode for a gradient stencil +\note this may be called before the gradient is setup +\param stencil the target stencil +\param mode the gradient mode +\return error if any +*/ +GF_Err gf_evg_stencil_set_gradient_mode(GF_EVGStencil *stencil, GF_GradientMode mode); +/*! sets linear gradient direction for a gradient stencil +Gradient line is defined by start and end. For example, {0,0}, {1,0} defines an horizontal gradient +\param stencil the target stencil +\param start_x horizontal coordinate of starting point +\param start_y vertical coordinate of starting point +\param end_x horizontal coordinate of ending point +\param end_y vertical coordinate of ending point +\return error if any +*/ +GF_Err gf_evg_stencil_set_linear_gradient(GF_EVGStencil *stencil, Fixed start_x, Fixed start_y, Fixed end_x, Fixed end_y); +/*! sets radial gradient center point, focal point and radius for a gradient stencil +\param stencil the target stencil +\param cx horizontal coordinate of center +\param cy vertical coordinate of center +\param fx horizontal coordinate of focal point +\param fy vertical coordinate of focal point +\param x_radius horizontal radius +\param y_radius vertical radius +\return error if any +*/ +GF_Err gf_evg_stencil_set_radial_gradient(GF_EVGStencil *stencil, Fixed cx, Fixed cy, Fixed fx, Fixed fy, Fixed x_radius, Fixed y_radius); + +/*! sets color interpolation for a gradient stencil +\note the colors at 0 and 1.0 MUST be provided +\note colors shall be fed in order from 0 to 1 + +\param stencil the target stencil +\param pos interpolation positions. Each position gives the distance from (center for radial, start for linear) expressed between 0 and 1 (1 being the gradient bounds) - may be NULL when count is 0 +\param col colors at the given position - may be NULL when count is 0 +\param count number of colors and position. A value of 0 resets the gradient colors +\return error if any +*/ +GF_Err gf_evg_stencil_set_gradient_interpolation(GF_EVGStencil *stencil, Fixed *pos, GF_Color *col, u32 count); + +/*! pushes a single color interpolation for a gradient stencil +\note the colors at 0 and 1.0 MUST be provided +\note colors shall be fed in order from 0 to 1 + +\param stencil the target stencil +\param pos interpolation position. A position gives the distance from (center for radial, start for linear) expressed between 0 and 1 (1 being the gradient bounds) +\param col color at the given position +\return error if any +*/ +GF_Err gf_evg_stencil_push_gradient_interpolation(GF_EVGStencil *stencil, Fixed pos, GF_Color col); + +/*! sets global alpha blending level for a stencil +The alpha channel will be combined with the color matrix if any +\warning do not use with solid brush stencil +\param stencil the target stencil +\param alpha the alpha value between 0 (full transparency) and 255 (full opacityy) +\return error if any +*/ +GF_Err gf_evg_stencil_set_alpha(GF_EVGStencil *stencil, u8 alpha); + +/*! gets global alpha blending level of a stencil +\param stencil the target stencil +\return the alpha value between 0 (full transparency) and 255 (full opacityy) +*/ +u8 gf_evg_stencil_get_alpha(GF_EVGStencil *stencil); + +/*! sets pixel data for a texture stencil +\param stencil the target stencil +\param pixels texture data starting from top row +\param width texture width in pixels +\param height texture height in pixels +\param stride texture horizontal pitch (bytes to skip to get to next row) +\param pixelFormat texture pixel format +\note this stencil acts as a data wrapper, the pixel data is not locally copied +\return error if any +*/ +GF_Err gf_evg_stencil_set_texture(GF_EVGStencil *stencil, u8 *pixels, u32 width, u32 height, u32 stride, GF_PixelFormat pixelFormat); + +/*! sets pixel planes data for a texture stencil +\param stencil the target stencil +\param width texture width in pixels +\param height texture height in pixels +\param pixelFormat texture pixel format +\param y_or_rgb Y plane or RGB plane +\param stride stride of the Y or RGB plane +\param u_plane U plane or UV plane for semi-planar YUV formats +\param v_plane V plane for planar YUV format, NULL for semi-planar YUV formats +\param uv_stride stride of the U and V or UV plane +\param alpha_plane alpha plane +\param alpha_stride stride of the alpha plane; if 0 and alpha plane is not NULL, the stride is assumed to be the same as the Y plane stride +\note this stencil acts as a data wrapper, the pixel data is not locally copied +\return error if any +*/ +GF_Err gf_evg_stencil_set_texture_planes(GF_EVGStencil *stencil, u32 width, u32 height, GF_PixelFormat pixelFormat, const u8 *y_or_rgb, u32 stride, const u8 *u_plane, const u8 *v_plane, u32 uv_stride, const u8 *alpha_plane, u32 alpha_stride); + +/*! callback function prototype for parametric textures +\param cbk user data callback +\param x horizontal coordinate in texture, ranging between 0 and width-1, (0,0) being top-left pixel, or horizontal coordinate of destination pixel on surface +\param y vertical coordinate in texture, ranging between 0 and height-1, (0,0) being top-left pixel, or vertical coordinate of destination pixel on surface +\param r set to the red value (not initialized before call) +\param g set to the green value (not initialized before call) +\param b set to the blue value (not initialized before call) +\param a set to the alpha value (not initialized before call) +*/ +typedef void (*gf_evg_texture_callback)(void *cbk, u32 x, u32 y, Float *r, Float *g, Float *b, Float *a); + +/*! sets parametric callback on a texture stencil. A parametric texture gets its pixel values from a callback function, the resulting value being blended according to the antialiasing level of the pixel. This allows creating rather complex custom textures, in a fashion similar to fragment shaders. +\param stencil the target stencil +\param width texture width in pixels +\param height texture height in pixels +\param pixelFormat texture pixel format, indicates if the returned values are in RGB or YUV +\param callback the callback function to use +\param cbk_udta user data to pass to the callback function +\param use_screen_coords if set, the coordinates passed to the callback function are screen coordinates rather than texture coordinates +\return error if any +*/ +GF_Err gf_evg_stencil_set_texture_parametric(GF_EVGStencil *stencil, u32 width, u32 height, GF_PixelFormat pixelFormat, gf_evg_texture_callback callback, void *cbk_udta, Bool use_screen_coords); + +/*! sets texture mapping options for a texture stencil +\param stencil the target stencil +\param map_mode texture mapping flags to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_mapping(GF_EVGStencil *stencil, GF_TextureMapFlags map_mode); + +/*! sets padding color in RGBA + If texture repeat is disabled and pad color is set, this color is used when outside of image, otherwise pixel is fetch from image border +\param stencil the target stencil +\param pad_color color to use , 0 disables color padding +\return error if any +*/ +GF_Err gf_evg_stencil_set_pad_color(GF_EVGStencil * stencil, GF_Color pad_color); + +/*! gets padding color in RGBA +\param stencil the target stencil +\return color used , 0 if error +*/ +u32 gf_evg_stencil_get_pad_color(GF_EVGStencil * stencil); + +/*! sets filtering mode for a texture stencil +\param stencil the target stencil +\param filter_mode texture filtering mode to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_filter(GF_EVGStencil *stencil, GF_TextureFilter filter_mode); + +/*! sets color matrix of a stencil +\param stencil the target stencil +\param cmat the color matrix to use. If NULL, resets current color matrix +\return error if any + */ +GF_Err gf_evg_stencil_set_color_matrix(GF_EVGStencil *stencil, GF_ColorMatrix *cmat); + +/*! gets color matrix of a stencil +\param stencil the target stencil +\param cmat filled with current color matrix of stencil +\return error if any + */ +GF_Err gf_evg_stencil_get_color_matrix(GF_EVGStencil *stencil, GF_ColorMatrix *cmat); + +/*! gets pixel at given position in ARGB format +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return pixel value + */ +u32 gf_evg_stencil_get_pixel(GF_EVGStencil *stencil, s32 x, s32 y); + +/*! gets pixel at given position in AYUV format +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return pixel value + */ +u32 gf_evg_stencil_get_pixel_yuv(GF_EVGStencil *stencil, s32 x, s32 y); + +/*! gets pixel at given position in ARGB format for more than 8 bits textures +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return pixel value + */ +u64 gf_evg_stencil_get_pixel_wide(GF_EVGStencil *stencil, s32 x, s32 y); + +/*! gets pixel at given position in AYUV format for more than 8 bits textures +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return pixel value + */ +u64 gf_evg_stencil_get_pixel_yuv_wide(GF_EVGStencil *stencil, s32 x, s32 y); + +/*! gets ARGB pixel at a given position + The position is given as normalize coordinates (between 0.0 and 1.0), {0.0,0.0} is top-left, {1.0,1.0} is bottom-right +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return output r, r, g, a components + */ +GF_Vec4 gf_evg_stencil_get_pixel_f(GF_EVGStencil *stencil, Float x, Float y); + +/*! gets AYUV pixel at a given position + The position is given as normalize coordinates (between 0.0 and 1.0), {0.0,0.0} is top-left, {1.0,1.0} is bottom-right +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return output y, u, v, a components + */ +GF_Vec4 gf_evg_stencil_get_pixel_yuv_f(GF_EVGStencil *stencil, Float x, Float y); + +/*! creates a canvas surface object +\param center_coords if GF_TRUE, indicates mathematical-like coord system (0,0) at the center of the canvas, otherwise indicates computer-like coord system (0,0) top-left corner +\return a new surface*/ +GF_EVGSurface *gf_evg_surface_new(Bool center_coords); + +/*! deletes a surface object +\param surf the surface object +*/ +void gf_evg_surface_delete(GF_EVGSurface *surf); + +/*! enables threading on a canvas surface object (can only be called once per surface) +\param surf the target surface +\param nb_threads number of additional threads to use for rendering. -1 use all availables core +\return error if any, GF_BAD_PARAM if threading was already allocated, GF_IO_ERR if only one core available and nb_threads not assigned to a positive value*/ +GF_Err gf_evg_enable_threading(GF_EVGSurface *surf, s32 nb_threads); + +/*! attaches a surface object to a texture stencil object +\param surf the surface object +\param sten the stencil object (shll be a texture stencil) +\return error if any +*/ +GF_Err gf_evg_surface_attach_to_texture(GF_EVGSurface *surf, GF_EVGStencil *sten); + +/*! attaches a surface object to a memory buffer +\param surf the surface object +\param pixels: texture data +\param width texture width in pixels +\param height texture height in pixels +\param pitch_x texture horizontal pitch (bytes to skip to get to next pixel). O means linear frame buffer (eg pitch_x==bytes per pixel) +\param pitch_y texture vertical pitch (bytes to skip to get to next line) +\param pixelFormat texture pixel format +\return error if any +*/ +GF_Err gf_evg_surface_attach_to_buffer(GF_EVGSurface *surf, u8 *pixels, u32 width, u32 height, s32 pitch_x, s32 pitch_y, GF_PixelFormat pixelFormat); + +/*! sets rasterizer precision +\param surf the surface object +\param level the raster quality level +\return error if any +*/ +GF_Err gf_evg_surface_set_raster_level(GF_EVGSurface *surf, GF_RasterQuality level); + +/*! gets rasterizer precision +\param surf the surface object +\return the raster quality level +*/ +GF_RasterQuality gf_evg_surface_get_raster_level(GF_EVGSurface *surf); + +/*! Force next path fill to use antialias, reset at each \ref gf_evg_surface_set_path +\param surf the surface object +\return error if any +*/ +GF_Err gf_evg_surface_force_aa(GF_EVGSurface *surf); + +/*! sets the given matrix as the current transformations for all drawn paths +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param mat the matrix to set; if NULL, resets the current transformation +\return error if any +*/ +GF_Err gf_evg_surface_set_matrix(GF_EVGSurface *surf, GF_Matrix2D *mat); + +/*! sets the given matrix as the current transformations for all drawn paths. The matrix shall be a projection matrix (ortho or perspective) +with normalized coordinates in [-1,1]. It may also contain a modelview part. + +Perspective correct mapping is supported for textures and gradients: +- the bottom-left corner of the path bounds is texture coordinate 0,0 +- the top-right corner of the path bounds is texture coordinate 1,1 + +\warning When 3D matrices are used, filling a path shall be done with a stencil in auto matrix mode. + +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param mat the matrix to set; if NULL, resets the current transformation +\return error if any +*/ +GF_Err gf_evg_surface_set_matrix_3d(GF_EVGSurface *surf, GF_Matrix *mat); + +/*! sets the given rectangle as a clipper +When a clipper is enabled, nothing is drawn outside of the clipper. The clipper is not affected by the surface matrix +\param surf the surface object +\param rc the clipper to set, in pixel coordinates of the surface; if NULL, disables clipper +\return error if any +*/ +GF_Err gf_evg_surface_set_clipper(GF_EVGSurface *surf, GF_IRect *rc); + +/*! checks if clipper is active +\param surf the surface object +\return GF_TRUE if clipper is active, GF_FALSE otherwise +*/ +Bool gf_evg_surface_use_clipper(GF_EVGSurface *surf); + +/*! sets the given path as the current one for drawing +\warning This will internally copy the path after transformation with the current transfom. Changing the surface transform will have no effect unless calling this function again. +The clipper and stencil mode may be changed at will + +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param path the target path to rasterize +\return error if any +*/ +GF_Err gf_evg_surface_set_path(GF_EVGSurface *surf, GF_Path *path); + +/*! draw (filling) the current path on a surface using the given stencil and current clipper if any. +If the stencil is a solid brush and its alpha value is 0, the surface is cleared if it has an alpha component, otherwise the call is ignored. +\note this can be called several times with the same current path +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param stencil the stencil to use to fill a path, if NULL uses 2D shader if setup +\return error if any +*/ +GF_Err gf_evg_surface_fill(GF_EVGSurface *surf, GF_EVGStencil *stencil); + + +/*! Operands for multitexture operations*/ +typedef enum { + /*! no multitexture*/ + GF_EVG_OPERAND_NONE = 0, + /*! mix texture 1 and texure 2 using coefficient in first param, returning tx1*coef + tx2*(1-coef) but forcing alpha to full opacity*/ + GF_EVG_OPERAND_MIX, + /*! mix texture 1 and texure 2 using coefficient in first param, returning tx1*coef + tx2*(1-coef), including alpha channel */ + GF_EVG_OPERAND_MIX_ALPHA, + /*! replace alpha of texture 1 with alpha of texture 2 + if first param is 0 or not set, use alpha channel from texture 2 + if first param is 1, use red channel from texture 2 + if first param is 2, use green/Cb/U channel from texture 2 + if first param is 3, use blue/Cr/V channel from texture 2 + */ + GF_EVG_OPERAND_REPLACE_ALPHA, + /*! replace alpha of texture 1 with (1-alpha) of texture 2 + if first param is 0 or not set, use alpha channel from texture 2 + if first param is 1, use red/Y channel from texture 2 + if first param is 2, use green/Cb/U channel from texture 2 + if first param is 3, use blue/Cr/V channel from texture 2 + */ + GF_EVG_OPERAND_REPLACE_ONE_MINUS_ALPHA, + /*! mix texture 1 and texure 2 using alpha coef of texture 3 but forcing alpha to full opacity + if first param is 0 or not set, use alpha channel from texture 3 + if first param is 1, use red channel from texture 3 + if first param is 2, use green/Cb/U channel from texture 3 + if first param is 3, use blue/Cr/V channel from texture 3 + */ + GF_EVG_OPERAND_MIX_DYN, + /*! mix texture 1 and texure 2 using alpha coef of texture 3, including alpha channel. + if first param is 0 or not set, use alpha channel from texture 3 + if first param is 1, use red channel from texture 3 + if first param is 2, use green/Cb/U channel from texture 3 + if first param is 3, use blue/Cr/V channel from texture 3 + */ + GF_EVG_OPERAND_MIX_DYN_ALPHA, + /*! use texture 1 for odd fill and texture 2 for even fill*/ + GF_EVG_OPERAND_ODD_FILL, +} GF_EVGMultiTextureMode; + +/*! draw (filling) the current path on a surface using a compinaison of stencils and current clipper if any. +\param surf the surface object +\param operand stencil combine effect +\param sten1 the first stencil to use, if NULL assumes shader and ignores operand +\param sten2 the second stencil to use, may be NULL depending on operand +\param sten3 the third stencil to use, may be NULL depending on operand +\param params the parameters to control the operand +\return error if any +*/ +GF_Err gf_evg_surface_multi_fill(GF_EVGSurface *surf, GF_EVGMultiTextureMode operand, GF_EVGStencil *sten1, GF_EVGStencil *sten2, GF_EVGStencil *sten3, Float params[4]); + +/*! clears given pixel rectangle on a surface with the given color +\warning this ignores any clipper set on the surface +\param surf the surface object +\param rc the rectangle in pixel coordinates to clear. This may lay outside the surface, and shall not be NULL +\param col the color used to clear the canvas. The alpha component is discarded if the surface does not have alpha, otherwise it is used +\return error if any +*/ +GF_Err gf_evg_surface_clear(GF_EVGSurface *surf, GF_IRect *rc, GF_Color col); + +/*! sets center coord mode of a surface +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param center_coords if GF_TRUE, indicates mathematical-like coord system (0,0) at the center of the canvas, otherwise indicates computer-like coord system (0,0) top-left corner +*/ +void gf_evg_surface_set_center_coords(GF_EVGSurface *surf, Bool center_coords); + +/*! Composition mode used for ARGB surfaces - cf Canvas2D modes*/ +typedef enum +{ + /*! source over*/ + GF_EVG_SRC_OVER = 0, + /*! source atop*/ + GF_EVG_SRC_ATOP, + /*! source in*/ + GF_EVG_SRC_IN, + /*! source out*/ + GF_EVG_SRC_OUT, + /*! destination atop*/ + GF_EVG_DST_ATOP, + /*! destination in*/ + GF_EVG_DST_IN, + /*! destination out*/ + GF_EVG_DST_OUT, + /*! destination over*/ + GF_EVG_DST_OVER, + /*! destination * source*/ + GF_EVG_LIGHTER, + /*! source copy*/ + GF_EVG_COPY, + /*! source XOR destination*/ + GF_EVG_XOR, +} GF_EVGCompositeMode; + +/*! sets surface composite mode, as defined in Canvas2D - only used for ARGB surfaces + \warning this is still experimental +\param surf the surface object +\param comp_mode the composition mode to use +*/ +void gf_evg_surface_set_composite_mode(GF_EVGSurface *surf, GF_EVGCompositeMode comp_mode); + +/*! callback type for alpha override +\param udta opaque data passed back to caller +\param src_alpha alpha value about to be blended +\param x horizontal coordinate of pixel to be drawn, {0,0} being top-left, positive Y go down +\param y vertical coordinate of pixel to be drawn, {0,0} being top-left, positive Y go down +\return final alpha value to use*/ +typedef u8 (*gf_evg_get_alpha)(void *udta, u8 src_alpha, s32 x, s32 y); + +/*! sets alpha callback function. This allows finer control over the blending, but does not allow for pixel color modification +\param surf the surface object +\param get_alpha the callback function to alter the alpha value +\param cbk opaque data for the callback function +*/ +void gf_evg_surface_set_alpha_callback(GF_EVGSurface *surf, gf_evg_get_alpha get_alpha, void *cbk); + + +/*! Primitive types for 3D software rasterize - see OpenGL terminology*/ +typedef enum +{ + //do NOT modify order + + /*! points, 1 vertex index per primitive */ + GF_EVG_POINTS=1, + /*! polygon, all vertex indices in array are used for a single face*/ + GF_EVG_POLYGON, + /*! lines, 2 vertex indices per primitive*/ + GF_EVG_LINES, + /*! triangles, 3 vertex indices per primitive*/ + GF_EVG_TRIANGLES, + /*! quads, 4 vertex indices per primitive*/ + GF_EVG_QUADS, + + /*! line strip, 2 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_LINE_STRIP, + /*! triangle strip, 3 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_TRIANGLE_STRIP, + /*! triangle fan, 3 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_TRIANGLE_FAN, + /*! quad strip, 4 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_QUAD_STRIP, +} GF_EVGPrimitiveType; + +/*! enables 3D software rasterizer for surface + \param surf the target 3D surface + \return error if any*/ +GF_Err gf_evg_surface_enable_3d(GF_EVGSurface *surf); + +/*! sets projection matrix + \note this is only used for 3D rasterizer, and fails 2D mode + \param surf the target 3D surface + \param mx the target projection matrix + \return error if any + */ +GF_Err gf_evg_surface_set_projection(GF_EVGSurface *surf, GF_Matrix *mx); +/*! sets modelview matrix +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param mx the target modelview matrix +\return error if any +*/ +GF_Err gf_evg_surface_set_modelview(GF_EVGSurface *surf, GF_Matrix *mx); +/*! draws a set of primitive using vertices and an index buffer modelview matrix +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param indices the array of indices to use in the vertex buffer +\param nb_indices the number of indices +\param vertices the array of vertices to use +\param nb_vertices the number of vertices +\param nb_comp the number of component per vertices (eg, 2, 3) +\param prim_type the primitive type to use +\return error if any +*/ +GF_Err gf_evg_surface_draw_array(GF_EVGSurface *surf, u32 *indices, u32 nb_indices, Float *vertices, u32 nb_vertices, u32 nb_comp, GF_EVGPrimitiveType prim_type); + +/*! clears the depth buffer + \note the depth buffer is set by the caller. If no depth buffer is assigned, this function returns GF_OK + \note the color buffer is cleared using \ref gf_evg_surface_clear +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param depth the depth value to use +\return error if any +*/ +GF_Err gf_evg_surface_clear_depth(GF_EVGSurface *surf, Float depth); +/*! sets the surface viewport to convert from projected coordiantes to screen coordinates +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param x the top-left coordinate of the viewport in pixels +\param y the top-left coordinate of the viewport in pixels +\param w the width of the viewport in pixels +\param h the height of the viewport in pixels +\return error if any +*/ +GF_Err gf_evg_surface_viewport(GF_EVGSurface *surf, u32 x, u32 y, u32 w, u32 h); +/*! draws path on a 3D surface with the given z. + +The vertex shader is ignored (surface matrix must be set), and the fragment shader is called using perspective interpolation weights derived from the path bounds, +using path bounds (top-left, top-right, bottom-right) vertices. + \note this is only used for 3D rasterizer, and fails 2D mode + +\param surf the target 3D surface +\param path the path to draw +\param z the z value to assign to the path in local coordinate system +\return error if any +*/ +GF_Err gf_evg_surface_draw_path(GF_EVGSurface *surf, GF_Path *path, Float z); + +/*! Depth test modes*/ +typedef enum +{ + /*! depth test is disabled*/ + GF_EVGDEPTH_DISABLE, + /*! depth test always fails*/ + GF_EVGDEPTH_NEVER, + /*! depth test always succeeds*/ + GF_EVGDEPTH_ALWAYS, + /*! depth test succeeds if fragment depth is == than depth buffer value*/ + GF_EVGDEPTH_EQUAL, + /*! depth test succeeds if fragment depth is != than depth buffer value*/ + GF_EVGDEPTH_NEQUAL, + /*! depth test succeeds if fragment depth is < than depth buffer value*/ + GF_EVGDEPTH_LESS, + /*! depth test succeeds if fragment depth is <= than depth buffer value*/ + GF_EVGDEPTH_LESS_EQUAL, + /*! depth test succeeds if fragment depth is > than depth buffer value*/ + GF_EVGDEPTH_GREATER, + /*! depth test succeeds if fragment depth is >= than depth buffer value*/ + GF_EVGDEPTH_GREATER_EQUAL +} GF_EVGDepthTest; + +/*! sets depth buffer test +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param mode the desired depth test mode +\return error if any +*/ +GF_Err gf_evg_set_depth_test(GF_EVGSurface *surf, GF_EVGDepthTest mode); + +/*! fragment color type */ +typedef enum +{ + /*! fragment is invalid (discarded or error) */ + GF_EVG_FRAG_INVALID = 0, + /*! fragment is RGB float*/ + GF_EVG_FRAG_RGB, + GF_EVG_FRAG_RGB_PACK, + /*! fragment is YUV */ + GF_EVG_FRAG_YUV, + GF_EVG_FRAG_YUV_PACK, +} GF_EVGFragmentType; + +/*! Parameters for the fragment callback */ +typedef struct +{ + /*! screen x in pixels - input param*/ + Float screen_x; + /*! screen y in pixels - input param*/ + Float screen_y; + /*! screen z in NDC - input param*/ + Float screen_z; + /*! depth - input and output param - 3D shaders only*/ + Float depth; + /*! primitive index in the current \ref gf_evg_surface_draw_array call, 0 being the first primitive - input param - 3D shaders only*/ + u32 prim_index; + /*! index of first vertex in the current primitive - input param - 3D shaders only*/ + u32 idx1; + /*! index of second vertex in the current primitive (for lines or triangles/quads) - input param - 3D shaders only*/ + u32 idx2; + /*! index of third vertex in the current primitive (for triangles/quads) - input param - 3D shaders only*/ + u32 idx3; + + /*! primitive type - input param - 3D shaders only*/ + GF_EVGPrimitiveType ptype; + + /*! fragment color, must be written if fragment is not discarded - output value*/ + GF_Vec4 color; + /*! fragment color in pack 32 bit ARGB/AYUV*/ + u32 color_pack; + /*! fragment color in pack 64 bit ARGB/AYUV*/ + u64 color_pack_wide; + /*! fragment valid state - output value*/ + GF_EVGFragmentType frag_valid; + + /*vars for lerp*/ + /*perspective correct interpolation is done according to OpenGL eq 14.9 + f = (a*fa/wa + b*fb/wb + c*fc/wc) / (a/w_a + b/w_b + c/w_c) + */ + /*! perspective corrected barycentric, eg bc1/q1, bc2/q2, bc3/q3 - 3D shaders only*/ + Float pbc1, pbc2, pbc3; + /*! perspective divider - 3D shaders only + \note this is also 1/W of the fragment, eg opengl gl_fragCoord.w + */ + Float persp_denum; + /* private for shader, valid between \ref gf_evg_fragment_shader_init (init, cleanup) calls */ + void *shader_udta; + + /*! horizontal texture coordinate, 0 is left of image - 2D shaders only */ + u32 tx_x; + /*! vertical texture coordinate, 0 is top of image - 2D shaders only */ + u32 tx_y; + /*! texture width - 2D shaders only */ + u32 tx_width; + /*! texture height - 2D shaders only */ + u32 tx_height; + + /*! coverage value between 0 and 0xFF - 2D shaders only */ + u8 coverage; + /*! odd / even flag for path drawn with zero/non-zero fill rule - 2D shaders only */ + u8 odd_flag; + +} GF_EVGFragmentParam; + + +/*! Parameters for the vertex callback */ +typedef struct +{ + /*! input vertex - input param*/ + GF_Vec4 in_vertex; + /*! primitive index in the current \ref gf_evg_surface_draw_array call, 0 being the first primitive - input param*/ + u32 prim_index; + /*! index of the vertex - input param*/ + u32 vertex_idx; + /*! index of the vertex in the current primitive - input param*/ + u32 vertex_idx_in_prim; + /*! primitive type - input param*/ + u32 ptype; + + /*! transformed vertex to use, must be written - output values*/ + GF_Vec4 out_vertex; +} GF_EVGVertexParam; + +/*! callback type for fragment shader + \param udta opaque data passed back to caller + \param fragp fragment paramters + \return GF_TRUE if success; GF_FALSE if error*/ +typedef Bool (*gf_evg_fragment_shader)(void *udta, GF_EVGFragmentParam *fragp); + +/*! callback type for fragment shader init, called once before each primitive - may be NULL + \param udta opaque data passed back to caller + \param fragp fragment paramters + \param th_id index of thread context + \param is_cleanup if TRUE indicates and of primitive rendering, otherwise begin + \return GF_TRUE if success; GF_FALSE if error*/ +typedef Bool (*gf_evg_fragment_shader_init)(void *udta, GF_EVGFragmentParam *fragp, u32 th_id, Bool is_cleanup); + + +/*! assigns fragment shader to the rasterizer. +\warning If multithread is enabled, the shader must be thread-safe +This can be used for 3D and 2D modes. In 2D mode, texture coordinates are derived from path bounds +\param surf the target 3D surface +\param shader the fragment shader callback to use +\param shader_init the fragment shader init callback to use +\param shader_udta opaque data to pass to the callback function +\return error if any + */ +GF_Err gf_evg_surface_set_fragment_shader(GF_EVGSurface *surf, gf_evg_fragment_shader shader, gf_evg_fragment_shader_init shader_init, void *shader_udta); + +/*! callback type for vertex shader +\param udta opaque data passed back to caller +\param vertp vertex paramters +\return GF_TRUE if success; GF_FALSE if error*/ +typedef Bool (*gf_evg_vertex_shader)(void *udta, GF_EVGVertexParam *vertp); +/*! assigns vertex shader to the rasterizer +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param shader the vertex shader callback to use +\param shader_udta opaque data to pass to the callback function +\return error if any + */ +GF_Err gf_evg_surface_set_vertex_shader(GF_EVGSurface *surf, gf_evg_vertex_shader shader, void *shader_udta); + +/*! sets face orientation for the primitives +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param is_ccw if GF_TRUE, face are given in counter-clockwise order, otherwise in clockwise order +\return error if any + */ +GF_Err gf_evg_surface_set_ccw(GF_EVGSurface *surf, Bool is_ccw); +/*! enables/disables backface culling + \warning not enabling backface culling when anti-aliasing is on may result in visual artefacts +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param backcull if GF_TRUE, faces with normals poiting away from camera will not be drawn +\return error if any + */ +GF_Err gf_evg_surface_set_backcull(GF_EVGSurface *surf, Bool backcull); +/*! enables/disables anti-aliased rendering + \warning not enabling backface culling when anti-aliasing is on may result in visual artefacts +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param antialias if GF_TRUE, edges will be anti-aliased +\return error if any + */ +GF_Err gf_evg_surface_set_antialias(GF_EVGSurface *surf, Bool antialias); +/*! sets minimum depth value for coordinates normalization - see OpenGL glDepthRange +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param min_depth the minimum depth value +\return error if any + */ +GF_Err gf_evg_surface_set_min_depth(GF_EVGSurface *surf, Float min_depth); +/*! sets maximum depth value for coordinates normalization - see OpenGL glDepthRange +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param max_depth the maximum depth value +\return error if any + */ +GF_Err gf_evg_surface_set_max_depth(GF_EVGSurface *surf, Float max_depth); +/*! indicates that the clip space after projection is [0,1] and not [-1, 1] +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param clip_zero if GF_TRUE, the projected vertices depth are in [0, 1], otherwise in [-1, 1] +\return error if any + */ +GF_Err gf_evg_surface_set_clip_zero(GF_EVGSurface *surf, Bool clip_zero); +/*! sets point size value for points primitives +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param size the desired point size in pixels +\return error if any + */ +GF_Err gf_evg_surface_set_point_size(GF_EVGSurface *surf, Float size); +/*! sets line size (width) value for points primitives +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param size the desired line size in pixels +\return error if any + */ +GF_Err gf_evg_surface_set_line_size(GF_EVGSurface *surf, Float size); +/*! enables point smoothing. + When point smoothing is enabled, the geometry drawn is a circle, otherwise a square +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param smooth if GF_TRUE, point smoothing is enabled +\return error if any + */ +GF_Err gf_evg_surface_set_point_smooth(GF_EVGSurface *surf, Bool smooth); +/*! disables early depth test +If your fragment shader modifies the depth of the fragment, you must call this function to avoid early depth test (fragment discarded before calling the shader) +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param disable if GF_TRUE, early depth test is disabled +\return error if any + */ +GF_Err gf_evg_surface_disable_early_depth(GF_EVGSurface *surf, Bool disable); +/*! 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) +\return error if any + */ +GF_Err gf_evg_surface_write_depth(GF_EVGSurface *surf, Bool do_write); +/*! sets depth buffer + The 3D rasterizer does not allocate any surface (depth or color buffers), it is the user responsability to set these buffers + \warning do NOT forget to resize your depth buffer when resizing the canvas ! +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param depth the depth buffer to use. Its size shall be Float*width*height +\return error if any + */ +GF_Err gf_evg_surface_set_depth_buffer(GF_EVGSurface *surf, Float *depth); + + +/*! performs RGB to YUV conversion +\param surf the target surface +\param r the source red component +\param g the source green component +\param b the source blue component +\param y the output Y component +\param cb the output U/Cb component +\param cr the output V/Cr component +\return error if any + */ +GF_Err gf_gf_evg_rgb_to_yuv_f(GF_EVGSurface *surf, Float r, Float g, Float b, Float *y, Float *cb, Float *cr); + +/*! performs YUV to RGB conversion +\param surf the target surface +\param y the source Y component +\param cb the source U/Cb component +\param cr the source V/Cr component +\param r the output red component +\param g the output green component +\param b the output blue component +\return error if any + */ +GF_Err gf_evg_yuv_to_rgb_f(GF_EVGSurface *surf, Float y, Float cb, Float cr, Float *r, Float *g, Float *b); + +/*! converts RGB to YUV + \param surf the target surface + \param col the source RGBA color + \param y the output Y value + \param cb the output Cb/U value + \param cr the output Cr/V value + */ +void gf_evg_rgb_to_yuv(GF_EVGSurface *surf, GF_Color col, u8*y, u8*cb, u8*cr); + +/*! converts ARGB to AYUV +\param surf the target surface +\param col the source ARGB color +\return the result AYUV color +*/ +GF_Color gf_evg_argb_to_ayuv(GF_EVGSurface *surf, GF_Color col); + +/*! converts AYUV to ARGB +\param surf the target surface +\param col the source AYUV color +\return the result ARGB color +*/ +GF_Color gf_evg_ayuv_to_argb(GF_EVGSurface *surf, GF_Color col); + +/*! converts ARGB to AYUV for wide colors +\param surf the target surface +\param col the source ARGB color +\return the result AYUV color +*/ +u64 gf_evg_argb_to_ayuv_wide(GF_EVGSurface *surf, u64 col); + +/*! converts AYUV to ARGB for wide color +\param surf the target surface +\param col the source AYUV color +\return the result ARGB color +*/ +u64 gf_evg_ayuv_to_argb_wide(GF_EVGSurface *surf, u64 col); + +/*! global alpha mask values */ +typedef enum +{ + /*! global alpha mask not used */ + GF_EVGMASK_NONE = 0, + /*! subsequent draw operations will target the global alpha mask */ + GF_EVGMASK_DRAW, + /*! subsequent draw operations will target the global alpha mask, but alpha mask is not cleared */ + GF_EVGMASK_DRAW_NO_CLEAR, + /*! subsequent draw operations will be filtered with the global alpha mask */ + GF_EVGMASK_USE, + /*! subsequent draw operations will be filtered with 1 minus the global alpha mask */ + GF_EVGMASK_USE_INV, + /*! combine draw and use: the mask is set to 0xFF, each pixel drawn turns the mask value to 0 */ + GF_EVGMASK_RECORD, +} GF_EVGMaskMode; + +/*! sets global alpha mask mode + +The global alpha mask is an 8-bit alpha channel the size of the surface. Any resize operation on the surface will reset the alpha mask +Typcial usage for the alpha mask is to setup the surface, use GF_EVGMASK_DRAW to draw your mask then GF_EVGMASK_USE to apply the mask on your draw calls + +Whenever the mask mode is changed to GF_EVGMASK_DRAW, the alpha masked is cleared +\param surf the target surface +\param mask_mode the current mask mode +\return error if any +*/ +GF_Err gf_evg_surface_set_mask_mode(GF_EVGSurface *surf, GF_EVGMaskMode mask_mode); + +/*! gets global alpha mask mode +\param surf the target surface +\return the current mask mode +*/ +GF_EVGMaskMode gf_evg_surface_get_mask_mode(GF_EVGSurface *surf); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_EVG_H_*/ + diff --git a/include/gpac/filters.h b/include/gpac/filters.h new file mode 100644 index 0000000..85fd869 --- /dev/null +++ b/include/gpac/filters.h @@ -0,0 +1,4526 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2022 + * All rights reserved + * + * This file is part of GPAC / filters 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_FILTERS_H_ +#define _GF_FILTERS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#include + +//for offsetof() +#include + +/*! +\file "gpac/filters.h" +\brief Filter management of GPAC. + +This file contains all exported functions for filter management of the GPAC framework. +*/ + +/*! +\addtogroup filters_grp Filter Management +\brief Filter Management of GPAC. + +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: + - 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 + - scheduling tasks between filters + - 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: + - 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. + - 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 + upon fetching the new packet. This is the one of the two reentrant code of a filter, the other one being the \ref GF_FEVT_INFO_UPDATE event. + - When blocking mode is not disabled at the session filter, a PID is also in charge of managing its occupancy through either a number of packets or the + cumulated duration of the packets it is holding. + - Whenever a PID holds too much data, it enters a blocking state. A filter with ALL its output PIDs in a blocked state won't be scheduled + 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. + - 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. + - A packet can hold allocated block on the output PID, a pointer to some filter internal data, a data reference to a single input packet, or a frame interface object used for accessing data or OpenGL textures of the emitting filter. + Packets holding data references rather than copy are notified back to their creators upon destruction. +- \ref __gf_prop_val holding various properties for a PID or a packet + - Properties can be copied/merged between input and output PIDs, or input and output packets. These properties are reference counted. + - Two kinds of properties are defined, built-in ones which use a 32 bit identifier (usually a four character code), and user properties identified by a string. + - PID properties are defined by the filter creating the PID. They can be overridden/added after being set by the filter by specifying fragment properties + in the filter arguments. For example \code fin=src=myfile.foo:#FEXT=bar \endcode will override the file extension property (FEXT) foo to bar AFTER the PID is being defined. +- \ref __gf_filter_event used to pass various events (play/stop/buffer requirements/...) up and down the filter chain. + This part of the API will likely change in the future, being merged with the global GF_Event of GPAC. + + +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 + with the following prototype: + + +\param fsess is set to NULL unless meta filters are listed, in which case the filter register should list all possible meta filters it supports +\return a GF_FilterRegister structure used for instantiating the filter. +\code const GF_FilterRegister *RegisterFilter(GF_FilterSession *fsess);\endcode + +*/ + +/*! \hideinitializer +A packet byte offset is set to this value when not valid +*/ +#define GF_FILTER_NO_BO 0xFFFFFFFFFFFFFFFFUL +/*! \hideinitializer +A packet timestamp (Decoding or Composition) is set to this value when not valid +*/ +#define GF_FILTER_NO_TS 0xFFFFFFFFFFFFFFFFUL + +/*! \hideinitializer +Filter Session object + */ +typedef struct __gf_filter_session GF_FilterSession; + +/*! \hideinitializer +Filter object + */ +typedef struct __gf_filter GF_Filter; +/*! \hideinitializer +Filter PID object + */ +typedef struct __gf_filter_pid GF_FilterPid; + +/*! \hideinitializer +Filter Packet object + */ +typedef struct __gf_filter_pck GF_FilterPacket; +/*! +Filter Packet destructor function prototype + */ +typedef void (*gf_fsess_packet_destructor)(GF_Filter *filter, GF_FilterPid *PID, GF_FilterPacket *pck); + +/*! +Filter Event object + */ +typedef union __gf_filter_event GF_FilterEvent; + +/*! +Filter Session Task object + */ +typedef struct __gf_fs_task GF_FSTask; + +/*! +Filter Register object + */ +typedef struct __gf_filter_register GF_FilterRegister; +/*! +Filter Property object + */ +typedef struct __gf_prop_val GF_PropertyValue; + +/*! +Filter Property Reference object, used for info query only + */ +typedef struct __gf_prop_entry GF_PropertyEntry; + +/*! +\addtogroup fs_grp Filter Session +\ingroup filters_grp +\brief Filter Session + + The GPAC filter session object allows building media pipelines using multiple sources and destinations and arbitrary filter chains. + + Filters are described through a \ref __gf_filter_register structure. A set of built-in filters are available, and user-defined filters can be added or removed at runtime. + + The filter session keeps an internal graph representation of all available filters and their possible input connections, which is used when resolving connections between filters. + + The number of \ref GF_FilterCapability matched between registries defines the weight of the connection. + + Paths from an instantiated filter are enabled/disabled based on the source PID capabilities. + + Paths to destination are recomputed for each destination, based on the instantiated destination filter capabilities. + + The graph edges are then enabled in the possible subgraphs allowed by the destination capabilities, and unused filter registries (without enabled input connections) are removed from the graph. + + The resulting weighted graph is then solved using Dijkstra's algorithm, using filter priority in case of weight equality. + + The filter session works by default in a semi-blocking state. Whenever output PID buffers on a filter are all full, the filter is marked as blocked and not scheduled for processing. Whenever one output PID buffer is not full, the filter unblocks. + + This implies that PID buffers may grow quite large if a filter is consuming data from a PID at a much faster rate than another filter consuming from that same PID. + * @{ + */ + + +/*! Filter session scheduler type */ +typedef enum +{ + /*! In this mode, the scheduler does not use locks for packet and property queues. Main task list is mutex-protected */ + GF_FS_SCHEDULER_LOCK_FREE=0, + /*! In this mode, the scheduler uses locks for packet and property queues. Defaults to lock-free if no threads are used. Main task list is mutex-protected */ + GF_FS_SCHEDULER_LOCK, + /*! In this mode, the scheduler does not use locks for packet and property queues, nor for the main task list */ + GF_FS_SCHEDULER_LOCK_FREE_X, + /*! In this mode, the scheduler uses locks for packet and property queues even if single-threaded (test mode) */ + GF_FS_SCHEDULER_LOCK_FORCE, + /*! In this mode, the scheduler uses direct dispatch and no threads, trying to nest task calls within task calls */ + GF_FS_SCHEDULER_DIRECT +} GF_FilterSchedulerType; + +/*! Flag set to indicate meta filters should be loaded. A meta filter is a filter providing various sub-filters. +The sub-filters are usually not exposed as filters, only the parent one is. +When set, all sub-filters are exposed. This should only be set when inspecting filters help*/ +#define GF_FS_FLAG_LOAD_META 1<<1 +/*! Flag set to run session in non-blocking mode. Each call to \ref gf_fs_run will return as soon as there are no more pending tasks on the main thread */ +#define GF_FS_FLAG_NON_BLOCKING 1<<2 +/*! Flag set to disable internal caching of filter graph connections. If disabled, the graph will be recomputed at each link resolution (less memory occupancy but slower)*/ +#define GF_FS_FLAG_NO_GRAPH_CACHE 1<<3 +/*! Flag set to disable session regulation (no sleep)*/ +#define GF_FS_FLAG_NO_REGULATION 1<<4 +/*! Flag set to disable data probe*/ +#define GF_FS_FLAG_NO_PROBE (1<<5) +/*! Flag set to disable source reassignment (e.g. switching from fin to ffdmx) in PID resolution*/ +#define GF_FS_FLAG_NO_REASSIGN (1<<6) +/*! Flag set to print enabled/disabled edges for debug of PID resolution*/ +#define GF_FS_FLAG_PRINT_CONNECTIONS (1<<7) +/*! Flag set to disable argument checking*/ +#define GF_FS_FLAG_NO_ARG_CHECK (1<<8) +/*! Disables reservoir for packets and properties, uses much less memory but much more alloc/free*/ +#define GF_FS_FLAG_NO_RESERVOIR (1<<9) +/*! Throws an error if any PID in the filter graph cannot be linked. The default behavior is to run the session even when some PIDs are not connected*/ +#define GF_FS_FLAG_FULL_LINK (1<<10) +/*! Flag set to disable implicit linking + By default the session runs in implicit linking when no link directives are set on any filter: linking aborts after the first successfull pid if destination is not a sink, or links only to sinks otherwise. + \note This implies that the order in which filters are added to the session matters +*/ +#define GF_FS_FLAG_NO_IMPLICIT (1<<11) + +/*! Creates a new filter session. This will also load all available filter registers not blacklisted. +\param nb_threads number of extra threads to allocate. A negative value means all core used by session (eg nb_cores-1 extra threads) +\param type scheduler type +\param flags set of above flags for the session. Modes set by flags cannot be changed at runtime +\param blacklist string containing comma-separated names of filters to disable. If first character is '-', this describes a whitelist, i.e. only filters listed in this string will be allowed +\return the created filter session +*/ +GF_FilterSession *gf_fs_new(s32 nb_threads, GF_FilterSchedulerType type, u32 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 +\return the created filter session +*/ +GF_FilterSession *gf_fs_new_defaults(u32 flags); + +/*! Destructs the filter session +\param session the filter session to destruct +*/ +void gf_fs_del(GF_FilterSession *session); +/*! Loads a given filter by its register name. Filter are created using their register name, with options appended as a list of colon-separated Name=Value pairs. +Value can be omitted for boolean, defaulting to true (eg :noedit). Using '!' before the name negates the result (eg :!moof_first). +Name can be omitted for enumerations (eg :disp=pbo is equivalent to :pbo), provided that filter developers pay attention to not reuse enum names in one filter. + +\param session filter session +\param name name and arguments of the filter register to instantiate. +\param err_code set to error code if any - may be NULL. If initially set to GF_EOS, disables log messages. +\return created filter or NULL if filter register cannot be found +*/ +GF_Filter *gf_fs_load_filter(GF_FilterSession *session, const char *name, GF_Err *err_code); + +/*! Checks if a filter register exists by name. +\param session filter session +\param name name of the filter register to check. +\return GF_TRUE if a filter register exists with the given name, GF_FALSE otherwise +*/ +Bool gf_fs_filter_exists(GF_FilterSession *session, const char *name); + +/*! Runs the session + +If the session is non-blocking ( created with \ref GF_FS_FLAG_NON_BLOCKING), process all tasks of oldest scheduled filter, process any pending PID connections and returns. +Otherwise (session is blocking), runs until session is over or aborted. + +\param session filter session +\return error if any, or GF_EOS. The last errors can be retrieved using \ref gf_fs_get_last_connect_error and \ref gf_fs_get_last_process_error +*/ +GF_Err gf_fs_run(GF_FilterSession *session); + +/*! The default separator set used*/ +#define GF_FS_DEFAULT_SEPS ":=#,!@" + +/*! Sets the set of separators to use when parsing args +\param session filter session +\param separator_set filter session. +The first char is used to separate argument names - default is ':' +The second char, if present, is used to separate names and values - default is '=' +The third char, if present, is used to separate fragments for PID sources - default is '#' +The fourth char, if present, is used for list separators (sourceIDs, gfreg, ...) - default is ',' +The fifth char, if present, is used for boolean negation - default is '!' +The sixth char, if present, is used for LINK directives - default is '@' +\return error if any +*/ +GF_Err gf_fs_set_separators(GF_FilterSession *session, const char *separator_set); + +/*! Sets the maximum length of a filter chain dynamically loaded to solve connection between two filters +\param session filter session +\param max_chain_length sets maximum chain length when resolving filter links. +Default value is 6 ([ in -> ] demux -> reframe -> decode -> encode -> reframe -> mux [ -> out ]) +(filter chains loaded for adaptation (eg pixel format change, audio resample) are loaded after the link resolution) +Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually +\return error if any +*/ +GF_Err gf_fs_set_max_resolution_chain_length(GF_FilterSession *session, u32 max_chain_length); + +/*! Sets the maximum sleep time when postponing tasks. +\param session filter session +\param max_sleep maximum sleep time in milliseconds. 0 means yield only. +\return error if any +*/ +GF_Err gf_fs_set_max_sleep_time(GF_FilterSession *session, u32 max_sleep); + +/*! gets the maximum filter chain lengtG +\param session filter session +\return maximum chain length when resolving filter links. +*/ +u32 gf_fs_get_max_resolution_chain_length(GF_FilterSession *session); + +/*! Stops the session, waiting for all additional threads to complete +\param session filter session +\return error if any +*/ +GF_Err gf_fs_stop(GF_FilterSession *session); + +/*! Gets the number of available filter registries (not blacklisted) +\param session filter session +\return number of filter registries +*/ +u32 gf_fs_filters_registers_count(GF_FilterSession *session); + +/*! Returns the register at the given index +\param session filter session +\param idx index of register, from 0 to \ref gf_fs_filters_registers_count +\return the register object, or NULL if index is out of bounds +*/ +const GF_FilterRegister *gf_fs_get_filter_register(GF_FilterSession *session, u32 idx); + +/*! Registers the test filters used for unit tests +\param session filter session +*/ +void gf_fs_register_test_filters(GF_FilterSession *session); + +/*! 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 +\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 +*/ +GF_Filter *gf_fs_load_source(GF_FilterSession *session, const char *url, const char *args, const char *parent_url, GF_Err *err); + +/*! 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 +\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 +*/ +GF_Filter *gf_fs_load_destination(GF_FilterSession *session, const char *url, const char *args, const char *parent_url, GF_Err *err); + +/*! Returns the last error which happened during a PID connection +\param session filter session +\return the error code if any +*/ +GF_Err gf_fs_get_last_connect_error(GF_FilterSession *session); + +/*! Returns the last error which happened during a filter process +\param session filter session +\return the error code if any +*/ +GF_Err gf_fs_get_last_process_error(GF_FilterSession *session); + +/*! Adds a user-defined register to the session +\param session filter session +\param freg filter register to add +*/ +void gf_fs_add_filter_register(GF_FilterSession *session, const GF_FilterRegister *freg); + +/*! Removes a user-defined register from the session +\param session filter session +\param freg filter register to remove +*/ +void gf_fs_remove_filter_register(GF_FilterSession *session, GF_FilterRegister *freg); + +/*! 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" +\return the error code if any +*/ +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); + +/*! Session flush types*/ +typedef enum +{ + /*! Do not flush session: everything is discarded, potentially breaking output files*/ + GF_FS_FLUSH_NONE=0, + /*! Flush all pending data before closing sessions: sources will be forced into end of stream and all emitted packets will be processed*/ + GF_FS_FLUSH_ALL, + /*! Stop session (resetting buffers) and flush pipeline*/ + GF_FS_FLUSH_FAST +} GF_FSFlushType; + +/*! Aborts the session. This can be called within a callback task to stop the session. Do NOT use \ref gf_fs_stop from within a user task callback, this will deadlock the session +\param session filter session +\param flush_type flush method to use +\return the error code if any +*/ +GF_Err gf_fs_abort(GF_FilterSession *session, GF_FSFlushType flush_type); +/*! Checks if the session is processing its last task. This can be called within a callback task to check if this is the last task, in order to avoid rescheduling the task +\param session filter session +\return GF_TRUE if no more task, GF_FALSE otherwise +*/ +Bool gf_fs_is_last_task(GF_FilterSession *session); + +/*! Checks if the session is in its final flush state (shutdown) +\param session filter session +\return GF_TRUE if no session is aborting, GF_FALSE otherwise +*/ +Bool gf_fs_in_final_flush(GF_FilterSession *session); + +/*! Checks if a given MIME type is supported as input +\param session filter session +\param mime MIME type to query +\return GF_TRUE if MIME is supported +*/ +Bool gf_fs_is_supported_mime(GF_FilterSession *session, const char *mime); + + +/*! Sets UI callback event +\param session filter session +\param ui_event_proc the event proc callback function. Its return value depends on the event type, usually 0 +\param cbk_udta pointer passed back to callback +*/ +void gf_fs_set_ui_callback(GF_FilterSession *session, Bool (*ui_event_proc)(void *opaque, GF_Event *event), void *cbk_udta); + +/*! Prints stats to logs using \code LOG_APP@LOG_INFO \endcode +\param session filter session +*/ +void gf_fs_print_stats(GF_FilterSession *session); + +/*! Prints connections between loaded filters in the session to logs using \code LOG_APP@LOG_INFO \endcode +\param session filter session +*/ +void gf_fs_print_connections(GF_FilterSession *session); + +/*! Prints the list of filters not connected using \code LOG_APP@LOG_WARNING \endcode +\param session filter session +*/ +void gf_fs_print_non_connected(GF_FilterSession *session); + +/*! Prints the list of filters not connected using \code LOG_APP@LOG_WARNING \endcode +\param session filter session +\param ignore_sinks if set, do not warn if some sinks are not connected (mostly used for playback cases) +*/ +void gf_fs_print_non_connected_ex(GF_FilterSession *session, Bool ignore_sinks); + +/*! Prints the list of arguments specified but not used by the filter session using \code LOG_APP@LOG_WARNING \endcode + \note This is simply a wrapper to \ref gf_fs_enum_unmapped_options +\param session filter session +\param ignore_args ignore unused arguments if present in this comma-seperated list - may be NULL +*/ +void gf_fs_print_unused_args(GF_FilterSession *session, const char *ignore_args); + +/*! Prints all possible connections between filter registries to logs using \code LOG_APP@LOG_INFO \endcode +\param session filter session +\param filter_name if not null, only prints input connection for this filter register +\param print_fn optional callback function for print, otherwise print to stderr +*/ +void gf_fs_print_all_connections(GF_FilterSession *session, char *filter_name, void (*print_fn)(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...) ); + +/*! Checks the presence of an input capability and an output capability in a target register. The caps are matched only if they belong to the same bundle. +\param filter_reg filter register to check +\param in_cap_code capability code (property type) of input capability to check +\param in_cap capabiility value of input capability to check +\param out_cap_code capability code (property type) of output capability to check +\param out_cap capability value of output capability to check +\param exact_match_only if true returns TRUE only if exact match (code and value), otherwise return TRUE if caps code are matched +\return GF_TRUE if filter register has such a match, GF_FALSE otherwise +*/ +Bool gf_fs_check_filter_register_cap(const GF_FilterRegister *filter_reg, u32 in_cap_code, GF_PropertyValue *in_cap, u32 out_cap_code, GF_PropertyValue *out_cap, Bool exact_match_only); + + +/*! Enables or disables filter reporting +\param session filter session +\param reporting_on if GF_TRUE, reporting will be enabled +*/ +void gf_fs_enable_reporting(GF_FilterSession *session, Bool reporting_on); + +/*! Locks global session mutex - mostly used to query filter reports and avoids concurrent destruction of a filter. + When adding a filter in an already running session, the session must be locked if set_source is to be used. +\param session filter session +\param do_lock if GF_TRUE, session is locked, otherwise session is unlocked +*/ +void gf_fs_lock_filters(GF_FilterSession *session, Bool do_lock); + +/*! Gets number of active filters in the session +\param session filter session +\return number of active filters +*/ +u32 gf_fs_get_filters_count(GF_FilterSession *session); + +/*! Gets a filter by its current index in the session +\param session filter session +\param idx index in the filter session +\return filter, or NULL if none found +*/ +GF_Filter *gf_fs_get_filter(GF_FilterSession *session, u32 idx); + +/*! Type of filter*/ +typedef enum +{ + /*! Generic filter type accepting input(s) and producing output(s)*/ + GF_FS_STATS_FILTER_GENERIC, + /*! raw input (file, socket, pipe) filter type*/ + GF_FS_STATS_FILTER_RAWIN, + /*! demultiplexer filter type*/ + GF_FS_STATS_FILTER_DEMUX, + /*! decoder filter type*/ + GF_FS_STATS_FILTER_DECODE, + /*! encoder filter type*/ + GF_FS_STATS_FILTER_ENCODE, + /*! multiplexer filter type*/ + GF_FS_STATS_FILTER_MUX, + /*! raw output (file, socket, pipe) filter type*/ + GF_FS_STATS_FILTER_RAWOUT, + /*! media sink (video out, audio out, ...) filter type*/ + GF_FS_STATS_FILTER_MEDIA_SINK, + /*! media source (capture audio or video ...) filter type*/ + GF_FS_STATS_FILTER_MEDIA_SOURCE, +} GF_FSFilterType; + +/*! Filter statistics object*/ +typedef struct +{ + /*!filter object*/ + const GF_Filter *filter; + /*!set if filter is only an alias, in which case all remaining fields of the structure are not set*/ + const GF_Filter *filter_alias; + + /*!number of tasks executed by this filter*/ + u64 nb_tasks_done; + /*!number of packets processed by this filter*/ + u64 nb_pck_processed; + /*!number of bytes processed by this filter*/ + u64 nb_bytes_processed; + /*!number of packets sent by this filter*/ + u64 nb_pck_sent; + /*!number of hardware frames packets sent by this filter*/ + u64 nb_hw_pck_sent; + /*!number of processing errors in the lifetime of the filter*/ + u32 nb_errors; + + /*!number of bytes sent by this filter*/ + u64 nb_bytes_sent; + /*!number of microseconds this filter was active*/ + u64 time_process; + /*!percentage of data processed (between 0 and 100), otherwise unknown (-1)*/ + s32 percent; + /*!last status report from filter, null if session reporting is not enabled*/ + const char *status; + /*!set to GF_TRUE of status or percent changed since last query for this filter, GF_FALSE otherwise*/ + Bool report_updated; + /*!filter name*/ + const char *name; + /*!filter register name*/ + const char *reg_name; + /*!filter register ID*/ + const char *filter_id; + /*!set to GF_TRUE if filter is done processing*/ + Bool done; + /*!number of input PIDs*/ + u32 nb_pid_in; + /*!number of input packets processed*/ + u64 nb_in_pck; + /*!number of output PIDs*/ + u32 nb_pid_out; + /*!number of output packets sent*/ + u64 nb_out_pck; + /*!set to GF_TRUE if filter has seen end of stream*/ + Bool in_eos; + /*!set to the filter class type*/ + GF_FSFilterType type; + /*!set to streamtype of output PID if single output, GF_STREAM_UNKNOWN otherwise*/ + u32 stream_type; + /*!set to codecid of output PID if single output, GF_CODECID_NONE otherwise*/ + u32 codecid; + /*! timestamp and timescale of last packet emitted on output pids*/ + GF_Fraction64 last_ts_sent; + /*! timestamp and timescale of last packet dropped on input pids*/ + GF_Fraction64 last_ts_drop; +} GF_FilterStats; + +/*! Gets statistics for a given filter index in the session +\param session filter session +\param idx index of filter to query +\param stats statistics for filter +\return error code if any +*/ +GF_Err gf_fs_get_filter_stats(GF_FilterSession *session, u32 idx, GF_FilterStats *stats); + + +/*! Enumerates filter and meta-filter arguments not matched in the session +\param session filter session +\param idx index of argument to query, 0 being first argument; this value is automatically incremented +\param argname set to argument name +\param argtype set to argument type: 0 was a filter param (eg :arg=val), 1 was a global arg (eg --arg=val) and 2 was a global meta arg (eg -+arg=val) +\return GF_TRUE if success, GF_FALSE if nothing more to enumerate +*/ +Bool gf_fs_enum_unmapped_options(GF_FilterSession *session, u32 *idx, char **argname, u32 *argtype); + + + +/*! Flags for argument update event*/ +typedef enum +{ + /*! the update event can be sent down the source chain*/ + GF_FILTER_UPDATE_DOWNSTREAM = 1<<1, + /*! the update event can be sent up the filter chain*/ + GF_FILTER_UPDATE_UPSTREAM = 1<<2, +} GF_EventPropagateType; + +/*! Enumerates filter and meta-filter arguments not matched in the session +\param session filter session +\param fid ID of filter on which to send the update, NULL if filter is set +\param filter filter on which to send the update, NULL if fid is set +\param name name of filter option to update +\param val value of filter option to update +\param propagate_mask propagation flags - 0 means no propagation +*/ +void gf_fs_send_update(GF_FilterSession *session, const char *fid, GF_Filter *filter, const char *name, const char *val, GF_EventPropagateType propagate_mask); + + +/*! Loads JS script for filter session +\param session filter session +\param jsfile path to local JS script file to use +\return error if any +*/ +GF_Err gf_fs_load_script(GF_FilterSession *session, const char *jsfile); + +/*! get max download rate allowed by download manager +\param session filter session +\return max rate in bps +*/ +u32 gf_fs_get_http_max_rate(GF_FilterSession *session); + +/*! set max download rate allowed by download manager +\param session filter session +\param rate max rate in bps +\return error if any +*/ +GF_Err gf_fs_set_http_max_rate(GF_FilterSession *session, u32 rate); + + +/*! get current download rate of download manager, all active resources together +\param session filter session +\return current rate in bps +*/ +u32 gf_fs_get_http_rate(GF_FilterSession *session); + +/*! check if a URL is likely to be supported +\param session filter session +\param url the URL to test +\param parent_url the parent URL +\return GF_TRUE if a filter for such a source could be loaded +*/ +Bool gf_fs_is_supported_source(GF_FilterSession *session, const char *url, const char *parent_url); + +/*! callback functions for external monitoring of filter creation or destruction +\param udta user data passed back to callback +\param filter created or destroyed filter +\param is_destroy if GF_TRUE, the filter is being destroyed, otherwise it is being created + */ +typedef void (*gf_fs_on_filter_creation)(void *udta, GF_Filter *filter, Bool is_destroy); + +/*! assign callbacks for filter creation and destruction monitoring +\param session filter session +\param on_create_destroy filter creation/destruction callback, may be NULL +\param udta user data for callbacks, may be NULL +\param force_sync execute tasks involving filter creation/setup and user tasks on main thread +\return error if any + */ +GF_Err gf_fs_set_filter_creation_callback(GF_FilterSession *session, gf_fs_on_filter_creation on_create_destroy, void *udta, Bool force_sync); + +/*! returns RT user data passed in \ref gf_fs_set_filter_creation_callback +\param session filter session +\return udta user data, NULL if error or none + */ +void *gf_fs_get_rt_udta(GF_FilterSession *session); + +/*! 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 +\param evt event to fire +\param upstream if true, send event toward sinks, otherwise towards sources +\return GF_TRUE if event was sent, GF_FALSE otherwise + */ +Bool gf_fs_fire_event(GF_FilterSession *session, GF_Filter *filter, GF_FilterEvent *evt, Bool upstream); + +/*! @} */ + + + + +/*! +\addtogroup fs_props Filter Properties +\ingroup filters_grp +\brief PID and filter properties + +Documents the property object used for PID and packets. +@{ + */ + +/*! Property types*/ + +//DO NOT MODIFY WITHOUT APPLYNG SIMILAR CHANGE TO share/python/libgpac.py +//DO NOT CHANGE A VALUE ASSIGNMENT WITHOUT CHANGING GF_GSF_VERSION + +typedef enum +{ + /*! not allowed*/ + GF_PROP_FORBIDEN = 0, + /*! signed 32 bit integer*/ + GF_PROP_SINT = 1, + /*! unsigned 32 bit integer*/ + GF_PROP_UINT = 2, + /*! signed 64 bit integer*/ + GF_PROP_LSINT = 3, + /*! unsigned 64 bit integer*/ + GF_PROP_LUINT = 4, + /*! boolean*/ + GF_PROP_BOOL = 5, + /*! 32 bit / 32 bit fraction*/ + GF_PROP_FRACTION = 6, + /*! 64 bit / 64 bit fraction*/ + GF_PROP_FRACTION64 = 7, + /*! float (Fixed) number*/ + GF_PROP_FLOAT = 8, + /*! double number*/ + GF_PROP_DOUBLE = 9, + /*! 2D signed integer vector*/ + GF_PROP_VEC2I = 10, + /*! 2D double number vector*/ + GF_PROP_VEC2 = 11, + /*! 3D signed integer vector*/ + GF_PROP_VEC3I = 12, + /*! 4D signed integer vector*/ + 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. + 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, + /*! data property, memory is duplicated when setting the property and managed internally*/ + GF_PROP_DATA = 16, + /*! const string property, memory is NOT duplicated when setting the property, stays user-managed*/ + GF_PROP_NAME = 17, + /*! data property, memory is NOT duplicated when setting the property but is then managed (and free) internally. + Only used when setting a property, the type then defaults to GF_PROP_DATA + DO NOT USE the associate data field upon return from setting the property, it might have been destroyed*/ + GF_PROP_DATA_NO_COPY= 18, + /*! const data property, memory is NOT duplicated when setting the property, stays user-managed*/ + GF_PROP_CONST_DATA = 19, + /*! user-managed pointer*/ + GF_PROP_POINTER = 20, + /*! string list, memory is NOT duplicated when setting the property, the passed array is directly assigned to the new property and will be and managed internally (freed by the filter session) + DO NOT USE the associate string array field upon return from setting the property, it might have been destroyed*/ + GF_PROP_STRING_LIST = 21, + /*! unsigned 32 bit integer list, memory is ALWAYS duplicated when setting the property*/ + GF_PROP_UINT_LIST = 22, + /*! signed 32 bit integer list, memory is ALWAYS duplicated when setting the property*/ + GF_PROP_SINT_LIST = 23, + /*! 2D signed integer vector list, memory is ALWAYS duplicated when setting the property*/ + GF_PROP_VEC2I_LIST = 24, + /*! 4CC on unsigned 32 bit integer*/ + GF_PROP_4CC = 25, + /*! 4CC list on unsigned 32 bit integer, memory is ALWAYS duplicated when setting the property*/ + GF_PROP_4CC_LIST = 26, + + /*! last non-enum property*/ + GF_PROP_LAST_NON_ENUM, + + /*! All constants are defined after this - constants are stored as u32*/ + GF_PROP_FIRST_ENUM = 40, //GSF will code prop type using vlen, try to keep all values between 1 and 127 to only use 1 byte + + /*! Video Pixel format*/ + GF_PROP_PIXFMT = GF_PROP_FIRST_ENUM, + /*! Audio PCM format*/ + GF_PROP_PCMFMT = GF_PROP_FIRST_ENUM+1, + /*! CICP Color Primaries*/ + GF_PROP_CICP_COL_PRIM = GF_PROP_FIRST_ENUM+2, + /*! CICP Color Transfer Characteristics*/ + GF_PROP_CICP_COL_TFC = GF_PROP_FIRST_ENUM+3, + /*! CICP Color Matrix*/ + GF_PROP_CICP_COL_MX = GF_PROP_FIRST_ENUM+4, + /*! not allowed*/ + GF_PROP_LAST_DEFINED +} GF_PropType; + +/*! GSF version (coded on 8 bits in gsf format) */ +#define GF_GSF_VERSION 2 + +/*! Data property*/ +typedef struct +{ + /*! data pointer */ + u8 *ptr; + /*! data size */ + u32 size; +} GF_PropData; + +/*! 2D signed integer vector property*/ +typedef struct +{ + /*! x coord */ + s32 x; + /*! y coord */ + s32 y; +} GF_PropVec2i; + + +/*! 2D double number vector property*/ +typedef struct +{ + /*! x coord */ + Double x; + /*! y coord */ + Double y; +} GF_PropVec2; + +/*! 3D signed integer vector property*/ +typedef struct +{ + /*! x coord */ + s32 x; + /*! y coord */ + s32 y; + /*! z coord */ + s32 z; +} GF_PropVec3i; + +/*! 4D signed integer vector property*/ +typedef struct +{ + /*! x coord */ + s32 x; + /*! y coord */ + s32 y; + /*! z coord */ + s32 z; + /*! w coord */ + s32 w; +} GF_PropVec4i; + + +/*! List of strings property - do not change field order !*/ +typedef struct +{ + /*! array of unsigned integers */ + char **vals; + /*! number of items in array */ + u32 nb_items; +} GF_PropStringList; + +/*! List of unsigned int property - do not change field order !*/ +typedef struct +{ + /*! array of unsigned integers */ + u32 *vals; + /*! number of items in array */ + u32 nb_items; +} GF_PropUIntList; + +/*! List of signed int property - do not change field order !*/ +typedef struct +{ + /*! array of signed integers */ + s32 *vals; + /*! number of items in array */ + u32 nb_items; +} GF_PropIntList; + +/*! List of unsigned int property*/ +typedef struct +{ + /*! array of vec2i */ + GF_PropVec2i *vals; + /*! number of items in array */ + u32 nb_items; +} GF_PropVec2iList; + +/*! Property value used by PIDs and packets*/ +struct __gf_prop_val +{ + /*! type of the property */ + GF_PropType type; + /*! union of all possible property data types */ + union { + /*! 64 bit unsigned integer value of property */ + u64 longuint; + /*! 64 bit signed integer value of property */ + s64 longsint; + /*! 32 bit signed integer value of property */ + s32 sint; + /*! 32 bit unsigned integer value of property */ + u32 uint; + /*! boolean value of property */ + Bool boolean; + /*! fraction (32/32 bits) value of property */ + GF_Fraction frac; + /*! fraction (64/64 bits) value of property */ + GF_Fraction64 lfrac; + /*! fixed number (float or 32 bit depending on compilation settings) value of property */ + Fixed fnumber; + /*! double value of property */ + Double number; + /*! 2D signed integer vector value of property */ + GF_PropVec2i vec2i; + /*! 2D double vector value of property */ + GF_PropVec2 vec2; + /*! 3D signed integer vector value of property */ + GF_PropVec3i vec3i; + /*! 4D signed integer vector value of property */ + GF_PropVec4i vec4i; + /*! data value of property. For non const data type, the memory is freed by filter session. + Otherwise caller is responsible to free it at end of filter/session*/ + GF_PropData data; + /*! string value of property. For non const string / names, the memory is freed by filter session, otherwise handled as const char *. */ + char *string; + /*! pointer value of property */ + void *ptr; + /*! string list value of property - memory is handled by filter session (always copy)*/ + GF_PropStringList string_list; + /*! unsigned integer list value of property - memory is handled by filter session (always copy)*/ + GF_PropUIntList uint_list; + /*! signed integer list value of property - memory is handled by filter session (always copy)*/ + GF_PropIntList sint_list; + /*! vec2i list value of property - memory is handled by filter session (always copy)*/ + GF_PropVec2iList v2i_list; + } value; +}; + +/*! Playback mode type supported on PID*/ +typedef enum +{ + /*! simplest playback mode, can play from 0 at speed=1 only*/ + GF_PLAYBACK_MODE_NONE=0, + /*! seek playback mode, can play from any position at speed=1 only*/ + GF_PLAYBACK_MODE_SEEK, + /*! fast forward playback mode, can play from any position at speed=N only, with N>=0*/ + GF_PLAYBACK_MODE_FASTFORWARD, + /*! rewind playback mode, can play from any position at speed=N, N positive or negative*/ + GF_PLAYBACK_MODE_REWIND +} GF_FilterPidPlaybackMode; + +/*! Built-in property types +See gpac help (gpac -h props) for codes, types, formats and and meaning + \hideinitializer +*/ +enum +{ + GF_PROP_PID_ID = GF_4CC('P','I','D','I'), + GF_PROP_PID_ESID = GF_4CC('E','S','I','D'), + GF_PROP_PID_ITEM_ID = GF_4CC('I','T','I','D'), + GF_PROP_PID_ITEM_NUM = GF_4CC('I','T','I','X'), + GF_PROP_PID_TRACK_NUM = GF_4CC('P','I','D','X'), + GF_PROP_PID_SERVICE_ID = GF_4CC('P','S','I','D'), + GF_PROP_PID_CLOCK_ID = GF_4CC('C','K','I','D'), + GF_PROP_PID_DEPENDENCY_ID = GF_4CC('D','P','I','D'), + GF_PROP_PID_SUBLAYER = GF_4CC('D','P','S','L'), + GF_PROP_PID_PLAYBACK_MODE = GF_4CC('P','B','K','M'), + GF_PROP_PID_SCALABLE = GF_4CC('S','C','A','L'), + GF_PROP_PID_TILE_BASE = GF_4CC('S','A','B','T'), + GF_PROP_PID_TILE_ID = GF_4CC('P','T','I','D'), + GF_PROP_PID_LANGUAGE = GF_4CC('L','A','N','G'), + GF_PROP_PID_SERVICE_NAME = GF_4CC('S','N','A','M'), + GF_PROP_PID_SERVICE_PROVIDER = GF_4CC('S','P','R','O'), + GF_PROP_PID_STREAM_TYPE = GF_4CC('P','M','S','T'), + GF_PROP_PID_SUBTYPE = GF_4CC('P','S','S','T'), + GF_PROP_PID_ISOM_SUBTYPE = GF_4CC('P','I','S','T'), + GF_PROP_PID_ORIG_STREAM_TYPE = GF_4CC('P','O','S','T'), + GF_PROP_PID_CODECID = GF_4CC('P','O','T','I'), + GF_PROP_PID_IN_IOD = GF_4CC('P','I','O','D'), + 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_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'), + GF_PROP_PID_TIMESHIFT_DEPTH = GF_4CC('P','T','S','D'), + GF_PROP_PID_TIMESHIFT_TIME = GF_4CC('P','T','S','T'), + GF_PROP_PID_TIMESHIFT_STATE = GF_4CC('P','T','S','S'), + GF_PROP_PID_TIMESCALE = GF_4CC('T','I','M','S'), + GF_PROP_PID_PROFILE_LEVEL = GF_4CC('P','R','P','L'), + GF_PROP_PID_DECODER_CONFIG = GF_4CC('D','C','F','G'), + GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT = GF_4CC('E','C','F','G'), + GF_PROP_PID_CONFIG_IDX = GF_4CC('I','C','F','G'), + GF_PROP_PID_SAMPLE_RATE = GF_4CC('A','U','S','R'), + GF_PROP_PID_SAMPLES_PER_FRAME = GF_4CC('F','R','M','S'), + GF_PROP_PID_NUM_CHANNELS = GF_4CC('C','H','N','B'), + GF_PROP_PID_AUDIO_BPS = GF_4CC('A','B','P','S'), + GF_PROP_PID_CHANNEL_LAYOUT = GF_4CC('C','H','L','O'), + 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_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'), + GF_PROP_PID_WIDTH = GF_4CC('W','I','D','T'), + GF_PROP_PID_HEIGHT = GF_4CC('H','E','I','G'), + GF_PROP_PID_PIXFMT = GF_4CC('P','F','M','T'), + GF_PROP_PID_PIXFMT_WRAPPED = GF_4CC('P','F','M','W'), + GF_PROP_PID_STRIDE = GF_4CC('V','S','T','Y'), + GF_PROP_PID_STRIDE_UV = GF_4CC('V','S','T','C'), + GF_PROP_PID_BIT_DEPTH_Y = GF_4CC('Y','B','P','S'), + GF_PROP_PID_BIT_DEPTH_UV = GF_4CC('C','B','P','S'), + GF_PROP_PID_FPS = GF_4CC('V','F','P','F'), + GF_PROP_PID_INTERLACED = GF_4CC('V','I','L','C'), + GF_PROP_PID_SAR = GF_4CC('P','S','A','R'), + GF_PROP_PID_PAR = GF_4CC('V','P','A','R'), + GF_PROP_PID_WIDTH_MAX = GF_4CC('M', 'W','I','D'), + GF_PROP_PID_HEIGHT_MAX = GF_4CC('M', 'H','E','I'), + GF_PROP_PID_ZORDER = GF_4CC('V', 'Z','I','X'), + GF_PROP_PID_TRANS_X = GF_4CC('V','T','R','X'), + GF_PROP_PID_TRANS_Y = GF_4CC('V','T','R','Y'), + 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_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'), + GF_PROP_PID_MIRROR = GF_4CC('V','M','I','R'), + GF_PROP_PID_ROTATE = GF_4CC('V','R','O','T'), + GF_PROP_PID_CLAP_W = GF_4CC('C','L','P','W'), + GF_PROP_PID_CLAP_H = GF_4CC('C','L','P','H'), + GF_PROP_PID_CLAP_X = GF_4CC('C','L','P','X'), + GF_PROP_PID_CLAP_Y = GF_4CC('C','L','P','Y'), + GF_PROP_PID_NUM_VIEWS = GF_4CC('P','N','B','V'), + GF_PROP_PID_DOLBY_VISION = GF_4CC('D','O','V','I'), + GF_PROP_PID_BITRATE = GF_4CC('R','A','T','E'), + GF_PROP_PID_MAXRATE = GF_4CC('M','R','A','T'), + GF_PROP_PID_TARGET_RATE = GF_4CC('T','B','R','T'), + GF_PROP_PID_DBSIZE = GF_4CC('D','B','S','Z'), + GF_PROP_PID_MEDIA_DATA_SIZE = GF_4CC('M','D','S','Z'), + GF_PROP_PID_CAN_DATAREF = GF_4CC('D','R','E','F'), + GF_PROP_PID_URL = GF_4CC('F','U','R','L'), + 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_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'), + GF_PROP_PID_FILE_CACHED = GF_4CC('C','A','C','H'), + GF_PROP_PID_DOWN_RATE = GF_4CC('D','L','B','W'), + GF_PROP_PID_DOWN_SIZE = GF_4CC('D','L','S','Z'), + GF_PROP_PID_DOWN_BYTES = GF_4CC('D','L','B','D'), + GF_PROP_PID_FILE_RANGE = GF_4CC('F','B','R','A'), + GF_PROP_PID_DISABLE_PROGRESSIVE = GF_4CC('N','P','R','G'), + GF_PROP_PID_ISOM_BRANDS = GF_4CC('A','B','R','D'), + GF_PROP_PID_ISOM_MBRAND = GF_4CC('M','B','R','D'), + GF_PROP_PID_ISOM_MOVIE_TIME = GF_4CC('M','H','T','S'), + GF_PROP_PID_HAS_SYNC = GF_4CC('P','S','Y','N'), + GF_PROP_SERVICE_WIDTH = GF_4CC('D','W','D','T'), + GF_PROP_SERVICE_HEIGHT = GF_4CC('D','H','G','T'), + GF_PROP_PID_CAROUSEL_RATE = GF_4CC('C','A','R','A'), + GF_PROP_PID_UTC_TIME = GF_4CC('U','T','C','D'), + GF_PROP_PID_UTC_TIMESTAMP = GF_4CC('U','T','C','T'), + GF_PROP_PID_AUDIO_VOLUME = GF_4CC('A','V','O','L'), + GF_PROP_PID_AUDIO_PAN = GF_4CC('A','P','A','N'), + GF_PROP_PID_AUDIO_PRIORITY = GF_4CC('A','P','R','I'), + GF_PROP_PID_PROTECTION_SCHEME_TYPE = GF_4CC('S','C','H','T'), + GF_PROP_PID_PROTECTION_SCHEME_VERSION = GF_4CC('S','C','H','V'), + GF_PROP_PID_PROTECTION_SCHEME_URI = GF_4CC('S','C','H','U'), + GF_PROP_PID_PROTECTION_KMS_URI = GF_4CC('K','M','S','U'), + 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'), + GF_PROP_PID_OMA_CLEAR_LEN = GF_4CC('O','M','P','T'), + GF_PROP_PID_CRYPT_INFO = GF_4CC('E','C','R','I'), + GF_PROP_PID_DECRYPT_INFO = GF_4CC('E','D','R','I'), + GF_PROP_PCK_SENDER_NTP = GF_4CC('N','T','P','S'), + GF_PROP_PCK_RECEIVER_NTP = GF_4CC('N','T','P','R'), + GF_PROP_PID_ADOBE_CRYPT_META = GF_4CC('A','M','E','T'), + GF_PROP_PID_ENCRYPTED = GF_4CC('E','P','C','K'), + GF_PROP_PID_OMA_PREVIEW_RANGE = GF_4CC('O','D','P','R'), + GF_PROP_PID_CENC_PSSH = GF_4CC('P','S','S','H'), + GF_PROP_PCK_CENC_SAI = GF_4CC('S','A','I','S'), + GF_PROP_PID_CENC_KEY_INFO = GF_4CC('C','B','I','V'), + GF_PROP_PID_CENC_PATTERN = GF_4CC('C','P','T','R'), + GF_PROP_PID_CENC_STORE = GF_4CC('C','S','T','R'), + GF_PROP_PID_CENC_STSD_MODE = GF_4CC('C','S','T','M'), + GF_PROP_PID_AMR_MODE_SET = GF_4CC('A','M','S','T'), + GF_PROP_PCK_SUBS = GF_4CC('S','U','B','S'), + GF_PROP_PID_MAX_NALU_SIZE = GF_4CC('N','A','L','S'), + GF_PROP_PCK_FILENUM = GF_4CC('F','N','U','M'), + GF_PROP_PCK_FILENAME = GF_4CC('F','N','A','M'), + GF_PROP_PCK_IDXFILENAME = GF_4CC('I','N','A','M'), + GF_PROP_PCK_FILESUF = GF_4CC('F','S','U','F'), + GF_PROP_PCK_EODS = GF_4CC('E','O','D','S'), + GF_PROP_PCK_CUE_START = GF_4CC('P','C','U','S'), + + GF_PROP_PID_MAX_FRAME_SIZE = GF_4CC('M','F','R','S'), + GF_PROP_PID_AVG_FRAME_SIZE = GF_4CC('A','F','R','S'), + GF_PROP_PID_MAX_TS_DELTA = GF_4CC('M','T','S','D'), + GF_PROP_PID_MAX_CTS_OFFSET = GF_4CC('M','C','T','O'), + GF_PROP_PID_CONSTANT_DURATION = GF_4CC('S','C','T','D'), + GF_PROP_PID_ISOM_TRACK_TEMPLATE = GF_4CC('I','T','K','T'), + GF_PROP_PID_ISOM_TREX_TEMPLATE = GF_4CC('I','T','X','T'), + GF_PROP_PID_ISOM_STSD_TEMPLATE = GF_4CC('I','S','T','D'), + GF_PROP_PID_ISOM_UDTA = GF_4CC('I','M','U','D'), + GF_PROP_PID_ISOM_HANDLER = GF_4CC('I','H','D','L'), + GF_PROP_PID_ISOM_TRACK_FLAGS = GF_4CC('I','T','K','F'), + GF_PROP_PID_ISOM_TRACK_MATRIX = GF_4CC('I','T','K','M'), + GF_PROP_PID_ISOM_ALT_GROUP = GF_4CC('I','A','L','G'), + GF_PROP_PID_ISOM_FORCE_NEGCTTS = GF_4CC('I','F','N','C'), + GF_PROP_PID_DISABLED = GF_4CC('I','T','K','D'), + GF_PROP_PID_PERIOD_ID = GF_4CC('P','E','I','D'), + 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_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_DASH_DUR = GF_4CC('D','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_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'), + GF_PROP_PID_AS_ANY_DESC = GF_4CC('A','A','D','S'), + GF_PROP_PID_REP_DESC = GF_4CC('R','D','E','S'), + GF_PROP_PID_BASE_URL = GF_4CC('B','U','R','L'), + GF_PROP_PID_TEMPLATE = GF_4CC('D','T','P','L'), + GF_PROP_PID_START_NUMBER = GF_4CC('D','R','S','N'), + GF_PROP_PID_XLINK = GF_4CC('X','L','N','K'), + 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_EXT_MASTER = GF_4CC('H','L','M','X'), + GF_PROP_PID_HLS_EXT_VARIANT = GF_4CC('H','L','V','X'), + GF_PROP_PID_DASH_CUE = GF_4CC('D','C','U','E'), + GF_PROP_PID_DASH_SEGMENTS = GF_4CC('D','C','N','S'), + GF_PROP_PID_CODEC = GF_4CC('C','O','D','S'), + GF_PROP_PID_SINGLE_SCALE = GF_4CC('D','S','T','S'), + GF_PROP_PID_UDP = GF_4CC('P','U','D','P'), + + GF_PROP_PID_PRIMARY_ITEM = GF_4CC('P','I','T','M'), + + GF_PROP_PID_PLAY_BUFFER = GF_4CC('P','B','P','L'), + GF_PROP_PID_MAX_BUFFER = GF_4CC('P','B','M','X'), + GF_PROP_PID_RE_BUFFER = GF_4CC('P','B','R','E'), + GF_PROP_PID_VIEW_IDX = GF_4CC('V','I','D','X'), + + 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_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'), + GF_PROP_PID_COLR_CHROMALOC = GF_4CC('C','L','O','C'), + GF_PROP_PID_CONTENT_LIGHT_LEVEL = GF_4CC('C','L','L','I'), + GF_PROP_PID_MASTER_DISPLAY_COLOUR = GF_4CC('M','D','C','V'), + GF_PROP_PID_SRC_MAGIC = GF_4CC('P','S','M','G'), + GF_PROP_PID_MUX_INDEX = GF_4CC('T','I','D','X'), + GF_PROP_NO_TS_LOOP = GF_4CC('N','T','S','L'), + GF_PROP_PID_MHA_COMPATIBLE_PROFILES = GF_4CC('M','H','C','P'), + GF_PROP_PCK_FRAG_START = GF_4CC('P','F','R','B'), + GF_PROP_PCK_FRAG_RANGE = GF_4CC('P','F','R','R'), + GF_PROP_PCK_SIDX_RANGE = GF_4CC('P','F','S','R'), + GF_PROP_PCK_MOOF_TEMPLATE = GF_4CC('M','F','T','P'), + GF_PROP_PCK_INIT = GF_4CC('P','C','K','I'), + GF_PROP_PID_RAWGRAB = GF_4CC('P','G','R','B'), + 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_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_STEREO_TYPE = GF_4CC('P','S','T','T'), + GF_PROP_PID_PROJECTION_TYPE = GF_4CC('P','P','J','T'), + GF_PROP_PID_VR_POSE = GF_4CC('P','P','O','S'), + GF_PROP_PID_CUBE_MAP_PAD = GF_4CC('P','C','M','P'), + GF_PROP_PID_EQR_CLAMP = GF_4CC('P','E','Q','C'), + + GF_PROP_PID_SCENE_NODE = GF_4CC('P','S','N','D'), + GF_PROP_PID_ORIG_CRYPT_SCHEME = GF_4CC('P','O','C','S'), + GF_PROP_PID_TIMESHIFT_SEGS = GF_4CC('P','T','S','N'), + + + //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'), + //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'), + //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'), + GF_PROP_PCK_HLS_VARIANT = GF_4CC('D','H','L','V'), + GF_PROP_PID_DASH_PERIOD_START = GF_4CC('D','P','S','T'), + GF_PROP_PCK_HLS_VARIANT_NAME = GF_4CC('D','H','L','N'), + GF_PROP_PID_HLS_KMS = GF_4CC('H','L','S','K'), + GF_PROP_PID_HLS_IV = GF_4CC('H','L','S','I'), + //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 foward mode of dasher) + GF_PROP_PID_NO_INIT = GF_4CC('P','N','I','N'), + + GF_PROP_PID_IS_MANIFEST = GF_4CC('P','H','S','M'), + + GF_PROP_PCK_XPS_MASK = GF_4CC('P','X','P','M'), + GF_PROP_PCK_END_RANGE = GF_4CC('P','C','E','R'), + + /*! Internal property used for 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_FFMPEG_CODEC_ID = GF_4CC('F','C','I','D'), +}; + +/*! Block patching requirements for FILE pids, as signaled by GF_PROP_PID_DISABLE_PROGRESSIVE + \hideinitializer +*/ +enum +{ + GF_PID_FILE_PATCH_NONE = 0, + GF_PID_FILE_PATCH_REPLACE = 1, + GF_PID_FILE_PATCH_INSERT = 2, +}; + +/*! Gets readable name of built-in property +\param prop_4cc property built-in 4cc +\return readable name +*/ +const char *gf_props_4cc_get_name(u32 prop_4cc); + +/*! Gets property type of built-in property +\param prop_4cc property built-in 4cc +\return property name +*/ +u32 gf_props_4cc_get_type(u32 prop_4cc); + +/*! Checks if two properties are equal +\param p1 first property to compare - shall not be NULL +\param p2 second property to compare - shall not be NULL +\return GF_TRUE if properties are equal, GF_FALSE otherwise +*/ +Bool gf_props_equal(const GF_PropertyValue *p1, const GF_PropertyValue *p2); + +/*! Same as \ref gf_props_equal but do not match string with value "*" to string with value different from "*" +\param p1 first property to compare - shall not be NULL +\param p2 second property to compare - shall not be NULL +\return GF_TRUE if properties are equal, GF_FALSE otherwise +*/ +Bool gf_props_equal_strict(const GF_PropertyValue *p1, const GF_PropertyValue *p2); + +/*! Gets the readable name for a property type +\param type property type +\return readable name +*/ +const char *gf_props_get_type_name(GF_PropType type); + +/*! Gets the description for a property type +\param type property type +\return description +*/ +const char *gf_props_get_type_desc(GF_PropType type); + +/*! Gets the description type for a given property type name +\param name property type name +\return property type or GF_PROP_FORBIDEN +*/ +GF_PropType gf_props_parse_type(const char *name); + +/*! Check if a property type is an enum type +\param type property type +\return GF_TRUE if constant, GF_FALSE otherwise +*/ +Bool gf_props_type_is_enum(GF_PropType type); + +/*! Parse a enum type property string +\param type property type +\param value value to parse +\return value, 0xFFFFFFFF if error +*/ +u32 gf_props_parse_enum(u32 type, const char *value); + +/*! Get the name of a constant type property value +\param type property type +\param value value of constant +\return value, 0xFFFFFFFF if error +*/ +const char *gf_props_enum_name(u32 type, u32 value); + +/*! Get the possible names of an enum type property +\param type property type +\return comma-seperated list of possible values +*/ +const char *gf_props_enum_all_names(u32 type); + +/*! Get the base type of a property type. Properties with same base type can be safely type-casted +\param type property type +\return base property type +*/ +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 +*/ +GF_PropertyValue gf_props_parse_value(u32 type, const char *name, const char *value, const char *enum_values, char list_sep_char); + +/*! Maximum string size to use when dumping a property*/ +#define GF_PROP_DUMP_ARG_SIZE 100 + +/*! Data property dump mode*/ +typedef enum +{ + /*! do not dump data*/ + GF_PROP_DUMP_DATA_NONE=0, + /*! dump data if less than 40 bytes, otherwise dump ptr address and CRC*/ + GF_PROP_DUMP_DATA_INFO, + /*! dump data to parsable property, as ADDRESS+'@'+POINTER*/ + GF_PROP_DUMP_DATA_PTR, +} GF_PropDumDataMode; + +/*! Dumps a property value to string +\param att property value +\param dump buffer holding the resulting value for types requiring string conversions (integers, ...) +\param dump_data_mode data dump mode +\param min_max_enum optional, gives the min/max or enum string when the property is a filter argument +\return string +*/ +const char *gf_props_dump_val(const GF_PropertyValue *att, char dump[GF_PROP_DUMP_ARG_SIZE], GF_PropDumDataMode dump_data_mode, const char *min_max_enum); + +/*! Dumps a property value to string, resolving any built-in types (pix formats, codec id, ...) +\param p4cc property 4CC +\param att property value +\param dump buffer holding the resulting value for types requiring string conversions (integers, ...) +\param dump_data_mode data dump mode +\return string +*/ +const char *gf_props_dump(u32 p4cc, const GF_PropertyValue *att, char dump[GF_PROP_DUMP_ARG_SIZE], GF_PropDumDataMode dump_data_mode); + +/*! Resets a property value, freeing allocated data or strings depending on the property type +\param prop property 4CC +*/ +void gf_props_reset_single(GF_PropertyValue *prop); + +/*! Property aplies only to packets */ +#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 + +/*! Structure describing a built-in property in GPAC*/ +typedef struct { + /*! identifier (4CC) */ + u32 type; + /*! name */ + const char *name; + /*! description */ + const char *description; + /*! data type (uint, float, etc ..) */ + u8 data_type; + /*! flags for the property */ + u8 flags; +} GF_BuiltInProperty; + +/*! Gets property description +\param prop_idx built-in property index, starting from 0 +\return associated property description or NULL if property was not found +*/ +const GF_BuiltInProperty *gf_props_get_description(u32 prop_idx); + +/*! Gets built-in property 4CC from name +\param name built-in property name +\return built-in property 4CC or 0 if not found +*/ +u32 gf_props_get_id(const char *name); + +/*! Gets flags of built-in property +\param prop_4cc built-in property 4CC +\return built-in property flags, 0 if not found +*/ +u8 gf_props_4cc_get_flags(u32 prop_4cc); + + +/*! Helper macro to set signed int property */ +#define PROP_SINT(_val) (GF_PropertyValue){.type=GF_PROP_SINT, .value.sint = _val} +/*! Helper macro to set unsigned int property */ +#define PROP_UINT(_val) (GF_PropertyValue){.type=GF_PROP_UINT, .value.uint = _val} +/*! Helper macro to set an enum property */ +#define PROP_ENUM(_val, _type) (GF_PropertyValue){.type=_type, .value.uint = _val} +/*! Helper macro to set 4CC unsigned int property */ +#define PROP_4CC(_val) (GF_PropertyValue){.type=GF_PROP_4CC, .value.uint = _val} +/*! Helper macro to set long signed int property */ +#define PROP_LONGSINT(_val) (GF_PropertyValue){.type=GF_PROP_LSINT, .value.longsint = _val} +/*! Helper macro to set long unsigned int property */ +#define PROP_LONGUINT(_val) (GF_PropertyValue){.type=GF_PROP_LUINT, .value.longuint = _val} +/*! Helper macro to set boolean property */ +#define PROP_BOOL(_val) (GF_PropertyValue){.type=GF_PROP_BOOL, .value.boolean = _val} +/*! Helper macro to set fixed-point number property */ +#define PROP_FIXED(_val) (GF_PropertyValue){.type=GF_PROP_FLOAT, .value.fnumber = _val} +/*! Helper macro to set float property */ +#define PROP_FLOAT(_val) (GF_PropertyValue){.type=GF_PROP_FLOAT, .value.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} +/*! Helper macro to set 32-bit fraction property*/ +#define PROP_FRAC(_val) (GF_PropertyValue){.type=GF_PROP_FRACTION, .value.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} +/*! 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} +/*! Helper macro to set double property */ +#define PROP_DOUBLE(_val) (GF_PropertyValue){.type=GF_PROP_DOUBLE, .value.number = _val} +/*! Helper macro to set string property */ +#define PROP_STRING(_val) (GF_PropertyValue){.type=GF_PROP_STRING, .value.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} +/*! Helper macro to set name property */ +#define PROP_NAME(_val) (GF_PropertyValue){.type=GF_PROP_NAME, .value.string = _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} +/*! 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} +/*! 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} +/*! Helper macro to set 2D float vector property */ +#define PROP_VEC2(_val) (GF_PropertyValue){.type=GF_PROP_VEC2, .value.vec2 = _val} +/*! Helper macro to set 2D integer vector property */ +#define PROP_VEC2I(_val) (GF_PropertyValue){.type=GF_PROP_VEC2I, .value.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} +/*! Helper macro to set 3D integer vector property */ +#define PROP_VEC3I(_val) (GF_PropertyValue){.type=GF_PROP_VEC3I, .value.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} +/*! Helper macro to set 4D integer vector property */ +#define PROP_VEC4I(_val) (GF_PropertyValue){.type=GF_PROP_VEC4I, .value.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} +/*! Helper macro to set pointer property */ +#define PROP_POINTER(_val) (GF_PropertyValue){.type=GF_PROP_POINTER, .value.ptr = (void*)_val} + + +/*! @} */ + + + + +/*! +\addtogroup fs_evt Filter Events +\ingroup filters_grp +\brief Filter Events + +PIDs may receive commands and may emit messages using this system. + +Events may flow downwards (towards the source), in which case they are commands, or upwards (towards the sink), in which case they are informative event. + +A filter not implementing a process_event will result in the event being forwarded down to all input PIDs or up to all output PIDs. + +A filter may decide to cancel an event, in which case the event is no longer forwarded down/up the chain. + +GF_FEVT_PLAY, GF_FEVT_STOP and GF_FEVT_SOURCE_SEEK events will trigger a reset of PID buffers. + +A GF_FEVT_PLAY event on a PID already playing is discarded. + +A GF_FEVT_STOP event on a PID already stopped is discarded. + +GF_FEVT_PLAY and GF_FEVT_SET_SPEED events will trigger larger (abs(speed)>1) or smaller (abs(speed)<1) PID buffer limit in blocking mode. + +GF_FEVT_STOP and GF_FEVT_SOURCE_SEEK events are filtered to reset the PID buffers. + +@{ + */ + +/*! Filter event types */ +typedef enum +{ + /*! PID control, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_PLAY = 1, + /*! PID speed control, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_SET_SPEED, + /*! PID control, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_STOP, + /*! PID pause, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_PAUSE, + /*! PID resume, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_RESUME, + /*! PID source seek, allows seeking in bytes the source*/ + GF_FEVT_SOURCE_SEEK, + /*! PID source switch, allows a source filter to switch its source URL for the same protocol*/ + GF_FEVT_SOURCE_SWITCH, + /*! DASH segment size info, sent down from muxers to manifest generators*/ + GF_FEVT_SEGMENT_SIZE, + /*! Scene attach event, sent down from compositor to filters (BIFS/OD/timedtext/any scene-related) to share the scene (resources and node graph)*/ + GF_FEVT_ATTACH_SCENE, + /*! Scene reset event, sent down from compositor to filters (BIFS/OD/timedtext/any scene-related) to indicate scene reset (resources and node graph). This is a direct filter call, only sent processed by filters running on the main thread*/ + GF_FEVT_RESET_SCENE, + /*! quality switching request event, helps filters decide how to adapt their processing*/ + GF_FEVT_QUALITY_SWITCH, + /*! visibility hint event, helps filters decide how to adapt their processing*/ + GF_FEVT_VISIBILITY_HINT, + /*! special event type sent to a filter whenever the PID info properties have been modified. No cancel because no forward - cf \ref gf_filter_pid_set_info. A filter returning GF_TRUE on this event will prevent info update notification on the destination filters*/ + GF_FEVT_INFO_UPDATE, + /*! buffer requirement event. This event is NOT sent to filters, it is internaly processed by the filter session. Filters may however send this + event to indicate their buffereing preference (real-time sinks mostly)*/ + GF_FEVT_BUFFER_REQ, + /*! filter session capability change, sent whenever global capabilities (max width, max heoght, ... ) 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*/ + GF_FEVT_CONNECT_FAIL, + /*! user event, sent from compositor/vout down to filters*/ + GF_FEVT_USER, + /*! PLAY hint event, used to signal if block dispatch is needed or not for the source*/ + GF_FEVT_PLAY_HINT, + /*! file delete event, sent upstream by dasher to notify file deletion, downstream by flist to ask for file deletion. The associated file processing (reading, writing) MUST be done when firing this event*/ + GF_FEVT_FILE_DELETE, + + /*! DASH fragment (cmaf chunk) size info, sent down from muxers to manifest generators*/ + GF_FEVT_FRAGMENT_SIZE, + + /*! Encoder hints*/ + GF_FEVT_ENCODE_HINTS, + /*! NTP source clock send by other services (eg from TS to dash using TEMI) */ + GF_FEVT_NTP_REF, +} GF_FEventType; + +/*! type: the type of the event*/ +/*! on_pid: PID to which the event is targeted. If NULL the event is targeted at the whole filter */ +#define FILTER_EVENT_BASE \ + GF_FEventType type; \ + GF_FilterPid *on_pid; \ + \ + + +/*! Macro helper for event structure initializing*/ +#define GF_FEVT_INIT(_a, _type, _on_pid) { memset(&_a, 0, sizeof(GF_FilterEvent)); _a.base.type = _type; _a.base.on_pid = _on_pid; } + +/*! Base type of events. All events start with the fields defined in \ref FILTER_EVENT_BASE*/ +typedef struct +{ + FILTER_EVENT_BASE +} GF_FEVT_Base; + +/*! Event structure for GF_FEVT_PLAY, GF_FEVT_SET_SPEED, GF_FEVT_PLAY_HINT, GF_FEVT_STOP*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*!GF_FEVT_PLAY only, play range in sec - if range is <0, it means end of file (eg [2, -1] with speed>0 means 2 +oo) */ + Double start_range; + /*!GF_FEVT_PLAY only, send range in sec - if range is less than start, ignored*/ + Double end_range; + /*! params for GF_FEVT_PLAY and GF_FEVT_SET_SPEED*/ + Double speed; + + /*! GF_FEVT_PLAY only, indicates playback should start from given packet number - used by dasher when reloading sources*/ + u32 from_pck; + + /*! GF_FEVT_PLAY only, set when PLAY event is sent upstream to audio out, indicates HW buffer reset*/ + u8 hw_buffer_reset; + /*! GF_FEVT_PLAY only: indicates this is the first PLAY on an element inserted from bcast*/ + u8 initial_broadcast_play; + /*! params for GF_FEVT_PLAY only + 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) + */ + u8 timestamp_based; + /*! GF_FEVT_PLAY only, 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 + for GF_FEVT_PLAY_HINT if upstream event: indicates a HAS segment switch has occured (used by tileagg to flush reassembly buffers) + for GF_FEVT_STOP: indicates the source filter has already received stop/play events and cancel event just before source + */ + u8 forced_dash_segment_switch; + /*! GF_FEVT_PLAY only, indicates non ref frames should be drawn for faster processing*/ + u8 drop_non_ref; + /*! GF_FEVT_PLAY only, indicates that a demuxer must not forward this event as a source seek because seek has already been done + (typically this play request is a segment play and byte range access within the file has already been performed by DASH client)*/ + u8 no_byterange_forward; +} GF_FEVT_Play; + +/*! Event structure for GF_FEVT_SOURCE_SEEK and GF_FEVT_SOURCE_SWITCH*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! start offset in source*/ + u64 start_offset; + /*! end offset in source*/ + u64 end_offset; + /*! GF_FEVT_SOURCE_SWITCH only, new path to switch to*/ + const char *source_switch; + /*!GF_FEVT_SOURCE_SWITCH only, indicates source is a DASH init segment and should be kept in memory cache*/ + 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*/ + u32 hint_block_size; +} GF_FEVT_SourceSeek; + +/*! Event structure for GF_FEVT_SEGMENT_SIZE*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! URL of segment this info is for, or NULL if single file*/ + const char *seg_url; + /*! media start range in segment file*/ + u64 media_range_start; + /*! media end range in segment file*/ + u64 media_range_end; + /*! index start range in segment file*/ + u64 idx_range_start; + /*! index end range in segment file*/ + u64 idx_range_end; + /*! global sidx is signaled using is_init=1 and range in idx range*/ + u8 is_init; + /*! if global sidx, indicates if this is is an insertion and that byte range previously received should be shifted*/ + u8 is_shift; +} GF_FEVT_SegmentSize; + +/*! Event structure for GF_FEVT_FRAGMENT_SIZE*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! set to TRUE if last fragment in segment*/ + Bool is_last; + /*! media start range in segment file*/ + u64 offset; + /*! media end range in segment file*/ + u64 size; + /*! media duration of fragment*/ + GF_Fraction64 duration; + /*! fragment contains an IDR*/ + Bool independent; +} GF_FEVT_FragmentSize; + +/*! Event structure for GF_FEVT_ATTACH_SCENE and GF_FEVT_RESET_SCENE +For GF_FEVT_RESET_SCENE, THIS IS A DIRECT FILTER CALL NOT THREADSAFE, filters processing this event SHALL run on the main thread*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! Pointer to a GF_ObjectManager structure for this PID*/ + void *object_manager; + /*! Pointer to a GF_Node structure for this PID if node decoder pid*/ + void *node; +} GF_FEVT_AttachScene; + +/*! Event structure for GF_FEVT_QUALITY_SWITCH*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! switch quality up or down */ + Bool up; + /*! 0: current group, otherwise index of the depending_on group */ + u32 dependent_group_index; + /*! index of the quality to switch, as indicated in "has:qualities" property. If < 0, sets to automatic quality switching*/ + s32 q_idx; + /*! 1+tile mode adaptation (does not change other selections) */ + u32 set_tile_mode_plus_one; + /*! quality degradation hint, between 0 (full quality) and 100 (lowest quality, stream not currently rendered)*/ + u32 quality_degradation; +} GF_FEVT_QualitySwitch; + +/*! Event structure for GF_FEVT_USER*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! GF_Event structure*/ + GF_Event event; +} GF_FEVT_Event; + +/*! Event structure for GF_FEVT_FILE_DELETE*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! URL to delete, or "__gpac_self__" when asking source filter to delete file */ + const char *url; +} GF_FEVT_FileDelete; + +/*! Event structure for GF_FEVT_VISIBILITY_HINT*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! gives minimun and maximum coordinates of the visible rectangle associated with channels. min_x may be greater than max_x in case of 360 videos */ + u32 min_x, max_x, min_y, max_y; + /*! if set, only min_x, min_y are used and indicate the gaze direction in pixels in the visual with/height frame (0,0) being top-left*/ + Bool is_gaze; +} GF_FEVT_VisibilityHint; + + +/*! Event structure for GF_FEVT_BUFFER_REQ*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! indicates the max buffer to set on PID - the buffer is only activated on PIDs connected to decoders*/ + u32 max_buffer_us; + /*! indicates the max playout buffer to set on PID (buffer level triggering playback) + \note This is not used internally by the blocking mechanisms, but may be needed by other filters to take decisions + */ + u32 max_playout_us; + /*! indicates the min playout buffer to set on PID (buffer level triggering rebuffering) + \note This is not used internally by the blocking mechanisms, but may be needed by other filters to take decisions + */ + u32 min_playout_us; + /*! if set, only the PID target of the event will have the buffer req set; otherwise, the buffer requirement event is passed down the chain until a raw media PID is found or a decoder is found. Used for muxers*/ + Bool pid_only; +} GF_FEVT_BufferRequirement; + + +/*! Event structure for GF_FEVT_ENCODE_HINT*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! duration of intra (IDR, closed GOP) as expected by the dasher */ + GF_Fraction intra_period; + +} GF_FEVT_EncodeHints; + + +/*! Event structure for GF_FEVT_NTP_REF*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! 64 bit NTP timestamp */ + u64 ntp; + +} GF_FEVT_NTPRef; + +/*! +Filter Event object + */ +union __gf_filter_event +{ + GF_FEVT_Base base; + GF_FEVT_Play play; + GF_FEVT_SourceSeek seek; + GF_FEVT_AttachScene attach_scene; + GF_FEVT_Event user_event; + GF_FEVT_QualitySwitch quality_switch; + GF_FEVT_VisibilityHint visibility_hint; + GF_FEVT_BufferRequirement buffer_req; + GF_FEVT_SegmentSize seg_size; + GF_FEVT_FragmentSize frag_size; + GF_FEVT_FileDelete file_del; + GF_FEVT_EncodeHints encode_hints; + GF_FEVT_NTPRef ntp; +}; + +/*! Gets readable name for event type +\param type type of the event +\return readable name of the event +*/ +const char *gf_filter_event_name(GF_FEventType type); + +/*! @} */ + + +/*! +\addtogroup fs_filter Filter +\ingroup filters_grp +\brief Filter + + +The API for filters documents declaration of filters, their creation and functions available to interact with the filter session. +The gf_filter_* functions shall only be called from within a given filter, this filter being the target of the function. This is not checked at runtime. + +Calling these functions on other filters will result in unpredictable behavior, very likely crashes in multi-threading conditions. + +A filter is instanciated through a filter register. The register holds entry points to a filter, arguments of a filter (basically property values) and capabilities. + +Capabilities are used to check if a filter can be connected to a given input PID, or if two filters can be directly connected (capability match). + +Capabilities are organized in so-called bundles, gathering the caps that shall be present or not present in the PID / connecting filter. Typically a capability bundle +will contain a stream type for input a stream type for output, a codec id for input and a codec id for output. + +Several capability bundles can be used if needed. A good example is the writegen filter in GPAC, which simply transforms a sequence of media frames into a raw file, hence converts +stream_type/codecID to file extension and MIME type - cf gpac/src/filters/write_generic.c + +When resolving a chain, PID properties are checked against these capabilities. If a property of the same type exists in the PID than in the capability, +it must match the capability requirement (equal, excluded). If no property exists for a given non-optional capability type, + the bundle is marked as not matching and the ext capability bundle in the filter is checked. + A PID property not listed in any capability of the filter does not impact the matching. + +The GF_PROP_PID_FILE_EXT and GF_PROP_PID_MIME are handled as alternate to each other, this allows matching a PID if either its MIME or extension map and avoids failing if the pid has no MIME or extension set. +@{ + */ + + +/*! Structure holding arguments for a filter*/ +typedef enum +{ + /*! used for GUI config: regular argument type */ + GF_FS_ARG_HINT_NORMAL = 0, + /*! used for GUI config: advanced argument type */ + GF_FS_ARG_HINT_ADVANCED = 1<<1, + /*! used for GUI config: expert argument type */ + GF_FS_ARG_HINT_EXPERT = 1<<2, + /*! used for GUI config: hidden argument type */ + GF_FS_ARG_HINT_HIDE = 1<<3, + /*! if set indicates that the argument is updatable. If so, the value will be changed if offset_in_private is valid, and the update_args function will be called if not NULL*/ + GF_FS_ARG_UPDATE = 1<<4, + /*! used by meta filters (ffmpeg & co) to indicate the parsing is handled by the filter in which case the type is overloaded to string and passed to the update_args function*/ + GF_FS_ARG_META = 1<<5, + /*! internal flag used by meta filters (ffmpeg & co) to indicate the description of the argument is a dynamic allocated memory*/ + GF_FS_ARG_META_ALLOC = 1<<6, + /*! internal flag used by filters acting as sinks (gsfmx in file mode) to allow retrieving dst url but avoid being used as direct sinks*/ + GF_FS_ARG_SINK_ALIAS = 1<<7, + /*! if set indicates that the argument is updatable only as a direct synchronous call (typically used for shared data). + If so, the value will only be updated if the update directly targets the filter, and the global filter mutex will be locked before calling update_arg. + It is however recommended for the calling app to lock the target filter whenever shared data is modified (see vout filter overlay argument for example) + The filter should lock itself whenever appropriate using \ref gf_filter_lock + */ + GF_FS_ARG_UPDATE_SYNC = 1<<8, +} GF_FSArgumentFlags; + +/*! Structure holding arguments for a filter*/ +typedef struct +{ + /*! argument name. Naming conventions: + - only use lowercase + - keep short and avoid '_' + */ + const char *arg_name; + /*! offset of the argument in the structure, -1 means not exposed/stored in structure, in which case it is notified through the update_arg function. + The offset is casted into the corresponding property value of the argument type*/ + s32 offset_in_private; + /*! description of argument. Format conventions: + - first letter is lowercase except if it is an acronym (eg, 'IP'). + - description does not end with a '.'; multiple sentences are however allowed with '.' separators. + - description does not end with a new line CR or LF; multiple sentences are however allowed with '.' separators. + - keep it short. If too long, use " - see filter help" and put description in filter help. + - only use markdown '`' and '*', except for enumeration lists which shall use "- ". + - do not use CR/LF in description except to start enum types description. + - use infinitive (eg \"set foo to bar\" and not \"sets foo to bar\"). + - Enumerations should be described as: + sentence(s)'\n' (no '.' nor ':' at end) + - name1: val'\n' (no '.' at end) + - nameN: val (no '.' nor '\n' at end of last value description) + and enum value description order shall match enum order + - links are allowed, see GF_FilterRegister doc + */ + const char *arg_desc; + /*! type of argument - this is a property type*/ + GF_PropType arg_type; + /*! default value of argument, can be NULL (for number types, value 0 for each dimension of the type is assumed)*/ + const char *arg_default_val; + /*! string describing an enum (unsigned integer type) or a min/max value. + For min/max, the syntax is "min,max", with -I = -infinity and +I = +infinity + For enum, the syntax is "a|b|...", resoling in a=0, b=1,... To skip a value insert '|' eg "|a|b" resolves to a=1, b=2, "a||b" resolves to a=0, b=2 + + Naming conventions for enumeration: + - use single word + - use lowercase escept for single/double characters name (eg 'A', 'V'). + */ + const char *min_max_enum; + /*! set of argument flags*/ + GF_FSArgumentFlags flags; +} 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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) } +/*! 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} + +/*! Flags for filter capabilities*/ +enum +{ + /*! when not set, indicates the start of a new set of capabilities. Set by default by the generic GF_CAPS_* */ + GF_CAPFLAG_IN_BUNDLE = 1, + /*! if set this is an input capability of the bundle*/ + GF_CAPFLAG_INPUT = 1<<1, + /*! if set this is an output capability of the bundle. A capability can be declared both as input and output. For example stream type is usually the same on both inputs and outputs*/ + GF_CAPFLAG_OUTPUT = 1<<2, + /*! when set, the capability is valid if the value does not match. If an excluded capability is not found in the destination PID, it is assumed to match*/ + GF_CAPFLAG_EXCLUDED = 1<<3, + /*! when set, the capability is validated only for filter loaded for this destination filter*/ + GF_CAPFLAG_LOADED_FILTER = 1<<4, + /*! Only used for output capabilities, indicates that this capability applies to all 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 */ + GF_CAPFLAG_OPTIONAL = 1<<6, +}; + +/*! Shortcut macro to set for input capability flags*/ +#define GF_CAPS_INPUT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT) +/*! Shortcut macro to set for optional input capability flags*/ +#define GF_CAPS_INPUT_OPT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OPTIONAL) +/*! Shortcut macro to set for static input capability flags*/ +#define GF_CAPS_INPUT_STATIC (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_STATIC) +/*! Shortcut macro to set for static optional input capability flags*/ +#define GF_CAPS_INPUT_STATIC_OPT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_STATIC|GF_CAPFLAG_OPTIONAL) +/*! Shortcut macro to set for excluded input capability flags*/ +#define GF_CAPS_INPUT_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_EXCLUDED) +/*! Shortcut macro to set for input for loaded filter only capability flags*/ +#define GF_CAPS_INPUT_LOADED_FILTER (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_LOADED_FILTER) +/*! Shortcut macro to set for output capability flags*/ +#define GF_CAPS_OUTPUT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT) +/*! Shortcut macro to set for output for loaded filter only capability flags*/ +#define GF_CAPS_OUTPUT_LOADED_FILTER (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_LOADED_FILTER) +/*! Shortcut macro to set for excluded output capability flags*/ +#define GF_CAPS_OUTPUT_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_EXCLUDED) +/*! Shortcut macro to set for static output capability flags*/ +#define GF_CAPS_OUTPUT_STATIC (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_STATIC) +/*! Shortcut macro to set for excluded static output capability flags*/ +#define GF_CAPS_OUTPUT_STATIC_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_EXCLUDED|GF_CAPFLAG_STATIC) +/*! Shortcut macro to set for input and output capability flags*/ +#define GF_CAPS_INPUT_OUTPUT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT) +/*! Shortcut macro to set for optional input and output capability flags*/ +#define GF_CAPS_INPUT_OUTPUT_OPT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_OPTIONAL) +/*! Shortcut macro to set for excluded input capability flags*/ +#define GF_CAPS_IN_OUT_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_EXCLUDED) + +/*! Filter capability description*/ +typedef struct +{ + /*! 4cc of the capability listed. This shall be 0 or the type of a built-in property */ + u32 code; + /*! default type and value of the capability listed*/ + GF_PropertyValue val; + /*! name of the capability listed, NULL if code is set. The special value * is used to indicate that the capability is solved at runtime (the filter must be loaded)*/ + const char *name; + /*! Flags of the capability*/ + u32 flags; + /*! overrides the filter register priority for this capability. Usually 0*/ + u8 priority; +} GF_FilterCapability; + +/*! Filter session capability structure*/ +typedef struct +{ + u32 max_screen_width; + u32 max_screen_height; + u32 max_screen_bpp; + u32 max_screen_fps; + u32 max_screen_nb_views; + u32 max_audio_channels; + u32 max_audio_sample_rate; + u32 max_audio_bit_depth; +} GF_FilterSessionCaps; + +/*! Gets filter session capability +\param filter source of the query - avoids fetching the filter session object +\param caps pointer filled with caps +*/ +void gf_filter_get_session_caps(GF_Filter *filter, GF_FilterSessionCaps *caps); + +/*! Sets filter session capability +\param filter source of the query - avoids fetching the filter session object +\param caps session capability new values - completely replace the old ones +*/ +void gf_filter_set_session_caps(GF_Filter *filter, GF_FilterSessionCaps *caps); + +/*! Check if the filter is an instance of a filter register +\param filter filter to test +\param freg filter register to test +\return GF_TRUE if filter is an instance of this register, GF_FALSE otehrwise +*/ +Bool gf_filter_is_instance_of(GF_Filter *filter, const GF_FilterRegister *freg); + + +/*! Aborts a filter, discarding and stopping all input PIDs and sending EOS on all output PIDs +\param filter filter to abort +*/ +void gf_filter_abort(GF_Filter *filter); + + +/*! Locks a filter. A filter should only lock itself when using updatable arguments of type GF_FS_ARG_UPDATE_SYNC +\param filter filter to lock +\param do_lock if GF_TRUE, locks the filter global mutex, otherwise unlocks it +*/ +void gf_filter_lock(GF_Filter *filter, Bool do_lock); + + + +/*! Lock global filter session. This is needed when assigning source IDs after a connect source or destination to the loaded source to connect in an async way +\param filter target filter +\param do_lock if GF_TRUE, locks the filter session global mutex, otherwise unlocks it +*/ +void gf_filter_lock_all(GF_Filter *filter, Bool do_lock); + +/*! Force all output pids created for this filter to require a source ID for linking. + + This is used by filters loading subchains to enforce that filters from these subchain only connect to each other or the target filter but not other filters outside this chain. + Filters using this function must setup source IDs on filters of the sunchain(s) they load. + +\param filter target filter +*/ +void gf_filter_require_source_id(GF_Filter *filter); + +/*! Sets opaque runtime data for filter - used by bindings +\param filter target filter +\param udta data to set +\return error if any +*/ +GF_Err gf_filter_set_rt_udta(GF_Filter *filter, void *udta); +/*! Gets opaque runtime data for filter - used by bindings +\param filter target filter +\return associated data or NULL +*/ +void *gf_filter_get_rt_udta(GF_Filter *filter); + + +/*! Filter probe score, used when probing a URL/MIME or when probing formats from data*/ +typedef enum +{ + /*! (de)mux format is not supported*/ + GF_FPROBE_NOT_SUPPORTED = 0, + /*! + For demux only: format is maybe a match but garbage data was found at the start + */ + GF_FPROBE_MAYBE_NOT_SUPPORTED, + /*! + - for demux: format is maybe a match and can maybe be demuxed + - for mux: format is supported with potentially missing features + */ + GF_FPROBE_MAYBE_SUPPORTED, + /*! (de)mux format is supported*/ + GF_FPROBE_SUPPORTED, + /*! demux format should be handled by this filter*/ + GF_FPROBE_FORCE, + /*! used by demux formats not supporting data prober*/ + GF_FPROBE_EXT_MATCH, +} GF_FilterProbeScore; + +/*! Quick macro for assigning the capability arrays to the register structure*/ +#define SETCAPS( __struct ) .caps = __struct, .nb_caps=sizeof(__struct)/sizeof(GF_FilterCapability) + +#ifndef GPAC_DISABLE_DOC +/*! Macro for assigning filter register description*/ +#define GF_FS_SET_DESCRIPTION(_desc) .description = _desc, +/*! Macro for assigning filter register author*/ +#define GF_FS_SET_AUTHOR(_author) .author = _author, +/*! Macro for assigning filter register help*/ +#define GF_FS_SET_HELP(_help) .help = _help, +/*! Macro for assigning filter register argument types and description*/ +#define GF_FS_DEF_ARG(_name, _offset, _desc, _type, _default, _enum, _flags) { _name, _offset, _desc, _type, _default, _enum, _flags } +#else +#define GF_FS_SET_DESCRIPTION(_desc) +#define GF_FS_SET_AUTHOR(_author) +#define GF_FS_SET_HELP(_help) +#define GF_FS_SET_ARGHELP(_help) +#define GF_FS_DEF_ARG(_name, _offset, _desc, _type, _default, _enum, _flags) {_name, _offset, _type, _default, _enum, _flags} +#endif + +/*! Filter register flags*/ +typedef enum +{ + /*! when set indicates all calls shall take place in the main thread (running GL output) - to be refined*/ + GF_FS_REG_MAIN_THREAD = 1<<1, + /*! when set indicates the initial call to configure_pid will happen in the main thread. This is typically called by decoders requiring + 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*/ + 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, + /*! Usually set for filters acting as sources but without exposing an src argument. This prevents throwing warnings on arguments not handled by the filter*/ + GF_FS_REG_ACT_AS_SOURCE = 1<<5, + /*! Indicates the filter can connect to another instance of the same class (avoids cyclic detection in linker graph) + Filters of the same class can only connect directly to each other if the destination filter is explictly loaded */ + GF_FS_REG_ALLOW_CYCLIC = 1<<6, + /*! Indicates the filter PIDs may be dynamically added during process (e.g.M2TS, GSF, etc). + This will prevent deactivating a filter when none of its output PIDs are connected*/ + GF_FS_REG_DYNAMIC_PIDS = 1<<7, + /*! Indicates the filter is a script-based filter. The registry is not valid until the script is loaded*/ + GF_FS_REG_SCRIPT = 1<<8, + /*! Indicates the filter is a meta filter, wrapping various underlying filters (e.g. FFmpeg)*/ + GF_FS_REG_META = 1<<9, + /*! Indicates that this filter, when dynamically loaded, allows the link resolver to redirect PID connection to this filter rather than to its next explicitly loaded filter in the chain. + This is typically used by mux filters + */ + GF_FS_REG_DYNAMIC_REDIRECT = 1<<10, + /*! Indicates the filter requires graph resolver (typically because it creates new destinations/sinks at run time)*/ + GF_FS_REG_REQUIRES_RESOLVER = 1<<11, + /*! + For sink filters, indicates the filter forces remux of input PIDs of type GF_STREAM_FILE (prevents direct file copy) + For source filters, indicates the PIDs should be remuxed to a destination filter with force remux set + */ + GF_FS_REG_FORCE_REMUX = 1<<12, + + /*! flag dynamically set at runtime for custom filters*/ + GF_FS_REG_CUSTOM = 0x40000000, +} GF_FSRegisterFlags; + +/*! 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 +{ + /*! mandatory - name of the filter as used when setting up filters. Naming conventions: + - shall not contain any space (breaks filter lookup) + - should use lowercase + - should not use '_' + */ + const char *name; + /*! optional - size of private stack structure. The structure is allocated by the framework and arguments are setup before calling any of the filter functions*/ + u32 private_size; + /*! indicates the max number of additional input PIDs - muxers and scalable filters typically set this to (u32) -1. A value of 0 implies the filter can only handle one PID*/ + u32 max_extra_pids; + /*! set of register flags*/ + GF_FSRegisterFlags flags; + + /*! list of PID capabilities*/ + const GF_FilterCapability *caps; + /*! number of PID capabilities*/ + u32 nb_caps; + + /*! optional - filter arguments if any*/ + const GF_FilterArgs *args; + + /*! mandatory - callback for filter processing + + This function is called whenever packets are available on the input PID and buffer space is available on the output. + The session will by default monitor a filter for errors, and throw en error if a filter is not consuming nor producing packets for a given amount of process calls. + In some cases, it might be needed to not consume nor produce a packet for a given time (for example, waiting for a packet drop before reconfiguring a filter). + A filter must signal this using \ref gf_filter_ask_rt_reschedule, possibly with no timeout. + + A filter may return GF_EOS to indicate no more data is expected to be produced by this filter + + A filter may return GF_PROFILE_NOT_SUPPORTED to indicate that the filter is not supported (when unable to detect this at configure) and trigger a relink of the filter graph unless disabled at session level. + */ + GF_Err (*process)(GF_Filter *filter); + + /*! optional for sources, mandatory for filters and sinks (any filter with an input cap set) - callback for PID update may be called several times + on the same PID if PID config is changed. + Since discontinuities may happen at any time, and a filter may fetch packets in burst, + this function may be called while the filter is calling \ref gf_filter_pid_get_packet (this is the only reentrant call for filters). + If an error is returned, the PID object shall no longer be used by the filter. + + \param filter the target filter + \param PID the input PID to configure + \param is_remove indicates the input PID is removed + \return error if any. + - a return error of GF_REQUIRES_NEW_INSTANCE indicates the PID cannot be processed in this instance but could be in a clone of the filter. + - a return error of GF_FILTER_NOT_SUPPORTED indicates the PID cannot be processed and no alternate chain resolution would help + - a return error of GF_EOS silently discard the input pid (same as GF_FILTER_NOT_SUPPORTED but does not throw error) + - a return error of GF_BAD_PARAM, GF_SERVICE_ERROR or GF_REMOTE_SERVICE_ERROR indicates the PID cannot be processed and no alternate chain resolution would help, and throws a log error message + - any other return error will trigger a reconfigure of the chain to find another filter unless disabled at session level. + + If an error is returned when (re)configuring a pid, the function is called again with is_remove set to GF_TRUE + */ + GF_Err (*configure_pid)(GF_Filter *filter, GF_FilterPid *PID, Bool is_remove); + + /*! optional - callback for filter initialization - private stack of filter is allocated by framework) + \param filter the target filter + \return error if any. A filter may return GF_EOS to indicate the filter session shall not be run, but that no error should be thrown (used by dasher in realtime regulation mode) + */ + GF_Err (*initialize)(GF_Filter *filter); + + /*! optional - callback for filter desctruction - private stack of filter is freed by framework + \param filter the target filter + */ + void (*finalize)(GF_Filter *filter); + + /*! optional - callback for arguments update. If GF_OK is returned, the filter private stack is updated accordingly. + If function is NULL, all updatable arguments will be changed in the filter private stack without the filter being notified. + If argument is a meta argument, it is the filter responsability to handle the update, as meta arguments do not live on the filter private stack. + If the filter is a meta filter and argument is not declared in the argument list, the function is always called. + + \param filter the target filter + \param arg_name the name of the argument being set + \param new_val the value of the argument being set + \return error if any, GF_NOT_FOUND to silently disable argument value change. + */ + GF_Err (*update_arg)(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val); + + /*! optional - process a given event. Retruns TRUE if the event has to be canceled, FALSE otherwise + - If a downstream (towards source) event is not canceled, it will be forwarded to each input PID of the filter. + - If you need to forward the event only to one input pid, send a copy of the event to the desired input and cancel the event. + \param filter the target filter + \param evt the event to process + \return GF_TRUE if the event should be canceled, GF_FALSE otherwise + */ + Bool (*process_event)(GF_Filter *filter, const GF_FilterEvent *evt); + + /*! optional - Called whenever an output PID needs format renegotiaition. If not set, a filter chain will be loaded to solve the negotiation + + \param filter the target filter + \param PID the filter output PID being reconfigured + \return error code if any + */ + GF_Err (*reconfigure_output)(GF_Filter *filter, GF_FilterPid *PID); + + /*! optional, mostly used for source filters and destination filters - probe the given URL, returning a score. + This function is called before opening the source (no data received yet) + + \param url the url of the source to probe, never NULL. + \param mime the MIME type of the source to probe. Can be NULL if mime not available at probe type + \return probe score + */ + GF_FilterProbeScore (*probe_url)(const char *url, const char *mime); + + /*! optional, usually set by demuxers. This function probes the mime type of a data chunk, usually located at the start of the file. + This function is called once the source is open, but is never called on an instanciated filter. The returned mime type (if any) is then used instead of the file extension + for solving filter graph. + \note Demux filters should always exposed 2 input caps bundle, one for specifying input cap by file extension and one for specifying input cap by mime type. + \param data data to probe + \param size size of the data to probe + \param score set to the probe score. Initially set to \ref GF_FPROBE_NOT_SUPPORTED before calling the function. If you are certain of the data type, use \ref GF_FPROBE_SUPPORTED, if unsure use \ref GF_FPROBE_MAYBE_SUPPORTED. If the format cannot be probed (bad design), set it to \ref GF_FPROBE_EXT_MATCH + \return mime type of content, list of known extensions for the format if score is set to GF_FPROBE_EXT_MATCH, or NULL if not detected. + */ + const char * (*probe_data)(const u8 *data, u32 size, GF_FilterProbeScore *score); + + /*! 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; + + /*! 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 + + \param session the filter session + \param freg the filter register to destroy + */ + void (*register_free)(GF_FilterSession *session, struct __gf_filter_register *freg); + /*! user data of register loader, not inspected/modified by filter session*/ + void *udta; + + /*! optional. Used by sink filters offering multiple sink support + GPAC instantiates filter chains based on filter capabilities and arguments of source and destination. When a sink/destination filter can handle + multiple file inputs of various formats/types, temporary filters called alias are needed to instantiate the proper chain. + If the return value is true, an alias filter of the same type as this filter will be created with the proper arguments and initialized (in order to modify the + alias filter capabilities if needed), and finalized when no longer needed. All other callbacks will however be made on the main filter, not the alias. + + A good example is HTTP output of a DASH session: + - the first PID will be a FILE stream for the manifest (MPD, M3U8), and the http output capabilities will be set to accept the manifest file extension.mime + - the other PIDs will be media PIDs (mp4, ts, mkv ...) and won't match the filter instance, creating a new instance which we don't want + + By creating filter alias, a temprary HTTP output (alias) will be created, holding the arguments for the chain (including destination type). Overloading the alias filter capabilities + with the desired file extension or MIME will trigger the proper chain resolution. The alias will be switched to the final filter upon connecting the PIDs + + To access original arguments of the chain, see \ref gf_filter_pid_get_alias_udta + To check if an initialize callback targets the main filter or the temporary one, see \ref gf_filter_is_alias + \param filter the target filter + \param url the url of the target destination, never NULL. + \param mime the MIME type of the target destination. Can be NULL if mime not available + \return GF_TRUE if this filter instance should be used to handle the given URL, in which case an alias filter will be created + */ + Bool (*use_alias)(GF_Filter *filter, const char *url, const char *mime); + + + /*! version of the filter, usually only for external libs + \note If this strings starts with "! " it indicates an error message at load time of the registry. This should only be set when \code gf_opts_get_bool("temp", "gendoc"); \endcode returns true, indicating the filter session is only loaded for documentation purposes (man/md generation and command line help). + */ + const char *version; +#ifndef GPAC_DISABLE_DOC + /*! short description of the filter. Conventions: + - should be small (used to generate UI / documentation) + - Should be Capitalized + - shall not use markdown + - shall be single line + */ + const char *description; + /*! author of the filter. Conventions: + - shall not use markdown + - first line if present is author name and should be normally capitalized + - second line if present is comma-separated list of contact info (eg http://foo.bar,mailto:foo@bar.com) + + If first character is a `-`, this field is interpreted as a configuration info (typically for meta filters such as ffmpeg). + */ + const char *author; + /*! help of the filter. Conventions: + - describe any non-obvious behavior of the filter here + - markdown is allowed. Use '`' for code, "__" for italic, "**" for bold and "~~" for strikethrough. + - mardown top-level sections shall use '#', second level '##', and so on + - mardown bullet lists shall use "- ", " - " etc... + - notes shall be identifed as a line starting with "Note: " + - warnings shall be identifed as a line starting with "Warning: " + - examples shall be identifed as a line starting with "EX " + - the sequence "[-" is reserved for option links. It is formatted as: + - "[-OPT]()": link to self page for option OPT + - "[-OPT](LINK)": link to other page for option OPT. + LINK can be: + - GPAC: resolves to general GPAC help + - CORE: resolves to general CORE help + - LOG: resolves to general LOG help + - ANY: resolves to filter with register name 'ANY' + + Note that [text](ANY) with ANY a filter register name will link to that filter help page + */ + const char *help; +#endif +}; + + +/*! Gets filter private stack - the stack is allocated and freed by the filter session, as are any arguments. The rest is up to the filter to delete it +\param filter target filter +\return filter private stack if any, NULL otherwise +*/ +void *gf_filter_get_udta(GF_Filter *filter); + +/*! Sets filter name - mostly used for logging purposes +\param filter target filter +\param name new name to assign. If NULL, name is reset to register name +*/ +void gf_filter_set_name(GF_Filter *filter, const char *name); + +/*! Gets filter name +\param filter target filter +\return name of the filter +*/ +const char *gf_filter_get_name(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 +*/ +void gf_filter_make_sticky(GF_Filter *filter); + +/*! Return the number of queued events on the filter. Events are not aggregated, some filter may want to wait until all events are processed before taking actions. The function recursively goes up the filter chain and count queued events. +\param filter target filter +\return number of queued events +*/ +u32 gf_filter_get_num_events_queued(GF_Filter *filter); + +/*! Returns the single instance of GPAC download manager. DO NOT DESTROY IT!! +\param filter target filter +\return the GPAC download manager +*/ +GF_DownloadManager *gf_filter_get_download_manager(GF_Filter *filter); + +/*! Returns the single instance of GPAC font manager. DO NOT DESTROY IT!! +\param filter target filter +\return the GPAC font manager +*/ +struct _gf_ft_mgr *gf_filter_get_font_manager(GF_Filter *filter); + +/*! 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 +*/ +void gf_filter_ask_rt_reschedule(GF_Filter *filter, u32 us_until_next); + +/*! Posts a filter process task to the parent session. This is needed for some filters not having any input packets to process but still needing to work +such as decoder flushes, servers, etc... The filter session will ignore this call if the filter is already scheduled for processing +\param filter target filter +*/ +void gf_filter_post_process_task(GF_Filter *filter); + +/*! Posts a task to the session scheduler. This task is not a process task but the filter will still be called by a single thread at any time (ie, might delay process) +\param filter target filter +\param task_execute the callback function for the task. The callback can return GF_TRUE to reschedule the task, in which case the task will be rescheduled +immediately or after reschedule_ms. +\param udta user data passed back to the task function +\param task_name name of the task for logging purposes +\return error code if failure +*/ +GF_Err gf_filter_post_task(GF_Filter *filter, Bool (*task_execute) (GF_Filter *filter, void *callback, u32 *reschedule_ms), void *udta, const char *task_name); + + +/*! Sets callback function on source filter setup failure +\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 +\param udta user data passed back to the task function +*/ +void gf_filter_set_setup_failure_callback(GF_Filter *filter, GF_Filter *source_filter, Bool (*on_setup_error)(GF_Filter *f, void *on_setup_error_udta, GF_Err e), void *udta); + +/*! Notify a filter setup error. This is typically called when a source filter or a filter having accepted input PIDs detects an issue. +For a source filter (no input PID), the failure callback will be called if any, and the filter will be removed. +For a connected filter, all input PIDs od the filter will be disconnected and the filter removed. +\param filter target filter +\param reason the failure reason code +*/ +void gf_filter_setup_failure(GF_Filter *filter, GF_Err reason); + +/*! Notify a filter fatal error but not a connection error. Typically, a 404 in HTTP. +\param filter target filter +\param reason the failure reason code +\param force_disconnect indicates if the filter chain should be disconnected or not +*/ +void gf_filter_notification_failure(GF_Filter *filter, GF_Err reason, Bool force_disconnect); + +/*! Disconnects a source filter chain between two filters +\param filter the calling filter. This filter is NOT disconnected +\param src_filter the source filter point of the chain to disconnect. +*/ +void gf_filter_remove_src(GF_Filter *filter, GF_Filter *src_filter); + +/*! Disconnects a filter from fthe chain +\param filter the filter to remove +*/ +void gf_filter_remove(GF_Filter *filter); + +/*! Sets the number of additional input PID a filter can accept. This overrides the default value of the filter register +\param filter the target filter +\param max_extra_pids the number of additional PIDs this filter can accept +*/ +void gf_filter_set_max_extra_input_pids(GF_Filter *filter, u32 max_extra_pids); + +/*! Gets the number of additional input PID a filter can accept. This overrides the default value of the filter register +\param filter the target filter +\return max_extra_pids the number of additional PIDs this filter can accept +*/ +u32 gf_filter_get_max_extra_input_pids(GF_Filter *filter); + + +/*! Queries if blocking mode is enabled for the filter +\param filter the target filter +\return GF_TRUE if blocking mode is enabled, GF_FALSE otherwise +*/ +Bool gf_filter_block_enabled(GF_Filter *filter); + +/*! Indicates the EOS status on input PIDs of this filter shall not be checked when probing for end of stream in the chain +\param filter the target filter +\param do_block if GF_TRUE, prevents EOS checking on input stream, otherwise enables it (default is FALSE upon creation) +*/ +void gf_filter_block_eos(GF_Filter *filter, Bool do_block); + + +/*! Connects a source to this filter. +\note Any filter loaded between the source and the calling filter will not use argument inheritance from the caller. + +\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 target filter's destination +\param err return code - can be NULL +\return the new source filter instance or NULL if error +*/ +GF_Filter *gf_filter_connect_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. +\param err return code - can be NULL +\return the new destination filter instance or NULL if error +*/ +GF_Filter *gf_filter_connect_destination(GF_Filter *filter, const char *url, GF_Err *err); + + +/*! Loads a new filter in the session - see \ref gf_fs_load_filter +\param filter the target filter +\param name name and arguments of the filter register to instantiate. +\param err_code error code if any +\return created filter or NULL if filter register cannot be found +*/ +GF_Filter *gf_filter_load_filter(GF_Filter *filter, const char *name, GF_Err *err_code); + +/*! Checks if a source filter can handle the given URL. The source filter is not loaded. + +\param filter the target filter +\param url url of source to connect to, with optional arguments. +\param parent_url url of parent if any +\return GF_TRUE if a source filter can be found for this URL, GF_FALSE otherwise +*/ +Bool gf_filter_is_supported_source(GF_Filter *filter, const char *url, const char *parent_url); + +/*! Gets the number of input PIDs connected to a filter +\param filter the target filter +\return number of input PIDs +*/ +u32 gf_filter_get_ipid_count(GF_Filter *filter); + +/*! Gets an input PIDs connected to a filter +\param filter the target filter +\param idx index of the input PID to retrieve, between 0 and \ref gf_filter_get_ipid_count +\return the input PID, or NULL if not found +*/ +GF_FilterPid *gf_filter_get_ipid(GF_Filter *filter, u32 idx); + +/*! Gets the number of output PIDs connected to a filter +\param filter the target filter +\return number of output PIDs +*/ +u32 gf_filter_get_opid_count(GF_Filter *filter); + +/*! Gets an output PIDs connected to a filter +\param filter the target filter +\param idx index of the output PID to retrieve, between 0 and \ref gf_filter_get_opid_count +\return the output PID, or NULL if not found +*/ +GF_FilterPid *gf_filter_get_opid(GF_Filter *filter, u32 idx); + +/*! Queries buffer max limits on a filter. This is the max of buffer limits on all its connected outputs +\param filter the target filter +\param max_buf will be set to the maximum buffer duration in microseconds - may be NULL +\param max_playout_buf will be set to the maximum playout buffer (the one triggering play) duration in microseconds - may be NULL +*/ +void gf_filter_get_output_buffer_max(GF_Filter *filter, u32 *max_buf, u32 *max_playout_buf); + +/*! Sets a clock state at session level indicating the time / timestamp of the last rendered frame. This is used by basic audio output +\param filter the target filter +\param time_in_us the system time in us, see \ref gf_sys_clock_high_res +\param media_timestamp the media timestamp associated with this time. +*/ +void gf_filter_hint_single_clock(GF_Filter *filter, u64 time_in_us, GF_Fraction64 media_timestamp); + +/*! Retrieves the clock state at session level, as set by \ref gf_filter_hint_single_clock +\param filter the target filter +\param time_in_us will be set to the system time in us, see \ref gf_sys_clock_high_res - may be NULL +\param media_timestamp will be set to the media timestamp associated with this time - may be NULL. +*/ +void gf_filter_get_clock_hint(GF_Filter *filter, u64 *time_in_us, GF_Fraction64 *media_timestamp); + +/*! Explicitly assigns a source ID to a filter. This shall be called before connecting the link_from filter +If no ID is assigned to the linked filter, a dynamic one in the form of _%08X_ (using the filter mem address) will be used + +\warning In multithreaded sessions, the session must be locked before the filter creation step and unlocked after calling this function, otherwise graph resolution might happen before \ref gf_filter_set_source is called + +\param filter the target filter +\param link_from the filter to link from +\param link_ext any link extensions allowed in link syntax: +\c \#PIDNAME: accepts only PID(s) with name PIDNAME +\c \#TYPE: accepts only PIDs of matching media type. TYPE can be 'audio' 'video' 'scene' 'text' 'font' +\c \#TYPEN: accepts only Nth PID of matching type from source +\c \#P4CC=VAL: accepts only PIDs with property matching VAL. +\c \#PName=VAL: same as above, using the built-in name corresponding to the property. +\c \#P4CC-VAL: accepts only PIDs with property strictly less than VAL (only for 1-dimension number properties). +\c \#P4CC+VAL: accepts only PIDs with property strictly greater than VAL (only for 1-dimension number properties). +\return error code if any +*/ +GF_Err gf_filter_set_source(GF_Filter *filter, GF_Filter *link_from, const char *link_ext); + +/*! Similar to \ref gf_filter_set_source + This should be used on source filters when the calling filter is expected to have more possible sources added in the future, thereby dynamically changing its source IDs. + Typically the compositor running the GUI is one such filter. This variant will make sure the sourceID used in the adaptation chain is always the ID of the source and not the source ID of the calling filter +\param filter the target filter +\param link_from the filter to link from +\param link_ext any link extensions allowed in link syntax: +\return error code if any +*/ +GF_Err gf_filter_set_source_restricted(GF_Filter *filter, GF_Filter *link_from, const char *link_ext); + +/*! Explicitly reset sourceID of a filter. This shall be called before connecting the filter (eg creating PIDs). + + This is mostly used to reset a source ID of a filter created from a destination (e.g. dasher creating muxers from the MPD URL) where the destination arguments could have sourceIDs specified/ + +\param filter the target filter +*/ +void gf_filter_reset_source(GF_Filter *filter); + +/*! Explicitly assigns an ID to a filter. This shall be called before running the session, and cannot be called on a filter with ID assign +\param filter the target filter +\param filter_id the ID to assign. If NULL, a dynmic ID is generated +\return error code if any +*/ +GF_Err gf_filter_assign_id(GF_Filter *filter, const char *filter_id); + +/*! Gets the ID of a filter +\param filter the target filter +\return ID of the filter, NULL if not defined +*/ +const char *gf_filter_get_id(GF_Filter *filter); + +/*! Indicates the filter is a likely to block for a long time (typically, network IO). +In multithread mode, this prevents the filter to be scheduled on the main thread, blocking video or audio output. +By default, all filters are created as non-blocking. + +\param filter the target filter +\param is_blocking if GF_TRUE, indicates the filter is likely to block +*/ +void gf_filter_set_blocking(GF_Filter *filter, Bool is_blocking); + +/*! Overrides the filter caps with new caps for this instance. Typically used when an option of the filter changes the capabilities + +The new caps are only taken into account for future graph resolutions, any current link from/to the target filter will not be re-solved when calling this function. + +\param filter the target filter +\param caps the new set of capabilities to use for the filter. These are NOT copied and shall be valid for the lifetime of the filter +\param nb_caps number of capabilities set +\return error code if any +*/ +GF_Err gf_filter_override_caps(GF_Filter *filter, const GF_FilterCapability *caps, u32 nb_caps ); + +/*! Indicates this filter acts as a sink and will be used as such in graph resolution. Such filters must provide a use_alias function in their registry. +\param filter the target filter +\return error code if any +*/ +GF_Err gf_filter_act_as_sink(GF_Filter *filter); + +/*! Filter session separator set query*/ +typedef enum +{ + /*! queries the character code used to separate between filter arguments*/ + GF_FS_SEP_ARGS=0, + /*! queries the character code used to separate between argument name and value*/ + GF_FS_SEP_NAME, + /*! queries the character code used to indicate fragment identifiers (source PIDs, PID properties)*/ + GF_FS_SEP_FRAG, + /*! queries the character code used to separate items in a list*/ + GF_FS_SEP_LIST, + /*! queries the character code used to negate values*/ + GF_FS_SEP_NEG, +} GF_FilterSessionSepType; + +/*! Queries the character code used as a given separator type in argument names. Used for formatting arguments when loading sources and destinations from inside a filter +\param filter the target filter +\param sep_type the separator type to query +\return character code of the separator +*/ +u8 gf_filter_get_sep(GF_Filter *filter, GF_FilterSessionSepType sep_type); + +/*! Queries the arguments of the destination arguments. The first output PID connected to a filter with non NULL args will be used (this is a recursive check until end of chain) +\param filter the target filter +\return the argument string of the destination args +*/ +const char *gf_filter_get_dst_args(GF_Filter *filter); + +/*! Queries the destination name. The first output PID connected to a filter with non NULL args will be used (this is a recursive check until end of chain) +\param filter the target filter +\return the argument string of the destination (SHALL be freed by caller), NULL if none found +*/ +char *gf_filter_get_dst_name(GF_Filter *filter); + +/*! Get the filter arguments. +\param filter the target filter +\return the argument string of the filter +*/ +const char *gf_filter_get_src_args(GF_Filter *filter); + +/*! Sends an event on all input PIDs (downstream) or on all output PIDs (upstream) +\param filter the target filter +\param evt the event to send +\param upstream send the even upstream +*/ +void gf_filter_send_event(GF_Filter *filter, GF_FilterEvent *evt, Bool upstream); + +/*! 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 +\return error if any +*/ +GF_Err gf_filter_reconnect_output(GF_Filter *filter); + + +/*! Indicates that the filter accept and can process events coming from outside the filter chain, typically used by application firing events. + The event is sent on the process_event function with no associated PID. +\param filter the target filter +\param enable_events if GF_TRUE, the filter is considered an event target +\return error if any +*/ +GF_Err gf_filter_set_event_target(GF_Filter *filter, Bool enable_events); + +/*! Looks for a built-in property value marked as informative on a filter on all PIDs (inputs and output) +This is a recursive call on both input and output chain. +There is no guarantee that a queried property will still be valid at the setter side upon returning the call, the setter could have +already reassigned it to NULL. To avoids random behavior, the property returned is reference counted so that it is not +destroyed by the setter while the caller uses it. + +Properties retrieved shall be released using \ref gf_filter_release_property. Failure to do so will cause memory leaks in the program. + +If the propentry pointer references a non-null value, this value will be released using \ref gf_filter_release_property, +so make sure to initialize the pointer to a NULL value on the first call. +This avoid calling \ref gf_filter_release_property after each get_info in the calling code: + +\code{.c} +GF_PropertyEntry *pe=NULL; +const GF_PropertyValue *p; +p = gf_filter_get_info(filter, FOO, &pe); +if (p) { } +p = gf_filter_get_info_str(filter, "BAR", &pe); +if (p) { } +p = gf_filter_pid_get_info(PID, ABCD, &pe); +if (p) { } +p = gf_filter_pid_get_info_str(PID, "MyProp", &pe); +if (p) { } +gf_filter_release_property(pe); +\endcode + + + +\param filter the target filter +\param prop_4cc the code of the built-in property to fetch +\param propentry the property reference object for later release. SHALL not be NULL +\return the property if found NULL otherwise. +*/ +const GF_PropertyValue *gf_filter_get_info(GF_Filter *filter, u32 prop_4cc, GF_PropertyEntry **propentry); + +/*! Looks for a property value on a filter on all PIDs (inputs and output). +This is a recursive call on both input and output chain +Properties retrieved shall be released using \ref gf_filter_release_property. See \ref gf_filter_pid_get_info for more details. +\param filter the target filter +\param prop_name the name of the property to fetch +\param propentry the property reference object for later release. See \ref gf_filter_pid_get_info for more details. +\return the property if found NULL otherwise +*/ +const GF_PropertyValue *gf_filter_get_info_str(GF_Filter *filter, const char *prop_name, GF_PropertyEntry **propentry); + +/*! Release a property previously queried, only used for []_get_info_[] functions. +This is a recursive call on both input and output chain +\param propentry the property reference object to be released +*/ +void gf_filter_release_property(GF_PropertyEntry *propentry); + +/*! Sends a filter argument update +\param filter the target filter +\param target_filter_id if set, the target filter will be changed to the filter with this id if any. otherwise the target filter is the one specified +\param arg_name argument name +\param arg_val argument value +\param propagate_mask propagation flags - 0 means no propagation +*/ +void gf_filter_send_update(GF_Filter *filter, const char *target_filter_id, const char *arg_name, const char *arg_val, GF_EventPropagateType propagate_mask); + + + +/*! Filters may request listening on global events.\n +\warning This is not thread safe, the callback will be called directly upon event firing, your filter has to take care of this + +API of event filter is likely to change any time soon - used for backward compatibility with some old modules +*/ +typedef struct +{ + /*! user data of the listener */ + void *udta; + /*! callback called when an event should be filtered. + \param udta user data of the listener + \param evt the event to be processed + \param consumed_by_compositor indicates the event was already used by the compositor + \return GF_TRUE if the event is to be cancelled, GF_FALSE otherwise + */ + Bool (*on_event)(void *udta, GF_Event *evt, Bool consumed_by_compositor); +} GF_FSEventListener; + +/*! Adds an event listener to the session +\param filter filter object +\param el the event listener to add +\return the error code if any +*/ +GF_Err gf_filter_add_event_listener(GF_Filter *filter, GF_FSEventListener *el); + +/*! Removes an event listener from the session +\param filter filter object +\param el the event listener to add +\return the error code if any +*/ +GF_Err gf_filter_remove_event_listener(GF_Filter *filter, GF_FSEventListener *el); + +/*! Forwards an event to the filter session +\param filter filter object +\param evt the event forwarded +\param consumed if set, indicates the event was already consumed/processed before forwarding +\param skip_user if set, indicates the event should only be dispatched to event listeners. +Otherwise, if a user is assigned to the session, the event is forwarded to the user +\return the error code if any +*/ +Bool gf_filter_forward_gf_event(GF_Filter *filter, GF_Event *evt, Bool consumed, Bool skip_user); + +/*! Forwards an event to the filter session. This is a shortcut for gf_filter_forward_gf_event(session, evt, GF_FALSE, GF_FALSE); +\param filter filter object +\param evt the event forwarded +\return the error code if any +*/ +Bool gf_filter_send_gf_event(GF_Filter *filter, GF_Event *evt); + +/*! Checks if all sink filters in the session have seen end of stream. +\param filter filter object +\return GF_TRUE if all sinks are in end of stream, GF_FALSE otherwise +*/ +Bool gf_filter_all_sinks_done(GF_Filter *filter); + +/*! Gets a filter argument value as string for a given argument name.. +\param filter filter object +\param arg_name name of the filter argument +\param dump buffer in which any formatting of argument value will take place +\return the string value of the argument, or NULL if argument is not found or is invalid +*/ +const char *gf_filter_get_arg_str(GF_Filter *filter, const char *arg_name, char dump[GF_PROP_DUMP_ARG_SIZE]); + +/*! Gets a filter argument value for a given argument name.. +\param filter filter object +\param arg_name name of the filter argument +\param prop filled with the arguments value, do NOT modify +\return GF_TURE if success, GF_FALSE otherwise (argument is not found or is invalid) +*/ +Bool gf_filter_get_arg(GF_Filter *filter, const char *arg_name, GF_PropertyValue *prop); + + +/*! Checks if a given mime type is supported as input in the parent session +\param filter querying filter +\param mime mime type to query +\return GF_TRUE if mime is supported +*/ +Bool gf_filter_is_supported_mime(GF_Filter *filter, const char *mime); + + +/*! Sends UI event from video output into the session. For now only direct callback to global event handler is used, event is not forwarded down the chain +\param filter triggering filter +\param uievt event to send +\return GF_TRUE if event has been cancelled, FALSE otherwise +*/ +Bool gf_filter_ui_event(GF_Filter *filter, GF_Event *uievt); + + +/*! Registers filter as an OpenGL provider. This is only used by sink filters creating a OpenGL context, to avoid creating another context. Filters registered as OpenGL providers will run on the main thread. +\param filter filter providing OpenGL context +\param do_register if TRUE the filter carries a valid gl context, if FALSE the filter no longer carries a valid GL context +*/ +void gf_filter_register_opengl_provider(GF_Filter *filter, Bool do_register); + +/*! Requests OpenGL support for this filter. If no OpenGL providers exist, a default provider will be created (GL context creation on hidden window). Filters requiring OpenGL will run on the main thread. + +\note Filters using OpenGL should not assume any persistent GL state among calls. Other filters using OpenGL might change the OpenGL context state (depth test, viewport, alpha blending, culling, etc...). + +\param filter filter providing OpenGL context +\return error code if any +*/ +GF_Err gf_filter_request_opengl(GF_Filter *filter); + +/*! Sets OpenGL context active for this filter. +\note There may be several OpenGL context created in the filter session, depending on activated filters. A filter using OpenGL must call this function before issuing any OpenGL calls + +\param filter filter asking for OpenGL context activation +\return error code if any +*/ +GF_Err gf_filter_set_active_opengl_context(GF_Filter *filter); + + +/*! Count the number of source filters for the given filter matching the given protocol type. +\param filter filter to inspect +\param protocol_scheme scheme of the protocol to test, without ://, eg "http", "rtp", "https" +\param expand_proto if set to GF_TRUE, any source protocol with a name beginning like protocol_scheme will be matched. For example, use this with "http" to match all http and https schemes. +\param enum_pids user function to enumerate PIDs against which the source should be checked . If not set, all source filters will be checked. +\param udta user data for the callback function +\return the number of source filters matched by protocols +*/ +u32 gf_filter_count_source_by_protocol(GF_Filter *filter, const char *protocol_scheme, Bool expand_proto, GF_FilterPid * (*enum_pids)(void *udta, u32 *idx), void *udta); + +/*! Disables data probing on the given filter. Typically used by filters loading source filters. +\param filter target filter +*/ +void gf_filter_disable_probe(GF_Filter *filter); + +/*! Disables input connection on the filter. This is used by filters acting both as true sources or demux/processing filters depending on their options. +\param filter target filter +*/ +void gf_filter_disable_inputs(GF_Filter *filter); + +/*! Checks if some PIDs are still not connected in the graph originating at filter. This is typically used by filters dynamically loading source filters to make sure all PIDs from the source are connected. + +\note This does not guarantee that no other PID remove or configure will happen later on, this depends on the source type and is unknown by GPAC's filter architecture. +\param filter target filter +\param stop_at_filter check connections until this filter. If NULL, connections are checked until upper (sink) end of graph +\return GF_TRUE if any filter in the path has pending PID connections +*/ +Bool gf_filter_has_pid_connection_pending(GF_Filter *filter, GF_Filter *stop_at_filter); + +/*! checks if the some PID connection tasks are still pending at the session level + +This can be needed by some filters needing to make sure all their inputs are known before starting producing packets. + +\param filter target filter +\return GF_TRUE if some connection tasks are pending, GF_FALSE otherwise +*/ +Bool gf_filter_connections_pending(GF_Filter *filter); + +/*! Disables blocking check for a given filter + +This can be needed by some filters internally managing their blocking state because one of their output is not managed by the filter session. + +\param filter target filter +\param prevent_blocking_enabled if GF_TRUE, filter will still be called even if one of its output is in blocking mode +\return error if any +*/ +GF_Err gf_filter_prevent_blocking(GF_Filter *filter, Bool prevent_blocking_enabled); + +/*! Checks if filter was loaded as part of a link resolution or explicitly loaded by application +\param filter target filter +\return GF_TRUE if filter was loaded for link resolution, GF_FALSE if filter was explicitly loaded by the application +*/ +Bool gf_filter_is_dynamic(GF_Filter *filter); + +/*! Checks if reporting is turned on at session level. +\param filter target filter +\return GF_TRUE if reporting is enabled, GF_FALSE otherwise +*/ +Bool gf_filter_reporting_enabled(GF_Filter *filter); + +/*! Updates filter status string and progress. Should not be called if reporting is turned off at session level. +This allows gathering stats from filter in realtime. The status string can be anything but shall not contain '\n' and +should not contain any source or destination URL except for sources and sinks. +\param filter target filter +\param percent percentage (from 0 to 10000) of operation status. If more than 10000 ignored +\param szStatus string giving a status of the filter +\return error code if any +*/ +GF_Err gf_filter_update_status(GF_Filter *filter, u32 percent, char *szStatus); + +/*! check if session has been scheduled for destruction +\param filter target filter +\return GF_TRUE if session is about to be destroyed +*/ +Bool gf_filter_end_of_session(GF_Filter *filter); + +/*! used by meta-filters (ffmpeg and co) to report used/unused options. This is needed since these filters might not +know the set of available options at initialize() time. +\param filter target filter +\param arg name of the argument not used/found +\param was_found indicate that this option was found +*/ +void gf_filter_report_meta_option(GF_Filter *filter, const char *arg, Bool was_found); + +/*! used by script to set a per-instance description +\param filter target filter +\param new_desc the new description to set +\return error if any +*/ +GF_Err gf_filter_set_description(GF_Filter *filter, const char *new_desc); + +/*! get a per-instance description +\param filter target filter +\return the filter instance description, NULL otherwise +*/ +const char *gf_filter_get_description(GF_Filter *filter); + +/*! used by script to set a per-instance version +\param filter target filter +\param new_version the new version to set +\return error if any +*/ +GF_Err gf_filter_set_version(GF_Filter *filter, const char *new_version); + +/*! get a per-instance version +\param filter target filter +\return the filter instance version, NULL otherwise +*/ +const char *gf_filter_get_version(GF_Filter *filter); + +/*! used by script to set a per-instance author +\param filter target filter +\param new_author the new author to set +\return error if any +*/ +GF_Err gf_filter_set_author(GF_Filter *filter, const char *new_author); + +/*! get a per-instance author +\param filter target filter +\return the filter instance author, NULL otherwise +*/ +const char *gf_filter_get_author(GF_Filter *filter); + +/*! used by script to set a per-instance help +\param filter target filter +\param new_help the new help to set +\return error if any +*/ +GF_Err gf_filter_set_help(GF_Filter *filter, const char *new_help); + +/*! get a per-instance help +\param filter target filter +\return the filter instance help, NULL otherwise +*/ +const char *gf_filter_get_help(GF_Filter *filter); + + +/*! used by script to set a per-instance arguments. The passed array shall have a 0 argument at the end and shall be valid +for the lifetime of the filter (not locally copied) +\param filter target filter +\param new_args the new args to set +\return error if any +*/ +GF_Err gf_filter_define_args(GF_Filter *filter, GF_FilterArgs *new_args); + +/*! get per-instance args +\param filter target filter +\return the filter instance args if any, NULL otherwise +*/ +GF_FilterArgs *gf_filter_get_args(GF_Filter *filter); + +/*! get per-instance caps +\param filter target filter +\param nb_caps set to the number of caps +\return the filter instance caps if any, NULL otherwise +*/ +const GF_FilterCapability *gf_filter_get_caps(GF_Filter *filter, u32 *nb_caps); + +/*! probes mime type of a given block of data (should be beginning of file ) +\param filter target filter +\param data buffer to probe +\param size size of buffer +\return the mime type probed, or NULL if not recognized +*/ +const char *gf_filter_probe_data(GF_Filter *filter, u8 *data, u32 size); + +/*! checks if the given filter is an alias filter created by a multiple sink filter +\param filter target filter +\return GF_TRUE if this is an alias filter created by a multiple sink filter, GF_FALSE otherwise +*/ +Bool gf_filter_is_alias(GF_Filter *filter); + +/*! checks if the given filter is in the chain ending up at parent +\param parent end of filter chain to check +\param filter target filter to check +\return GF_TRUE if filter is present in the parent chain, GF_FALSE otherwise +*/ +Bool gf_filter_in_parent_chain(GF_Filter *parent, GF_Filter *filter); + + +/*! Gets statistics for filter +\param filter filter session +\param stats statistics for filter +\return error code if any +*/ +GF_Err gf_filter_get_stats(GF_Filter *filter, GF_FilterStats *stats); + + +/*! Enumerates default arguments of a filter +\param filter filter session +\param idx the 0-based index of the argument to query +\return the argument definition, or NULL if error +*/ +const GF_FilterArgs *gf_filter_enumerate_args(GF_Filter *filter, u32 idx); + + +/*! Reslves URL against locales settings +\param filter filter +\param service_url URL of service to relocate +\param parent_url parent URL of service +\param out_relocated_url - must be GF_MAX_PATH size +\param out_localized_url - must be GF_MAX_PATH size +\return GF_TRUE if success +*/ +Bool gf_filter_relocate_url(GF_Filter *filter, const char *service_url, const char *parent_url, char *out_relocated_url, char *out_localized_url); + + +/*! Returns the register of a given filter +\param filter target filter +\return the register object, or NULL if error +*/ +const GF_FilterRegister *gf_filter_get_register(GF_Filter *filter); + +/*! Prints all possible connections between filter registries for a loaded filter using \code LOG_APP@LOG_INFO \endcode +\param filter filter object +\param print_fn optional callback function for print, otherwise print to stderr +*/ +void gf_filter_print_all_connections(GF_Filter *filter, void (*print_fn)(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...) ); + +/*! Force a filter to run on main thread. + +\param filter filter object +\param do_tag if GF_TRUE, tags filter to run on main thread, otherwise untags filters + +\warning There shall be at most as many with do_tag=GF_FALSE as there were calls with do_tag=GF_TRUE +*/ +void gf_filter_force_main_thread(GF_Filter *filter, Bool do_tag); + +/*! Check if a filter is a sink, i.e. it has no output capabilities. + \note a filter may have no outputs but still not be a sink due to dynamic nature of filter linking +\param filter target filter +\return GF_TRUE if filter is a sink, GF_FALSE otherwise +*/ +Bool gf_filter_is_sink(GF_Filter *filter); + +/*! Check if a filter is a source, i.e. it has no input capabilities + \note a filter may have no inputs but still not be a source, typically the case for some custom filters loading their sources dynamically +\param filter target filter +\return GF_TRUE if filter is a source, GF_FALSE otherwise +*/ +Bool gf_filter_is_source(GF_Filter *filter); + +/*! Tags a filter in a given subsession, ignored in non-implicit mode. + +If sourceIDs are used on destination filter, subsession and source IDs are ignored. +If filters do not have the same subsession ID, they cannot link to each +If filters do not have the same sourceID, they cannot link to each other except if destination is a sink + +\note In non-implicit mode, subsession tagging must be done through filter option :FS= + +\param filter target filter +\param subsession_id subsession identifier +\param source_id subsession identifier +\return error if any +*/ +GF_Err gf_filter_tag_subsession(GF_Filter *filter, u32 subsession_id, u32 source_id); + +/*! @} */ + + +/*! +\addtogroup fs_pid Filter PID +\ingroup filters_grp +\brief Filter Interconnection + + +A PID is a connection between two filters, holding packets to process. Internally, a PID created by a filter (output PID) is different from an input PID to a filter (configure_pid) +but the API has been designed to hide this, so that most PID functions can be called regardless of the input/output nature of the PID. + +All setters functions (gf_filter_pid_set*) will fail on an input PID. + +The generic design of the architecture is that each filter is free to decide how it handle PIDs and their packets. This implies that the filter session has no clue how +an output PID relates to an input PID (same goes for packets). +Developpers must therefore manually copy PID properties that seem relevant, or more practically copy all properties from input PID to output PID and +reassign output PID properties changed by the filter. + +PIDs can be reconfigured multiple times, even potentially changing caps on the fly. The current architecture does not check for capability matching during reconfigure, it is up to +the filter to do so. + +It is also up to filters to decide how to handle a an input PID removal: remove the output PID immediately, keep it open to flush internal data or keep generating data on the output. +The usual practice is to remove the output as soon as the input is removed. + +Once an input PID has been notified to be removed, it shall no longer be used by the filter, as it may be discarded/freed (PID are NOT reference counted). + +@{ + */ + + + +/*! Creates a new output PID for the filter. If the filter has a single input PID already connected, the PID properties are copied by default +\param filter the target filter +\return the new output PID +*/ +GF_FilterPid *gf_filter_pid_new(GF_Filter *filter); + +/*! Removes an output PID from the filter. This will trigger removal of the PID upward in the chain +\param PID the target filter PID to remove +*/ +void gf_filter_pid_remove(GF_FilterPid *PID); + +/*! Creates an output PID for a raw input filter (file, sockets, pipe, etc). This will assign file name, local name, mime and extension properties to the created PID +\param filter the target filter +\param url URL of the source, SHALL be set +\param local_file path on local host, can be NULL +\param mime_type mime type of the content. If none provided and propbe_data is not null, the data will be probed for mime type resolution +\param fext file extension of the content. If NULL, will be extracted from URL +\param probe_data data of the stream to probe in order to solve its mime type +\param probe_size size of the probe data +\param trust_mime if set and mime_type is set, disables data probing +\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_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); + +/*! 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. +There cannot be two instances of a property a given type/name: +- If a property with same type/name exists and has the same value, assignment will be skipped: this avoids triggering PID reconfiguration when not needed. In that case, if the property contains memory to be passed to the filter session, this memory will be destroyed (eg GF_PROP_STRING_NO_COPY, GF_PROP_DATA_NO_COPY, GF_PROP_STRING_LIST). +- If the values differ, the property will be reassigned. There cannot be tow instances of a proerty value with a given type/name. + +Warning: changing a property at the final end of stream (i.e. if no more packets are sent) will have no effect. You must use \ref gf_filter_pid_set_info and \ref gf_filter_pid_get_info for this. + +\param PID the target filter PID +\param prop_4cc the built-in property code to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_property(GF_FilterPid *PID, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Sets a new property on an output PID - see \ref gf_filter_pid_set_property. +\param PID the target filter PID +\param name the name of the property to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_property_str(GF_FilterPid *PID, const char *name, const GF_PropertyValue *value); + +/*! Sets a new property on an output PID - see \ref gf_filter_pid_set_property. +\param PID the target filter PID +\param name the name of the property to modify. The name will be copied to the property, and memory destruction performed by the filter session +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +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. +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. + +If an info property with same type/name exists and has the same value, assignment will be skipped. In that case, if the property contains memory to be passed to the filter session, this memory will be destroyed (eg GF_PROP_STRING_NO_COPY, GF_PROP_DATA_NO_COPY, GF_PROP_STRING_LIST). + + +\param PID the target filter PID +\param prop_4cc the built-in property code to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_info(GF_FilterPid *PID, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Sets a new info property on an output PID - see \ref gf_filter_pid_set_info +See \ref gf_filter_pid_set_info +\param PID the target filter PID +\param name the name of the property to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_info_str(GF_FilterPid *PID, const char *name, const GF_PropertyValue *value); + +/*! Sets a new property on an output PID. +See \ref gf_filter_pid_set_info +\param PID the target filter PID +\param name the name of the property to modify. The name will be copied to the property, and memory destruction performed by the filter session +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_info_dyn(GF_FilterPid *PID, char *name, const GF_PropertyValue *value); + +/*! Sets user data pointer for a PID - see \ref gf_filter_pid_set_info +\param PID the target filter PID +\param udta user data pointer +*/ +void gf_filter_pid_set_udta(GF_FilterPid *PID, void *udta); + +/*! Gets user data pointer for a PID +\param PID the target filter PID +\return udta user data pointer +*/ +void *gf_filter_pid_get_udta(GF_FilterPid *PID); + +/*! get user 32-bits flags +\param PID the target filter PID +\return flags +*/ +u32 gf_filter_pid_get_udta_flags(GF_FilterPid *PID); + +/*! set user 32-bits flags +\param PID the target filter PID +\param flags the flags (replaces the entire flags)) +\return error if any +*/ +GF_Err gf_filter_pid_set_udta_flags(GF_FilterPid *PID, u32 flags); + +/*! Gets PID name. Mostly used for logging purposes +\param PID the target filter PID +\param name the new PID name. function ignored if NULL. +*/ +void gf_filter_pid_set_name(GF_FilterPid *PID, const char *name); + +/*! Gets PID name +\param PID the target filter PID +\return PID name +*/ +const char *gf_filter_pid_get_name(GF_FilterPid *PID); + +/*! Gets filter name of input PID +\param PID the target filter PID +\return name of the filter owning this input PID +*/ +const char *gf_filter_pid_get_filter_name(GF_FilterPid *PID); + +/*! Gets the source arguments of the PID, walking down the chain until the source filter +\param PID the target filter PID +\param for_unicity if GF_TRUE, will return the arguments of the first filter responsible for a fan-out leading to this PID +\return argument of the source filter +*/ +const char *gf_filter_pid_orig_src_args(GF_FilterPid *PID, Bool for_unicity); + +/*! Gets the source filter name or class name for the PID, walking down the chain until the source filter (only the first input PID of each filter is used). +\param PID the target filter PID +\return argument of the source filter +*/ +const char *gf_filter_pid_get_source_filter_name(GF_FilterPid *PID); + +/*! Gets the arguments for the filter +\param PID the target filter PID +\return arguments of the source filter +*/ +const char *gf_filter_pid_get_args(GF_FilterPid *PID); + +/*! Sets max buffer requirement of an output PID. Typically used by audio to make sure several packets can be dispatched on a PID +that would otherwise block after one packet +\param PID the target filter PID +\param total_duration_us buffer max occupancy in us +*/ +void gf_filter_pid_set_max_buffer(GF_FilterPid *PID, u32 total_duration_us); + +/*! Returns max buffer requirement of a PID. +\param PID the target filter PID +\return buffer max in us +*/ +u32 gf_filter_pid_get_max_buffer(GF_FilterPid *PID); + +/*! Checks if a given filter is in the PID parent chain. This is used to identify sources (rather than checking URL/...) +\param PID the target filter PID +\param filter the source filter to check +\return GF_TRUE if filter is a source for that PID, GF_FALSE otherwise +*/ +Bool gf_filter_pid_is_filter_in_parents(GF_FilterPid *PID, GF_Filter *filter); + +/*! Checks if a given PID has a common filter with another PID in the parent graph +\param PID the target filter PID +\param other_pid the other PID to check +\return GF_TRUE if a filter is found that is outputing these two PIDs, GF_FALSE otherwise +*/ +Bool gf_filter_pid_share_origin(GF_FilterPid *PID, GF_FilterPid *other_pid); + +/*! Gets current buffer levels of the PID +\param PID the target filter PID +\param max_units maximum number of packets allowed - can be 0 if buffer is measured in time +\param nb_pck number of packets in PID +\param max_duration maximum buffer duration allowed in us - can be 0 if buffer is measured in units +\param duration buffer duration in us +\return GF_TRUE if normal buffer query, GF_FALSE if final session flush, in which case buffer might never complete +*/ +Bool gf_filter_pid_get_buffer_occupancy(GF_FilterPid *PID, u32 *max_units, u32 *nb_pck, u32 *max_duration, u32 *duration); + +/*! Sets loose connect for a PID, avoiding to throw an error if connection of the PID fails. Used by the compositor filter which acts as both sink and filter. +\param PID the target filter PID +*/ +void gf_filter_pid_set_loose_connect(GF_FilterPid *PID); + +/*! Adds PID properties from textual description - this does not reset the PID properties +\param PID the target filter PID +\param args one or more serialized properties to set, as documented in gpac -h doc +\param direct_merge if true, next packet to be sent will not trigger a property change +\param use_default_seps if GF_TRUE, the serialized properties are using the default separator set, otherwise they are using the current separator set of the session +\return Error if any +*/ +GF_Err gf_filter_pid_push_properties(GF_FilterPid *PID, char *args, Bool direct_merge, Bool use_default_seps); + + +/*! Negotiate a given property on an input PID for built-in properties +Filters may accept some PID connection but may need an adaptaion chain to be able to process packets, eg change pixel format or sample rate +This function will trigger a reconfiguration of the filter chain to try to adapt this. If failing, the filter chain will disconnect +This process is asynchronous, the filter asking for a PID negociation will see the notification through a pid_reconfigure if success. +\param PID the target filter PID - this MUST be an input PID +\param prop_4cc the built-in property code to negotiate +\param value the new value to negotiate, SHALL NOT be NULL +\return error code if any +*/ +GF_Err gf_filter_pid_negociate_property(GF_FilterPid *PID, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Negotiate a given property on an input PID for regular properties +see \ref gf_filter_pid_negociate_property +\param PID the target filter PID - this MUST be an input PID +\param name name of the property to negotiate +\param value the new value to negotiate, SHALL NOT be NULL +\return error code if any +*/ +GF_Err gf_filter_pid_negociate_property_str(GF_FilterPid *PID, const char *name, const GF_PropertyValue *value); + +/*! Negotiate a given property on an input PID for regular properties +see \ref gf_filter_pid_negociate_property +\param PID the target filter PID - this MUST be an input PID +\param name the name of the property to modify. The name will be copied to the property, and memory destruction performed by the filter session +\param value the new value to negotiate, SHALL NOT be NULL +\return error code if any +*/ +GF_Err gf_filter_pid_negociate_property_dyn(GF_FilterPid *PID, char *name, const GF_PropertyValue *value); + +/*! Queries a negotiated built-in capability on an output PID +Filters may check if a property negotiation was done on an output PID, and check the property value. +This can be done on an output PID in a filter->reconfigure_output if the filter accepts caps negotiation +This can be done on an input PID in a generic reconfigure_pid + +\param PID the target filter PID +\param prop_4cc the built-in property code to negotiate +\return the negociated property value +*/ +const GF_PropertyValue *gf_filter_pid_caps_query(GF_FilterPid *PID, u32 prop_4cc); + +/*! Queries a negotiated capability on an output PID - see \ref gf_filter_pid_caps_query +\param PID the target filter PID +\param prop_name the property name to negotiate +\return the negociated property value +*/ +const GF_PropertyValue *gf_filter_pid_caps_query_str(GF_FilterPid *PID, const char *prop_name); + +/*! Statistics for PID*/ +typedef struct +{ + /*! if set, indicates the PID is disconnected and stats are not valid*/ + u32 disconnected; + /*! average process rate on that PID in bits per seconds*/ + u32 average_process_rate; + /*! max process rate on that PID in bits per seconds*/ + u32 max_process_rate; + /*! average bitrate for that PID*/ + u32 avgerage_bitrate; + /*! max bitrate for that PID*/ + u32 max_bitrate; + /*! number of packets processed on that PID*/ + u32 nb_processed; + /*! max packet process time of the filter in us*/ + u32 max_process_time; + /*! total process time of the filter in us*/ + u64 total_process_time; + /*! process time of first packet of the filter in us*/ + u64 first_process_time; + /*! process time of the last packet on that PID in us*/ + u64 last_process_time; + /*! minimum frame duration on that PID in us*/ + u32 min_frame_dur; + /*! number of saps 1/2/3 on that PID*/ + u32 nb_saps; + /*! max process time of SAP packets on that PID in us*/ + u32 max_sap_process_time; + /*! total process time of SAP packets on that PID in us*/ + u64 total_sap_process_time; + + /*! max buffer time in us - only set when querying decoder stats*/ + u64 max_buffer_time; + /*! max playout buffer time in us - only set when querying decoder stats*/ + u64 max_playout_time; + /*! min playout buffer time in us - only set when querying decoder stats*/ + u64 min_playout_time; + /*! current buffer time in us - only set when querying decoder stats*/ + u64 buffer_time; + /*! number of units in input buffer of the filter - only set when querying decoder stats*/ + u32 nb_buffer_units; +} GF_FilterPidStatistics; + +/*! Direction for stats querying*/ +typedef enum +{ + /*! statistics are fetched on the current PID's parent filter. If the PID is an output PID, the statistics are fetched on all the destinations for that PID*/ + GF_STATS_LOCAL = 0, + /*! statistics are fetched on the current PID's parent filter. The statistics are fetched on all input of the parent filter*/ + GF_STATS_LOCAL_INPUTS, + /*! statistics are fetched on all inputs of the next decoder filter up the chain (towards the sink)*/ + GF_STATS_DECODER_SINK, + /*! statistics are fetched on all inputs of the previous decoder filter down the chain (towards the source)*/ + GF_STATS_DECODER_SOURCE, + /*! statistics are fetched on all inputs of the next encoder filter up the chain (towards the sink)*/ + GF_STATS_ENCODER_SINK, + /*! statistics are fetched on all inputs of the previous encoder filter down the chain (towards the source)*/ + GF_STATS_ENCODER_SOURCE +} GF_FilterPidStatsLocation; + +/*! Gets statistics for the PID +\param PID the target filter PID +\param stats the retrieved statistics +\param location indicates where to locate the filter to query stats on it. +\return error code if any +*/ +GF_Err gf_filter_pid_get_statistics(GF_FilterPid *PID, GF_FilterPidStatistics *stats, GF_FilterPidStatsLocation location); + +/*! Resets current properties of the PID +\param PID the target filter PID +\return error code if any +*/ +GF_Err gf_filter_pid_reset_properties(GF_FilterPid *PID); + + +/*! Function protoype for filtering properties. +\param cbk callback data +\param prop_4cc the built-in property code +\param prop_name property name +\param src_prop the property value in the source packet +\return GF_TRUE if the property shall be merged, GF_FALSE otherwise +*/ +typedef Bool (*gf_filter_prop_filter)(void *cbk, u32 prop_4cc, const char *prop_name, const GF_PropertyValue *src_prop); + +/*! Push a new set of properties on destination PID using all properties from source PID. Old properties in destination will be lost (i.e. reset properties is always performed during copy properties) +\param dst_pid the destination filter PID +\param src_pid the source filter PID +\return error code if any +*/ +GF_Err gf_filter_pid_copy_properties(GF_FilterPid *dst_pid, GF_FilterPid *src_pid); + +/*! Push a new set of properties on destination PID, using all properties from source PID, potentially filtering them. Currently defined properties are not reseted. +\param dst_pid the destination filter PID +\param src_pid the source filter PID +\param filter_prop callback filtering function +\param cbk callback data passed to the callback function +\return error code if any +*/ +GF_Err gf_filter_pid_merge_properties(GF_FilterPid *dst_pid, GF_FilterPid *src_pid, gf_filter_prop_filter filter_prop, void *cbk ); + +/*! Gets a built-in property of the PID +Warning: properties are only valid until the next configure_pid is called. Attempting to use a property +value (either the pointer or one of the value) queried before the current configure_pid will result in + unpredictable behavior, potentially crashes. +\param PID the target filter PID +\param prop_4cc the code of the built-in property to retrieve +\return the property if found or NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_property(GF_FilterPid *PID, u32 prop_4cc); + +/*! Gets a property of the PID - see \ref gf_filter_pid_get_property +\param PID the target filter PID +\param prop_name the name of the property to retrieve +\return the property if found or NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_property_str(GF_FilterPid *PID, const char *prop_name); + +/*! Enumerates properties of a PID - see \ref gf_filter_pid_get_property +\param PID the target filter PID +\param idx input/output index of the current property. 0 means first. Incremented by 1 upon success +\param prop_4cc set to the built-in code of the property if built-in +\param prop_name set to the name of the property if not built-in +\return the property if found or NULL otherwise (end of enumeration) +*/ +const GF_PropertyValue *gf_filter_pid_enum_properties(GF_FilterPid *PID, u32 *idx, u32 *prop_4cc, const char **prop_name); + +/*! Enumerates info of a PID +\param PID the target filter PID +\param idx input/output index of the current info. 0 means first. Incremented by 1 upon success +\param prop_4cc set to the built-in code of the info if built-in +\param prop_name set to the name of the info if not built-in +\return the property if found or NULL otherwise (end of enumeration) +*/ +const GF_PropertyValue *gf_filter_pid_enum_info(GF_FilterPid *PID, u32 *idx, u32 *prop_4cc, const char **prop_name); + +/*! Sets PID framing mode. filters can consume packets as they arrive, or may want to only process full frames/files +\param PID the target filter PID +\param requires_full_blocks if GF_TRUE, the packets on the PID will be reaggregated to form complete frame/files. +\return error code if any +*/ +GF_Err gf_filter_pid_set_framing_mode(GF_FilterPid *PID, Bool requires_full_blocks); + +/*! Gets cumulated buffer duration of PID (recursive until source) +\param PID the target filter PID +\param check_pid_full if GF_TRUE, returns 0 if the PID buffer is not yet full +\return the duration in us, or -1 if session is in final flush +*/ +u64 gf_filter_pid_query_buffer_duration(GF_FilterPid *PID, Bool check_pid_full); + +/*! Try to force a synchronous flush of the filter chain downwards this PID. If refetching a packet returns NULL, this failed. +\param PID the target filter PID +*/ +void gf_filter_pid_try_pull(GF_FilterPid *PID); + + +/*! Looks for a built-in property value on a PIDs. This is a recursive call on input chain +Info query is NOT threadsafe in gpac, you +Properties retrieved shall be released using \ref gf_filter_release_property. See \ref gf_filter_pid_get_info for more details. +\param PID the target filter PID to query +\param prop_4cc the code of the built-in property to fetch +\param propentry the property reference object for later release. See \ref gf_filter_pid_get_info for more details. +\return the property if found NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_info(GF_FilterPid *PID, u32 prop_4cc, GF_PropertyEntry **propentry); + +/*! Looks for a property value on a PIDs. This is a recursive call on both input and output chain +Properties retrieved shall be released using \ref gf_filter_release_property. See \ref gf_filter_pid_get_info for more details. +\param PID the target filter PID to query +\param prop_name the name of the property to fetch +\param propentry the property reference object for later release. See \ref gf_filter_pid_get_info for more details. +\return the property if found NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_info_str(GF_FilterPid *PID, const char *prop_name, GF_PropertyEntry **propentry); + + +/*! Signals end of stream on a PID. Each filter needs to call this when EOS is reached on a given stream since there is no explicit link between input PIDs and output PIDs +\param PID the target filter PID +*/ +void gf_filter_pid_set_eos(GF_FilterPid *PID); + +/*! Checks for end of stream has been signaled a PID input chain. +This is a recursive call on input chain. The function is typically used to abort buffering or synchronisation init in muxers. +\param PID the target filter PID +\return GF_TRUE if end of stream was signaled on the input chain +*/ +Bool gf_filter_pid_has_seen_eos(GF_FilterPid *PID); + +/*! Checks for end of stream has been signaled a PID. Contrary to \ref gf_filter_pid_has_seen_eos this is not a recursive call and only checks the given pid. +\param PID the target filter PID +\return GF_TRUE if end of stream was signaled for that pid (there may be pending packets in queue) +*/ +Bool gf_filter_pid_eos_received(GF_FilterPid *PID); + +/*! Checks for end of stream signaling on a PID. +\param PID the target filter PID +\return GF_TRUE if end of stream is set on that PID (no more packet in queue) +*/ +Bool gf_filter_pid_is_eos(GF_FilterPid *PID); + +/*! Checks if there is a packet ready on an input PID. +\param PID the target filter PID +\return GF_TRUE if no packet in buffers +*/ +Bool gf_filter_pid_first_packet_is_empty(GF_FilterPid *PID); + +/*! Gets the first packet in the input PID buffer. +This may trigger a reconfigure signal on the filter. If reconfigure is not OK, returns NULL and the PID passed to the filter NO LONGER EXISTS (implicit remove) +The packet is still present in the PID buffer until explicitly removed by \ref gf_filter_pid_drop_packet +\param PID the target filter PID +\return packet or NULL of empty or reconfigure error +*/ +GF_FilterPacket * gf_filter_pid_get_packet(GF_FilterPid *PID); + +/*! Fetches the CTS of the first packet in the input PID buffer. +\param PID the target filter PID +\param cts set to the composition time of the first packet, in PID timescale +\return GF_TRUE if cts was fetched, GF_FALSE otherwise +*/ +Bool gf_filter_pid_get_first_packet_cts(GF_FilterPid *PID, u64 *cts); + +/*! Drops the first packet in the input PID buffer. +\param PID the target filter PID +*/ +void gf_filter_pid_drop_packet(GF_FilterPid *PID); + +/*! Gets the number of packets in input PID buffer. +\param PID the target filter PID +\return the number of packets +*/ +u32 gf_filter_pid_get_packet_count(GF_FilterPid *PID); + +/*! Checks the capability of the input PID match its destination filter. +\param PID the target filter PID +\return GF_TRUE if match , GF_FALSE otherwise +*/ +Bool gf_filter_pid_check_caps(GF_FilterPid *PID); + +/*! Checks if the PID would enter a blocking state if a new packet is sent. +This function should be called by eg demuxers to regulate the rate at which they send packets + +\note PIDs are never fully blocking in GPAC, a filter requesting an output packet should usually get one unless something goes wrong +\param PID the target filter PID +\return GF_TRUE if PID would enter blocking state , GF_FALSE otherwise +*/ +Bool gf_filter_pid_would_block(GF_FilterPid *PID); + +/*! Shortcut to access the timescale of the PID - faster than get property as the timescale is locally cached for buffer management +\param PID the target filter PID +\return the PID timescale +*/ +u32 gf_filter_pid_get_timescale(GF_FilterPid *PID); + +/*! Clears the end of stream flag on a PID. +\note The end of stream is automatically cleared when a new packet is dispatched; This function is used to clear it asap, before next packet dispacth (period switch in dash for example). +\param PID the target filter PID +\param all_pids if sets, clear end oof stream for all PIDs coming from the same filter as the target PID +*/ +void gf_filter_pid_clear_eos(GF_FilterPid *PID, Bool all_pids); + +/*! Indicates how clock references (PCR of MPEG-2) should be handled. +By default these references are passed from input packets to output packets by the filter session (this assumes the filter doesn't modify composition timestamps). +This default can be changed with this function. +\param PID the target filter PID +\param filter_in_charge if set to GF_TRUE, clock references are not forwarded by the filter session and the filter is in charge of handling them +*/ +void gf_filter_pid_set_clock_mode(GF_FilterPid *PID, Bool filter_in_charge); + +/*! Resolves file template using PID properties and file index. Templates follow the DASH mechanism: + +- $KEYWORD$ or $KEYWORD%0nd$ are replaced in the template with the resolved value, + +- $$ is an escape for $ + +Supported KEYWORD (case insensitive): +- num: replaced by file_number (usually matches GF_PROP_PCK_FILENUM, but this property is not used in the solving mechanism) +- PID: ID of the source PID +- URL: URL of source file +- File: path on disk for source file +- p4cc=ABCD: uses PID property with 4CC ABCD +- pname=VAL: uses PID property with name VAL (either built-in prop name or other peroperty name) + +\param PID the target filter PID +\param szTemplate source template to solve +\param szFinalName buffer for final name +\param file_number number of file to use +\param file_suffix if not null, will be appended after the value of the §File$ keyword if present +\return error if any +*/ +GF_Err gf_filter_pid_resolve_file_template(GF_FilterPid *PID, char szTemplate[GF_MAX_PATH], char szFinalName[GF_MAX_PATH], u32 file_number, const char *file_suffix); + + +/*! Same as \ref gf_filter_pid_resolve_file_template but overrides file name with given name +\param PID the target filter PID +\param szTemplate source template to solve +\param szFinalName buffer for final name +\param file_number number of file to use +\param file_suffix if not null, will be appended after the value of the §File$ keyword if present +\param file_name if not null, will be used instead of PID URL or local path +\return error if any +*/ +GF_Err gf_filter_pid_resolve_file_template_ex(GF_FilterPid *PID, char szTemplate[GF_MAX_PATH], char szFinalName[GF_MAX_PATH], u32 file_number, const char *file_suffix, const char *file_name); + + +/*! Sets discard mode on or off on an input PID. When discard is on, all input packets for this PID are no longer dispatched. + +This only affect the current PID, not the source filter(s) for that PID. + +PID reconfigurations are still forwarded to the filter, so that a filter may decide to re-enable regular mode. Packets sent after a PID reconfiguration are kept until the PID is reconfigured, and discarded if the PID is still in discard mode. + +This is typically needed for filters that stop consuming data for a while (dash forced period duration for example) but may resume +consumption later on (stream moving from period 1 to period 2 for example). + +\param PID the target filter PID +\param discard_on enables/disables discard +\return error if any +*/ +GF_Err gf_filter_pid_set_discard(GF_FilterPid *PID, Bool discard_on); + +/*! Discard blocking mode for PID on end of stream. The filter is blocked when all output PIDs are in end of stream, this function unblocks the filter. +This can be needed for playlist type filters dispatching end of stream at the end of each file but setting up next file in +the following process() call. + +\param PID the target filter PID +*/ +void gf_filter_pid_discard_block(GF_FilterPid *PID); + +/*! Gets URL argument of first destination of PID if any - memory shall be freed by caller. +\param PID the target filter PID +\return destination URL string or NULL if error +*/ +char *gf_filter_pid_get_destination(GF_FilterPid *PID); + +/*! Gets URL argument of first source of PID if any - memory shall be freed by caller. +\param PID the target filter PID +\return source URL string or NULL if error +*/ +char *gf_filter_pid_get_source(GF_FilterPid *PID); + +/*! Indicates that this output PID requires a sourceID on the destination filter to be present. This prevents trying to link to other filters with no source IDs but +accepting the PID +\param PID the target filter PID +\return error code if any +*/ +GF_Err gf_filter_pid_require_source_id(GF_FilterPid *PID); + +/*! Enables decoding time reconstruction on PID for packets with DTS not set. If not enabled (default), dts not set implies dts = cts +\param PID the target filter PID +\param do_recompute if set, dts will be recomputed when not set +*/ +void gf_filter_pid_recompute_dts(GF_FilterPid *PID, Bool do_recompute); + +/*! Queries minimum packet duration as computed from DTS/CTS info on the PID +\param PID the target filter PID +\return minimum packet duration computed +*/ +u32 gf_filter_pid_get_min_pck_duration(GF_FilterPid *PID); + + +/*! Sends an event down the filter chain for input PID, or up the filter chain for output PIDs. +\param PID the target filter PID +\param evt the event to send +*/ +void gf_filter_pid_send_event(GF_FilterPid *PID, GF_FilterEvent *evt); + + +/*! Helper function to init play events, that checks the PID \ref GF_FilterPidPlaybackMode and adjust start/speed accordingly. This does not send the event. +\param PID the target filter PID +\param evt the event to initialize +\param start playback start time of request +\param speed playback speed time of request +\param log_name name used for logs in case of capability mismatched +*/ +void gf_filter_pid_init_play_event(GF_FilterPid *PID, GF_FilterEvent *evt, Double start, Double speed, const char *log_name); + +/*! Check if the given PID is marked as playing or not. +\param PID the target filter PID +\return GF_TRUE if PID is currently playing, GF_FALSE otherwise +*/ +Bool gf_filter_pid_is_playing(GF_FilterPid *PID); + +/*! Enables direct dispatch of packets to connected filters. This mode is useful when a filter may send a very large number of packets +in one process() call; this is for example the case of the isobmff muxer in interleave mode. Using this mode avoids overloading +the PID buffer with packets. +If the session is multi-threaded, this parameter has no effect. +\param PID the target filter PID +\return GF_TRUE if PID is currently playing, GF_FALSE otherwise +*/ +GF_Err gf_filter_pid_allow_direct_dispatch(GF_FilterPid *PID); + +/*! Gets the private stack of the alias filter associated with an input PID, if any + + \note The filter type of the alias is always the type of the filter holding the input PID connection. +\param PID the target filter PID +\return the temporary alias filter private stack, NULL otherwise +*/ +void *gf_filter_pid_get_alias_udta(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 +*/ +GF_Filter *gf_filter_pid_get_source_filter(GF_FilterPid *PID); + +/*! Enumerates the destination filters of an output PID +\param PID the target filter PID +\param idx the target destination index +\return the destination filter for the given index, or NULL if error +*/ +GF_Filter *gf_filter_pid_enum_destinations(GF_FilterPid *PID, u32 idx); + +/*! Ignore this PID in blocking mode estimations. + +This is typically used when a filter consumes N pids, with some at very low frequency for which an empty queue should not imply unblocking the filter to refill the queue. + +\param PID the target filter PID +\param do_ignore if GF_TRUE, the PID will not be considered when trying to unblock the filter +\return error if any +*/ +GF_Err gf_filter_pid_ignore_blocking(GF_FilterPid *PID, Bool do_ignore); + +/*! Gets next estimated time on this PID, ie last_pck(DTS+dur) or last_pck(CTS+dur) + +\param PID the target filter PID +\return GF_FILTER_NO_TS or estimated time +*/ +u64 gf_filter_pid_get_next_ts(GF_FilterPid *PID); + + +/*! @} */ + + +/*! +\addtogroup fs_pck Filter Packet +\ingroup filters_grp +\brief Filter data exchange + + Packets consist in block of data or reference to such blocks, passed from the source to the sink only. +Internally, a packet created by a filter (output packet) is different from an input packet to a filter (\ref gf_filter_pid_get_packet) +but the API has been designed to hide this, so that most packet functions can be called regardless of the input/output nature of the PID. + +Packets have native attributes (timing, sap state, ...) but may also have any number of properties associated to them. + +The generic design of the architecture is that each filter is free to decide how it handle PIDs and their packets. This implies that the filter session has no clue how +an output packet relates to an input packet. +Developpers must therefore manually copy packet properties that seem relevant, or more practically copy all properties from input packet to output packet and +reassign output packet properties changed by the filter. + +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. + +@{ + */ + + +/*! Keeps a reference to the given input packet. The packet shall be released at some point using \ref gf_filter_pck_unref + + Filters keeping reference to packets should check if the packet is a blocking reference using gf_filter_pck_is_blocking_ref. If this is the case, the input chain will likely be blocked until the packet reference is released. +\param pck the target input packet +\return error if any +*/ +GF_Err gf_filter_pck_ref(GF_FilterPacket **pck); + +/*! Same as \ref gf_filter_pck_ref but doesn't use pointer to packet +\param pck the target input packet +\return the new reference to the packet +*/ +GF_FilterPacket *gf_filter_pck_ref_ex(GF_FilterPacket *pck); + +/*! Remove a reference to the given input packet. The packet might be destroyed after that call. +\param pck the target input packet +*/ +void gf_filter_pck_unref(GF_FilterPacket *pck); + +/*! Creates a reference to the packet properties, but not to the data. +This is mostly useful for encoders/decoders/filters with delay, where the input packet needs to be released before getting the corresponding output (frame reordering & co). +This allows merging back packet properties after some delay without blocking the filter chain. +\param pck the target input packet +\return error if any +*/ +GF_Err gf_filter_pck_ref_props(GF_FilterPacket **pck); + + +/*! Allocates a new packet on the output PID with associated allocated data. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data_size the desired size of the packet - can be changed later +\param data set to the writable buffer of the created packet +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_new_alloc(GF_FilterPid *PID, u32 data_size, u8 **data); + + +/*! Allocates a new packet on the output PID referencing internal data. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data the data block to dispatch +\param data_size the size of the data block to dispatch +\param destruct the callback function used to destroy the packet when no longer used - may be NULL +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_new_shared(GF_FilterPid *PID, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct); + +/*! Allocates a new packet on the output PID referencing data of some input packet. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data_offset offset in the source data block +\param data_size the size of the data block to dispatch - if 0, the entire data of the source packet beginning at offset is used +\param source_packet the source packet this data belongs to (at least from the filter point of view). +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_new_ref(GF_FilterPid *PID, u32 data_offset, u32 data_size, GF_FilterPacket *source_packet); + +/*! Allocates a new packet on the output PID with associated allocated data. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data_size the desired size of the packet - can be changed later +\param data set to the writable buffer of the created packet +\param destruct the callback function used to destroy the packet when no longer used - may be NULL +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_new_alloc_destructor(GF_FilterPid *PID, u32 data_size, u8 **data, gf_fsess_packet_destructor destruct); + +/*! Clones a new 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. +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 filter wishing to perform in-place processing of input data. +\param PID the target output PID +\param pck_source the desired source packet to clone +\param data set to the writable buffer of the created packet +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_new_clone(GF_FilterPid *PID, GF_FilterPacket *pck_source, u8 **data); + +/*! Copies a new packet from a source packet and copy all source properties to output. +\param PID the target output PID +\param pck_source the desired source packet to clone +\param data set to the writable buffer of the created packet +\return new packet or NULL if allocation error or not an output PID +*/ +GF_FilterPacket *gf_filter_pck_new_copy(GF_FilterPid *PID, GF_FilterPacket *pck_source, u8 **data); + +/*! Creates a read-only detached copy 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. +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. + +\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_copy(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. +Note that packets created with \ref gf_filter_pck_new_frame_interface are always treated as read-only packets +\param pck the target output packet to send +\return error if any +*/ +GF_Err gf_filter_pck_set_readonly(GF_FilterPacket *pck); + +/*! 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. +\param pck the target output packet to send +\return error if any +*/ +GF_Err gf_filter_pck_send(GF_FilterPacket *pck); + +/*! Destructs a packet allocated but that cannot be sent. Shall not be used on packet references. +\param pck the target output packet to send +*/ +void gf_filter_pck_discard(GF_FilterPacket *pck); + +/*! Destructs a packet allocated but that cannot be sent. Shall not be used on packet references. +This is a shortcut to \ref gf_filter_pck_new_ref + \ref gf_filter_pck_merge_properties + \ref gf_filter_pck_send +\param reference the input packet to forward +\param PID the output PID to forward to +\return error code if any +*/ +GF_Err gf_filter_pck_forward(GF_FilterPacket *reference, GF_FilterPid *PID); + +/*! Gets data associated with the packet. +\param pck the target packet +\param size set to the packet data size +\return packet data if any, NULL if empty or if the packet uses a frame interface object. see \ref gf_filter_pck_get_frame_interface +*/ +const u8 *gf_filter_pck_get_data(GF_FilterPacket *pck, u32 *size); + +/*! Sets a built-in property of a packet +\param pck the target packet +\param prop_4cc the code of the built-in property to set +\param value the property value to set +\return error code if any +*/ +GF_Err gf_filter_pck_set_property(GF_FilterPacket *pck, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Sets a property of a packet +\param pck the target packet +\param name the name of the property to set +\param value the property value to set +\return error code if any +*/ +GF_Err gf_filter_pck_set_property_str(GF_FilterPacket *pck, const char *name, const GF_PropertyValue *value); + +/*! Sets a property of a packet +\param pck the target packet +\param name the code of the property to set. The name will be copied to the property, and memory destruction performed by the filter session +\param value the property value to set +\return error code if any +*/ +GF_Err gf_filter_pck_set_property_dyn(GF_FilterPacket *pck, char *name, const GF_PropertyValue *value); + +/*! Checks if a packet has properties other than packet built-in ones + + This is typically needed to decide whether a packet with no data should be forwarded or not + +\param pck the target packet +\return GF_TRUE if packet has properties, GF_FALSE otherwise +*/ +Bool gf_filter_pck_has_properties(GF_FilterPacket *pck); + +/*! Merge properties of source packet into destination packet but does NOT reset destination packet properties +\param pck_src source packet +\param pck_dst destination packet +\return error code if any +*/ +GF_Err gf_filter_pck_merge_properties(GF_FilterPacket *pck_src, GF_FilterPacket *pck_dst); + +/*! Same as \ref gf_filter_pck_merge_properties but uses a filter callback to select properties to merge +\param pck_src source packet +\param pck_dst destination packet +\param filter_prop callback filtering function +\param cbk callback data passed to the callback function +\return error code if any +*/ +GF_Err gf_filter_pck_merge_properties_filter(GF_FilterPacket *pck_src, GF_FilterPacket *pck_dst, gf_filter_prop_filter filter_prop, void *cbk); + +/*! Gets built-in property of packet. +\param pck target packet +\param prop_4cc the code of the built-in property +\return the property if found, NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pck_get_property(GF_FilterPacket *pck, u32 prop_4cc); + +/*! Gets property of packet. +\param pck target packet +\param prop_name the name of the property +\return the property if found, NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pck_get_property_str(GF_FilterPacket *pck, const char *prop_name); + +/*! Enumerates properties on packets. +\param pck target packet +\param idx input/output index of the current property. 0 means first. Incremented by 1 upon success +\param prop_4cc set to the code of the built-in property +\param prop_name set to the name of the property +\return the property if found, NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pck_enum_properties(GF_FilterPacket *pck, u32 *idx, u32 *prop_4cc, const char **prop_name); + + +/*! Sets packet framing info. A full frame is a complete entity for the stream type, ie an access unit for media streams and a complete file for file streams +\param pck target packet +\param is_start packet is start of the frame +\param is_end packet is end of the frame +\return error code if any +*/ +GF_Err gf_filter_pck_set_framing(GF_FilterPacket *pck, Bool is_start, Bool is_end); + +/*! Gets packet framing info. A full frame is a complete entity for the stream type, ie an access unit for media streams and a complete file for file streams +\param pck target packet +\param is_start set to GF_TRUE if packet is start of the frame, to GF_FALSE otherwise +\param is_end set to GF_TRUE if packet is end of the frame, to GF_FALSE otherwise +\return error code if any +*/ +GF_Err gf_filter_pck_get_framing(GF_FilterPacket *pck, Bool *is_start, Bool *is_end); + +/*! Sets Decoding Timestamp (DTS) of the packet. Do not set if unknown - automatic packet duration is based on DTS diff if DTS is present, otherwise in CTS diff. +\param pck target packet +\param dts decoding timestamp of packet, in PID timescale units +\return error code if any +*/ +GF_Err gf_filter_pck_set_dts(GF_FilterPacket *pck, u64 dts); + +/*! Gets Decoding Timestamp (DTS) of the packet. +\param pck target packet +\return dts decoding timestamp of packet, in PID timescale units +*/ +u64 gf_filter_pck_get_dts(GF_FilterPacket *pck); + +/*! Sets Composition Timestamp (CTS) of the packet. Do not set if unknown - automatic packet duration is based on DTS diff if DTS is present, otherwise in CTS diff. +\param pck target packet +\param cts composition timestamp of packet, in PID timescale units +\return error code if any +*/ +GF_Err gf_filter_pck_set_cts(GF_FilterPacket *pck, u64 cts); + +/*! Gets Composition Timestamp (CTS) of the packet. +\param pck target packet +\return cts composition timestamp of packet, in PID timescale units +*/ +u64 gf_filter_pck_get_cts(GF_FilterPacket *pck); + +/*! Returns packet timescale (same as PID timescale) +\param pck target packet +\return packet timescale +*/ +u32 gf_filter_pck_get_timescale(GF_FilterPacket *pck); + +/*! Sets packet duration +\param pck target packet +\param duration duration of packet, in PID timescale units +\return error code if any +*/ +GF_Err gf_filter_pck_set_duration(GF_FilterPacket *pck, u32 duration); + +/*! Gets packet duration +\param pck target packet +\return duration duration of packet, in PID timescale units +*/ +u32 gf_filter_pck_get_duration(GF_FilterPacket *pck); + +/*! reallocates packet not yet sent. Returns data start and new range of data. This will reset byte offset information to not available. +\param pck target packet +\param nb_bytes_to_add bytes to add to packet +\param data_start realloc pointer of packet data start - may be NULL if new_range_start is set +\param new_range_start pointer to new (apppended space) data - may be NULL if data_start is set +\param new_size full size of allocated block. - may be be NULL +\return error code if any +*/ +GF_Err gf_filter_pck_expand(GF_FilterPacket *pck, u32 nb_bytes_to_add, u8 **data_start, u8 **new_range_start, u32 *new_size); + +/*! Truncates packet not yet sent to given size +\param pck target packet +\param size new size to truncate to +\return error code if any +*/ +GF_Err gf_filter_pck_truncate(GF_FilterPacket *pck, u32 size); + +/*! SAP types as defined in annex I of ISOBMFF */ +typedef enum +{ + /*! no SAP */ + GF_FILTER_SAP_NONE = 0, + /*! closed gop no leading */ + GF_FILTER_SAP_1, + /*! closed gop leading */ + GF_FILTER_SAP_2, + /*! open gop */ + GF_FILTER_SAP_3, + /*! roll period (GDR or audio roll) - roll distance must be indicated in packet */ + GF_FILTER_SAP_4, + /*! Audio preroll period - roll distance must be indicated in packet */ + GF_FILTER_SAP_4_PROL +} GF_FilterSAPType; + +/*! Sets packet SAP type +\param pck target packet +\param sap_type SAP type of the packet +\return error code if any +*/ +GF_Err gf_filter_pck_set_sap(GF_FilterPacket *pck, GF_FilterSAPType sap_type); + +/*! Sets packet SAP type +\param pck target packet +\return sap_type SAP type of the packet +*/ +GF_FilterSAPType gf_filter_pck_get_sap(GF_FilterPacket *pck); + + +/*! Sets packet video interlacing flag +\param pck target packet +\param is_interlaced set to 0 if not interlaced, 1 for top field first/contains only top field, 2 for bottom field first/contains only bottom field. +\return error code if any +*/ +GF_Err gf_filter_pck_set_interlaced(GF_FilterPacket *pck, u32 is_interlaced); + +/*! Gets packet video interlacing flag +\param pck target packet +\return interlaced flag, set to 0 if not interlaced, 1 for top field first, 2 otherwise. +*/ +u32 gf_filter_pck_get_interlaced(GF_FilterPacket *pck); + +/*! Sets packet corrupted data flag +\param pck target packet +\param is_corrupted indicates if data in packet is corrupted +\return error code if any +*/ +GF_Err gf_filter_pck_set_corrupted(GF_FilterPacket *pck, Bool is_corrupted); + +/*! Gets packet corrupted data flag +\param pck target packet +\return GF_TRUE if data in packet is corrupted +*/ +Bool gf_filter_pck_get_corrupted(GF_FilterPacket *pck); + +/*! Sets seek flag of packet. +For PIDs of stream type FILE with GF_PROP_PID_DISABLE_PROGRESSIVE set, the seek flag set to GF_TRUE indicates +that the packet is a PATCH packet, replacing bytes located at gf_filter_pck_get_byte_offset in file if the interlaced flag of the packet is not set, or +inserting bytes located at gf_filter_pck_get_byte_offset in file if the interlaced flag of the packet is set. +If the corrupted flag is set, this indicates the data will be replaced later on. +A seek packet is not meant to be displayed but is needed for decoding. +\note If a packet is partially skiped but completely decoded, it shall not be marked as seek but have the property "SkipBegin" set. +\note Raw audio packets MUST be split at the proper boundary +\param pck target packet +\param is_seek indicates packet is seek frame +\return error code if any +*/ +GF_Err gf_filter_pck_set_seek_flag(GF_FilterPacket *pck, Bool is_seek); + +/*! Gets packet seek data flag +\param pck target packet +\return GF_TRUE if packet is a seek packet +*/ +Bool gf_filter_pck_get_seek_flag(GF_FilterPacket *pck); + +/*! Sets packet byte offset in source. +byte offset should only be set if the data in the packet is exactly the same as the one at the given byte offset +\param pck target packet +\param byte_offset indicates the byte offset in the source. By default packets are created with no byte offset +\return error code if any +*/ +GF_Err gf_filter_pck_set_byte_offset(GF_FilterPacket *pck, u64 byte_offset); + +/*! Sets packet byte offset in source +\param pck target packet +\return byte offset in the source +*/ +u64 gf_filter_pck_get_byte_offset(GF_FilterPacket *pck); + +/*! Sets packet roll info (number of packets to rewind/forward and decode to get a clean access point). +\param pck target packet +\param roll_count indicates the roll distance of this packet - only used for SAP 4 for now +\return error code if any +*/ +GF_Err gf_filter_pck_set_roll_info(GF_FilterPacket *pck, s16 roll_count); + +/*! Gets packet roll info. +\param pck target packet +\return roll distance of this packet +\return error code if any +*/ +s16 gf_filter_pck_get_roll_info(GF_FilterPacket *pck); + +/*! Crypt flags for packet */ +#define GF_FILTER_PCK_CRYPT 1 + +/*! Sets packet crypt flags +\param pck target packet +\param crypt_flag packet crypt flag +\return error code if any +*/ +GF_Err gf_filter_pck_set_crypt_flags(GF_FilterPacket *pck, u8 crypt_flag); + +/*! Gets packet crypt flags +\param pck target packet +\return packet crypt flag +*/ +u8 gf_filter_pck_get_crypt_flags(GF_FilterPacket *pck); + +/*! Packet clock reference types - used for MPEG-2 TS*/ +typedef enum +{ + /*! packet is not a clock reference */ + GF_FILTER_CLOCK_NONE=0, + /*! packet is a PCR clock reference, expressed in PID timescale */ + GF_FILTER_CLOCK_PCR, + /*! packet is a PCR clock discontinuity, expressed in PID timescale */ + GF_FILTER_CLOCK_PCR_DISC, +} GF_FilterClockType; + +/*! Sets packet clock type +\param pck target packet +\param ctype packet clock flag +\return error code if any +*/ +GF_Err gf_filter_pck_set_clock_type(GF_FilterPacket *pck, GF_FilterClockType ctype); + +/*! Gets last clock type and clock value on PID. Always returns 0 if the source filter manages clock references internally cd \ref gf_filter_pid_set_clock_mode. +\param PID target PID to query for clock +\param clock_val last clock reference found +\param timescale last clock reference timescale +\return ctype packet clock flag +*/ +GF_FilterClockType gf_filter_pid_get_clock_info(GF_FilterPid *PID, u64 *clock_val, u32 *timescale); + +/*! Gets clock type of packet. Always returns 0 if the source filter does NOT manages clock references internally cd \ref gf_filter_pid_set_clock_mode. +\param pck target packet +\return ctype packet clock flag +*/ +GF_FilterClockType gf_filter_pck_get_clock_type(GF_FilterPacket *pck); + +/*! Sets packet carousel info +\param pck target packet +\param version_number carousel version number associated with this data chunk +\return error code if any +*/ +GF_Err gf_filter_pck_set_carousel_version(GF_FilterPacket *pck, u8 version_number); + +/*! Gets packet carousel info +\param pck target packet +\return version_number carousel version number associated with this data chunk +*/ +u8 gf_filter_pck_get_carousel_version(GF_FilterPacket *pck); + +/*! Sets packet sample dependency flags. + +Sample dependency flags have the same semantics as ISOBMFF:\n +bit(2)is_leading bit(2)sample_depends_on (2)sample_is_depended_on (2)sample_has_redundancy\n +\n +is_leading takes one of the following four values:\n +0: the leading nature of this sample is unknown;\n +1: this sample is a leading sample that has a dependency before the referenced I-picture (and is therefore not decodable);\n +2: this sample is not a leading sample;\n +3: this sample is a leading sample that has no dependency before the referenced I-picture (and is therefore decodable);\n +sample_depends_on takes one of the following four values:\n +0: the dependency of this sample is unknown;\n +1: this sample does depend on others (not an I picture);\n +2: this sample does not depend on others (I picture);\n +3: reserved\n +sample_is_depended_on takes one of the following four values:\n +0: the dependency of other samples on this sample is unknown;\n +1: other samples may depend on this one (not disposable);\n +2: no other sample depends on this one (disposable);\n +3: reserved\n +sample_has_redundancy takes one of the following four values:\n +0: it is unknown whether there is redundant coding in this sample;\n +1: there is redundant coding in this sample;\n +2: there is no redundant coding in this sample;\n +3: reserved\n + +\param pck target packet +\param dep_flags sample dependency flags +\return error code if any +*/ +GF_Err gf_filter_pck_set_dependency_flags(GF_FilterPacket *pck, u8 dep_flags); + +/*! Gets packet sample dependency flags. +\param pck target packet +\return dep_flags sample dependency flags - see \ref gf_filter_pck_set_dependency_flags for syntax. +*/ +u8 gf_filter_pck_get_dependency_flags(GF_FilterPacket *pck); + + +/*! Sets packet sequence number. Shall only be used when a filter handles a PLAY request based on packet sequence number +\param pck target packet +\param seq_num sequence number of packet +\return error code if any +*/ +GF_Err gf_filter_pck_set_seq_num(GF_FilterPacket *pck, u32 seq_num); + +/*! Gets packet sequence number info +\param pck target packet +\return sequence number associated with this packet +*/ +u32 gf_filter_pck_get_seq_num(GF_FilterPacket *pck); + +/*! Redefinition of GF_Matrix but without the maths.h include which breaks VideoToolBox on OSX/iOS */ +typedef struct __matrix GF_Matrix_unexposed; + +/*! frame interface flags*/ +enum +{ + /*! When set , indicates that the emitting filter will block until this frame is released. + Consumers of such a packet shall drop the packet as soon as possible, since it blocks the emiting filter.*/ + GF_FRAME_IFCE_BLOCKING = 1, + /*! When set , indicates that the associated framebuffer is the main GL framebuffer.*/ + GF_FRAME_IFCE_MAIN_GLFB = 1<<1 +}; + +/*! Frame interface object + +The frame interface object can be used to expose an interface between the packet generated by a filter and its consumers, when dispatching a regular data block is not possible. +This is typically used by decoders exposing the output image planes or by OpenGL filters outputing textures. +Currently only video frames use this interface object. +*/ +typedef struct _gf_filter_frame_interface +{ + /*! get video frame plane + \param frame interface object for the video frame + \param plane_idx plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane + \param outPlane address of target color plane + \param outStride stride in bytes of target color plane + \return error code if any + */ + GF_Err (*get_plane)(struct _gf_filter_frame_interface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride); + + /*! get video frame plane texture + \param frame interface object for the video frame + \param plane_idx plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane + \param gl_tex_format GL texture format used + \param gl_tex_id GL texture ID used + \param texcoordmatrix texture transform to fill. The texture is expected to be layed out as an image (first pixel is top-first). If not the case, add a vertical flip (eg dispatching an OpenGL FBO). \return error code if any + */ + GF_Err (*get_gl_texture)(struct _gf_filter_frame_interface *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_Matrix_unexposed * texcoordmatrix); + + /*! Flags for this frame interface.*/ + u32 flags; + + /*! private space for the emitting filter, consumers shall not modify this*/ + void *user_data; +} GF_FilterFrameInterface; + + +/*! Allocates a new packet holding a reference to a frame interface object. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param frame_ifce the associated frame interface object +\param destruct the destructor to be called upon packet release +\return new packet +*/ +GF_FilterPacket *gf_filter_pck_new_frame_interface(GF_FilterPid *PID, GF_FilterFrameInterface *frame_ifce, gf_fsess_packet_destructor destruct); + +/*! Gets a frame interface associated with a packet if any. + +Consumers will typically first check if the packet has associated data using \ref gf_filter_pck_get_data. + +\param pck the target packet +\return the associated frame interface object if any, or NULL otherwise +*/ +GF_FilterFrameInterface *gf_filter_pck_get_frame_interface(GF_FilterPacket *pck); + + +/*! Checks if the packet is a blocking reference, i.e. a parent filter in the chain is waiting for its destruction to emit a new packet. +This is typically used by sink filters to decide if they can hold references to input packets without blocking the chain. +\param pck the target packet +\return GF_TRUE if the packet is blocking or is a reference to a blocking packet, GF_FALSE otherwise +*/ +Bool gf_filter_pck_is_blocking_ref(GF_FilterPacket *pck); + +/*! @} */ + + +/*! +\addtogroup fs_props Filter Properties +\ingroup filters__cust_grp +\brief Custom Filter + +Custom filters are filters created by the app with no associated registry. +The app is responsible for assigning capabilities to the filter, and setting callback functions. +Each callback is optionnal, 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. +@{ + */ + +/*! Loads custom filter +\param session filter session +\param name name of filter to use - optional, may be NULL +\param flags flags for filter registry, currently only GF_FS_REG_MAIN_THREAD is used +\param e set to the error code if any - optional, may be NULL +\return filter or NULL if error +*/ +GF_Filter *gf_fs_new_filter(GF_FilterSession *session, const char *name, u32 flags, GF_Err *e); + +/*! 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 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 +\return error if any + */ +GF_Err gf_filter_push_caps(GF_Filter *filter, u32 code, GF_PropertyValue *value, const char *name, u32 flags, u8 priority); + +/*! Set the process function for a custom filter +\param filter the target filter +\param process_cbk the process callback, may be NULL - cf process in \ref __gf_filter_register +\return error if any + */ +GF_Err gf_filter_set_process_ckb(GF_Filter *filter, GF_Err (*process_cbk)(GF_Filter *filter) ); + +/*! Set the PID configuration function for a custom filter +\param filter the target filter +\param configure_cbk the configure callback, may be NULL - cf configure_pid in \ref __gf_filter_register +\return error if any + */ +GF_Err gf_filter_set_configure_ckb(GF_Filter *filter, GF_Err (*configure_cbk)(GF_Filter *filter, GF_FilterPid *PID, Bool is_remove) ); + +/*! Set the process event function for a custom filter +\param filter the target filter +\param process_event_cbk the process event callback, may be NULL - cf process_event in \ref __gf_filter_register +\return error if any + */ +GF_Err gf_filter_set_process_event_ckb(GF_Filter *filter, Bool (*process_event_cbk)(GF_Filter *filter, const GF_FilterEvent *evt) ); + +/*! Set the reconfigure output function for a custom filter +\param filter the target filter +\param reconfigure_output_cbk the reconfigure callback, may be NULL - cf reconfigure_output_cbk in \ref __gf_filter_register +\return error if any + */ +GF_Err gf_filter_set_reconfigure_output_ckb(GF_Filter *filter, GF_Err (*reconfigure_output_cbk)(GF_Filter *filter, GF_FilterPid *PID) ); + +/*! Set the data prober function for a custom filter +\param filter the target filter +\param probe_data_cbk the data prober callback , may be NULL- cf probe_data in \ref __gf_filter_register +\return error if any + */ +GF_Err gf_filter_set_probe_data_cbk(GF_Filter *filter, const char * (*probe_data_cbk)(const u8 *data, u32 size, GF_FilterProbeScore *score) ); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif //_GF_FILTERS_H_ + diff --git a/include/gpac/html5_media.h b/include/gpac/html5_media.h new file mode 100644 index 0000000..c849c1e --- /dev/null +++ b/include/gpac/html5_media.h @@ -0,0 +1,387 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2013-2019 + * All rights reserved + * + * This file is part of GPAC / HTML Media Element header + * + * 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_HTMLMEDIA_H_ +#define _GF_HTMLMEDIA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file +\brief Scene graph extensions for HTML5 media. + */ + +/*! +\defgroup html5_grp HTML5 +\ingroup scene_grp +\brief HTML5 extensions of the scene graph. +*/ + +/*! +\addtogroup html5media_grp HTML5 media +\ingroup html5_grp +\brief Scene graph extensions for HTML5 media. + +This section documents the scene graph extensions used for HTML5 media (video). + +@{ + */ + +#include + +/*base SVG type*/ +#include +/*dom events*/ +#include +/*dom text event*/ +#include + +#include +#include +#include +#include + +#ifdef GPAC_ENABLE_HTML5_MEDIA + +typedef struct +{ + u32 nb_inst; + /* Basic classes */ + JSClassDef arrayBufferClass; + + /*HTML Media classes*/ + JSClassDef htmlVideoElementClass; + JSClassDef htmlAudioElementClass; + JSClassDef htmlSourceElementClass; + JSClassDef htmlTrackElementClass; + JSClassDef htmlMediaElementClass; + JSClassDef mediaControllerClass; + JSClassDef audioTrackListClass; + JSClassDef audioTrackClass; + JSClassDef videoTrackListClass; + JSClassDef videoTrackClass; + JSClassDef textTrackListClass; + JSClassDef textTrackClass; + JSClassDef textTrackCueListClass; + JSClassDef textTrackCueClass; + JSClassDef timeRangesClass; + JSClassDef trackEventClass; + JSClassDef mediaErrorClass; + + /* Media Source Extensions */ + JSClassDef mediaSourceClass; + JSClassDef sourceBufferClass; + JSClassDef sourceBufferListClass; + JSClassDef URLClass; + + /* Media Capture */ + JSClassDef mediaStreamClass; + JSClassDef localMediaStreamClass; + JSClassDef mediaStreamTrackClass; + JSClassDef mediaStreamTrackListClass; + JSClassDef navigatorUserMediaClass; + JSClassDef navigatorUserMediaErrorClass; +} GF_HTML_MediaRuntime; + +/************************************************************ + * + * HTML 5 Media Element + * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#media-element + * + *************************************************************/ + +typedef enum +{ + MEDIA_ERR_ABORTED = 1, + MEDIA_ERR_NETWORK = 2, + MEDIA_ERR_DECODE = 3, + MEDIA_ERR_SRC_NOT_SUPPORTED = 4 +} GF_HTML_MediaErrorCode; + +typedef enum +{ + NETWORK_EMPTY = 0, + NETWORK_IDLE = 1, + NETWORK_LOADING = 2, + NETWORK_NO_SOURCE = 3 +} GF_HTML_NetworkState; + +typedef enum +{ + HAVE_NOTHING = 0, + HAVE_METADATA = 1, + HAVE_CURRENT_DATA = 2, + HAVE_FUTURE_DATA = 3, + HAVE_ENOUGH_DATA = 4 +} GF_HTML_MediaReadyState; + +typedef struct +{ + /* JavaScript context associated to this object */ + JSContext *c; + /* JavaScript counterpart */ + JSObject *_this; + + GF_HTML_MediaErrorCode code; +} GF_HTML_MediaError; + +typedef struct _timerange +{ + /* JavaScript context associated to this object */ + JSContext *c; + /* JavaScript counterpart */ + JSObject *_this; + + GF_List *times; + u32 timescale; +} GF_HTML_MediaTimeRanges; + +typedef enum { + HTML_MEDIA_TRACK_TYPE_UNKNOWN = 0, + HTML_MEDIA_TRACK_TYPE_AUDIO = 1, + HTML_MEDIA_TRACK_TYPE_VIDEO = 2, + HTML_MEDIA_TRACK_TYPE_TEXT = 3 +} GF_HTML_TrackType; + +#define BASE_HTML_TRACK \ + /* JavaScript context associated to this object */\ + JSContext *c;\ + /* JavaScript counterpart */\ + JSObject *_this;\ + /* GPAC-specific properties */\ + u32 bin_id; /* track id */\ + void * channel; /* channel object used by the terminal */\ + GF_ObjectDescriptor *od; /* MPEG-4 Object descriptor for this track */\ + GF_List *buffer; /* List of MSE Packets */\ + u32 packet_index; /* index of MSE Packets*/\ + GF_Mutex *buffer_mutex;\ + Bool last_dts_set; \ + u64 last_dts; /* MSE last decode timestamp (in timescale units)*/ \ + u32 last_dur; /* MSE last frame duration (in timescale units)*/ \ + Bool highest_pts_set; \ + u64 highest_pts; /* MSE highest presentation timestamp (in timescale units)*/ \ + Bool needs_rap; /* MSE need random access point flag */ \ + u32 timescale; /* used by time stamps in MSE Packets */ \ + s64 timestampOffset; /* MSE SourceBuffer value (in timescale units) */ \ + /* standard HTML properties */ \ + GF_HTML_TrackType type;\ + char *id;\ + char *kind;\ + char *label;\ + char *language;\ + char *mime; \ + Bool enabled_or_selected; + +typedef struct +{ + BASE_HTML_TRACK +} GF_HTML_Track; + +typedef struct +{ + BASE_HTML_TRACK + JSFunction *oncuechange; + char *inBandMetadataTrackDispatchType; + //GF_HTMLTextTrackMode mode; + //GF_HTMLTextTrackCueList cues; + //GF_HTMLTextTrackCueList activeCues; +} GF_HTML_TextTrack; + +#define BASE_HTML_TRACK_LIST \ + /* JavaScript context associated to this object */\ + JSContext *c;\ + /* JavaScript counterpart */\ + JSObject *_this;\ + GF_List *tracks; \ + jsval onchange; \ + jsval onaddtrack; \ + jsval onremovetrack; \ + u32 selected_index; + +typedef struct +{ + BASE_HTML_TRACK_LIST +} GF_HTML_TrackList; + +typedef enum +{ + MEDIA_CONTROLLER_WAITING = 0, + MEDIA_CONTROLLER_PLAYING = 1, + MEDIA_CONTROLLER_ENDED = 2 +} GF_HTML_MediaControllerPlaybackState; + +typedef struct +{ + /* JavaScript context associated to this object */ + JSContext *c; + /* JavaScript counterpart */ + JSObject *_this; + + GF_HTML_MediaTimeRanges *buffered; + GF_HTML_MediaTimeRanges *seekable; + GF_HTML_MediaTimeRanges *played; + Bool paused; + GF_HTML_MediaControllerPlaybackState playbackState; + double defaultPlaybackRate; + + /* list of media elements using this media controller */ + GF_List *media_elements; +} GF_HTML_MediaController; + +typedef struct +{ + /* JavaScript context associated to this object */ + JSContext *c; + /* JavaScript counterpart */ + JSObject *_this; + + /* The audio or video node */ + GF_Node *node; + + /* error state */ + GF_HTML_MediaError error; + + /* src: not stored in this structure, + using the value stored in the node ( see HTML 5 "must reflect the content of the attribute")*/ + /* currentSrc: the actual source used for the video (src attribute on video, audio or source elements) */ + char *currentSrc; + /* crossOrigin: "must reflect the content of the attribute of the same name", use the node */ + /* networkState: retrieved dynamically from GPAC Service */ + /* preload: "must reflect the content of the attribute of the same name", use the node */ + GF_HTML_MediaTimeRanges *buffered; + /* ready state */ + /* readyState: retrieved from GPAC Media Object dynamically */ + Bool seeking; + + /* playback state */ + /* currentTime: retrieved from GPAC Media Object */ + /* duration: retrieved from GPAC Media Object */ + char *startDate; + Bool paused; + double defaultPlaybackRate; + GF_HTML_MediaTimeRanges *played; + GF_HTML_MediaTimeRanges *seekable; + /* ended: retrieved from the state of GPAC Media Object */ + /* autoplay: "must reflect the content of the attribute of the same name", use the node */ + /* loop: "must reflect the content of the attribute of the same name", use the node */ + + /* media controller*/ + /* mediaGroup: "must reflect the content of the attribute of the same name", use the node */ + GF_HTML_MediaController *controller; + + /* controls*/ + /* controls: "must reflect the content of the attribute of the same name", use the node */ + /* volume: retrieved from GPAC Audio Input in GPAC Media Object */ + /* muted: retrieved from GPAC media object */ + /* defaultMuted: "must reflect the content of the attribute of with the name" muted */ + + /* tracks*/ + GF_HTML_TrackList audioTracks; + GF_HTML_TrackList videoTracks; + GF_HTML_TrackList textTracks; + + GF_DOMEventTarget *evt_target; +} GF_HTML_MediaElement; + +typedef struct +{ + /* JavaScript context used to create the JavaScript object below */ + JSContext *c; + + /* JavaScript counterpart for this object*/ + JSObject *_this; + + char *data; + u32 length; + char *url; + Bool is_init; + GF_Blob blob; + /* used to do proper garbage collection between JS and Terminal */ + u32 reference_count; +} GF_HTML_ArrayBuffer; + +/* + * TimeRanges + */ +GF_HTML_MediaTimeRanges *gf_html_timeranges_new(u32 timescale); +GF_Err gf_html_timeranges_add_start(GF_HTML_MediaTimeRanges *timeranges, u64 start); +GF_Err gf_html_timeranges_add_end(GF_HTML_MediaTimeRanges *timeranges, u64 end); +void gf_html_timeranges_reset(GF_HTML_MediaTimeRanges *range); +void gf_html_timeranges_del(GF_HTML_MediaTimeRanges *range); +GF_HTML_MediaTimeRanges *gf_html_timeranges_intersection(GF_HTML_MediaTimeRanges *a, GF_HTML_MediaTimeRanges *b); +GF_HTML_MediaTimeRanges *gf_html_timeranges_union(GF_HTML_MediaTimeRanges *a, GF_HTML_MediaTimeRanges *b); + +/* + * HTML5 TrackList + */ +GF_HTML_Track *html_media_add_new_track_to_list(GF_HTML_TrackList *tracklist, + GF_HTML_TrackType type, const char *mime, Bool enable_or_selected, + const char *id, const char *kind, const char *label, const char *lang); +Bool html_media_tracklist_has_track(GF_HTML_TrackList *tracklist, const char *id); +GF_HTML_Track *html_media_tracklist_get_track(GF_HTML_TrackList *tracklist, const char *id); +void gf_html_tracklist_del(GF_HTML_TrackList *tlist); + +/* + * HTML5 Tracks + */ +GF_HTML_Track *gf_html_media_track_new(GF_HTML_TrackType type, const char *mime, Bool enable_or_selected, + const char *id, const char *kind, const char *label, const char *lang); +void gf_html_track_del(GF_HTML_Track *track); + +/* + * HTML5 Media Element + */ +GF_HTML_MediaElement *gf_html_media_element_new(GF_Node *media_node, GF_HTML_MediaController *mc); +void gf_html_media_element_del(GF_HTML_MediaElement *me); + +void html_media_element_js_init(JSContext *c, JSObject *new_obj, GF_Node *n); + +/* + * HTML5 Media Controller + */ +GF_HTML_MediaController *gf_html_media_controller_new(); +void gf_html_media_controller_del(GF_HTML_MediaController *mc); + +/* + * HTML5 Array Buffer + */ +GF_HTML_ArrayBuffer *gf_arraybuffer_new(u8 *data, u32 length); +JSObject *gf_arraybuffer_js_new(JSContext *c, u8 *data, u32 length, JSObject *parent); +void gf_arraybuffer_del(GF_HTML_ArrayBuffer *buffer, Bool del_js); + + +#endif //GPAC_ENABLE_HTML5_MEDIA + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif // _GF_HTMLMEDIA_H_ diff --git a/include/gpac/html5_mse.h b/include/gpac/html5_mse.h new file mode 100644 index 0000000..5f056fb --- /dev/null +++ b/include/gpac/html5_mse.h @@ -0,0 +1,251 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato + * Copyright (c) Telecom ParisTech 2013-2019 + * All rights reserved + * + * This file is part of GPAC / HTML Media Source header + * + * 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_HTMLMSE_H_ +#define _GF_HTMLMSE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file +\brief Scene graph extensions for Media Source Extensions. + */ + +/*! +\addtogroup html5mse_grp HTML5 MSE +\ingroup html5_grp +\brief Scene graph extensions for HTML5 media source extensions. + +This section documents the scene graph extensions used for HTML5 media source extensions. +@{ +*/ + +#include + +#include + +#ifdef GPAC_ENABLE_HTML5_MEDIA + +typedef enum +{ + MEDIA_SOURCE_READYSTATE_CLOSED = 0, + MEDIA_SOURCE_READYSTATE_OPEN = 1, + MEDIA_SOURCE_READYSTATE_ENDED = 2, +} GF_HTML_MediaSource_ReadyState; + +typedef enum +{ + MEDIA_SOURCE_APPEND_MODE_SEGMENTS = 0, + MEDIA_SOURCE_APPEND_MODE_SEQUENCE = 1 +} GF_HTML_MediaSource_AppendMode; + +typedef enum +{ + MEDIA_SOURCE_APPEND_STATE_WAITING_FOR_SEGMENT = 0, + MEDIA_SOURCE_APPEND_STATE_PARSING_INIT_SEGMENT = 1, + MEDIA_SOURCE_APPEND_STATE_PARSING_MEDIA_SEGMENT = 2 +} GF_HTML_MediaSource_AppendState; + +typedef struct +{ + /* Pointer back to the MediaSource object to which this source buffer is attached */ + struct _html_mediasource *mediasource; + + /* JavaScript counterpart for this object*/ + JSObject *_this; + + /* MSE defined properties */ + Bool updating; + GF_HTML_MediaTimeRanges *buffered; + s64 timestampOffset; + double appendWindowStart; + double appendWindowEnd; + u32 timescale; + + GF_HTML_MediaSource_AppendState append_state; + Bool buffer_full_flag; + /* Mode used to append media data: + - "segments" uses the timestamps in the media, + - "sequence" ignores them and appends just after the previous data */ + GF_HTML_MediaSource_AppendMode append_mode; + + /* time (in timescale units) of the first frame in the group */ + u64 group_start_timestamp; + Bool group_start_timestamp_flag; + /* time (in timescale units) of the frame end time (start + duration) in the group */ + u64 group_end_timestamp; + Bool group_end_timestamp_set; + + Bool first_init_segment; + + /* times (in timescale units) of the frames to be removed */ + u64 remove_start; + u64 remove_end; + + /* + * GPAC internal objects + */ + + /* Media tracks (GF_HTML_Track) associated to this source buffer */ + GF_List *tracks; + /* Buffers to parse */ + GF_List *input_buffer; + /* We can only delete a buffer when we know it has been parsed, + i.e. when the next buffer is asked for, + so we need to keep the buffer in the meantime */ + void *prev_buffer; + + /* Media parser */ + void *parser; + + /* MPEG-4 Object descriptor as returned by the media parser */ + GF_ObjectDescriptor *service_desc; + + /* Boolean indicating that the parser has parsed the initialisation segment */ + Bool parser_connected; + + /* Threads used to asynchronously parse the buffer and remove media data */ + GF_List *threads; + GF_Thread *parser_thread; + GF_Thread *remove_thread; + + /* Object used to fire JavaScript events to */ + GF_DOMEventTarget *evt_target; +} GF_HTML_SourceBuffer; + +typedef struct +{ + /* JavaScript counterpart for this object */ + JSObject *_this; + + GF_List *list; + + struct _html_mediasource *parent; + +#ifndef GPAC_DISABLE_SVG + /* Object used to fire JavaScript events to */ + GF_DOMEventTarget *evt_target; +#endif +} GF_HTML_SourceBufferList; + +typedef enum +{ + DURATION_NAN = 0, + DURATION_INFINITY = 1, + DURATION_VALUE = 2 +} GF_HTML_MediaSource_DurationType; + +typedef struct _html_mediasource +{ + /* JavaScript context associated to all the objects */ + JSContext *c; + + /* JavaScript counterpart for this object*/ + JSObject *_this; + + /* Used to determine if the object can be safely deleted (not used by JS, not used by the service) */ + u32 reference_count; + + GF_HTML_SourceBufferList sourceBuffers; + GF_HTML_SourceBufferList activeSourceBuffers; + + double duration; + GF_HTML_MediaSource_DurationType durationType; + + u32 readyState; + + /* URL created by the call to createObjectURL on this MediaSource*/ + char *blobURI; + + /* GPAC Terminal Service object + it is associated to this MediaSource when the Media element uses the blobURI of this MediaSource + should be NULL when the MediaSource is not open + we use only one service object for all sourceBuffers + */ + void *service; + + /* SceneGraph to be used before the node is actually attached */ + GF_SceneGraph *sg; + + /* Node the MediaSource is attached to */ + GF_Node *node; + +#ifndef GPAC_DISABLE_SVG + /* object implementing Event Target Interface */ + GF_DOMEventTarget *evt_target; +#endif +} GF_HTML_MediaSource; + +GF_HTML_MediaSource *gf_mse_media_source_new(); +void gf_mse_mediasource_del(GF_HTML_MediaSource *ms, Bool del_js); +void gf_mse_mediasource_open(GF_HTML_MediaSource *ms, struct _mediaobj *mo); +void gf_mse_mediasource_close(GF_HTML_MediaSource *ms); +void gf_mse_mediasource_end(GF_HTML_MediaSource *ms); +void gf_mse_mediasource_add_source_buffer(GF_HTML_MediaSource *ms, GF_HTML_SourceBuffer *sb); + +GF_HTML_SourceBuffer *gf_mse_source_buffer_new(GF_HTML_MediaSource *mediasource); +void gf_mse_source_buffer_set_timestampOffset(GF_HTML_SourceBuffer *sb, double d); +void gf_mse_source_buffer_set_timescale(GF_HTML_SourceBuffer *sb, u32 timescale); +GF_Err gf_mse_source_buffer_load_parser(GF_HTML_SourceBuffer *sourcebuffer, const char *mime); +GF_Err gf_mse_remove_source_buffer(GF_HTML_MediaSource *ms, GF_HTML_SourceBuffer *sb); +void gf_mse_source_buffer_del(GF_HTML_SourceBuffer *sb); +GF_Err gf_mse_source_buffer_abort(GF_HTML_SourceBuffer *sb); +void gf_mse_source_buffer_append_arraybuffer(GF_HTML_SourceBuffer *sb, GF_HTML_ArrayBuffer *buffer); +void gf_mse_source_buffer_update_buffered(GF_HTML_SourceBuffer *sb); +void gf_mse_remove(GF_HTML_SourceBuffer *sb, double start, double end); + +typedef struct +{ + char *data; + u32 size; + GF_SLHeader sl_header; + Bool is_compressed; + Bool is_new_data; + GF_Err status; +} GF_MSE_Packet; + +GF_Err gf_mse_proxy(void *parser, void *command); +void gf_mse_packet_del(GF_MSE_Packet *packet); + +GF_Err gf_mse_track_buffer_get_next_packet(GF_HTML_Track *track, + char **out_data_ptr, u32 *out_data_size, + GF_SLHeader *out_sl_hdr, Bool *sl_compressed, + GF_Err *out_reception_status, Bool *is_new_data); +GF_Err gf_mse_track_buffer_release_packet(GF_HTML_Track *track); + +#endif //GPAC_ENABLE_HTML5_MEDIA + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/include/gpac/ietf.h b/include/gpac/ietf.h new file mode 100644 index 0000000..68c3b2b --- /dev/null +++ b/include/gpac/ietf.h @@ -0,0 +1,1609 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / IETF RTP/RTSP/SDP 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_IETF_H_ +#define _GF_IETF_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Tools for real-time streaming over IP using RTP/RTCP/RTSP/SDP . +*/ + +/*! +\addtogroup ietf_grp RTP Streaming +\ingroup media_grp +\brief Tools for real-time streaming over IP using RTP/RTCP/RTSP/SDP. + + +This section documents the tools used for real-time streaming over IP using RTP/RTCP/RTSP/SDP. + +@{ + */ + +#include + +#ifndef GPAC_DISABLE_STREAMING + +#include +#include +#include + + +/*! RTSP version supported by GPAC*/ +#define GF_RTSP_VERSION "RTSP/1.0" + + +/*! RTSP NOTIF CODES */ +enum +{ + NC_RTSP_Continue = 100, + NC_RTSP_OK = 200, + NC_RTSP_Created = 201, + NC_RTSP_Low_on_Storage_Space = 250, + + NC_RTSP_Multiple_Choice = 300, + NC_RTSP_Moved_Permanently = 301, + NC_RTSP_Moved_Temporarily = 302, + NC_RTSP_See_Other = 303, + NC_RTSP_Use_Proxy = 305, + + NC_RTSP_Bad_Request = 400, + NC_RTSP_Unauthorized = 401, + NC_RTSP_Payment_Required = 402, + NC_RTSP_Forbidden = 403, + NC_RTSP_Not_Found = 404, + NC_RTSP_Method_Not_Allowed = 405, + NC_RTSP_Not_Acceptable = 406, + NC_RTSP_Proxy_Authentication_Required = 407, + NC_RTSP_Request_Timeout = 408, + NC_RTSP_Gone = 410, + NC_RTSP_Length_Required = 411, + NC_RTSP_Precondition_Failed = 412, + NC_RTSP_Request_Entity_Too_Large = 413, + NC_RTSP_Request_URI_Too_Long = 414, + NC_RTSP_Unsupported_Media_Type = 415, + + NC_RTSP_Invalid_parameter = 451, + NC_RTSP_Illegal_Conference_Identifier = 452, + NC_RTSP_Not_Enough_Bandwidth = 453, + NC_RTSP_Session_Not_Found = 454, + NC_RTSP_Method_Not_Valid_In_This_State = 455, + NC_RTSP_Header_Field_Not_Valid = 456, + NC_RTSP_Invalid_Range = 457, + NC_RTSP_Parameter_Is_ReadOnly = 458, + NC_RTSP_Aggregate_Operation_Not_Allowed = 459, + NC_RTSP_Only_Aggregate_Operation_Allowed = 460, + NC_RTSP_Unsupported_Transport = 461, + NC_RTSP_Destination_Unreachable = 462, + + NC_RTSP_Internal_Server_Error = 500, + NC_RTSP_Not_Implemented = 501, + NC_RTSP_Bad_Gateway = 502, + NC_RTSP_Service_Unavailable = 503, + NC_RTSP_Gateway_Timeout = 504, + NC_RTSP_RTSP_Version_Not_Supported = 505, + + NC_RTSP_Option_not_support = 551, +}; + +/*! Gives string description of error code +\param ErrCode the RTSP error code +\return the description of the RTSP error code +*/ +const char *gf_rtsp_nc_to_string(u32 ErrCode); + +/* + Common structures between commands and responses +*/ + +/*! RTSP Range information + RTSP Session level only, although this is almost the same + format as an SDP range, this is not used in the SDP lib as "a=range" is not part of SDP + but part of RTSP +*/ +typedef struct { + /* start and end range. If end is -1, the range is open (from start to unknown) */ + Double start, end; + /* use SMPTE range (Start and End specify the number of frames) (currently not supported) */ + u32 UseSMPTE; + /* framerate for SMPTE range */ + Double FPS; +} GF_RTSPRange; + +/*! parses a Range line and returns range header structure. This can be used for RTSP extension of SDP +\note Only support for npt for now +\param range_buf the range string +\return a newly allocated RTSP range +*/ +GF_RTSPRange *gf_rtsp_range_parse(char *range_buf); +/*! creates a new RTSP range +\return a newly allocated RTSP range +*/ +GF_RTSPRange *gf_rtsp_range_new(); +/*! destroys a RTSP range +\param range the target RTSP range +*/ +void gf_rtsp_range_del(GF_RTSPRange *range); + +/* + Transport structure + contains all network info for RTSP sessions (ports, uni/multi-cast, ...) +*/ + +/*! RTSP AVP Transport Profile */ +#define GF_RTSP_PROFILE_RTP_AVP "RTP/AVP" +/*! RTSP AVP + TCP Transport Profile */ +#define GF_RTSP_PROFILE_RTP_AVP_TCP "RTP/AVP/TCP" +/*! RTSP UDP Transport Profile */ +#define GF_RTSP_PROFILE_UDP "udp" + +/*! RTSP transport structure*/ +typedef struct +{ + /* set to 1 if unicast */ + Bool IsUnicast; + /* for multicast */ + char *destination; + /* for redirections internal to servers */ + char *source; + /*IsRecord is usually 0 (PLAY) . If set, Append specify that the stream should + be concatenated to existing resources */ + Bool IsRecord, Append; + /* in case transport is on TCP/RTSP, If only 1 ID is specified, it is stored in rtpID (this + is not RTP interleaving) */ + Bool IsInterleaved; + u32 rtpID, rtcpID; + /* Multicast specific */ + u32 MulticastLayers; + u8 TTL; + /*RTP specific*/ + + /*port for multicast*/ + /*server port in unicast - RTP implies low is even , and last is low+1*/ + u16 port_first, port_last; + /*client port in unicast - RTP implies low is even , and last is low+1*/ + u16 client_port_first, client_port_last; + u32 SSRC; + + /*Transport protocol. In this version we only support RTP/AVP, the following flag tells + us if this is RTP/AVP/TCP or RTP/AVP (default)*/ + char *Profile; + + Bool is_sender; +} GF_RTSPTransport; + +/*! clones a RTSP transport +\param transp the target RTSP transport +\return an allocated copy RTSP transport +*/ +GF_RTSPTransport *gf_rtsp_transport_clone(GF_RTSPTransport *transp); +/*! destroys a RTSP transport +\param transp the target RTSP transport +*/ +void gf_rtsp_transport_del(GF_RTSPTransport *transp); + + +/* + RTSP Command + the RTSP Response is sent by a client / received by a server + text Allocation is done by the lib when parsing a command, and + is automatically freed when calling reset / delete. Therefore you must + set/allocate the fields yourself when writing a command (client) + +*/ + +/*ALL RTSP METHODS - all other methods will be ignored*/ +/*! RTSP DESCRIBE method*/ +#define GF_RTSP_DESCRIBE "DESCRIBE" +/*! RTSP SETUP method*/ +#define GF_RTSP_SETUP "SETUP" +/*! RTSP PLAY method*/ +#define GF_RTSP_PLAY "PLAY" +/*! RTSP PAUSE method*/ +#define GF_RTSP_PAUSE "PAUSE" +/*! RTSP RECORD method*/ +#define GF_RTSP_RECORD "RECORD" +/*! RTSP TEARDOWN method*/ +#define GF_RTSP_TEARDOWN "TEARDOWN" +/*! RTSP GET_PARAMETER method*/ +#define GF_RTSP_GET_PARAMETER "GET_PARAMETER" +/*! RTSP SET_PARAMETER method*/ +#define GF_RTSP_SET_PARAMETER "SET_PARAMETER" +/*! RTSP OPTIONS method*/ +#define GF_RTSP_OPTIONS "OPTIONS" +/*! RTSP ANNOUCE method*/ +#define GF_RTSP_ANNOUNCE "ANNOUNCE" +/*! RTSP REDIRECT method*/ +#define GF_RTSP_REDIRECT "REDIRECT" + +/*! RTSP command structure*/ +typedef struct +{ + char *Accept; + char *Accept_Encoding; + char *Accept_Language; + char *Authorization; + u32 Bandwidth; + u32 Blocksize; + char *Cache_Control; + char *Conference; + char *Connection; + u32 Content_Length; + u32 CSeq; + char *From; + char *Proxy_Authorization; + char *Proxy_Require; + GF_RTSPRange *Range; + char *Referer; + Double Scale; + char *Session; + Double Speed; + /*nota : RTSP allows several configurations for a single channel (multicast and + unicast , ...). Usually only 1*/ + GF_List *Transports; + char *User_Agent; + + /*type of the command, one of the described above*/ + char *method; + + /*Header extensions*/ + GF_List *Xtensions; + + /*body of the command, size is Content-Length (auto computed when sent). It is not + terminated by a NULL char*/ + char *body; + + /* + Specify ControlString if your request targets + a specific media stream in the service. If null, the service name only will be used + for control (for ex, both A and V streams in a single file) + If the request is GF_RTSP_OPTIONS, you must provide a control string containing the options + you want to query + */ + char *ControlString; + + /*user data: this is never touched by the lib, its intend is to help stacking + RTSP commands in your app*/ + void *user_data; + /*user flags: this is never touched by the lib, its intend is to help stacking + RTSP commands in your app*/ + u32 user_flags; + + + /* + Server side Extensions + */ + + /*full URL of the command. Not used at client side, as the URL is ALWAYS relative + to the server / service of the RTSP session + On the server side however redirections are up to the server, so we cannot decide for it */ + char *service_name; + /*RTSP status code of the command as parsed. One of the above RTSP StatusCode*/ + u32 StatusCode; +} GF_RTSPCommand; + +/*! creates an RTSP command +\return the newly allocated RTSP command +*/ +GF_RTSPCommand *gf_rtsp_command_new(); +/*! destroys an RTSP command +\param com the target RTSP command +*/ +void gf_rtsp_command_del(GF_RTSPCommand *com); +/*! resets an RTSP command +\param com the target RTSP command +*/ +void gf_rtsp_command_reset(GF_RTSPCommand *com); + + + +/* + RTSP Response + the RTSP Response is received by a client / sent by a server + text Allocation is done by the lib when parsing a response, and + is automatically freed when calling reset / delete. Therefore you must + allocate the fields yourself when writing a response (server) + +*/ + +/*! RTP-Info for RTP channels. + + There may be several RTP-Infos in one response + based on the server implementation (DSS/QTSS begaves this way) +*/ +typedef struct +{ + /*control string of the channel*/ + char *url; + /*seq num for asociated rtp_time*/ + u32 seq; + /*rtp TimeStamp corresponding to the Range start specified in the PLAY request*/ + u32 rtp_time; + /*ssrc of sender if known, 0 otherwise*/ + u32 ssrc; +} GF_RTPInfo; + + + +/*! RTSP Response */ +typedef struct +{ + /* response code*/ + u32 ResponseCode; + /* comment from the server */ + char *ResponseInfo; + + /* Header Fields */ + char *Accept; + char *Accept_Encoding; + char *Accept_Language; + char *Allow; + char *Authorization; + u32 Bandwidth; + u32 Blocksize; + char *Cache_Control; + char *Conference; + char *Connection; + char *Content_Base; + char *Content_Encoding; + char *Content_Language; + u32 Content_Length; + char *Content_Location; + char *Content_Type; + u32 CSeq; + char *Date; + char *Expires; + char *From; + char *Host; + char *If_Match; + char *If_Modified_Since; + char *Last_Modified; + char *Location; + char *Proxy_Authenticate; + char *Proxy_Require; + char *Public; + GF_RTSPRange *Range; + char *Referer; + char *Require; + char *Retry_After; + GF_List *RTP_Infos; + Double Scale; + char *Server; + char *Session; + u32 SessionTimeOut; + Double Speed; + u32 StreamID; //only when sess->satip is true + char *Timestamp; + /*nota : RTSP allows several configurations for a single channel (multicast and + unicast , ...). Usually only 1*/ + GF_List *Transports; + char *Unsupported; + char *User_Agent; + char *Vary; + char *Via; + char *WWW_Authenticate; + + /*Header extensions*/ + GF_List *Xtensions; + + /*body of the response, size is Content-Length (auto computed when sent). It is not + terminated by a NULL char when response is parsed but must be null-terminated when + response is being sent*/ + char *body; +} GF_RTSPResponse; + +/*! creates an RTSP response +\return the newly allocated RTSP response +*/ +GF_RTSPResponse *gf_rtsp_response_new(); +/*! deletes an RTSP response +\param rsp the target RTSP response +*/ +void gf_rtsp_response_del(GF_RTSPResponse *rsp); +/*! resets an RTSP response +\param rsp the target RTSP response +*/ +void gf_rtsp_response_reset(GF_RTSPResponse *rsp); + + +/*! RTSP session*/ +typedef struct _tag_rtsp_session GF_RTSPSession; + +/*! creates a new RTSP session +\param sURL the target RTSP session URL +\param DefaultPort the target RTSP session port +\return a newly allocated RTSP session +*/ +GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort); +/*! destroys an RTSP session +\param sess the target RTSP session +*/ +void gf_rtsp_session_del(GF_RTSPSession *sess); + +/*! sets TCP buffer size of an RTSP session +\param sess the target RTSP session +\param BufferSize desired buffer size in bytes +\return error if any +*/ +GF_Err gf_rtsp_set_buffer_size(GF_RTSPSession *sess, u32 BufferSize); + + +/*! resets state machine, invalidate SessionID +\note RFC2326 requires that the session is reseted when all RTP streams +are closed. As this lib doesn't maintain the number of valid streams +you MUST call reset when all your streams are shutdown (either requested through +TEARDOWN or signaled through RTCP BYE packets for RTP, or any other signaling means +for other protocols) +\param sess the target RTSP session +\param ResetConnection if set, this will destroy the associated TCP socket. This is useful in case of timeouts, because +some servers do not restart with the right CSeq. +*/ +void gf_rtsp_session_reset(GF_RTSPSession *sess, Bool ResetConnection); + +/*! checks if an RTSP session matches an RTSP URL +\param sess the target RTSP session +\param url the URL to test +\return GF_TRUE if the session matches the URL, GF_FALSE otherwise +*/ +Bool gf_rtsp_is_my_session(GF_RTSPSession *sess, char *url); + +/*! gets server name of an RTSP session +\param sess the target RTSP session +\return the server name +*/ +char *gf_rtsp_get_server_name(GF_RTSPSession *sess); +/*! gets server port of an RTSP session +\param sess the target RTSP session +\return the server port +*/ +u16 gf_rtsp_get_session_port(GF_RTSPSession *sess); + +/*! fetches an RTSP response from the server. the GF_RTSPResponse will be reseted before fetch +\param sess the target RTSP session +\param rsp the response object to fill with the response. This will be reseted before TCP fetch +\return error if any +*/ +GF_Err gf_rtsp_get_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp); + + +/*! RTSP States. The only non blocking mode is GF_RTSP_STATE_WAIT_FOR_CONTROL*/ +enum +{ + /*! Initialized (connection might be off, but all structures are in place) + This is the default state between # requests (aka, DESCRIBE and SETUP + or SETUP and PLAY ...)*/ + GF_RTSP_STATE_INIT = 0, + /*! Waiting*/ + GF_RTSP_STATE_WAITING, + /*! PLAY, PAUSE, RECORD. Aggregation is allowed for the same type, you can send several command + in a row. However the session will return GF_SERVICE_ERROR if you do not have + a valid SessionID in the command + You cannot issue a SETUP / DESCRIBE while in this state*/ + GF_RTSP_STATE_WAIT_FOR_CONTROL, + + /*! FATAL ERROR: session is invalidated by server. Call reset and restart from SETUP if needed*/ + GF_RTSP_STATE_INVALIDATED +}; + +/*! gets the RTSP session state +\param sess the target RTSP session +\return the session state +*/ +u32 gf_rtsp_get_session_state(GF_RTSPSession *sess); +/*! forces a reset of the state to GF_RTSP_STATE_INIT +\param sess the target RTSP session +*/ +void gf_rtsp_reset_aggregation(GF_RTSPSession *sess); + +/*! sends an RTSP request to the server. +\param sess the target RTSP session +\param com the RTSP command to send +\return error if any +*/ +GF_Err gf_rtsp_send_command(GF_RTSPSession *sess, GF_RTSPCommand *com); + + +/*! callback function for interleaved RTSP/TCP transport +\param sess the target RTSP session +\param cbk_ptr opaque data +\param buffer RTP or RTCP packet +\param bufferSize packet size in bytes +\param IsRTCP set to GF_TRUE if the packet is an RTCP packet, GF_FALSE otherwise +\return error if any +*/ +typedef GF_Err (*gf_rtsp_interleave_callback)(GF_RTSPSession *sess, void *cbk_ptr, u8 *buffer, u32 bufferSize, Bool IsRTCP); + +/*! assigns the callback function for interleaved RTSP/TCP transport +\param sess the target RTSP session +\param SignalData the callback function on each interleaved packet +\return error if any +*/ +GF_Err gf_rtsp_set_interleave_callback(GF_RTSPSession *sess, gf_rtsp_interleave_callback SignalData); + +/*! reads RTSP session (response fetch and interleaved RTSP/TCP transport) +\param sess the target RTSP session +\return error if any +*/ +GF_Err gf_rtsp_session_read(GF_RTSPSession *sess); + +/*! registers a new interleaved RTP channel over an RTSP connection +\param sess the target RTSP session +\param the_ch opaque data passed to \ref gf_rtsp_interleave_callback +\param LowInterID ID of the RTP interleave channel +\param HighInterID ID of the RCTP interleave channel +\return error if any +*/ +GF_Err gf_rtsp_register_interleave(GF_RTSPSession *sess, void *the_ch, u8 LowInterID, u8 HighInterID); +/*! unregisters a new interleaved RTP channel over an RTSP connection +\param sess the target RTSP session +\param LowInterID ID of the RTP interleave channel +\return the numbers of registered interleaved channels remaining +*/ +u32 gf_rtsp_unregister_interleave(GF_RTSPSession *sess, u8 LowInterID); + + +/*! creates a new RTSP session from an existing socket in listen state. If no pending connection + is detected, return NULL +\param rtsp_listener the listening server socket +\return the newly allocated RTSP session if any, NULL otherwise +*/ +GF_RTSPSession *gf_rtsp_session_new_server(GF_Socket *rtsp_listener); + +/*! fetches an RTSP request +\param sess the target RTSP session +\param com the RTSP command to fill with the command. This will be reseted before fetch +\return error if any +*/ +GF_Err gf_rtsp_get_command(GF_RTSPSession *sess, GF_RTSPCommand *com); + +/*! generates a session ID for the given session +\param sess the target RTSP session +\return an allocated string containing a session ID +*/ +char *gf_rtsp_generate_session_id(GF_RTSPSession *sess); + +/*! sends an RTSP response +\param sess the target RTSP session +\param rsp the response to send +\return error if any +*/ +GF_Err gf_rtsp_send_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp); + +/*! gets the IP address of the local host running the session +\param sess the target RTSP session +\param buffer buffer to store the local host name +\return error if any +*/ +GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char buffer[GF_MAX_IP_NAME_LEN]); + +/*! gets the IP address of the connected peer +\param sess the target RTSP session +\param buffer buffer to store the connected peer name +\return error if any +*/ +GF_Err gf_rtsp_get_remote_address(GF_RTSPSession *sess, char *buffer); + +/*! writes a packet on an interleaved channel over RTSP +\param sess the target RTSP session +\param idx ID (RTP or RTCP) of the interleaved channel +\param pck packet data (RTP or RTCP packet) to write +\param pck_size packet size in bytes +\return error if any +*/ +GF_Err gf_rtsp_session_write_interleaved(GF_RTSPSession *sess, u32 idx, u8 *pck, u32 pck_size); + +/* + RTP LIB EXPORTS +*/ + +/*! RTP header */ +typedef struct tagRTP_HEADER { + /*version, must be 2*/ + u8 Version; + /*padding bits in the payload*/ + u8 Padding; + /*header extension is defined*/ + u8 Extension; + /*number of CSRC (<=15)*/ + u8 CSRCCount; + /*Marker Bit*/ + u8 Marker; + /*payload type on 7 bits*/ + u8 PayloadType; + /*packet seq number*/ + u16 SequenceNumber; + /*packet time stamp*/ + u32 TimeStamp; + /*sync source identifier*/ + u32 SSRC; + /*in our basic client, CSRC should always be NULL*/ + u32 CSRC[16]; + + /*internal to out lib*/ + u64 recomputed_ntp_ts; +} GF_RTPHeader; + + +/*! RTPMap information*/ +typedef struct +{ + /*dynamic payload type of this map*/ + u32 PayloadType; + /*registered payload name of this map*/ + char *payload_name; + /*RTP clock rate (TS resolution) of this map*/ + u32 ClockRate; + /*optional parameters for audio, specifying number of channels. Unused for other media types.*/ + u32 AudioChannels; +} GF_RTPMap; + + +/*! RTP channel*/ +typedef struct __tag_rtp_channel GF_RTPChannel; + +/*! creates a new RTP channel +\return a newly allocated RTP channel +*/ +GF_RTPChannel *gf_rtp_new(); +/*! destroys an RTP channel +\param ch the target RTP channel +*/ +void gf_rtp_del(GF_RTPChannel *ch); + +/*! setup transport for an RTP channel +A server channelis configured through the transport structure, with the same info as a +client channel, the client_port_* info designing the REMOTE client and port_* designing +the server channel +\param ch the target RTP channel +\param trans_info the transport info for this channel +\param remote_address the remote / destination address of the channel +\return error if any +*/ +GF_Err gf_rtp_setup_transport(GF_RTPChannel *ch, GF_RTSPTransport *trans_info, const char *remote_address); + +/*! setup of rtp/rtcp transport ports +This only applies in unicast, non interleaved cases. +For multicast port setup MUST be done through the above gf_rtp_setup_transport function +This will take care of port reuse +\param ch the target RTP channel +\param first_port RTP port number of the RTP channel +\return error if any +*/ +GF_Err gf_rtp_set_ports(GF_RTPChannel *ch, u16 first_port); + +/*! init of RTP payload information. Only ONE payload per sync source is supported in this +version of the library (a sender cannot switch payload types on a single media) +\param ch the target RTP channel +\param PayloadType identifier of RTP payload +\param ClockRate clock rate in (1/Hz) of RTP channel +\return error if any +*/ +GF_Err gf_rtp_setup_payload(GF_RTPChannel *ch, u32 PayloadType, u32 ClockRate); + +/*! enables sending of NAT keep-alive packets for NAT traversal +\param ch the target RTP channel +\param nat_timeout specifies the inactivity period in ms after which NAT keepalive packets are sent. If 0, disables NAT keep-alive packets +*/ +void gf_rtp_enable_nat_keepalive(GF_RTPChannel *ch, u32 nat_timeout); + + +/*! initializes the RTP channel. +\param ch the target RTP channel +\param UDPBufferSize UDP stack buffer size if configurable by OS/ISP - ignored otherwise +\note On WinCE devices, this is not configurable on an app bases but for the whole OS +you must update the device registry with: +\code + [HKEY_LOCAL_MACHINE\Comm\Afd] + DgramBuffer=dword:N +\endcode + + where N is the number of UDP datagrams a socket should be able to buffer. For multimedia +app you should set N as large as possible. The device MUST be reseted for the param to take effect + +\param IsSource if true, the channel is a sender (media data, sender report, Receiver report processing) +if source, you must specify the Path MTU size. The RTP lib won't send any packet bigger than this size +your application shall perform payload size splitting if needed +\param PathMTU desired path MTU (max packet size) in bytes +\param ReorederingSize max number of packets to queue for reordering. 0 means no reordering +\param MaxReorderDelay max time to wait in ms before releasing first packet in reoderer when only one packet is present. +If 0 and reordering size is specified, defaults to 200 ms (usually enough). + +\param local_interface_ip local interface address to use for multicast. If NULL, default address is used +\return error if any +*/ +GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u32 PathMTU, u32 ReorederingSize, u32 MaxReorderDelay, char *local_interface_ip); + +/*! stops the RTP channel. This destrpys RTP and RTCP sockets as well as packet reorderer +\param ch the target RTP channel +\return error if any +*/ +GF_Err gf_rtp_stop(GF_RTPChannel *ch); + +/*! inits the RTP info after a PLAY or PAUSE, rtp_time is the rtp TimeStamp of the RTP packet +with seq_num sequence number. This info is needed to compute the CurrentTime of the RTP channel +ssrc may not be known if sender hasn't indicated it (use 0 then) +\param ch the target RTP channel +\param seq_num the seq num of the next packet to be received +\param rtp_time the time in RTP timescale of the next packet to be received +\param ssrc the SSRC identifier of the next packet to be received +\return error if any +*/ +GF_Err gf_rtp_set_info_rtp(GF_RTPChannel *ch, u32 seq_num, u32 rtp_time, u32 ssrc); + +/*! retrieves current RTP time in sec. If rtp_time was unknown (not on demand media) the time is absolute. +Otherwise this is the time in ms elapsed since the last PLAY range start value +Not supported yet if played without RTSP (aka RTCP time not supported) +\param ch the target RTP channel +\return NTP clock in seconds +*/ +Double gf_rtp_get_current_time(GF_RTPChannel *ch); + +/*! resets both sockets and packet reorderer +\param ch the target RTP channel +*/ +void gf_rtp_reset_buffers(GF_RTPChannel *ch); + +/*! resets sender SSRC +\param ch the target RTP channel +*/ +void gf_rtp_reset_ssrc(GF_RTPChannel *ch); + +/*! reads any RTP data on UDP only (not valid for TCP). Performs re-ordering if configured for it +\param ch the target RTP channel +\param buffer the buffer where to store the RTP packet +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_read_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); + +/*! flushes any pending data in packet reorderer, but does not flush packet reorderer if reorderer timeout is not exceeded +\param ch the target RTP channel +\param buffer the buffer where to store the data +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_flush_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); + +/*! reads any RTCP data on UDP only (not valid for TCP). Performs re-ordering if configured for it +\param ch the target RTP channel +\param buffer the buffer where to store the RTCP packet +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_read_rtcp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); + +/*! flushes any pending data in packet reorderer, and flushes packet reorderer if reorderer timeout is not exceeded. Typically called several times until returning 0. +\param ch the target RTP channel +\param buffer the buffer where to store the data +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_read_flush(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); + +/*! decodes an RTP packet and gets the beginning of the RTP payload +\param ch the target RTP channel +\param pck the RTP packet buffer +\param pck_size the size of the RTP packet +\param rtp_hdr filled with decoded RTP header information +\param PayloadStart set to the offset in bytes of the start of the payload in the RTP packet +\return error if any +*/ +GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, GF_RTPHeader *rtp_hdr, u32 *PayloadStart); + +/*! decodes an RTCP packet and update timing info, send ReceiverReport too +\param ch the target RTP channel +\param pck the RTP packet buffer +\param pck_size the size of the RTP packet +\param has_sr set to GF_TRUE if the RTCP packet contained an SenderReport +\return error if any +*/ +GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, Bool *has_sr); + +/*! computes and send Receiver report. +If the channel is a TCP channel, you must specify +the callback function. +\note Many RTP implementation do NOT process RTCP info received on TCP... +the lib will decide whether the report shall be sent or not, therefore you should call +this function at regular times +\param ch the target RTP channel +\return error if any +*/ +GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch); + +/*! sends a BYE info (leaving the session) +\param ch the target RTP channel +\return error if any +*/ +GF_Err gf_rtp_send_bye(GF_RTPChannel *ch); + + +/*! sends an RTP packet. In fast_send mode, +\param ch the target RTP channel +\param rtp_hdr the RTP header of the packet +\param pck the RTP payload buffer +\param pck_size the RTP payload size +\param fast_send if set, the payload buffer contains 12 bytes available BEFORE its indicated start where the RTP header is written in place +\return error if any +*/ +GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, u8 *pck, u32 pck_size, Bool fast_send); + + +/*! callback used for writing rtp over TCP +\param cbk1 opaque user data +\param cbk2 opaque user data +\param is_rtcp indicates the data is an RTCP packet +\param pck the RTP/RTCP buffer +\param pck_size the RTP/RTCP size +\return error if any +*/ +typedef GF_Err (*gf_rtp_tcp_callback)(void *cbk1, void *cbk2, Bool is_rtcp, u8 *pck, u32 pck_size); + +/*! sets RTP interleaved callback on the RTP channel +\param ch the target RTP channel +\param tcp_callback the callback function +\param cbk1 user data for the callback function +\param cbk2 user data for the callback function +\return error if any +*/ +GF_Err gf_rtp_set_interleave_callbacks(GF_RTPChannel *ch, gf_rtp_tcp_callback tcp_callback, void *cbk1, void *cbk2); + +/*! checks if an RTP channel is unicast +\param ch the target RTP channel +\return GF_TRUE if unicast, GF_FALSE otherwise +*/ +u32 gf_rtp_is_unicast(GF_RTPChannel *ch); +/*! checks if an RTP channel is interleaved +\param ch the target RTP channel +\return GF_TRUE if interleaved, GF_FALSE otherwise +*/ +u32 gf_rtp_is_interleaved(GF_RTPChannel *ch); +/*! gets clockrate/timescale of an RTP channel +\param ch the target RTP channel +\return the RTP clock rate +*/ +u32 gf_rtp_get_clockrate(GF_RTPChannel *ch); +/*! gets the low interleave ID of an RTP channel +\param ch the target RTP channel +\return the low (RTP) interleave ID of the channel, or 0 if not interleaved +*/ +u8 gf_rtp_get_low_interleave_id(GF_RTPChannel *ch); +/*! gets the high interleave ID of an RTP channel +\param ch the target RTP channel +\return the high (RTCP) interleave ID of the channel, or 0 if not interleaved +*/ +u8 gf_rtp_get_hight_interleave_id(GF_RTPChannel *ch); +/*! gets the transport associated with an RTP channel +\param ch the target RTP channel +\return the RTSP transport information +*/ +const GF_RTSPTransport *gf_rtp_get_transport(GF_RTPChannel *ch); + +/*! gets loss rate of the RTP channel +\param ch the target RTP channel +\return the loss rate of the channel, between 0 and 100 % +*/ +Float gf_rtp_get_loss(GF_RTPChannel *ch); +/*! gets number of TCP bytes send for an interleaved channel +\param ch the target RTP channel +\return the number of bytes sent +*/ +u32 gf_rtp_get_tcp_bytes_sent(GF_RTPChannel *ch); +/*! gets ports of an non-interleaved RTP channel +\param ch the target RTP channel +\param rtp_port the RTP port number +\param rtcp_port the RCTP port number +*/ +void gf_rtp_get_ports(GF_RTPChannel *ch, u16 *rtp_port, u16 *rtcp_port); + + + +/**************************************************************************** + + SDP LIBRARY EXPORTS + + SDP is mainly a text protocol with + well defined containers. The following structures are used to write / read + SDP informations, and the library also provides consistency checking + + When reading SDP, all text items/structures are allocated by the lib, and you + must call gf_sdp_info_reset(GF_SDPInfo *sdp) or gf_sdp_info_del(GF_SDPInfo *sdp) to release the memory + + When writing the SDP from a GF_SDPInfo, the output buffer is allocated by the library, + and you must release it yourself + + Some quick constructors are available for GF_SDPConnection and GF_SDPMedia in order to set up + some specific parameters to their default value +****************************************************************************/ + +/*! Extension Attribute + + All attributes x-ZZZZ are considered as extensions attributes. If no "x-" is found + the attributes in the RTSP response is SKIPPED. The "x-" radical is removed in the structure + when parsing commands / responses +*/ +typedef struct +{ + char *Name; + char *Value; +} GF_X_Attribute; + + +/*! SDP bandwidth info*/ +typedef struct +{ + /*"CT", "AS" are defined. Private extensions must be "X-*" ( * "are recommended to be short")*/ + char *name; + /*in kBitsPerSec*/ + u32 value; +} GF_SDPBandwidth; + +/*! SDP maximum time offset +We do not support more than this offset / zone adjustment +if more are needed, RFC recommends to use several entries rather than a big offset*/ +#define GF_SDP_MAX_TIMEOFFSET 10 + +/*! SDP Timing information*/ +typedef struct +{ + /*NPT time in sec*/ + u32 StartTime; + /*if 0, session is unbound. NPT time in sec*/ + u32 StopTime; + /*if 0 session is not repeated. Expressed in sec. + Session is signaled repeated every repeatInterval*/ + u32 RepeatInterval; + /*active duration of the session in sec*/ + u32 ActiveDuration; + + /*time offsets to use with repeat. Specify a non-regular repeat time from the Start time*/ + u32 OffsetFromStart[GF_SDP_MAX_TIMEOFFSET]; + /*Number of offsets*/ + u32 NbRepeatOffsets; + + /*EX of repeat: + a session happens 3 times a week, on mon 1PM, thu 3PM and fri 10AM + 1- StartTime should be NPT for the session on the very first monday, StopTime + the end of this session + 2- the repeatInterval should be 1 week, ActiveDuration the length of the session + 3- 3 offsets: 0 (for monday) (3*24+2)*3600 for thu and (4*24-3) for fri + */ + + + /*timezone adjustments, to cope with #timezones, daylight saving countries and co ... + Ex: adjTime = [2882844526 2898848070] adjOffset=[-1h 0] + [0]: at 2882844526 the time base by which the session's repeat times are calculated + is shifted back by 1 hour + [1]: at time 2898848070 the session's original time base is restored + */ + + /*Adjustment time at which the corresponding time offset is to be applied to the + session time line (time used to compute the "repeat session"). + All Expressed in NPT*/ + u32 AdjustmentTime[GF_SDP_MAX_TIMEOFFSET]; + /* Offset with the session time line, ALWAYS ABSOLUTE OFFSET TO the specified StartTime*/ + s32 AdjustmentOffset[GF_SDP_MAX_TIMEOFFSET]; + /*Number of offsets.*/ + u32 NbZoneOffsets; +} GF_SDPTiming; + +/*! SDP Connection information*/ +typedef struct +{ + /*only "IN" currently defined*/ + char *net_type; + /*"IP4","IP6"*/ + char *add_type; + /*hex IPv6 address or doted IPv4 address*/ + char *host; + /*TTL - MUST BE PRESENT if IP is multicast - -1 otherwise*/ + s32 TTL; + /*multiple address counts - ONLY in media descriptions if needed. This + is used for content scaling, when # quality of the same media are multicasted on + # IP addresses*/ + u32 add_count; +} GF_SDPConnection; + +/*! SDP FormatTypePayload + + description of dynamic payload types. This is opaque at the SDP level. + Each attributes is assumed to be formatted as + If not the case the attribute will have an empty value string and only the + parameter name. +*/ +typedef struct +{ + /*payload type of the format described*/ + u32 PayloadType; + /*list of GF_X_Attribute elements. The Value field may be NULL*/ + GF_List *Attributes; +} GF_SDP_FMTP; + +/*! SDP Media information*/ +typedef struct +{ + /*m= + 0: application - 1:video - 2: audio - 3: text - 4:data - 5: control*/ + u32 Type; + /*Port Number - For transports based on UDP, the value should be in the range 1024 + to 65535 inclusive. For RTP compliance it should be an even number*/ + u32 PortNumber; + /*number of ports described. If >= 2, the next media(s) in the SDP will be configured + to use the next tuple (for RTP). If 0 or 1, ignored + \note This is used for scalable media: PortNumber indicates the port of the base + media and NumPorts the ports||total number of the upper layers*/ + u32 NumPorts; + /*currently only "RTP/AVP" and "udp" defined*/ + char *Profile; + + /*list of GF_SDPConnection's. A media can have several connection in case of scalable content*/ + GF_List *Connections; + + /*RTPMaps contains a list SDPRTPMaps*/ + GF_List *RTPMaps; + + /*FMTP contains a list of FMTP structures*/ + GF_List *FMTP; + + /*for RTP this is PayloadType, but can be opaque (string) depending on the app. + Formatted as XX WW QQ FF + When reading the SDP, the payloads defined in RTPMap are removed from this list + When writing the SDP for RTP, you should only specify static payload types here, + as dynamic ones are stored in RTPMaps and automatically written*/ + char *fmt_list; + + /*all attributes not defined in RFC 2327 for the media*/ + GF_List *Attributes; + + /*Other SDP attributes for media desc*/ + + /*k= + method is 'clear' (key follows), 'base64' (key in base64), 'uri' (key is the URI) + or 'prompt' (key not included)*/ + char *k_method, *k_key; + + GF_List *Bandwidths; + + /*0 if not present*/ + u32 PacketTime; + /*0: none - 1: recv, 2: send, 3 both*/ + u32 SendReceive; + char *orientation, *sdplang, *lang; + /*for video only, 0.0 if not present*/ + Double FrameRate; + /*between 0 and 10, -1 if not present*/ + s32 Quality; +} GF_SDPMedia; + +/*! SDP information*/ +typedef struct +{ + /*v=*/ + u32 Version; + /*o=*/ + char *o_username, *o_session_id, *o_version, *o_address; + /*"IN" for Net, "IP4" or "IP6" for address are currently valid*/ + char *o_net_type, *o_add_type; + + /*s=*/ + char *s_session_name; + /*i=*/ + char *i_description; + /*u=*/ + char *u_uri; + /*e=*/ + char *e_email; + /*p=*/ + char *p_phone; + /*c= either 1 or 0 GF_SDPConnection */ + GF_SDPConnection *c_connection; + /*b=*/ + GF_List *b_bandwidth; + /*All time info (t, r, z)*/ + GF_List *Timing; + /*k= + method is 'clear' (key follows), 'base64' (key in base64), 'uri' (key is the URI) + or 'prompt' (key not included)*/ + char *k_method, *k_key; + /*all possible attributes (a=), session level*/ + char *a_cat, *a_keywds, *a_tool; + /*0: none, 1: recv, 2: send, 3 both*/ + u32 a_SendReceive; + /*should be `broadcast', `meeting', `moderated', `test' or `H332'*/ + char *a_type; + char *a_charset; + char *a_sdplang, *a_lang; + + /*all attributes not defined in RFC 2327 for the presentation*/ + GF_List *Attributes; + + /*list of media in the SDP*/ + GF_List *media_desc; +} GF_SDPInfo; + + +/* +*/ +/*! creates a new SDP info +\return the newly allocated SDP +*/ +GF_SDPInfo *gf_sdp_info_new(); +/*! destrucs an SDP info + Memory Consideration: the destructors free all non-NULL string. You should therefore + be careful while (de-)assigning the strings. The function gf_sdp_info_parse() performs a complete + reset of the GF_SDPInfo + +\param sdp the target SDP to destroy +*/ +void gf_sdp_info_del(GF_SDPInfo *sdp); +/*! resets all structures and destroys substructures too +\param sdp the target SDP to destroy +*/ +void gf_sdp_info_reset(GF_SDPInfo *sdp); +/*! parses a memory SDP buffer +\param sdp the target SDP to destroy +\param sdp_text the encoded SDP buffer +\param text_size sizeo if the encoded SDP buffer +\return error if any +*/ +GF_Err gf_sdp_info_parse(GF_SDPInfo *sdp, char *sdp_text, u32 text_size); + + +/*! creates a new SDP media +\return the newly allocated SDP media +*/ +GF_SDPMedia *gf_sdp_media_new(); +/*! destroys an SDP media +\param media the target SDP media +*/ +void gf_sdp_media_del(GF_SDPMedia *media); + +/*! creates a new SDP connection +\return the newly allocated SDP connection +*/ +GF_SDPConnection *gf_sdp_conn_new(); +/*! destroys an SDP connection +\param conn the target SDP connection +*/ +void gf_sdp_conn_del(GF_SDPConnection *conn); + +/*! creates a new SDP payload +\return the newly allocated SDP payload +*/ +GF_SDP_FMTP *gf_sdp_fmtp_new(); +/*! destroys an SDP payload +\param fmtp the target SDP payload +*/ +void gf_sdp_fmtp_del(GF_SDP_FMTP *fmtp); + + + +/* + RTP packetizer +*/ + +/*! Mapping between RTP and GPAC / MPEG-4 Systems SyncLayer */ +typedef struct +{ + /*1 - required options*/ + + /*mode, or "" if no mode ("generic" should be used instead)*/ + char mode[30]; + + /*config of the stream if carried in SDP*/ + u8 *config; + u32 configSize; + /* Stream Type*/ + u8 StreamType; + /* stream profile and level indication - for AVC/H264, 0xPPCCLL, with PP:profile, CC:compatibility, LL:level*/ + u32 PL_ID; + + /*rvc config of the stream if carried in SDP*/ + u16 rvc_predef; + u8 *rvc_config; + u32 rvc_config_size; + + /*2 - optional options*/ + + /*size of AUs if constant*/ + u32 ConstantSize; + /*duration of AUs if constant, in RTP timescale*/ + u32 ConstantDuration; + + /* CodecID */ + u32 CodecID; + /*audio max displacement when interleaving (eg, de-interleaving window buffer max length) in RTP timescale*/ + u32 maxDisplacement; + /*de-interleaveBufferSize if not recomputable from maxDisplacement*/ + u32 deinterleaveBufferSize; + + /*The number of bits on which the AU-size field is encoded in the AU-header*/ + u32 SizeLength; + /*The number of bits on which the AU-Index is encoded in the first AU-header*/ + u32 IndexLength; + /*The number of bits on which the AU-Index-delta field is encoded in any non-first AU-header*/ + u32 IndexDeltaLength; + + /*The number of bits on which the DTS-delta field is encoded in the AU-header*/ + u32 DTSDeltaLength; + /*The number of bits on which the CTS-delta field is encoded in the AU-header*/ + u32 CTSDeltaLength; + /*random access point flag present*/ + Bool RandomAccessIndication; + + /*The number of bits on which the Stream-state field is encoded in the AU-header (systems only)*/ + u32 StreamStateIndication; + /*The number of bits that is used to encode the auxiliary-data-size field + (no normative usage of this section)*/ + u32 AuxiliaryDataSizeLength; + + /*ISMACryp stuff*/ + u8 IV_length, IV_delta_length; + u8 KI_length; + + /*internal stuff*/ + /*len of first AU header in an RTP payload*/ + u32 auh_first_min_len; + /*len of non-first AU header in an RTP payload*/ + u32 auh_min_len; +} GP_RTPSLMap; + + +/*! packetizer config flags - some flags are dynamically re-assigned when detecting multiSL / B-Frames / ...*/ +enum +{ + /*forces MPEG-4 generic transport if MPEG-4 systems mapping is available*/ + GP_RTP_PCK_FORCE_MPEG4 = (1), + /*Enables AUs concatenation in an RTP packet (if payload supports it) - this forces GP_RTP_PCK_SIGNAL_SIZE for MPEG-4*/ + GP_RTP_PCK_USE_MULTI = (1<<1), + /*if set, audio interleaving is used if payload supports it (forces GP_RTP_PCK_USE_MULTI flag) + THIS IS CURRENTLY NOT IMPLEMENTED*/ + GP_RTP_PCK_USE_INTERLEAVING = (1<<2), + /*uses static RTP payloadID if any defined*/ + GP_RTP_PCK_USE_STATIC_ID = (1<<3), + + /*MPEG-4 generic transport option*/ + /*if flag set, RAP flag is signaled in RTP payload*/ + GP_RTP_PCK_SIGNAL_RAP = (1<<4), + /*if flag set, AU indexes are signaled in RTP payload - only usable for AU interleaving (eg audio)*/ + GP_RTP_PCK_SIGNAL_AU_IDX = (1<<5), + /*if flag set, AU size is signaled in RTP payload*/ + GP_RTP_PCK_SIGNAL_SIZE = (1<<6), + /*if flag set, CTS is signaled in RTP payload - DTS is automatically set if needed*/ + GP_RTP_PCK_SIGNAL_TS = (1<<7), + + /*setup payload for carouseling of systems streams*/ + GP_RTP_PCK_SYSTEMS_CAROUSEL = (1<<8), + + /*use LATM payload for AAC-LC*/ + GP_RTP_PCK_USE_LATM_AAC = (1<<9), + + /*ISMACryp options*/ + /*signals that input data is selectively encrypted (eg not all input frames are encrypted) + - this is usually automatically set by hinter*/ + GP_RTP_PCK_SELECTIVE_ENCRYPTION = (1<<10), + /*signals that each sample will have its own key indicator - ignored in non-multi modes + if not set and key indicator changes, a new RTP packet will be forced*/ + GP_RTP_PCK_KEY_IDX_PER_AU = (1<<11), + + /*is zip compression used in DIMS unit ?*/ + GP_RTP_DIMS_COMPRESSED = (1<<12), +}; + + + +/* + Generic packetization tools - used by track hinters and future live tools +*/ + +/*! Supported payload types*/ +enum +{ + /*assigned payload types*/ + /*cf RFC 3551*/ + GF_RTP_PAYT_PCMU = 0, + /*cf RFC 3551*/ + GF_RTP_PAYT_GSM, + /*cf RFC 3551*/ + GF_RTP_PAYT_G723, + /*cf RFC 3551*/ + GF_RTP_PAYT_DVI4_8K, + /*cf RFC 3551*/ + GF_RTP_PAYT_DVI4_16K, + /*cf RFC 3551*/ + GF_RTP_PAYT_LPC, + /*cf RFC 3551*/ + GF_RTP_PAYT_PCMA, + /*cf RFC 3551*/ + GF_RTP_PAYT_G722, + /*cf RFC 3551*/ + GF_RTP_PAYT_L16_STEREO, + /*cf RFC 3551*/ + GF_RTP_PAYT_L16_MONO, + /*cf RFC 3551*/ + GF_RTP_PAYT_QCELP_BASIC, + /*cf RFC 3389*/ + GF_RTP_PAYT_CN, + /*use generic MPEG-1/2 audio transport - RFC 2250*/ + GF_RTP_PAYT_MPEG12_AUDIO, + /*cf RFC 3551*/ + GF_RTP_PAYT_G728, + GF_RTP_PAYT_DVI4_11K, + GF_RTP_PAYT_DVI4_22K, + /*cf RFC 3551*/ + GF_RTP_PAYT_G729, + /*cf RFC 2029*/ + GF_RTP_PAYT_CelB = 25, + /*cf RFC 2435*/ + GF_RTP_PAYT_JPEG = 26, + /*cf RFC 3551*/ + GF_RTP_PAYT_nv = 28, + /*cf RFC 4587*/ + GF_RTP_PAYT_H261 = 31, + /* generic MPEG-1/2 video transport - RFC 2250*/ + GF_RTP_PAYT_MPEG12_VIDEO = 32, + /*MPEG-2 TS - RFC 2250*/ + GF_RTP_PAYT_MP2T = 33, + /*use H263 transport - RFC 2429*/ + GF_RTP_PAYT_H263 = 34, + + GF_RTP_PAYT_LAST_STATIC_DEFINED = 35, + + /*not defined*/ + GF_RTP_PAYT_UNKNOWN = 128, + + /*internal types for all dynamic payloads*/ + + /*use generic MPEG-4 transport - RFC 3016 and RFC 3640*/ + GF_RTP_PAYT_MPEG4, + /*use AMR transport - RFC 3267*/ + GF_RTP_PAYT_AMR, + /*use AMR-WB transport - RFC 3267*/ + GF_RTP_PAYT_AMR_WB, + /*use QCELP transport - RFC 2658*/ + GF_RTP_PAYT_QCELP, + /*use EVRC/SMV transport - RFC 3558*/ + GF_RTP_PAYT_EVRC_SMV, + /*use 3GPP Text transport - no RFC yet, only draft*/ + GF_RTP_PAYT_3GPP_TEXT, + /*use H264 transport - no RFC yet, only draft*/ + GF_RTP_PAYT_H264_AVC, + /*use LATM for AAC-LC*/ + GF_RTP_PAYT_LATM, + /*use AC3 audio format*/ + GF_RTP_PAYT_AC3, + /*use H264-SVC transport*/ + GF_RTP_PAYT_H264_SVC, + /*use HEVC/H265 transport (RFC 7798)*/ + GF_RTP_PAYT_HEVC, + GF_RTP_PAYT_LHVC, +#if GPAC_ENABLE_3GPP_DIMS_RTP + /*use 3GPP DIMS format*/ + GF_RTP_PAYT_3GPP_DIMS, +#endif + /*use VVC transport (no RFC yet)*/ + GF_RTP_PAYT_VVC, +}; + + + +/* + RTP packetizer +*/ + + +/*! RTP builder (packetizer)*/ +typedef struct __tag_rtp_packetizer GP_RTPPacketizer; + +/*! creates a new builder +\param rtp_payt rtp payload format, one of the above GF_RTP_PAYT_* +\param slc user-given SL config to use. If none specified, default RFC config is used +\param flags packetizer flags, one of the above GP_RTP_PCK_* +\param cbk_obj callback object passed back in functions +\param OnNewPacket callback function starting new RTP packet + header: rtp header for new packet - note that RTP header flags are not used until PacketDone is called +\param OnPacketDone callback function closing current RTP packet + header: final rtp header for packet +\param OnDataReference optional, to call each time data from input buffer is added to current RTP packet. + If not set, data must be added through OnData + payload_size: size of reference data + offset_from_orig: start offset in input buffer +\param OnData to call each time data is added to current RTP packet (either extra data from payload or + data from input when not using referencing) + is_head: signal the data added MUST be inserted at the beginning of the payload. Otherwise data is concatenated as received +\return a new RTP packetizer object +*/ +GP_RTPPacketizer *gf_rtp_builder_new(u32 rtp_payt, + GF_SLConfig *slc, + u32 flags, + void *cbk_obj, + void (*OnNewPacket)(void *cbk, GF_RTPHeader *header), + void (*OnPacketDone)(void *cbk, GF_RTPHeader *header), + void (*OnDataReference)(void *cbk, u32 payload_size, u32 offset_from_orig), + void (*OnData)(void *cbk, u8 *data, u32 data_size, Bool is_head) + ); + +/*! destroys an RTP packetizer +\param builder the target RTP packetizer +*/ +void gf_rtp_builder_del(GP_RTPPacketizer *builder); + +/*! inits the RTP packetizer +\param builder the target RTP packetizer +\param PayloadType the payload type to use +\param MaxPayloadSize maximum payload size of RTP packets (eg MTU minus IP/UDP/RTP headers) +\param max_ptime maximum packet duration IN RTP TIMESCALE +\param StreamType MPEG-4 system stream type - MUST always be provided for payloads format specifying + audio or video streams +\param codecid ID of the media codec +\param PL_ID profile and level identifier for the stream + + *** all other params are for RFC 3640 *** + +\param avgSize average size of an AU. This is not always known (real-time encoding). +In this case you should specify a rough compute indicating how many packets could be +stored per RTP packet. for ex AAC stereo at 44100 k / 64kbps , one AU ~= 380 bytes +so 3 AUs for 1500 MTU is ok - BE CAREFUL: MultiSL adds some SL info on top of the 12 +byte RTP header so you should specify a smaller size +The packetizer will ALWAYS make sure there's no pb storing the packets so specifying +more will result in a slight overhead in the SL mapping but the gain to singleSL +will still be worth it. + -Nota: at init, the packetizer can decide to switch to SingleSL if the average size +specified is too close to the PathMTU + +\param maxSize max size of an AU. If unknown (real-time) set to 0 +\param avgTS average CTS progression (1000/FPS for video) +\param maxDTS maximum DTS offset in case of bidirectional coding. +\param IV_length size (in bytes) of IV when ISMACrypted +\param KI_length size (in bytes) of key indicator when ISMACrypted +\param pref_mode MPEG-4 generic only, specifies the payload mode - can be NULL (mode generic) +*/ +void gf_rtp_builder_init(GP_RTPPacketizer *builder, u8 PayloadType, u32 MaxPayloadSize, u32 max_ptime, + u32 StreamType, u32 codecid, u32 PL_ID, + u32 avgSize, u32 maxSize, + u32 avgTS, u32 maxDTS, + u32 IV_length, u32 KI_length, + char *pref_mode); + +/*! sets frame crypto info (ISMA E&A) for frame starting in next RTP packet +\param builder the target RTP packetizer +\param IV initialization vector +\param key_indicator key indicator +\param is_encrypted encrypted flag +*/ +void gf_rtp_builder_set_cryp_info(GP_RTPPacketizer *builder, u64 IV, char *key_indicator, Bool is_encrypted); +/*! packetizes input buffer +\param builder the target RTP packetizer +\param data input buffer +\param data_size input buffer size +\param IsAUEnd set to one if this buffer is the last of the AU +\param FullAUSize complete access unit size if known, 0 otherwise +\param duration sample duration in rtp timescale (mostly needed for 3GPP text streams) +\param descIndex sample description index (mostly needed for 3GPP text streams) +\return error if any +*/ +GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex); + +/*! 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 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); +/*! formats SDP payload name and media name +\param builder the target RTP packetizer +\param payload_name the buffer to fill with the payload name +\param media_name the buffer to fill with the payload name +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *builder, char payload_name[20], char media_name[20]); + + +/*! rtp payload flags*/ +enum +{ + /*AU end was detected (eg next packet is AU start)*/ + GF_RTP_NEW_AU = (1), + /*AMR config*/ + GF_RTP_AMR_ALIGN = (1<<1), + /*for RFC3016, signals bitstream inspection for RAP discovery*/ + GF_RTP_M4V_CHECK_RAP = (1<<2), + + /*AWFULL hack at rtp level to cope with ffmpeg h264 crashes when jumping in stream without IDR*/ + GF_RTP_AVC_WAIT_RAP = (1<<4), + /*ISMACryp stuff*/ + GF_RTP_HAS_ISMACRYP = (1<<5), + GF_RTP_ISMA_SEL_ENC = (1<<6), + GF_RTP_ISMA_HAS_KEY_IDX = (1<<7), + + GF_RTP_AVC_USE_ANNEX_B = (1<<8) +}; + +/*! Static RTP map definition, for static payload types*/ +typedef struct rtp_static_payt { + u32 fmt; + u32 clock_rate; + u32 stream_type; + u32 codec_id; + const char *mime; +} GF_RTPStaticMap; + + +/*! RTP depacketizer callback +\param udta opaque user data +\param payload depacketized payload data +\param size payload size +\param hdr MPEG-4 Sync Layer structure for the payload (with AU start/end flag, timestamps, etc) +\param e error if any +*/ +typedef void (*gf_rtp_packet_cbk)(void *udta, u8 *payload, u32 size, GF_SLHeader *hdr, GF_Err e); + + + +/*! RTP parser (depacketizer)*/ +typedef struct __tag_rtp_depacketizer GF_RTPDepacketizer; + +/*! creates a new depacketizer +\param media the SDP media structure describing the RTP stream - can be NULL for static payload types +\param hdr_payt the static RTP payload type when no SDP is used +\param sl_packet_cbk callback function of the depacketizer to retrieves payload +\param udta opaque data for the callback function +\return a newly allocated RTP depacketizer, or NULL if not supported*/ +GF_RTPDepacketizer *gf_rtp_depacketizer_new(GF_SDPMedia *media, u32 hdr_payt, gf_rtp_packet_cbk sl_packet_cbk, void *udta); +/*! destroys an RTP depacketizer +\param rtpd the target RTP depacketizer +*/ +void gf_rtp_depacketizer_del(GF_RTPDepacketizer *rtpd); +/*! resets an RTP depacketizer, assuming next packet should be an AU start +\param rtpd the target RTP depacketizer +\param full_reset if set, completely reset the SL header +*/ +void gf_rtp_depacketizer_reset(GF_RTPDepacketizer *rtpd, Bool full_reset); +/*! process an RTP depacketizer +\param rtpd the target RTP depacketizer +\param hdr the RTP packet header +\param payload the RTP packet payload +\param size the RTP packet payload size +*/ +void gf_rtp_depacketizer_process(GF_RTPDepacketizer *rtpd, GF_RTPHeader *hdr, u8 *payload, u32 size); + +#endif /*GPAC_DISABLE_STREAMING*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_IETF_H_*/ + diff --git a/include/gpac/internal/avilib.h b/include/gpac/internal/avilib.h new file mode 100644 index 0000000..5d31166 --- /dev/null +++ b/include/gpac/internal/avilib.h @@ -0,0 +1,427 @@ +/* + * avilib.h + * + * Copyright (C) Thomas Östreich - June 2001 + * multiple audio track support Copyright (C) 2002 Thomas Östreich + * + * Original code: + * Copyright (C) 1999 Rainer Johanni + * + * This file is part of transcode, a linux video stream processing tool + * + * transcode 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. + * + * transcode 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_AVILIB_H_ +#define _GF_AVILIB_H_ + +#include + +#ifndef GPAC_DISABLE_AVILIB + +#define AVI_MAX_TRACKS 8 + +typedef struct +{ + u64 key; + u64 pos; + u64 len; +} video_index_entry; + +typedef struct +{ + u64 pos; + u64 len; + u64 tot; +} audio_index_entry; + + +// Index types + + +#define AVI_INDEX_OF_INDEXES 0x00 // when each entry in aIndex +// array points to an index chunk +#define AVI_INDEX_OF_CHUNKS 0x01 // when each entry in aIndex +// array points to a chunk in the file +#define AVI_INDEX_IS_DATA 0x80 // when each entry is aIndex is +// really the data +// bIndexSubtype codes for INDEX_OF_CHUNKS +// +#define AVI_INDEX_2FIELD 0x01 // when fields within frames +// are also indexed + + + +typedef struct _avisuperindex_entry { + u64 qwOffset; // absolute file offset + u32 dwSize; // size of index chunk at this offset + u32 dwDuration; // time span in stream ticks +} avisuperindex_entry; + +typedef struct _avistdindex_entry { + u32 dwOffset; // qwBaseOffset + this is absolute file offset + u32 dwSize; // bit 31 is set if this is NOT a keyframe +} avistdindex_entry; + +// Standard index +typedef struct _avistdindex_chunk { + char fcc[4]; // ix## + u32 dwSize; // size of this chunk + u16 wLongsPerEntry; // must be sizeof(aIndex[0])/sizeof(DWORD) + u8 bIndexSubType; // must be 0 + u8 bIndexType; // must be AVI_INDEX_OF_CHUNKS + u32 nEntriesInUse; // + char dwChunkId[4]; // '##dc' or '##db' or '##wb' etc.. + u64 qwBaseOffset; // all dwOffsets in aIndex array are relative to this + u32 dwReserved3; // must be 0 + avistdindex_entry *aIndex; +} avistdindex_chunk; + + +// Base Index Form 'indx' +typedef struct _avisuperindex_chunk { + char fcc[4]; + u32 dwSize; // size of this chunk + u16 wLongsPerEntry; // size of each entry in aIndex array (must be 8 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 + char dwChunkId[4]; // fcc of what is indexed + u32 dwReserved[3]; // meaning differs for each index type/subtype. + // 0 if unused + avisuperindex_entry *aIndex; // where are the ix## chunks + avistdindex_chunk **stdindex; // the ix## chunks itself (array) +} avisuperindex_chunk; + + + +typedef struct track_s +{ + + int a_fmt; /* Audio format, see #defines below */ + int a_chans; /* Audio channels, 0 for no audio */ + int a_rate; /* Rate in Hz */ + int a_bits; /* bits per audio sample */ + int mp3rate; /* mp3 bitrate kbs*/ + int a_vbr; /* 0 == no Variable BitRate */ + int padrate; /* byte rate used for zero padding */ + + int audio_strn; /* Audio stream number */ + u64 audio_bytes; /* Total number of bytes of audio data */ + int audio_chunks; /* Chunks of audio data in the file */ + + char audio_tag[4]; /* Tag of audio data */ + int audio_posc; /* Audio position: chunk */ + int audio_posb; /* Audio position: byte within chunk */ + + u64 a_codech_off; /* absolut offset of audio codec information */ + u64 a_codecf_off; /* absolut offset of audio codec information */ + + audio_index_entry *audio_index; + avisuperindex_chunk *audio_superindex; + +} track_t; + +typedef struct +{ + u32 bi_size; + u32 bi_width; + u32 bi_height; + u16 bi_planes; + u16 bi_bit_count; + u32 bi_compression; + u32 bi_size_image; + u32 bi_x_pels_per_meter; + u32 bi_y_pels_per_meter; + u32 bi_clr_used; + u32 bi_clr_important; +} alBITMAPINFOHEADER; + +typedef struct +{ + u16 w_format_tag; + u16 n_channels; + u32 n_samples_per_sec; + u32 n_avg_bytes_per_sec; + u16 n_block_align; + u16 w_bits_per_sample; + u16 cb_size; +} alWAVEFORMATEX; + +typedef struct +{ + u32 fcc_type; + u32 fcc_handler; + u32 dw_flags; + u32 dw_caps; + u16 w_priority; + u16 w_language; + u32 dw_scale; + u32 dw_rate; + u32 dw_start; + u32 dw_length; + u32 dw_initial_frames; + u32 dw_suggested_buffer_size; + u32 dw_quality; + u32 dw_sample_size; + u16 dw_left; + u16 dw_top; + u16 dw_right; + u16 dw_bottom; +} alAVISTREAMHEADER; + +typedef struct +{ + + FILE *fdes; /* File descriptor of AVI file */ + int mode; /* 0 for reading, 1 for writing */ + + int width; /* Width of a video frame */ + int height; /* Height of a video frame */ + double fps; /* Frames per second */ + char compressor[8]; /* Type of compressor, 4 bytes + padding for 0 byte */ + char compressor2[8]; /* Type of compressor, 4 bytes + padding for 0 byte */ + u32 video_strn; /* Video stream number */ + int video_frames; /* Number of video frames */ + char video_tag[4]; /* Tag of video data */ + int video_pos; /* Number of next frame to be read + (if index present) */ + alAVISTREAMHEADER video_stream_header; + + u32 max_len; /* maximum video chunk present */ + + track_t track[AVI_MAX_TRACKS]; // up to AVI_MAX_TRACKS audio tracks supported + + s64 pos; /* position in file */ + int n_idx; /* number of index entries actually filled */ + int max_idx; /* number of index entries actually allocated */ + + s64 v_codech_off; /* absolut offset of video codec (strh) info */ + s64 v_codecf_off; /* absolut offset of video codec (strf) info */ + + u8 (*idx)[16]; /* index entries (AVI idx1 tag) */ + + video_index_entry *video_index; + avisuperindex_chunk *video_superindex; /* index of indices */ + int is_opendml; /* set to 1 if this is an odml file with multiple index chunks */ + + s64 last_pos; /* Position of last frame written */ + u32 last_len; /* Length of last frame written */ + int must_use_index; /* Flag if frames are duplicated */ + s64 movi_start; + int total_frames; /* total number of frames if dmlh is present */ + + u32 anum; // total number of audio tracks + u32 aptr; // current audio working track + char *index_file; // read the avi index from this file + + alBITMAPINFOHEADER *bitmap_info_header; + alWAVEFORMATEX *wave_format_ex[AVI_MAX_TRACKS]; + alAVISTREAMHEADER stream_headers[AVI_MAX_TRACKS]; + + void* extradata; + unsigned int extradata_size; + + // add a new riff chunk after this amount of bytes + u64 new_riff_threshold; +} avi_t; + +#define AVI_MODE_WRITE 0 +#define AVI_MODE_READ 1 + +/* The error codes delivered by avi_open_input_file */ + +#define AVI_ERR_SIZELIM 1 /* The write of the data would exceed + the maximum size of the AVI file. + This is more a warning than an error + since the file may be closed safely */ + +#define AVI_ERR_OPEN 2 /* Error opening the AVI file - wrong path + name or file nor readable/writable */ + +#define AVI_ERR_READ 3 /* Error reading from AVI File */ + +#define AVI_ERR_WRITE 4 /* Error writing to AVI File, + disk full ??? */ + +#define AVI_ERR_WRITE_INDEX 5 /* Could not write index to AVI file + during close, file may still be + usable */ + +#define AVI_ERR_CLOSE 6 /* Could not write header to AVI file + or not truncate the file during close, + file is most probably corrupted */ + +#define AVI_ERR_NOT_PERM 7 /* Operation not permitted: + trying to read from a file open + for writing or vice versa */ + +#define AVI_ERR_NO_MEM 8 /* malloc failed */ + +#define AVI_ERR_NO_AVI 9 /* Not an AVI file */ + +#define AVI_ERR_NO_HDRL 10 /* AVI file has no has no header list, + corrupted ??? */ + +#define AVI_ERR_NO_MOVI 11 /* AVI file has no has no MOVI list, + corrupted ??? */ + +#define AVI_ERR_NO_VIDS 12 /* AVI file contains no video data */ + +#define AVI_ERR_NO_IDX 13 /* The file has been opened with + getIndex==0, but an operation has been + performed that needs an index */ + +/* Possible Audio formats */ + +#ifndef WAVE_FORMAT_PCM +#define WAVE_FORMAT_UNKNOWN (0x0000) +#ifndef WAVE_FORMAT_PCM +#define WAVE_FORMAT_PCM (0x0001) +#endif +#endif + +#define WAVE_FORMAT_ADPCM (0x0002) +#define WAVE_FORMAT_IBM_CVSD (0x0005) +#define WAVE_FORMAT_ALAW (0x0006) +#define WAVE_FORMAT_MULAW (0x0007) +#define WAVE_FORMAT_OKI_ADPCM (0x0010) +#define WAVE_FORMAT_DVI_ADPCM (0x0011) +#define WAVE_FORMAT_DIGISTD (0x0015) +#define WAVE_FORMAT_DIGIFIX (0x0016) +#define WAVE_FORMAT_YAMAHA_ADPCM (0x0020) +#define WAVE_FORMAT_DSP_TRUESPEECH (0x0022) +#define WAVE_FORMAT_GSM610 (0x0031) +#define IBM_FORMAT_MULAW (0x0101) +#define IBM_FORMAT_ALAW (0x0102) +#define IBM_FORMAT_ADPCM (0x0103) +#define WAVE_FORMAT_MP3 (0x0055) +#define WAVE_FORMAT_AAC_ADTS (0x706d) +#define WAVE_FORMAT_AAC (0x00FF) +#define WAVE_FORMAT_AC3 (0x2000) + +avi_t* AVI_open_output_file(char * filename, u64 opendml_threshold); +void AVI_set_video(avi_t *AVI, int width, int height, double fps, char *compressor); +void AVI_set_audio(avi_t *AVI, int channels, int rate, int bits, int format, int mp3rate); +int AVI_write_frame(avi_t *AVI, u8 *data, int bytes, int keyframe); +int AVI_write_audio(avi_t *AVI, u8 *data, int bytes); +int AVI_close(avi_t *AVI); + +avi_t *AVI_open_input_file(char *filename, int getIndex); +avi_t *AVI_open_input_indexfile(char *filename, int getIndex, char *indexfile); +avi_t *AVI_open_indexfd(FILE *fd, int getIndex, char *indexfile); +int avi_parse_input_file(avi_t *AVI, int getIndex); +int avi_parse_index_from_file(avi_t *AVI, char *filename); +int AVI_audio_mp3rate(avi_t *AVI); +int AVI_video_frames(avi_t *AVI); +int AVI_video_width(avi_t *AVI); +int AVI_video_height(avi_t *AVI); +double AVI_frame_rate(avi_t *AVI); +char* AVI_video_compressor(avi_t *AVI); + +int AVI_audio_channels(avi_t *AVI); +int AVI_audio_bits(avi_t *AVI); +int AVI_audio_format(avi_t *AVI); +int AVI_audio_rate(avi_t *AVI); +u64 AVI_audio_bytes(avi_t *AVI); +int AVI_audio_chunks(avi_t *AVI); + +int AVI_frame_size(avi_t *AVI, int frame); +int AVI_audio_size(avi_t *AVI, int frame); + +u64 AVI_get_video_position(avi_t *AVI, int frame); +int AVI_read_frame(avi_t *AVI, u8 *vidbuf, int *keyframe); + +int AVI_read_audio(avi_t *AVI, u8 *audbuf, int bytes, int *continuous); + +int AVI_seek_start(avi_t *AVI); +int AVI_set_video_position(avi_t *AVI, int frame); +int AVI_set_audio_position(avi_t *AVI, int byte); + +int AVI_scan(char *name); +int AVI_dump(char *name, int mode); + +char *AVI_codec2str(short cc); +int AVI_file_check(char *import_file); + +void AVI_info(avi_t *avifile); +int avi_update_header(avi_t *AVI); + +int AVI_set_audio_track(avi_t *AVI, u32 track); +int AVI_get_audio_track(avi_t *AVI); +int AVI_audio_tracks(avi_t *AVI); + +void AVI_set_comment_fd(avi_t *AVI, int fd); +int AVI_get_comment_fd(avi_t *AVI); + +struct riff_struct +{ + u8 id[4]; /* RIFF */ + u32 len; + u8 wave_id[4]; /* WAVE */ +}; + + +struct chunk_struct +{ + u8 id[4]; + u32 len; +}; + +struct common_struct +{ + u16 wFormatTag; + u16 wChannels; + u32 dwSamplesPerSec; + u32 dwAvgBytesPerSec; + u16 wBlockAlign; + u16 wBitsPerSample; /* Only for PCM */ +}; + +struct wave_header +{ + struct riff_struct riff; + struct chunk_struct format; + struct common_struct common; + struct chunk_struct data; +}; + +// Simple WAV IO +int AVI_read_wave_header( int fd, struct wave_header * wave ); +int AVI_write_wave_header( int fd, const struct wave_header * wave ); +size_t AVI_read_wave_pcm_data( int fd, void * buffer, size_t buflen ); +size_t AVI_write_wave_pcm_data( int fd, const void * buffer, size_t buflen ); + + +struct AVIStreamHeader { + int fccType; + int fccHandler; + int dwFlags; + int dwPriority; + int dwInitialFrames; + int dwScale; + int dwRate; + int dwStart; + int dwLength; + int dwSuggestedBufferSize; + int dwQuality; + int dwSampleSize; +}; + +#endif /*GPAC_DISABLE_AVILIB*/ + +#endif /*_GF_AVILIB_H_*/ + diff --git a/include/gpac/internal/bifs_dev.h b/include/gpac/internal/bifs_dev.h new file mode 100644 index 0000000..2adcfa5 --- /dev/null +++ b/include/gpac/internal/bifs_dev.h @@ -0,0 +1,242 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / BIFS codec 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_BIFS_DEV_H_ +#define _GF_BIFS_DEV_H_ + + +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_BIFS + +/*defined to support BIFS predictive MF fields*/ +//#define GPAC_ENABLE_BIFS_PMF + +typedef struct { + /*node this mask is for*/ + GF_Node *node; + /*in case node is not defined yet*/ + u32 node_id; + /*the rest is not needed at the current time, we only support simple signaling for FDP, BDP and IFS2D + which are using predefined masks*/ +} BIFSElementaryMask; + +typedef struct +{ + /*v1 or v2*/ + u8 version; + /*BIFS config - common fields*/ + u16 NodeIDBits; + u16 RouteIDBits; + Bool PixelMetrics; + /*set to 0, 0 if no size is specified*/ + u16 Width, Height; + + /*BIFS-Anim - not supported */ + /*if 1 the BIFS_Anim codec is reset at each intra frame*/ + Bool BAnimRAP; + /*list of elementary masks for BIFS anim*/ + GF_List *elementaryMasks; + + /*BIFS v2 add-on*/ + Bool Use3DMeshCoding; + Bool UsePredictiveMFField; + u16 ProtoIDBits; +} BIFSConfig; + + + +/*per_stream config support*/ +typedef struct +{ + BIFSConfig config; + u16 ESID; +} BIFSStreamInfo; + +/*per_stream config support*/ +typedef struct +{ + //node is registered with NULL as parent + GF_Node *node; + SFCommandBuffer *cb; +} CommandBufferItem; + + +struct __tag_bifs_dec +{ + GF_Err LastError; + /*all attached streams*/ + GF_List *streamInfo; + /*active stream*/ + BIFSStreamInfo *info; + + Bool UseName; + Bool has_conditionnals; + + GF_SceneGraph *scenegraph; + /*modified during conditional execution / proto parsing*/ + GF_SceneGraph *current_graph; + + /*Quantization*/ + /*QP stack*/ + GF_List *QPs; + /*active QP*/ + M_QuantizationParameter *ActiveQP; + + /*QP 14 stuff: we need to store the last numb of fields in the last received Coord //field (!!!)*/ + + /*number of iten in the Coord field*/ + u32 NumCoord; + Bool coord_stored, storing_coord; + + /*only set at SceneReplace during proto parsing, NULL otherwise*/ + GF_Proto *pCurrentProto; + + /*when set the decoder works with commands rather than modifying the scene graph directly*/ + Bool dec_memory_mode; + Bool force_keep_qp; + /*only set in mem mode. Conditionals/InputSensors are stacked while decoding, then decoded once the AU is decoded + to make sure all nodes potentially used by the conditional command buffer are created*/ + GF_List *command_buffers; + + Bool ignore_size; + Bool is_com_dec; + Double cts_offset; + +}; + + +/*decodes an GF_Node*/ +GF_Node *gf_bifs_dec_node(GF_BifsDecoder * codec, GF_BitStream *bs, u32 NDT_Tag); +/*decodes an SFField (to get a ptr to the field, use gf_node_get_field ) +the FieldIndex is used for Quantzation*/ +GF_Err gf_bifs_dec_sf_field(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node, GF_FieldInfo *field, Bool is_mem_com); +/*decodes a Field (either SF or MF). The field MUST BE EMPTY*/ +GF_Err gf_bifs_dec_field(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node, GF_FieldInfo *field, Bool is_mem_com); +/*decodes a route*/ +GF_Err gf_bifs_dec_route(GF_BifsDecoder * codec, GF_BitStream *bs, Bool is_insert); +/*get name*/ +void gf_bifs_dec_name(GF_BitStream *bs, char *name, u32 size); + +BIFSStreamInfo *gf_bifs_dec_get_stream(GF_BifsDecoder * codec, u16 ESID); +/*decodes a BIFS command frame*/ +GF_Err gf_bifs_dec_command(GF_BifsDecoder * codec, GF_BitStream *bs); +/*decodes proto list - if proto_list is not NULL, protos parsed are not registered with the parent graph +and added to the list*/ +GF_Err gf_bifs_dec_proto_list(GF_BifsDecoder * codec, GF_BitStream *bs, GF_List *proto_list); +/*decodes field(s) of a node - exported for MultipleReplace*/ +GF_Err gf_bifs_dec_node_list(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node, Bool is_proto); +GF_Err gf_bifs_dec_node_mask(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node, Bool is_proto); + +/*called once a field has been modified through a command, send eventOut or propagate eventIn if needed*/ +void gf_bifs_check_field_change(GF_Node *node, GF_FieldInfo *field); + +GF_Err gf_bifs_flush_command_list(GF_BifsDecoder *codec); + +#ifndef GPAC_DISABLE_BIFS_ENC + +struct __tag_bifs_enc +{ + GF_Err LastError; + /*all attached streams*/ + GF_List *streamInfo; + /*active stream*/ + BIFSStreamInfo *info; + + Bool UseName; + + /*the scene graph the codec is encoding (set htrough ReplaceScene or manually)*/ + GF_SceneGraph *scene_graph; + /*current proto graph for DEF/USE*/ + GF_SceneGraph *current_proto_graph; + + /*Quantization*/ + /*QP stack*/ + GF_List *QPs; + /*active QP*/ + M_QuantizationParameter *ActiveQP; + + u32 NumCoord; + Bool coord_stored, storing_coord; + + GF_Proto *encoding_proto; + + /*keep track of DEF/USE*/ + GF_List *encoded_nodes; + Bool is_encoding_command; + + char *src_url; +}; + +GF_Err gf_bifs_enc_commands(GF_BifsEncoder *codec, GF_List *comList, GF_BitStream *bs); + +GF_Err gf_bifs_enc_node(GF_BifsEncoder * codec, GF_Node *node, u32 NDT_Tag, GF_BitStream *bs, GF_Node *parent_node); +GF_Err gf_bifs_enc_sf_field(GF_BifsEncoder *codec, GF_BitStream *bs, GF_Node *node, GF_FieldInfo *field); +GF_Err gf_bifs_enc_field(GF_BifsEncoder * codec, GF_BitStream *bs, GF_Node *node, GF_FieldInfo *field); +GF_Err gf_bifs_enc_mf_field(GF_BifsEncoder *codec, GF_BitStream *bs, GF_Node *node, GF_FieldInfo *field); +GF_Err gf_bifs_enc_route(GF_BifsEncoder *codec, GF_Route *r, GF_BitStream *bs); +void gf_bifs_enc_name(GF_BifsEncoder *codec, GF_BitStream *bs, char *name); +GF_Node *gf_bifs_enc_find_node(GF_BifsEncoder *codec, u32 nodeID); + +#define GF_BIFS_WRITE_INT(codec, bs, val, nbBits, str, com) {\ + gf_bs_write_int(bs, val, nbBits); \ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("[BIFS] %s\t\t%d\t\t%d\t\t%s\n", str, nbBits, val, com ? com : "") ); \ + } \ + +GF_Route *gf_bifs_enc_is_field_ised(GF_BifsEncoder *codec, GF_Node *node, u32 fieldIndex); + +#endif /*GPAC_DISABLE_BIFS_ENC*/ + +/*get field QP and anim info*/ +Bool gf_bifs_get_aq_info(GF_Node *Node, u32 FieldIndex, u8 *QType, u8 *AType, Fixed *b_min, Fixed *b_max, u32 *QT13_bits); + +/*get the absolute field 0_based index (or ALL mode) given the field index in IndexMode*/ +GF_Err gf_bifs_get_field_index(GF_Node *Node, u32 inField, u8 IndexMode, u32 *allField); + +/*returns the opaque NodeDataType of the node "children" field if any, or 0*/ +u32 gf_bifs_get_child_table(GF_Node *Node); + +/*returns binary type of node in the given version of the desired NDT*/ +u32 gf_bifs_get_node_type(u32 NDT_Tag, u32 NodeTag, u32 Version); + +/*converts field index from all_mode to given mode*/ +GF_Err gf_bifs_field_index_by_mode(GF_Node *node, u32 all_ind, u8 indexMode, u32 *outField); + +/*return number of bits needed to code all nodes present in the specified NDT*/ +u32 gf_bifs_get_ndt_bits(u32 NDT_Tag, u32 Version); +/*return absolute node tag given its type in the NDT and the NDT version number*/ +u32 gf_bifs_ndt_get_node_type(u32 NDT_Tag, u32 NodeType, u32 Version); + + +#endif /*GPAC_DISABLE_BIFS*/ + +#endif //_GF_BIFS_DEV_H_ + + diff --git a/include/gpac/internal/bifs_tables.h b/include/gpac/internal/bifs_tables.h new file mode 100644 index 0000000..4265a8a --- /dev/null +++ b/include/gpac/internal/bifs_tables.h @@ -0,0 +1,835 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / BIFS codec 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. + * + */ + + +/* + DO NOT MOFIFY - File generated on GMT Tue Nov 08 09:10:57 2011 + + BY MPEG4Gen for GPAC Version 0.5.0 +*/ + +#ifndef _NDT_H +#define _NDT_H + +#include + + + +#ifndef GPAC_DISABLE_BIFS + + +u32 ALL_GetNodeType(const u32 *table, const u32 count, u32 NodeTag, u32 Version); + + + +/* NDT BIFS Version 1 */ + +#define SFWorldNode_V1_NUMBITS 7 +#define SFWorldNode_V1_Count 100 + +static const u32 SFWorldNode_V1_TypeToTag[100] = { + TAG_MPEG4_Anchor, TAG_MPEG4_AnimationStream, TAG_MPEG4_Appearance, TAG_MPEG4_AudioBuffer, TAG_MPEG4_AudioClip, TAG_MPEG4_AudioDelay, TAG_MPEG4_AudioFX, TAG_MPEG4_AudioMix, TAG_MPEG4_AudioSource, TAG_MPEG4_AudioSwitch, TAG_MPEG4_Background, TAG_MPEG4_Background2D, TAG_MPEG4_Billboard, TAG_MPEG4_Bitmap, TAG_MPEG4_Box, TAG_MPEG4_Circle, TAG_MPEG4_Collision, TAG_MPEG4_Color, TAG_MPEG4_ColorInterpolator, TAG_MPEG4_CompositeTexture2D, TAG_MPEG4_CompositeTexture3D, TAG_MPEG4_Conditional, TAG_MPEG4_Cone, TAG_MPEG4_Coordinate, TAG_MPEG4_Coordinate2D, TAG_MPEG4_CoordinateInterpolator, TAG_MPEG4_CoordinateInterpolator2D, TAG_MPEG4_Curve2D, TAG_MPEG4_Cylinder, TAG_MPEG4_CylinderSensor, TAG_MPEG4_DirectionalLight, TAG_MPEG4_DiscSensor, TAG_MPEG4_ElevationGrid, TAG_MPEG4_Expression, TAG_MPEG4_Extrusion, TAG_MPEG4_Face, TAG_MPEG4_FaceDefMesh, TAG_MPEG4_FaceDefTables, TAG_MPEG4_FaceDefTransform, TAG_MPEG4_FAP, TAG_MPEG4_FDP, TAG_MPEG4_FIT, TAG_MPEG4_Fog, TAG_MPEG4_FontStyle, TAG_MPEG4_Form, TAG_MPEG4_Group, TAG_MPEG4_ImageTexture, TAG_MPEG4_IndexedFaceSet, TAG_MPEG4_IndexedFaceSet2D, TAG_MPEG4_IndexedLineSet, TAG_MPEG4_IndexedLineSet2D, TAG_MPEG4_Inline, TAG_MPEG4_LOD, TAG_MPEG4_Layer2D, TAG_MPEG4_Layer3D, TAG_MPEG4_Layout, TAG_MPEG4_LineProperties, TAG_MPEG4_ListeningPoint, TAG_MPEG4_Material, TAG_MPEG4_Material2D, TAG_MPEG4_MovieTexture, TAG_MPEG4_NavigationInfo, TAG_MPEG4_Normal, TAG_MPEG4_NormalInterpolator, TAG_MPEG4_OrderedGroup, TAG_MPEG4_OrientationInterpolator, TAG_MPEG4_PixelTexture, TAG_MPEG4_PlaneSensor, TAG_MPEG4_PlaneSensor2D, TAG_MPEG4_PointLight, TAG_MPEG4_PointSet, TAG_MPEG4_PointSet2D, TAG_MPEG4_PositionInterpolator, TAG_MPEG4_PositionInterpolator2D, TAG_MPEG4_ProximitySensor2D, TAG_MPEG4_ProximitySensor, TAG_MPEG4_QuantizationParameter, TAG_MPEG4_Rectangle, TAG_MPEG4_ScalarInterpolator, TAG_MPEG4_Script, TAG_MPEG4_Shape, TAG_MPEG4_Sound, TAG_MPEG4_Sound2D, TAG_MPEG4_Sphere, TAG_MPEG4_SphereSensor, TAG_MPEG4_SpotLight, TAG_MPEG4_Switch, TAG_MPEG4_TermCap, TAG_MPEG4_Text, TAG_MPEG4_TextureCoordinate, TAG_MPEG4_TextureTransform, TAG_MPEG4_TimeSensor, TAG_MPEG4_TouchSensor, TAG_MPEG4_Transform, TAG_MPEG4_Transform2D, TAG_MPEG4_Valuator, TAG_MPEG4_Viewpoint, TAG_MPEG4_VisibilitySensor, TAG_MPEG4_Viseme, TAG_MPEG4_WorldInfo +}; + +#define SF3DNode_V1_NUMBITS 6 +#define SF3DNode_V1_Count 52 + +static const u32 SF3DNode_V1_TypeToTag[52] = { + TAG_MPEG4_Anchor, TAG_MPEG4_AnimationStream, TAG_MPEG4_Background, TAG_MPEG4_Background2D, TAG_MPEG4_Billboard, TAG_MPEG4_Collision, TAG_MPEG4_ColorInterpolator, TAG_MPEG4_Conditional, TAG_MPEG4_CoordinateInterpolator, TAG_MPEG4_CoordinateInterpolator2D, TAG_MPEG4_CylinderSensor, TAG_MPEG4_DirectionalLight, TAG_MPEG4_DiscSensor, TAG_MPEG4_Face, TAG_MPEG4_Fog, TAG_MPEG4_Form, TAG_MPEG4_Group, TAG_MPEG4_Inline, TAG_MPEG4_LOD, TAG_MPEG4_Layer2D, TAG_MPEG4_Layer3D, TAG_MPEG4_Layout, TAG_MPEG4_ListeningPoint, TAG_MPEG4_NavigationInfo, TAG_MPEG4_NormalInterpolator, TAG_MPEG4_OrderedGroup, TAG_MPEG4_OrientationInterpolator, TAG_MPEG4_PlaneSensor, TAG_MPEG4_PlaneSensor2D, TAG_MPEG4_PointLight, TAG_MPEG4_PositionInterpolator, TAG_MPEG4_PositionInterpolator2D, TAG_MPEG4_ProximitySensor2D, TAG_MPEG4_ProximitySensor, TAG_MPEG4_QuantizationParameter, TAG_MPEG4_ScalarInterpolator, TAG_MPEG4_Script, TAG_MPEG4_Shape, TAG_MPEG4_Sound, TAG_MPEG4_Sound2D, TAG_MPEG4_SphereSensor, TAG_MPEG4_SpotLight, TAG_MPEG4_Switch, TAG_MPEG4_TermCap, TAG_MPEG4_TimeSensor, TAG_MPEG4_TouchSensor, TAG_MPEG4_Transform, TAG_MPEG4_Transform2D, TAG_MPEG4_Valuator, TAG_MPEG4_Viewpoint, TAG_MPEG4_VisibilitySensor, TAG_MPEG4_WorldInfo +}; + +#define SF2DNode_V1_NUMBITS 5 +#define SF2DNode_V1_Count 31 + +static const u32 SF2DNode_V1_TypeToTag[31] = { + TAG_MPEG4_Anchor, TAG_MPEG4_AnimationStream, TAG_MPEG4_Background2D, TAG_MPEG4_ColorInterpolator, TAG_MPEG4_Conditional, TAG_MPEG4_CoordinateInterpolator2D, TAG_MPEG4_DiscSensor, TAG_MPEG4_Face, TAG_MPEG4_Form, TAG_MPEG4_Group, TAG_MPEG4_Inline, TAG_MPEG4_LOD, TAG_MPEG4_Layer2D, TAG_MPEG4_Layer3D, TAG_MPEG4_Layout, TAG_MPEG4_OrderedGroup, TAG_MPEG4_PlaneSensor2D, TAG_MPEG4_PositionInterpolator2D, TAG_MPEG4_ProximitySensor2D, TAG_MPEG4_QuantizationParameter, TAG_MPEG4_ScalarInterpolator, TAG_MPEG4_Script, TAG_MPEG4_Shape, TAG_MPEG4_Sound2D, TAG_MPEG4_Switch, TAG_MPEG4_TermCap, TAG_MPEG4_TimeSensor, TAG_MPEG4_TouchSensor, TAG_MPEG4_Transform2D, TAG_MPEG4_Valuator, TAG_MPEG4_WorldInfo +}; + +#define SFStreamingNode_V1_NUMBITS 3 +#define SFStreamingNode_V1_Count 5 + +static const u32 SFStreamingNode_V1_TypeToTag[5] = { + TAG_MPEG4_AnimationStream, TAG_MPEG4_AudioClip, TAG_MPEG4_AudioSource, TAG_MPEG4_Inline, TAG_MPEG4_MovieTexture +}; + +#define SFAppearanceNode_V1_NUMBITS 1 +#define SFAppearanceNode_V1_Count 1 + +static const u32 SFAppearanceNode_V1_TypeToTag[1] = { + TAG_MPEG4_Appearance +}; + +#define SFAudioNode_V1_NUMBITS 3 +#define SFAudioNode_V1_Count 7 + +static const u32 SFAudioNode_V1_TypeToTag[7] = { + TAG_MPEG4_AudioBuffer, TAG_MPEG4_AudioClip, TAG_MPEG4_AudioDelay, TAG_MPEG4_AudioFX, TAG_MPEG4_AudioMix, TAG_MPEG4_AudioSource, TAG_MPEG4_AudioSwitch +}; + +#define SFBackground3DNode_V1_NUMBITS 1 +#define SFBackground3DNode_V1_Count 1 + +static const u32 SFBackground3DNode_V1_TypeToTag[1] = { + TAG_MPEG4_Background +}; + +#define SFBackground2DNode_V1_NUMBITS 1 +#define SFBackground2DNode_V1_Count 1 + +static const u32 SFBackground2DNode_V1_TypeToTag[1] = { + TAG_MPEG4_Background2D +}; + +#define SFGeometryNode_V1_NUMBITS 5 +#define SFGeometryNode_V1_Count 17 + +static const u32 SFGeometryNode_V1_TypeToTag[17] = { + TAG_MPEG4_Bitmap, TAG_MPEG4_Box, TAG_MPEG4_Circle, TAG_MPEG4_Cone, TAG_MPEG4_Curve2D, TAG_MPEG4_Cylinder, TAG_MPEG4_ElevationGrid, TAG_MPEG4_Extrusion, TAG_MPEG4_IndexedFaceSet, TAG_MPEG4_IndexedFaceSet2D, TAG_MPEG4_IndexedLineSet, TAG_MPEG4_IndexedLineSet2D, TAG_MPEG4_PointSet, TAG_MPEG4_PointSet2D, TAG_MPEG4_Rectangle, TAG_MPEG4_Sphere, TAG_MPEG4_Text +}; + +#define SFColorNode_V1_NUMBITS 1 +#define SFColorNode_V1_Count 1 + +static const u32 SFColorNode_V1_TypeToTag[1] = { + TAG_MPEG4_Color +}; + +#define SFTextureNode_V1_NUMBITS 3 +#define SFTextureNode_V1_Count 5 + +static const u32 SFTextureNode_V1_TypeToTag[5] = { + TAG_MPEG4_CompositeTexture2D, TAG_MPEG4_CompositeTexture3D, TAG_MPEG4_ImageTexture, TAG_MPEG4_MovieTexture, TAG_MPEG4_PixelTexture +}; + +#define SFCoordinateNode_V1_NUMBITS 1 +#define SFCoordinateNode_V1_Count 1 + +static const u32 SFCoordinateNode_V1_TypeToTag[1] = { + TAG_MPEG4_Coordinate +}; + +#define SFCoordinate2DNode_V1_NUMBITS 1 +#define SFCoordinate2DNode_V1_Count 1 + +static const u32 SFCoordinate2DNode_V1_TypeToTag[1] = { + TAG_MPEG4_Coordinate2D +}; + +#define SFExpressionNode_V1_NUMBITS 1 +#define SFExpressionNode_V1_Count 1 + +static const u32 SFExpressionNode_V1_TypeToTag[1] = { + TAG_MPEG4_Expression +}; + +#define SFFaceDefMeshNode_V1_NUMBITS 1 +#define SFFaceDefMeshNode_V1_Count 1 + +static const u32 SFFaceDefMeshNode_V1_TypeToTag[1] = { + TAG_MPEG4_FaceDefMesh +}; + +#define SFFaceDefTablesNode_V1_NUMBITS 1 +#define SFFaceDefTablesNode_V1_Count 1 + +static const u32 SFFaceDefTablesNode_V1_TypeToTag[1] = { + TAG_MPEG4_FaceDefTables +}; + +#define SFFaceDefTransformNode_V1_NUMBITS 1 +#define SFFaceDefTransformNode_V1_Count 1 + +static const u32 SFFaceDefTransformNode_V1_TypeToTag[1] = { + TAG_MPEG4_FaceDefTransform +}; + +#define SFFAPNode_V1_NUMBITS 1 +#define SFFAPNode_V1_Count 1 + +static const u32 SFFAPNode_V1_TypeToTag[1] = { + TAG_MPEG4_FAP +}; + +#define SFFDPNode_V1_NUMBITS 1 +#define SFFDPNode_V1_Count 1 + +static const u32 SFFDPNode_V1_TypeToTag[1] = { + TAG_MPEG4_FDP +}; + +#define SFFITNode_V1_NUMBITS 1 +#define SFFITNode_V1_Count 1 + +static const u32 SFFITNode_V1_TypeToTag[1] = { + TAG_MPEG4_FIT +}; + +#define SFFogNode_V1_NUMBITS 1 +#define SFFogNode_V1_Count 1 + +static const u32 SFFogNode_V1_TypeToTag[1] = { + TAG_MPEG4_Fog +}; + +#define SFFontStyleNode_V1_NUMBITS 1 +#define SFFontStyleNode_V1_Count 1 + +static const u32 SFFontStyleNode_V1_TypeToTag[1] = { + TAG_MPEG4_FontStyle +}; + +#define SFTopNode_V1_NUMBITS 3 +#define SFTopNode_V1_Count 4 + +static const u32 SFTopNode_V1_TypeToTag[4] = { + TAG_MPEG4_Group, TAG_MPEG4_Layer2D, TAG_MPEG4_Layer3D, TAG_MPEG4_OrderedGroup +}; + +#define SFLinePropertiesNode_V1_NUMBITS 1 +#define SFLinePropertiesNode_V1_Count 1 + +static const u32 SFLinePropertiesNode_V1_TypeToTag[1] = { + TAG_MPEG4_LineProperties +}; + +#define SFMaterialNode_V1_NUMBITS 2 +#define SFMaterialNode_V1_Count 2 + +static const u32 SFMaterialNode_V1_TypeToTag[2] = { + TAG_MPEG4_Material, TAG_MPEG4_Material2D +}; + +#define SFNavigationInfoNode_V1_NUMBITS 1 +#define SFNavigationInfoNode_V1_Count 1 + +static const u32 SFNavigationInfoNode_V1_TypeToTag[1] = { + TAG_MPEG4_NavigationInfo +}; + +#define SFNormalNode_V1_NUMBITS 1 +#define SFNormalNode_V1_Count 1 + +static const u32 SFNormalNode_V1_TypeToTag[1] = { + TAG_MPEG4_Normal +}; + +#define SFTextureCoordinateNode_V1_NUMBITS 1 +#define SFTextureCoordinateNode_V1_Count 1 + +static const u32 SFTextureCoordinateNode_V1_TypeToTag[1] = { + TAG_MPEG4_TextureCoordinate +}; + +#define SFTextureTransformNode_V1_NUMBITS 1 +#define SFTextureTransformNode_V1_Count 1 + +static const u32 SFTextureTransformNode_V1_TypeToTag[1] = { + TAG_MPEG4_TextureTransform +}; + +#define SFViewpointNode_V1_NUMBITS 1 +#define SFViewpointNode_V1_Count 1 + +static const u32 SFViewpointNode_V1_TypeToTag[1] = { + TAG_MPEG4_Viewpoint +}; + +#define SFVisemeNode_V1_NUMBITS 1 +#define SFVisemeNode_V1_Count 1 + +static const u32 SFVisemeNode_V1_TypeToTag[1] = { + TAG_MPEG4_Viseme +}; + + +u32 NDT_V1_GetNumBits(u32 NDT_Tag); +u32 NDT_V1_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V1_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 2 */ + +#define SFWorldNode_V2_NUMBITS 4 +#define SFWorldNode_V2_Count 12 + +static const u32 SFWorldNode_V2_TypeToTag[12] = { + TAG_MPEG4_AcousticMaterial, TAG_MPEG4_AcousticScene, TAG_MPEG4_ApplicationWindow, TAG_MPEG4_BAP, TAG_MPEG4_BDP, TAG_MPEG4_Body, TAG_MPEG4_BodyDefTable, TAG_MPEG4_BodySegmentConnectionHint, TAG_MPEG4_DirectiveSound, TAG_MPEG4_Hierarchical3DMesh, TAG_MPEG4_MaterialKey, TAG_MPEG4_PerceptualParameters +}; + +#define SF3DNode_V2_NUMBITS 3 +#define SF3DNode_V2_Count 3 + +static const u32 SF3DNode_V2_TypeToTag[3] = { + TAG_MPEG4_AcousticScene, TAG_MPEG4_Body, TAG_MPEG4_DirectiveSound +}; + +#define SF2DNode_V2_NUMBITS 2 +#define SF2DNode_V2_Count 2 + +static const u32 SF2DNode_V2_TypeToTag[2] = { + TAG_MPEG4_ApplicationWindow, TAG_MPEG4_Body +}; + +#define SFGeometryNode_V2_NUMBITS 2 +#define SFGeometryNode_V2_Count 1 + +static const u32 SFGeometryNode_V2_TypeToTag[1] = { + TAG_MPEG4_Hierarchical3DMesh +}; + +#define SFMaterialNode_V2_NUMBITS 2 +#define SFMaterialNode_V2_Count 2 + +static const u32 SFMaterialNode_V2_TypeToTag[2] = { + TAG_MPEG4_AcousticMaterial, TAG_MPEG4_MaterialKey +}; + +#define SFBAPNode_V2_NUMBITS 2 +#define SFBAPNode_V2_Count 1 + +static const u32 SFBAPNode_V2_TypeToTag[1] = { + TAG_MPEG4_BAP +}; + +#define SFBDPNode_V2_NUMBITS 2 +#define SFBDPNode_V2_Count 1 + +static const u32 SFBDPNode_V2_TypeToTag[1] = { + TAG_MPEG4_BDP +}; + +#define SFBodyDefTableNode_V2_NUMBITS 2 +#define SFBodyDefTableNode_V2_Count 1 + +static const u32 SFBodyDefTableNode_V2_TypeToTag[1] = { + TAG_MPEG4_BodyDefTable +}; + +#define SFBodySegmentConnectionHintNode_V2_NUMBITS 2 +#define SFBodySegmentConnectionHintNode_V2_Count 1 + +static const u32 SFBodySegmentConnectionHintNode_V2_TypeToTag[1] = { + TAG_MPEG4_BodySegmentConnectionHint +}; + +#define SFPerceptualParameterNode_V2_NUMBITS 2 +#define SFPerceptualParameterNode_V2_Count 1 + +static const u32 SFPerceptualParameterNode_V2_TypeToTag[1] = { + TAG_MPEG4_PerceptualParameters +}; + + +u32 NDT_V2_GetNumBits(u32 NDT_Tag); +u32 NDT_V2_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V2_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 3 */ + +#define SFWorldNode_V3_NUMBITS 2 +#define SFWorldNode_V3_Count 3 + +static const u32 SFWorldNode_V3_TypeToTag[3] = { + TAG_MPEG4_TemporalTransform, TAG_MPEG4_TemporalGroup, TAG_MPEG4_ServerCommand +}; + +#define SF3DNode_V3_NUMBITS 2 +#define SF3DNode_V3_Count 3 + +static const u32 SF3DNode_V3_TypeToTag[3] = { + TAG_MPEG4_TemporalTransform, TAG_MPEG4_TemporalGroup, TAG_MPEG4_ServerCommand +}; + +#define SF2DNode_V3_NUMBITS 2 +#define SF2DNode_V3_Count 3 + +static const u32 SF2DNode_V3_TypeToTag[3] = { + TAG_MPEG4_TemporalTransform, TAG_MPEG4_TemporalGroup, TAG_MPEG4_ServerCommand +}; + +#define SFTemporalNode_V3_NUMBITS 2 +#define SFTemporalNode_V3_Count 2 + +static const u32 SFTemporalNode_V3_TypeToTag[2] = { + TAG_MPEG4_TemporalTransform, TAG_MPEG4_TemporalGroup +}; + + +u32 NDT_V3_GetNumBits(u32 NDT_Tag); +u32 NDT_V3_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V3_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 4 */ + +#define SFWorldNode_V4_NUMBITS 3 +#define SFWorldNode_V4_Count 5 + +static const u32 SFWorldNode_V4_TypeToTag[5] = { + TAG_MPEG4_InputSensor, TAG_MPEG4_MatteTexture, TAG_MPEG4_MediaBuffer, TAG_MPEG4_MediaControl, TAG_MPEG4_MediaSensor +}; + +#define SF3DNode_V4_NUMBITS 3 +#define SF3DNode_V4_Count 5 + +static const u32 SF3DNode_V4_TypeToTag[5] = { + TAG_MPEG4_InputSensor, TAG_MPEG4_MatteTexture, TAG_MPEG4_MediaBuffer, TAG_MPEG4_MediaControl, TAG_MPEG4_MediaSensor +}; + +#define SF2DNode_V4_NUMBITS 3 +#define SF2DNode_V4_Count 5 + +static const u32 SF2DNode_V4_TypeToTag[5] = { + TAG_MPEG4_InputSensor, TAG_MPEG4_MatteTexture, TAG_MPEG4_MediaBuffer, TAG_MPEG4_MediaControl, TAG_MPEG4_MediaSensor +}; + +#define SFTextureNode_V4_NUMBITS 1 +#define SFTextureNode_V4_Count 1 + +static const u32 SFTextureNode_V4_TypeToTag[1] = { + TAG_MPEG4_MatteTexture +}; + + +u32 NDT_V4_GetNumBits(u32 NDT_Tag); +u32 NDT_V4_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V4_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 5 */ + +#define SFWorldNode_V5_NUMBITS 6 +#define SFWorldNode_V5_Count 39 + +static const u32 SFWorldNode_V5_TypeToTag[39] = { + TAG_MPEG4_BitWrapper, TAG_MPEG4_CoordinateInterpolator4D, TAG_MPEG4_DepthImage, TAG_MPEG4_FFD, TAG_MPEG4_Implicit, TAG_MPEG4_XXLFM_Appearance, TAG_MPEG4_XXLFM_BlendList, TAG_MPEG4_XXLFM_FrameList, TAG_MPEG4_XXLFM_LightMap, TAG_MPEG4_XXLFM_SurfaceMapList, TAG_MPEG4_XXLFM_ViewMapList, TAG_MPEG4_MeshGrid, TAG_MPEG4_NonLinearDeformer, TAG_MPEG4_NurbsCurve, TAG_MPEG4_NurbsCurve2D, TAG_MPEG4_NurbsSurface, TAG_MPEG4_OctreeImage, TAG_MPEG4_XXParticles, TAG_MPEG4_XXParticleInitBox, TAG_MPEG4_XXPlanarObstacle, TAG_MPEG4_XXPointAttractor, TAG_MPEG4_PointTexture, TAG_MPEG4_PositionAnimator, TAG_MPEG4_PositionAnimator2D, TAG_MPEG4_PositionInterpolator4D, TAG_MPEG4_ProceduralTexture, TAG_MPEG4_Quadric, TAG_MPEG4_SBBone, TAG_MPEG4_SBMuscle, TAG_MPEG4_SBSegment, TAG_MPEG4_SBSite, TAG_MPEG4_SBSkinnedModel, TAG_MPEG4_SBVCAnimation, TAG_MPEG4_ScalarAnimator, TAG_MPEG4_SimpleTexture, TAG_MPEG4_SolidRep, TAG_MPEG4_SubdivisionSurface, TAG_MPEG4_SubdivSurfaceSector, TAG_MPEG4_WaveletSubdivisionSurface +}; + +#define SF3DNode_V5_NUMBITS 5 +#define SF3DNode_V5_Count 17 + +static const u32 SF3DNode_V5_TypeToTag[17] = { + TAG_MPEG4_BitWrapper, TAG_MPEG4_CoordinateInterpolator4D, TAG_MPEG4_DepthImage, TAG_MPEG4_FFD, TAG_MPEG4_OctreeImage, TAG_MPEG4_XXParticles, TAG_MPEG4_PositionAnimator, TAG_MPEG4_PositionAnimator2D, TAG_MPEG4_PositionInterpolator4D, TAG_MPEG4_SBBone, TAG_MPEG4_SBMuscle, TAG_MPEG4_SBSegment, TAG_MPEG4_SBSite, TAG_MPEG4_SBSkinnedModel, TAG_MPEG4_SBVCAnimation, TAG_MPEG4_ScalarAnimator, TAG_MPEG4_WaveletSubdivisionSurface +}; + +#define SF2DNode_V5_NUMBITS 4 +#define SF2DNode_V5_Count 9 + +static const u32 SF2DNode_V5_TypeToTag[9] = { + TAG_MPEG4_BitWrapper, TAG_MPEG4_PositionAnimator2D, TAG_MPEG4_SBBone, TAG_MPEG4_SBMuscle, TAG_MPEG4_SBSegment, TAG_MPEG4_SBSite, TAG_MPEG4_SBSkinnedModel, TAG_MPEG4_SBVCAnimation, TAG_MPEG4_ScalarAnimator +}; + +#define SFAppearanceNode_V5_NUMBITS 1 +#define SFAppearanceNode_V5_Count 1 + +static const u32 SFAppearanceNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXLFM_Appearance +}; + +#define SFGeometryNode_V5_NUMBITS 4 +#define SFGeometryNode_V5_Count 10 + +static const u32 SFGeometryNode_V5_TypeToTag[10] = { + TAG_MPEG4_BitWrapper, TAG_MPEG4_Implicit, TAG_MPEG4_MeshGrid, TAG_MPEG4_NonLinearDeformer, TAG_MPEG4_NurbsCurve, TAG_MPEG4_NurbsCurve2D, TAG_MPEG4_NurbsSurface, TAG_MPEG4_Quadric, TAG_MPEG4_SolidRep, TAG_MPEG4_SubdivisionSurface +}; + +#define SFTextureNode_V5_NUMBITS 1 +#define SFTextureNode_V5_Count 1 + +static const u32 SFTextureNode_V5_TypeToTag[1] = { + TAG_MPEG4_ProceduralTexture +}; + +#define SFDepthImageNode_V5_NUMBITS 1 +#define SFDepthImageNode_V5_Count 1 + +static const u32 SFDepthImageNode_V5_TypeToTag[1] = { + TAG_MPEG4_DepthImage +}; + +#define SFBlendListNode_V5_NUMBITS 1 +#define SFBlendListNode_V5_Count 1 + +static const u32 SFBlendListNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXLFM_BlendList +}; + +#define SFFrameListNode_V5_NUMBITS 1 +#define SFFrameListNode_V5_Count 1 + +static const u32 SFFrameListNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXLFM_FrameList +}; + +#define SFLightMapNode_V5_NUMBITS 1 +#define SFLightMapNode_V5_Count 1 + +static const u32 SFLightMapNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXLFM_LightMap +}; + +#define SFSurfaceMapNode_V5_NUMBITS 1 +#define SFSurfaceMapNode_V5_Count 1 + +static const u32 SFSurfaceMapNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXLFM_SurfaceMapList +}; + +#define SFViewMapNode_V5_NUMBITS 1 +#define SFViewMapNode_V5_Count 1 + +static const u32 SFViewMapNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXLFM_ViewMapList +}; + +#define SFParticleInitializerNode_V5_NUMBITS 1 +#define SFParticleInitializerNode_V5_Count 1 + +static const u32 SFParticleInitializerNode_V5_TypeToTag[1] = { + TAG_MPEG4_XXParticleInitBox +}; + +#define SFInfluenceNode_V5_NUMBITS 2 +#define SFInfluenceNode_V5_Count 2 + +static const u32 SFInfluenceNode_V5_TypeToTag[2] = { + TAG_MPEG4_XXPlanarObstacle, TAG_MPEG4_XXPointAttractor +}; + +#define SFDepthTextureNode_V5_NUMBITS 2 +#define SFDepthTextureNode_V5_Count 2 + +static const u32 SFDepthTextureNode_V5_TypeToTag[2] = { + TAG_MPEG4_PointTexture, TAG_MPEG4_SimpleTexture +}; + +#define SFSBBoneNode_V5_NUMBITS 1 +#define SFSBBoneNode_V5_Count 1 + +static const u32 SFSBBoneNode_V5_TypeToTag[1] = { + TAG_MPEG4_SBBone +}; + +#define SFSBMuscleNode_V5_NUMBITS 1 +#define SFSBMuscleNode_V5_Count 1 + +static const u32 SFSBMuscleNode_V5_TypeToTag[1] = { + TAG_MPEG4_SBMuscle +}; + +#define SFSBSegmentNode_V5_NUMBITS 1 +#define SFSBSegmentNode_V5_Count 1 + +static const u32 SFSBSegmentNode_V5_TypeToTag[1] = { + TAG_MPEG4_SBSegment +}; + +#define SFSBSiteNode_V5_NUMBITS 1 +#define SFSBSiteNode_V5_Count 1 + +static const u32 SFSBSiteNode_V5_TypeToTag[1] = { + TAG_MPEG4_SBSite +}; + +#define SFBaseMeshNode_V5_NUMBITS 1 +#define SFBaseMeshNode_V5_Count 1 + +static const u32 SFBaseMeshNode_V5_TypeToTag[1] = { + TAG_MPEG4_SubdivisionSurface +}; + +#define SFSubdivSurfaceSectorNode_V5_NUMBITS 1 +#define SFSubdivSurfaceSectorNode_V5_Count 1 + +static const u32 SFSubdivSurfaceSectorNode_V5_TypeToTag[1] = { + TAG_MPEG4_SubdivSurfaceSector +}; + + +u32 NDT_V5_GetNumBits(u32 NDT_Tag); +u32 NDT_V5_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V5_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 6 */ + +#define SFWorldNode_V6_NUMBITS 4 +#define SFWorldNode_V6_Count 12 + +static const u32 SFWorldNode_V6_TypeToTag[12] = { + TAG_MPEG4_Clipper2D, TAG_MPEG4_ColorTransform, TAG_MPEG4_Ellipse, TAG_MPEG4_LinearGradient, TAG_MPEG4_PathLayout, TAG_MPEG4_RadialGradient, TAG_MPEG4_SynthesizedTexture, TAG_MPEG4_TransformMatrix2D, TAG_MPEG4_Viewport, TAG_MPEG4_XCurve2D, TAG_MPEG4_XFontStyle, TAG_MPEG4_XLineProperties +}; + +#define SF3DNode_V6_NUMBITS 3 +#define SF3DNode_V6_Count 5 + +static const u32 SF3DNode_V6_TypeToTag[5] = { + TAG_MPEG4_Clipper2D, TAG_MPEG4_ColorTransform, TAG_MPEG4_PathLayout, TAG_MPEG4_TransformMatrix2D, TAG_MPEG4_Viewport +}; + +#define SF2DNode_V6_NUMBITS 3 +#define SF2DNode_V6_Count 5 + +static const u32 SF2DNode_V6_TypeToTag[5] = { + TAG_MPEG4_Clipper2D, TAG_MPEG4_ColorTransform, TAG_MPEG4_PathLayout, TAG_MPEG4_TransformMatrix2D, TAG_MPEG4_Viewport +}; + +#define SFGeometryNode_V6_NUMBITS 2 +#define SFGeometryNode_V6_Count 2 + +static const u32 SFGeometryNode_V6_TypeToTag[2] = { + TAG_MPEG4_Ellipse, TAG_MPEG4_XCurve2D +}; + +#define SFTextureNode_V6_NUMBITS 2 +#define SFTextureNode_V6_Count 3 + +static const u32 SFTextureNode_V6_TypeToTag[3] = { + TAG_MPEG4_LinearGradient, TAG_MPEG4_RadialGradient, TAG_MPEG4_SynthesizedTexture +}; + +#define SFFontStyleNode_V6_NUMBITS 1 +#define SFFontStyleNode_V6_Count 1 + +static const u32 SFFontStyleNode_V6_TypeToTag[1] = { + TAG_MPEG4_XFontStyle +}; + +#define SFLinePropertiesNode_V6_NUMBITS 1 +#define SFLinePropertiesNode_V6_Count 1 + +static const u32 SFLinePropertiesNode_V6_TypeToTag[1] = { + TAG_MPEG4_XLineProperties +}; + +#define SFTextureTransformNode_V6_NUMBITS 1 +#define SFTextureTransformNode_V6_Count 1 + +static const u32 SFTextureTransformNode_V6_TypeToTag[1] = { + TAG_MPEG4_TransformMatrix2D +}; + +#define SFViewportNode_V6_NUMBITS 1 +#define SFViewportNode_V6_Count 1 + +static const u32 SFViewportNode_V6_TypeToTag[1] = { + TAG_MPEG4_Viewport +}; + + +u32 NDT_V6_GetNumBits(u32 NDT_Tag); +u32 NDT_V6_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V6_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 7 */ + +#define SFWorldNode_V7_NUMBITS 4 +#define SFWorldNode_V7_Count 11 + +static const u32 SFWorldNode_V7_TypeToTag[11] = { + TAG_MPEG4_AdvancedAudioBuffer, TAG_MPEG4_AudioChannelConfig, TAG_MPEG4_DepthImageV2, TAG_MPEG4_MorphShape, TAG_MPEG4_MultiTexture, TAG_MPEG4_PointTextureV2, TAG_MPEG4_SBVCAnimationV2, TAG_MPEG4_SimpleTextureV2, TAG_MPEG4_SurroundingSound, TAG_MPEG4_Transform3DAudio, TAG_MPEG4_WideSound +}; + +#define SF3DNode_V7_NUMBITS 3 +#define SF3DNode_V7_Count 6 + +static const u32 SF3DNode_V7_TypeToTag[6] = { + TAG_MPEG4_DepthImageV2, TAG_MPEG4_MorphShape, TAG_MPEG4_SBVCAnimationV2, TAG_MPEG4_SurroundingSound, TAG_MPEG4_Transform3DAudio, TAG_MPEG4_WideSound +}; + +#define SF2DNode_V7_NUMBITS 2 +#define SF2DNode_V7_Count 3 + +static const u32 SF2DNode_V7_TypeToTag[3] = { + TAG_MPEG4_MorphShape, TAG_MPEG4_SBVCAnimationV2, TAG_MPEG4_Transform3DAudio +}; + +#define SFAudioNode_V7_NUMBITS 2 +#define SFAudioNode_V7_Count 2 + +static const u32 SFAudioNode_V7_TypeToTag[2] = { + TAG_MPEG4_AdvancedAudioBuffer, TAG_MPEG4_AudioChannelConfig +}; + +#define SFTextureNode_V7_NUMBITS 1 +#define SFTextureNode_V7_Count 1 + +static const u32 SFTextureNode_V7_TypeToTag[1] = { + TAG_MPEG4_MultiTexture +}; + +#define SFDepthImageNode_V7_NUMBITS 1 +#define SFDepthImageNode_V7_Count 1 + +static const u32 SFDepthImageNode_V7_TypeToTag[1] = { + TAG_MPEG4_DepthImageV2 +}; + +#define SFDepthTextureNode_V7_NUMBITS 2 +#define SFDepthTextureNode_V7_Count 2 + +static const u32 SFDepthTextureNode_V7_TypeToTag[2] = { + TAG_MPEG4_PointTextureV2, TAG_MPEG4_SimpleTextureV2 +}; + + +u32 NDT_V7_GetNumBits(u32 NDT_Tag); +u32 NDT_V7_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V7_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 8 */ + +#define SFWorldNode_V8_NUMBITS 2 +#define SFWorldNode_V8_Count 2 + +static const u32 SFWorldNode_V8_TypeToTag[2] = { + TAG_MPEG4_ScoreShape, TAG_MPEG4_MusicScore +}; + +#define SF3DNode_V8_NUMBITS 1 +#define SF3DNode_V8_Count 1 + +static const u32 SF3DNode_V8_TypeToTag[1] = { + TAG_MPEG4_ScoreShape +}; + +#define SF2DNode_V8_NUMBITS 1 +#define SF2DNode_V8_Count 1 + +static const u32 SF2DNode_V8_TypeToTag[1] = { + TAG_MPEG4_ScoreShape +}; + +#define SFMusicScoreNode_V8_NUMBITS 1 +#define SFMusicScoreNode_V8_Count 1 + +static const u32 SFMusicScoreNode_V8_TypeToTag[1] = { + TAG_MPEG4_MusicScore +}; + + +u32 NDT_V8_GetNumBits(u32 NDT_Tag); +u32 NDT_V8_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V8_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 9 */ + +#define SFWorldNode_V9_NUMBITS 3 +#define SFWorldNode_V9_Count 6 + +static const u32 SFWorldNode_V9_TypeToTag[6] = { + TAG_MPEG4_FootPrintSetNode, TAG_MPEG4_FootPrintNode, TAG_MPEG4_BuildingPartNode, TAG_MPEG4_RoofNode, TAG_MPEG4_FacadeNode, TAG_MPEG4_Shadow +}; + +#define SF3DNode_V9_NUMBITS 3 +#define SF3DNode_V9_Count 6 + +static const u32 SF3DNode_V9_TypeToTag[6] = { + TAG_MPEG4_FootPrintSetNode, TAG_MPEG4_FootPrintNode, TAG_MPEG4_BuildingPartNode, TAG_MPEG4_RoofNode, TAG_MPEG4_FacadeNode, TAG_MPEG4_Shadow +}; + +#define SFGeometryNode_V9_NUMBITS 3 +#define SFGeometryNode_V9_Count 6 + +static const u32 SFGeometryNode_V9_TypeToTag[6] = { + TAG_MPEG4_FootPrintSetNode, TAG_MPEG4_FootPrintNode, TAG_MPEG4_BuildingPartNode, TAG_MPEG4_RoofNode, TAG_MPEG4_FacadeNode, TAG_MPEG4_Shadow +}; + + +u32 NDT_V9_GetNumBits(u32 NDT_Tag); +u32 NDT_V9_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V9_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + + +/* NDT BIFS Version 10 */ + +#define SFWorldNode_V10_NUMBITS 3 +#define SFWorldNode_V10_Count 5 + +static const u32 SFWorldNode_V10_TypeToTag[5] = { + TAG_MPEG4_CacheTexture, TAG_MPEG4_EnvironmentTest, TAG_MPEG4_KeyNavigator, TAG_MPEG4_SpacePartition, TAG_MPEG4_Storage +}; + +#define SF3DNode_V10_NUMBITS 3 +#define SF3DNode_V10_Count 5 + +static const u32 SF3DNode_V10_TypeToTag[5] = { + TAG_MPEG4_CacheTexture, TAG_MPEG4_EnvironmentTest, TAG_MPEG4_KeyNavigator, TAG_MPEG4_SpacePartition, TAG_MPEG4_Storage +}; + +#define SF2DNode_V10_NUMBITS 3 +#define SF2DNode_V10_Count 4 + +static const u32 SF2DNode_V10_TypeToTag[4] = { + TAG_MPEG4_CacheTexture, TAG_MPEG4_EnvironmentTest, TAG_MPEG4_KeyNavigator, TAG_MPEG4_Storage +}; + +#define SFTextureNode_V10_NUMBITS 1 +#define SFTextureNode_V10_Count 1 + +static const u32 SFTextureNode_V10_TypeToTag[1] = { + TAG_MPEG4_CacheTexture +}; + + +u32 NDT_V10_GetNumBits(u32 NDT_Tag); +u32 NDT_V10_GetNodeTag(u32 Context_NDT_Tag, u32 NodeType); +u32 NDT_V10_GetNodeType(u32 NDT_Tag, u32 NodeTag); + + + +u32 NDT_GetChildTable(u32 NodeTag); + + + + +#endif /*GPAC_DISABLE_BIFS*/ + + + +#endif /*_NDT_H*/ + diff --git a/include/gpac/internal/camera.h b/include/gpac/internal/camera.h new file mode 100644 index 0000000..589a6c4 --- /dev/null +++ b/include/gpac/internal/camera.h @@ -0,0 +1,195 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Scene Compositor 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 _CAMERA_H_ +#define _CAMERA_H_ + +//#include +#include + +/*camera flags*/ +enum +{ + /*if set frustum needs to be recomputed + we avoid computing it at each frame/interaction since that's a lot of matrix maths*/ + CAM_IS_DIRTY = 1, + /*if set when ortho, indicates the viewport matrix shall be used when computing modelview (2D only)*/ + CAM_HAS_VIEWPORT = 1<<2, + /*if set when ortho to disable LookAt mode*/ + CAM_NO_LOOKAT = 1<<3, +}; + +enum +{ + /*only valid at root node*/ + CULL_NOT_SET = 0, + /*subtree completely outside view vol*/ + CULL_OUTSIDE, + /*subtree completely inside view vol*/ + CULL_INSIDE, + /*subtree overlaps view vol - FIXME: would be nice to keep track of intersecting planes*/ + CULL_INTERSECTS +}; + +/*navigation info flags - non-VRML ones are simply blaxxun contact ones */ +enum +{ + /*headlight is on*/ + NAV_HEADLIGHT = 1, + /*navigtion is selectable*/ + NAV_SELECTABLE = 1<<1, + /*any navigation (eg, user-interface navigation control allowed)*/ + NAV_ANY = 1<<2 +}; + +/*frustum object*/ +enum +{ + FRUS_NEAR_PLANE = 0, + FRUS_FAR_PLANE, + FRUS_LEFT_PLANE, + FRUS_RIGHT_PLANE, + FRUS_BOTTOM_PLANE, + FRUS_TOP_PLANE +}; + + + +enum +{ + /*nothing detected*/ + CF_NONE = 0, + /*collision detected*/ + CF_COLLISION = 1, + /*gravity detecion enabled*/ + CF_DO_GRAVITY = (1<<1), + /*gravity detected*/ + CF_GRAVITY = (1<<2), + /*viewpoint is stored at end of animation*/ + CF_STORE_VP = (1<<3), +}; + +typedef struct _camera +{ + /*this flag MUST be set by the owner of the camera*/ + Bool is_3D; + + u32 flags; + + /*viewport info*/ + GF_Rect vp, proj_vp; + /*not always same as VP due to aspect ratio*/ + Fixed width, height; + Fixed z_near, z_far; + + /*current vectors*/ + Fixed fieldOfView; + SFVec3f up, position, target; + + /*initial vp for reset*/ + SFVec3f vp_position; + SFRotation vp_orientation; + Fixed vp_fov, vp_dist; + + /*animation path*/ + SFVec3f start_pos, end_pos; + SFRotation start_ori, end_ori; + Fixed start_fov, end_fov; + /*for 2D cameras we never animate except for vp reset*/ + Fixed start_zoom, end_zoom; + SFVec2f start_trans, start_rot; + + /*center of examine movement*/ + SFVec3f examine_center; + + /*anim*/ + u32 anim_len, anim_start; + Bool jumping; + Fixed dheight; + + /*navigation info - overwridden by any bindable NavigationInfo node*/ + u32 navigation_flags, navigate_mode; + SFVec3f avatar_size; + Fixed visibility, speed; + Bool had_nav_info; + u32 had_viewpoint; + + /*last camera position before collision& gravity detection*/ + SFVec3f last_pos; + u32 collide_flags; + /*collision point in world coord*/ + SFVec3f collide_point; + /*collide dist in world coord, used to check if we have a closer collision*/ + Fixed collide_dist; + /*ground in world coord*/ + SFVec3f ground_point; + /*ground dist in world coord, used to check if we have a closer ground*/ + Fixed ground_dist; + /*for obstacle detection*/ + Bool last_had_ground; + Bool last_had_col; + + /*projection & modelview matrices*/ + GF_Matrix projection, modelview; + /*unprojection matrix = INV(P*M) used for screen->world compute*/ + GF_Matrix unprojection; + /*viewport matrix*/ + GF_Matrix viewport; + /*frustum planes*/ + GF_Plane planes[6]; + /*p vertex idx per plane (for bbox-frustum intersection checks)*/ + u32 p_idx[6]; + /*frustrum bounding sphere (for sphere-sphere frustum intersection checks)*/ + SFVec3f center; + Fixed radius; + + GF_BBox world_bbox; +} GF_Camera; + +/*invalidate camera to force recompute of all params*/ +void camera_invalidate(GF_Camera *cam); +/*updates camera. user transform is only used in 2D to set global user zoom/pan/translate*/ +void camera_update(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords); +/*updates camera. user transform is only used in 2D to set global user zoom/pan/translate + stereo param*/ +void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords, Fixed horizontal_shift, Fixed viewing_distance, Fixed viewing_distance_offset, u32 camlay); +/*reset to last viewport*/ +void camera_reset_viewpoint(GF_Camera *cam, Bool animate); +/*move camera to given vp*/ +void camera_move_to(GF_Camera *cam, SFVec3f pos, SFVec3f target, SFVec3f up); +Bool camera_animate(GF_Camera *cam, void *compositor); +void camera_stop_anim(GF_Camera *cam); +/*start jump mode*/ +void camera_jump(GF_Camera *cam); + +void camera_set_vectors(GF_Camera *cam, SFVec3f pos, SFRotation ori, Fixed fov); + +SFRotation camera_get_orientation(SFVec3f pos, SFVec3f target, SFVec3f up); +SFVec3f camera_get_pos_dir(GF_Camera *cam); +SFVec3f camera_get_target_dir(GF_Camera *cam); +SFVec3f camera_get_right_dir(GF_Camera *cam); +void camera_set_2d(GF_Camera *cam); + +#endif + diff --git a/include/gpac/internal/compositor_dev.h b/include/gpac/internal/compositor_dev.h new file mode 100644 index 0000000..d56f9de --- /dev/null +++ b/include/gpac/internal/compositor_dev.h @@ -0,0 +1,2599 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / Scene Rendering 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 _COMPOSITOR_DEV_H_ +#define _COMPOSITOR_DEV_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include +/*include scene graph API*/ +#include +/*bridge between the rendering engine and the systems media engine*/ +#include +#include +#include + +/*raster2D API*/ +#include +/*font engine API*/ +#include +/*AV hardware API*/ +#include +#include + +/*SVG properties*/ +#ifndef GPAC_DISABLE_SVG +#include +#endif + +#include + +#include +#include + +typedef struct _gf_scene GF_Scene; +typedef struct _gf_addon_media GF_AddonMedia; +typedef struct _object_clock GF_Clock; +//typedef struct _od_manager GF_ObjectManager; + +Bool gf_sc_send_event(GF_Compositor *compositor, GF_Event *evt); + +/*if defined, events are queued before being processed, otherwise they are handled whenever triggered*/ +//#define GF_SR_EVENT_QUEUE + + +/*use 2D caching for groups*/ +//#define GF_SR_USE_VIDEO_CACHE + +//#define GPAC_USE_TINYGL + +/*depth-enabled version for autostereoscopic displays */ +#define GF_SR_USE_DEPTH + +/*FPS computed on this number of frame*/ +#define GF_SR_FPS_COMPUTE_SIZE 60 + + + +enum +{ + GF_SR_CFG_OVERRIDE_SIZE = 1, + GF_SR_CFG_SET_SIZE = 1<<1, + GF_SR_CFG_AR = 1<<2, + GF_SR_CFG_FULLSCREEN = 1<<3, + /*flag is set whenever we're reconfiguring visual. This will discard all UI + messages during this phase in order to avoid any deadlocks*/ + GF_SR_IN_RECONFIG = 1<<4, + /*special flag indicating the set size is actually due to a notif by the plugin*/ + GF_SR_CFG_WINDOWSIZE_NOTIF = 1<<5, + /*special flag indicating this is the initial resize, and video setup should be sent*/ + GF_SR_CFG_INITIAL_RESIZE = 1<<6, +}; + + +enum +{ + GF_3D_STEREO_NONE = 0, + GF_3D_STEREO_TOP, + GF_3D_STEREO_SIDE, + GF_3D_STEREO_HEADSET, + + GF_3D_STEREO_LAST_SINGLE_BUFFER = GF_3D_STEREO_HEADSET, + + /*all modes above GF_3D_STEREO_LAST_SINGLE_BUFFER require shaders and textures for view storage*/ + + /*custom interleaving using GLSL shaders*/ + GF_3D_STEREO_CUSTOM, + /*some built-in interleaving modes*/ + /*each pixel correspond to a different view*/ + GF_3D_STEREO_COLUMNS, + GF_3D_STEREO_ROWS, + /*special case of sub-pixel interleaving for 2 views*/ + GF_3D_STEREO_ANAGLYPH, + /*SpatialView 19'' 5views interleaving*/ + GF_3D_STEREO_5VSP19, + /*Alioscopy 8 views interleaving*/ + GF_3D_STEREO_8VALIO +}; + + +/*forward definition of the visual manager*/ +typedef struct _visual_manager GF_VisualManager; +typedef struct _draw_aspect_2d DrawAspect2D; +typedef struct _traversing_state GF_TraverseState; +typedef struct _gf_ft_mgr GF_FontManager; + +#ifndef GPAC_DISABLE_3D +#include +#include + +typedef struct +{ + Bool multisample; + Bool bgra_texture; + Bool abgr_texture; + Bool npot_texture; + Bool rect_texture; + Bool point_sprite; + Bool vbo, pbo, fbo; + Bool gles2_unpack; + Bool has_shaders; + Bool npot; + s32 max_texture_size; +} GLCaps; + +#endif + +#define DOUBLECLICK_TIME_MS 250 + +enum +{ + TILE_DEBUG_NONE=0, + TILE_DEBUG_PARTIAL, + TILE_DEBUG_FULL +}; + +enum +{ + /*no text selection*/ + GF_SC_TSEL_NONE = 0, + /*text selection in progress*/ + GF_SC_TSEL_ACTIVE, + /*text selection frozen*/ + GF_SC_TSEL_FROZEN, + /*text selection has just been released*/ + GF_SC_TSEL_RELEASED, +}; + +enum +{ + GF_SC_DRAW_NONE, + GF_SC_DRAW_FRAME, + GF_SC_DRAW_FLUSH, +}; + +enum +{ + GF_SC_DEPTH_GL_NONE=0, + GF_SC_DEPTH_GL_POINTS, + GF_SC_DEPTH_GL_STRIPS, +}; + + +enum +{ + GF_SC_GLMODE_AUTO=0, + GF_SC_GLMODE_OFF, + GF_SC_GLMODE_HYBRID, + GF_SC_GLMODE_ON +}; + + +enum +{ + GF_SC_DRV_OFF=0, + GF_SC_DRV_ON, + GF_SC_DRV_AUTO, +}; + +struct __tag_compositor +{ + u32 magic; //must be "comp" + void *magic_ptr; //must point to this structure + + u32 init_flags; + void *os_wnd; + + u32 drv; + GF_Err last_error; + + //filter mode, we can be a source for our built-in URLs + char *src; + + /*audio renderer*/ + struct _audio_render *audio_renderer; + /*video out*/ + GF_VideoOutput *video_out; + + Bool softblt; + + Bool discard_input_events; + u32 video_th_id; + + /*compositor exclusive access to the scene and display*/ + GF_Mutex *mx; + + //args have been updated + Bool reload_config; + + //list of pids we need to monitor at each render pass. For now BIFS and OD only + GF_List *systems_pids; + + /*list of modules containing hardcoded proto implementations*/ + GF_List *proto_modules; + + /*the main scene graph*/ + GF_SceneGraph *scene; + /*extra scene graphs (OSD, etc), always registered in draw order. That's the module responsability + to draw them*/ + GF_List *extra_scenes; + + Bool inherit_type_3d; + + Bool force_late_frame_draw; + /*all time nodes registered*/ + GF_List *time_nodes; + /*all textures (texture handlers)*/ + GF_List *textures; + Bool texture_inserted; + + /*all textures to be destroyed (needed for OpenGL context ...)*/ + GF_List *textures_gc; + + /*event queue*/ + GF_List *event_queue, *event_queue_back; + GF_Mutex *evq_mx; + + Bool video_setup_failed; + + //dur config option + Double dur; + //simulation frame rate option + GF_Fraction fps; + //timescale option + u32 timescale; + //autofps option + Bool autofps; + //autofps option + Bool vfr; + Bool nojs; + Bool dyn_filter_mode; + Bool noback; + //frame duration in ms, used to match closest frame in input video streams + u32 frame_duration; + Bool frame_was_produced; + Bool bench_mode; + //0: no frame pending, 1: frame pending, needs clock increase, 2: frames are pending but one frame has been decoded, do not increase clock + u32 force_bench_frame; + //number of audio frames sent in call to send_frame + u32 audio_frames_sent; + + u32 frame_time[GF_SR_FPS_COMPUTE_SIZE]; + u32 frame_dur[GF_SR_FPS_COMPUTE_SIZE]; + u32 current_frame; + u32 last_frame_time, caret_next_draw_time; + Bool show_caret; + Bool text_edit_changed; + //sampled value of audio clock used in bench mode only + u32 scene_sampled_clock; + u32 last_frame_ts; + + u32 last_click_time; + s32 ms_until_next_frame; + s32 frame_delay; + Bool fullscreen_postponed; + Bool sys_frames_pending; + + Bool amc, async; + u32 asr, ach, alayout, afmt, asize, avol, apan, abuf; + Double max_aspeed, max_vspeed; + u32 buffer, rbuffer, mbuffer, ntpsync; + + u32 ogl, mode2d; + + /*display size*/ + u32 display_width, display_height; + + /*visual output location on window (we may draw background color outside of it) + vp_x & vp_y: horizontal & vertical offset of the drawing area in the video output + vp_width & vp_height: width & height of the drawing area + * in scalable mode, this is the display size + * in not scalable mode, this is the final drawing area size (dst_w & dst_h of the blit) + */ + u32 vp_x, vp_y, vp_width, vp_height; + /*backbuffer size - in scalable mode, matches display size, otherwise matches scene size*/ + u32 output_width, output_height; + Bool out8b; + u8 multiview_mode; + /*scene size if any*/ + u32 scene_width, scene_height; + Bool has_size_info; + Bool fullscreen; + /*!! paused will not stop display (this enables pausing a VRML world and still examining it)*/ + Bool paused, step_mode; + u32 frame_draw_type; + u32 force_next_frame_redraw; + /*freeze_display prevents any screen updates - needed when output driver uses direct video memory access*/ + Bool is_hidden, freeze_display; + Bool timed_nodes_valid; + //player option, by default disabled. In player mode the video driver is always loaded + //and no passthrough checks are done + u32 player; + //output pixel format option for passthrough mode, none by default + u32 opfmt; + //allocated framebuffer and size for passthrough mode + u8 *framebuffer; + u32 framebuffer_size, framebuffer_alloc; + + //passthrough texture object - only assigned by background2D + struct _gf_sc_texture_handler *passthrough_txh; + //passthrough packet - this is only created if the associated input packet doesn't use frame interface + //the packet might be using the same buffer as the input data + //otherwise, the resulting packet needs to be created from the framebuffer + GF_FilterPacket *passthrough_pck; + //data associated with the passthrough output - can be the same pointer as input packet or a clone + u8 *passthrough_data; + //timescale of the passthrough pid + u32 passthrough_timescale; + //pixel format of the passthrough pid at last emitted frame + u32 passthrough_pfmt; + //set if inplace processing is used: + //- matching size and pixel format for input packet and output framebuffer + //- inplace data processing (will skip background texture blit) + Bool passthrough_inplace; + //set to true if passthrough object is buffering, in whcih case scene clock has to be updated + //once buffering is done + Bool passthrough_check_buffer; + + //debug non-immediate mode ny erasing the parts that would have been drawn + Bool debug_defer; + + Bool disable_composite_blit, disable_hardware_blit, rebuild_offscreen_textures; + + /*current frame number*/ + u32 frame_number; + /*count number of initialized sensors*/ + u32 interaction_sensors; + + //in player mode, exit if set + //in non player mode, check for eos + u32 check_eos_state; + u32 last_check_pass; + + /*set whenever 3D HW ctx changes (need to rebuild dlists/textures if any used)*/ + u32 reset_graphics; + + /*font engine*/ + GF_FontManager *font_manager; + /*set whenever a new font has been received*/ + Bool reset_fonts; + s32 fonts_pending; + + /*options*/ + u32 aspect_ratio, aa, textxt; + Bool fast, stress; + Bool is_opengl; + Bool autoconfig_opengl; + u32 force_opengl_2d; + + //in this mode all 2D raster is done through and RGBA canvas except background IO and textures which are done by the GPU. The canvas is then flushed to GPU. + //the mode supports defer and immediate rendering + Bool hybrid_opengl; + + Bool fsize; + + /*key modif*/ + u32 key_states; + u32 interaction_level; + + //set whenever a scene has Layer3D or CompositeTexture3D + Bool needs_offscreen_gl; + + /*size override when no size info is present + flags: 1: size override is requested (cfg) + 2: size override has been applied + */ + u32 override_size_flags; + + /*any of the above flags - reseted at each simulation tick*/ + u32 msg_type; + /*for size*/ + u32 new_width, new_height; + + /*current background color*/ + u32 back_color, bc, ckey; + + /*bounding box draw type: none, unit box/rect and sphere (3D only)*/ + u32 bvol; + /*list of system colors*/ + u32 sys_colors[28]; + + /*all visual managers created*/ + GF_List *visuals; + /*all outlines cached*/ + GF_List *strike_bank; + + /*main visual manager - the one managing the primary video output*/ + GF_VisualManager *visual; + /*set to false whenever a new scene is attached to compositor*/ + Bool root_visual_setup; + + /*indicates whether the aspect ratio shall be recomputed: + 1: AR changed + 2: AR changed and root visual type changed between 2D and 3D + */ + u32 recompute_ar; + + /*to copy!*/ + u32 nbviews, stereo, camlay; + Bool rview, dbgpack; + Fixed dispdist; + char *mvshader; + + GF_PropVec2i osize, dpi; + + Bool zoom_changed; + + /*traversing context*/ + struct _traversing_state *traverse_state; + + /*current picked node if any*/ + GF_Node *grab_node; + /*current picked node's parent use if any*/ + GF_Node *grab_use; + /*current focus node if any*/ + GF_Node *focus_node; + /*parent use node of the current focus node if any*/ + GF_Node *focus_used; + /*current parent focus node if any - needed to navigate within PROTOs*/ + GF_List *focus_ancestors; + GF_List *focus_use_stack; + /*focus node uses dom events*/ + Bool focus_uses_dom_events; + /*current sensor type*/ + u32 sensor_type; + /*list of VRML sensors active before the picking phase (eg active at the previous pass)*/ + GF_List *previous_sensors; + /*list of VRML sensors active after the picking phase*/ + GF_List *sensors; + /*indicates a sensor is currently active*/ + u32 grabbed_sensor; + + /*current keynav node if any*/ + GF_Node *keynav_node; + + /*current keynav node if any*/ + GF_List *env_tests; + + /*hardware handle for 2D screen access - currently only used with win32 (HDC) */ + void *hw_context; + /*indicates whether HW is locked*/ + Bool hw_locked; + /*screen buffer for direct access*/ + GF_VideoSurface hw_surface; + /*output buffer is configured in video memory*/ + u32 video_memory; + Bool request_video_memory, was_system_memory; + /*indicate if overlays were prezsent in the previous frame*/ + Bool last_had_overlays; + + /*options*/ + Bool sz; + + Bool yuvhw; + /*disables partial hardware blit (eg during dirty rect) to avoid artefacts*/ + Bool blitp; + + /*user navigation mode*/ + u32 navigate_mode; + /*set if content doesn't allow navigation*/ + Bool navigation_disabled; + + u32 rotate_mode; + + /*user mouse navigation state: + 0: not active + 1: pre-active phase: mouse has been clicked and waiting for mouse move to confirm. This allows + for clicking on objects in the navigation mode + 2: navigation is grabbed + */ + u32 navigation_state; + /*navigation x & y grab point in scene coord system*/ + Fixed grab_x, grab_y; + /*aspect ratio scale factor*/ + Fixed scale_x, scale_y; + /*user zoom level*/ + Fixed zoom; + /*user pan*/ + Fixed trans_x, trans_y; + /*user rotation angle - ALWAYS CENTERED*/ + Fixed rotation; + + u32 auto_rotate; + + /*0: flush to be done - 1: flush can be skipped - 2: forces flush*/ + u32 skip_flush; + Bool flush_pending; + +#ifndef GPAC_DISABLE_SVG + u32 num_clicks; +#endif + + /*a dedicated drawable for focus highlight */ + struct _drawable *focus_highlight; + /*highlight fill and stroke colors (ARGB)*/ + u32 hlfill, hlline; + Fixed hllinew; + Bool disable_focus_highlight; + + /*picking info*/ + + /*picked node*/ + GF_Node *hit_node; + /*appearance at hit point - used for composite texture*/ + GF_Node *hit_appear, *prev_hit_appear; + /*parent use stack - SVG only*/ + GF_List *hit_use_stack, *prev_hit_use_stack; + /*picked node uses DOM event or VRML events ?*/ + Bool hit_use_dom_events; + + /*world->local and local->world transform at hit point*/ + GF_Matrix hit_world_to_local, hit_local_to_world; + /*hit point in local coord & world coord*/ + SFVec3f hit_local_point, hit_world_point; + /*tex coords at hit point*/ + SFVec2f hit_texcoords; + /*picking ray in world coord system*/ + GF_Ray hit_world_ray; + /*normal at hit point, local coord system*/ + SFVec3f hit_normal; + /*distance from ray origin used to discards further hits - FIXME: may not properly work with transparent layer3D*/ + Fixed hit_square_dist; + + /*text selection and edition*/ + + /*the active parent text node under selection*/ + GF_Node *text_selection; + /*text selection start/end in world coord system*/ + SFVec2f start_sel, end_sel; + /*text selection state*/ + u32 store_text_state; + /*parent text node when a text is hit (to handle tspan selection)*/ + GF_Node *hit_text; + u32 sel_buffer_len, sel_buffer_alloc; + u16 *sel_buffer; + u8 *selected_text; + /*text selection color - reverse video not yet supported*/ + u32 text_sel_color; + s32 picked_glyph_idx, picked_span_idx; + + /*set whenever the focus node is a text node*/ + u32 focus_text_type; + Bool edit_is_tspan; + /*pointer to edited text*/ + char **edited_text; + u32 caret_pos, dom_text_pos; + +#ifndef GPAC_DISABLE_3D + /*options*/ + /*emulate power-of-2 for video texturing by using a po2 texture and texture scaling. If any textureTransform + is applied to this texture, black stripes will appear on the borders. + If not set video is written through glDrawPixels with bitmap (slow scaling), or converted to + po2 texture*/ + Bool epow2; + /*use OpenGL for outline rather than vectorial ones*/ + Bool linegl; + /*disable RECT extensions (except for Bitmap...)*/ + Bool rext; + /*disable RECT extensions (except for Bitmap...)*/ + u32 norms; + /*backface cull type: 0 off, 1: on, 2: on with transparency*/ + u32 bcull; + /*polygon atialiasing*/ + Bool paa; + /*wireframe/solid mode*/ + u32 wire; + /*collision detection mode*/ + u32 collide_mode; + /*gravity enabled*/ + Bool gravity_on; + /*AABB tree-based culling is disabled*/ + Bool cull; + //use PBO to start pushing textures at the beginning of the render pass + Bool pbo; + + u32 nav; + + /*unit box (1.0 size) and unit sphere (1.0 radius)*/ + GF_Mesh *unit_bbox; + + /*active layer3D for layer navigation - may be NULL*/ + GF_Node *active_layer; + + GLCaps gl_caps; + + u32 offscreen_width, offscreen_height; + +#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + u32 shader_mode_disabled; +#endif + char *vertshader, *fragshader; + + //force video frame packing (0=no packing or GF_3D_STEREO_SIDE or GF_3D_STEREO_TOP) + u32 fpack; + +#ifdef GPAC_USE_TINYGL + void *tgl_ctx; +#endif + + Fixed depth_gl_scale, depth_gl_strips_filter; + u32 depth_gl_type; + Fixed iod; + /*increase/decrease the standard interoccular offset by the specified distance in cm*/ + Fixed interoccular_offset; + /*specifies distance the camera focal point and the screen plane : <0 is behind the screen, >0 is in front*/ + Fixed focdist; + + struct _gf_sc_texture_handler *hybgl_txh; + GF_Mesh *hybgl_mesh; + GF_Mesh *hybgl_mesh_background; + + Bool force_type_3d; + u8 *screen_buffer, *line_buffer; + u32 screen_buffer_alloc_size; + + u32 tvtn, tvtt, tvtd; + Bool tvtf; + u32 vrhud_mode; + Fixed fov; + + //offscreen rendering + u32 fbo_tx_id, fbo_id, fbo_depth_id; + Bool external_tx_id; + +#endif + + Bool orientation_sensors_active; + + Bool texture_from_decoder_memory; + + u32 networks_time; + u32 decoders_time; + + u32 visual_config_time; + u32 traverse_setup_time; + u32 traverse_and_direct_draw_time; + u32 indirect_draw_time; + + +#ifdef GF_SR_USE_VIDEO_CACHE + /*video cache size / max size in kbytes*/ + u32 video_cache_current_size, vcsize; + u32 vcscale, vctol; + /*sorted list (by cache priority) of cached groups - permanent for the lifetime of the scene/cache object*/ + GF_List *cached_groups; + /*list of groups being cached in one frame */ + GF_List *cached_groups_queue; +#endif + +#ifdef GF_SR_USE_DEPTH + Bool autocal; + /*display depth in pixels - if -1, it is the height of the display area*/ + s32 dispdepth; +#endif + + Bool gazer_enabled, sgaze; + s32 gaze_x, gaze_y; + + Bool validator_mode; + + //moved from old GF_Terminal + struct _gf_scene *root_scene; + Bool drop; + Bool sclock; + u32 timeout; + u32 play_state; + Bool use_step_mode; + Bool reload_scene_size; + //associated filter, used to load input filters + GF_Filter *filter; +// GF_FilterSession *fsess; + GF_FilterPid *vout; + GF_FilterFrameInterface frame_ifce; + GF_VideoSurface fb; + + Bool dbgpvr; + Bool noaudio; + + /*all X3D key/mouse/string sensors*/ + GF_List *x3d_sensors; + /*all input stream decoders*/ + GF_List *input_streams; + + + /*special list used by nodes needing a call to RenderNode but not in the traverese scene graph + (VRML/MPEG-4 protos only). + For such nodes the traverse state will be NULL + This is only used by InputSensor node at the moment + */ + GF_List *nodes_pending; + GF_List *extensions, *unthreaded_extensions; +}; + +typedef struct +{ + GF_Event evt; + GF_DOM_Event dom_evt; + GF_Node *node; + GF_DOMEventTarget *target; + GF_SceneGraph *sg; +} GF_QueuedEvent; + +void gf_sc_queue_dom_event(GF_Compositor *compositor, GF_Node *node, GF_DOM_Event *evt); +void gf_sc_queue_dom_event_on_target(GF_Compositor *compositor, GF_DOM_Event *evt, GF_DOMEventTarget *target, GF_SceneGraph *sg); + +/*base stack for timed nodes (nodes that activate themselves at given times) + @UpdateTimeNode: shall be setup by the node handler and is called once per simulation frame + @is_registerd: all handlers indicate store their state if wanted (provided for conveniency but not inspected by the compositor) + @needs_unregister: all handlers indicate they can be removed from the list of active time nodes +in order to save time. THIS IS INSPECTED by the compositor at each simulation tick after calling UpdateTimeNode +and if set, the node is removed right away from the list +*/ +typedef struct _time_node +{ + void (*UpdateTimeNode)(struct _time_node *); + Bool is_registered, needs_unregister; + /*user data*/ + void *udta; +} GF_TimeNode; + +void gf_sc_register_time_node(GF_Compositor *sr, GF_TimeNode *tn); +void gf_sc_unregister_time_node(GF_Compositor *sr, GF_TimeNode *tn); + +enum +{ + /*texture repeat along s*/ + GF_SR_TEXTURE_REPEAT_S = (1<<0), + /*texture repeat along t*/ + GF_SR_TEXTURE_REPEAT_T = (1<<1), + /*texture is a matte texture*/ + GF_SR_TEXTURE_MATTE = (1<<2), + /*texture doesn't need vertical flip for OpenGL*/ + GF_SR_TEXTURE_NO_GL_FLIP = (1<<3), + /*Set durin a composition cycle. If not set at the end of the cycle, + the hardware binding is released*/ + GF_SR_TEXTURE_USED = (1<<4), + + /*texture is SVG (needs special treatment in OpenGL)*/ + GF_SR_TEXTURE_SVG = (1<<5), + + /*special flag indicating the underlying media directly handled by the hardware (decoding and composition)*/ + GF_SR_TEXTURE_PRIVATE_MEDIA = (1<<6), + + /*texture blit is disabled, must go through rasterizer*/ + GF_SR_TEXTURE_DISABLE_BLIT = (1<<7), +}; + +typedef struct _gf_sc_texture_handler +{ + GF_Node *owner; + GF_Compositor *compositor; + /*low-level texture object for internal rasterizer and OpenGL - this is not exposed out of libgpac*/ + struct __texture_wrapper *tx_io; + /*media stream*/ + GF_MediaObject *stream; + /*texture is open (for DEF/USE)*/ + Bool is_open; + /*this is needed in case the Url is changed*/ +// MFURL current_url; + /*to override by each texture node*/ + void (*update_texture_fcnt)(struct _gf_sc_texture_handler *txh); + /*needs_release if a visual frame is grabbed (not used by modules)*/ + u32 needs_release; + /*stream_finished: indicates stream is over (not used by modules)*/ + Bool stream_finished; + /*needs_refresh: indicates texture content has been changed - needed by modules performing tile drawing*/ + Bool needs_refresh; + /*needed to discard same frame fetch*/ + u32 last_frame_time; + /*active display in the texture (0, 0 == top, left)*/ + //GF_Rect active_window; + /*texture is transparent*/ + Bool transparent; + /*flags for user - the repeatS and repeatT are set upon creation, the rest is NEVER touched by compositor*/ + u32 flags; + /*gradients are relative to the object bounds, therefore a gradient is not the same if used on 2 different + objects - since we don't want to build an offscreen texture for the gradient, gradients have to be updated + at each draw - the matrix shall be updated to the gradient transformation in the local system + MUST be set for gradient textures*/ + void (*compute_gradient_matrix)(struct _gf_sc_texture_handler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d); + + /*image data for natural media*/ + u8 *data; + //we need a local copy of width/height/etc since some textures may be defined without a stream object + u32 size, width, height, pixelformat, pixel_ar, stride, stride_chroma, nb_planes; + Bool is_flipped; + + GF_FilterFrameInterface *frame_ifce; + u32 nb_frames, upload_time; + +#ifndef GPAC_DISABLE_VRML + /*if set texture has been transformed by MatteTexture -> disable blit*/ + Bool has_cmat; + + /*matteTexture parent if any*/ + GF_Node *matteTexture; +#endif + + u32 probe_time_ms; + /*user data for video output module, if needed*/ + void *vout_udta; +} GF_TextureHandler; + +/*setup texturing object*/ +void gf_sc_texture_setup(GF_TextureHandler *hdl, GF_Compositor *sr, GF_Node *owner); +/*destroy texturing object*/ +void gf_sc_texture_destroy(GF_TextureHandler *txh); + +/*return texture handle for built-in textures (movieTexture, ImageTexture and PixelTexture)*/ +GF_TextureHandler *gf_sc_texture_get_handler(GF_Node *n); + +/*these ones are needed by modules only for Background(2D) handling*/ + +/*returns 1 if url changed from current one*/ +Bool gf_sc_texture_check_url_change(GF_TextureHandler *txh, MFURL *url); +/* opens associated object */ +GF_Err gf_sc_texture_open(GF_TextureHandler *txh, MFURL *url, Bool lock_scene_timeline); +/*starts associated object*/ +GF_Err gf_sc_texture_play(GF_TextureHandler *txh, MFURL *url); +GF_Err gf_sc_texture_play_from_to(GF_TextureHandler *txh, MFURL *url, Double start_offset, Double end_offset, Bool can_loop, Bool lock_scene_timeline); +/*stops associated object*/ +void gf_sc_texture_stop_no_unregister(GF_TextureHandler *txh); +/*stops associated object and unregister it*/ +void gf_sc_texture_stop(GF_TextureHandler *txh); + +/*restarts associated object - DO NOT CALL stop/start*/ +void gf_sc_texture_restart(GF_TextureHandler *txh); +/*common routine for all video texture: fetches a frame and update the 2D texture object */ +void gf_sc_texture_update_frame(GF_TextureHandler *txh, Bool disable_resync); +/*release video memory if needed*/ +void gf_sc_texture_release_stream(GF_TextureHandler *txh); + +void gf_sc_texture_cleanup_hw(GF_Compositor *compositor); + + +/*sensor node handler - this is not defined as a stack because Anchor is both a grouping node and a +sensor node, and we DO need the groupingnode stack...*/ +typedef struct _sensor_handler +{ + /*sensor enabled or not ?*/ + Bool (*IsEnabled)(GF_Node *node); + /*user input on sensor: + is_over: pointing device is over a shape the sensor is attached to + is_cancel: the sensor state has been canceled due to another sensor. This typically happens following "click" events in SVG + which do not consume the mousedown but consumes the mouseup + evt_type: mouse event type + compositor: pointer to compositor - hit info is stored at compositor level + return: was the event consumed ? + */ + Bool (*OnUserEvent)(struct _sensor_handler *sh, Bool is_over, Bool is_cancel, GF_Event *ev, GF_Compositor *compositor); + Bool grabbed; + /*pointer to the sensor node*/ + GF_Node *sensor; +} GF_SensorHandler; + +/*returns TRUE if the node is a pointing device sensor node that can be stacked during traversing (all sensor except anchor)*/ +Bool compositor_mpeg4_is_sensor_node(GF_Node *node); +/*returns associated sensor handler from traversable stack (the node handler is always responsible for creation/deletion) +returns NULL if not a sensor or sensor is not activated*/ +GF_SensorHandler *compositor_mpeg4_get_sensor_handler(GF_Node *n); +GF_SensorHandler *compositor_mpeg4_get_sensor_handler_ex(GF_Node *n, Bool skip_anchors); + +/*rendering modes*/ +enum +{ + /*regular traversing mode for z-sorting: + - 2D mode: builds the display list (may draw directly if requested) + - 3D mode: sort & queue transparent objects + */ + TRAVERSE_SORT = 0, + /*explicit draw routine used when flushing 2D display list*/ + TRAVERSE_DRAW_2D, + /*pick routine*/ + TRAVERSE_PICK, + /*get bounds routine: returns bounds in local coord system (including node transform if any)*/ + TRAVERSE_GET_BOUNDS, + /*set to signal bindable render - only called on bindable stack top if present. + for background (drawing request), viewports/viewpoints fog and navigation (setup) + all other nodes SHALL NOT RESPOND TO THIS CALL + */ + TRAVERSE_BINDABLE, + + /*writes the text selection into the compositor buffer - we need a traversing mode for this operation + to handle properly text and tspans*/ + TRAVERSE_GET_TEXT, + +#ifndef GPAC_DISABLE_3D + /*explicit draw routine used when flushing 3D display list*/ + TRAVERSE_DRAW_3D, + /*set global lights on. Since the model_matrix is not pushed to the target in this + pass, global lights shall not forget to do it (cf lighting.c)*/ + TRAVERSE_LIGHTING, + /*collision routine*/ + TRAVERSE_COLLIDE, +#endif +}; + + +typedef struct _group_cache_candidate GF_CacheCandidate; + + + +#define MAX_USER_CLIP_PLANES 4 + + +/*the traversing context: set_up at top-level and passed through SFNode_Render. Each node is responsible for +restoring the context state before returning*/ +struct _traversing_state +{ + struct _audio_group *audio_parent; + struct _soundinterface *sound_holder; + +#ifndef GPAC_DISABLE_SVG + SVGPropertiesPointers *svg_props; + u32 svg_flags; +#endif + + /*current traversing mode*/ + u32 traversing_mode; + /*for 2D drawing, indicates objects are to be drawn as soon as traversed, at each frame*/ + Bool immediate_draw; + //flag set when immediate_draw whn in defer mode, so that canvas is not erased in hybgl mode + Bool immediate_for_defer; + + + /*current subtree is part of a switched-off subtree (needed for audio)*/ + Bool switched_off; + /*set by the traversed subtree to indicate no cull shall be performed*/ + Bool disable_cull; + + /*indicates if we are in a layer or not*/ + Bool is_layer; + /*current graph traversed is in pixel metrics*/ + Bool pixel_metrics; + /*minimal half-dimension (w/2, h/2)*/ + Fixed min_hsize; + + /*indicates if the current subtree is fliped compared to the target visual*/ + Bool fliped_coords; + + /*current size of viewport being traverse (root scene, layers)*/ + SFVec2f vp_size; + + /*the one and only visual manager currently being traversed*/ + GF_VisualManager *visual; + +#ifndef GPAC_DISABLE_VRML + /*current background and viewport stacks*/ + GF_List *backgrounds; + GF_List *viewpoints; +#endif + + /*disable partial sphere rendrering in VR*/ + Bool disable_partial_sphere; + + Bool reverse_backface; + + /*current transformation from top-level*/ + GF_Matrix2D transform; + /*current color transformation from top-level*/ + GF_ColorMatrix color_mat; + /* Contains the viewbox transform, used for svg ref() transform */ + GF_Matrix2D vb_transform; + + /*only used for bitmap drawing*/ + GF_ColorKey *col_key; + + /*if set all nodes shall be redrawn - set only at specific places in the tree*/ + Bool invalidate_all; + + /*text splitting: 0: no splitting, 1: word by word, 2:letter by letter*/ + u32 text_split_mode; + /*1-based idx of text element drawn*/ + u32 text_split_idx; + + /*all VRML sensors for the current level*/ + GF_List *vrml_sensors; + + /*current appearance when traversing geometry nodes*/ + GF_Node *appear; + /*parent group for composition: can be Form, Layout or Layer2D*/ + struct _parent_node_2d *parent; + + /*override appearance of all nodes with this one*/ + GF_Node *override_appearance; + + /*group/object bounds in local coordinate system*/ + GF_Rect bounds; + + /*node for which bounds should be fetched - SVG only*/ + GF_Node *for_node; + Bool abort_bounds_traverse; + GF_Matrix2D mx_at_node; + Bool ignore_strike; + + GF_List *use_stack; + + /* Styling Property and others for SVG context */ +#ifndef GPAC_DISABLE_SVG + SVG_Number *parent_use_opacity; + SVGAllAttributes *parent_anim_atts; + Bool parent_is_use; + + /*SVG text rendering state*/ + Bool in_svg_text; + Bool in_svg_text_area; + + /* current chunk & position of last placed text chunk*/ + u32 chunk_index; + Fixed text_end_x, text_end_y; + + /* text & tspan state*/ + GF_List *x_anchors; + SVG_Coordinates *text_x, *text_y, *text_rotate; + u32 count_x, count_y, count_rotate, idx_rotate; + + /* textArea state*/ + Fixed max_length, max_height; + Fixed base_x, base_y; + Fixed line_spacing; + Fixed base_shift; + /*quick and dirty hack to try to solve xml:space across text and tspans without + flattening the DOMText nodes + 0: first block of text + 1: previous block of text ended with a space + 2: previous block of text did NOT end with a space + */ + u32 last_char_type; + /*in textArea, indicates that the children bounds must be refreshed due to a baseline adjustment*/ + u32 refresh_children_bounds; +#endif + GF_Node *text_parent; + + /*current context to be drawn - only set when drawing in 2D mode or 3D for SVG*/ + struct _drawable_context *ctx; + + /*world ray for picking - in 2D, orig is 2D mouse pos and direction is -z*/ + GF_Ray ray; + s32 pick_x, pick_y; + + /*we have 2 clippers, one for regular clipping (layout, form if clipping) which is maintained in world coord system + and one for layer2D which is maintained in parent coord system (cf layer rendering). The layer clipper + is used only when cascading layers - layer3D doesn't use clippers*/ + Bool has_clip, has_layer_clip; + /*active clipper in world coord system */ + GF_Rect clipper, layer_clipper; + /*current object (model) transformation at the given layer*/ + GF_Matrix layer_matrix; + + + /*set when traversing a cached group during offscreen bitmap construction.*/ + Bool in_group_cache; + + Bool in_svg_filter; + + u32 subscene_not_over; + +#ifndef GPAC_DISABLE_3D + /*the current camera*/ + GF_Camera *camera; + + /*current object (model) transformation from top-level, view is NOT included*/ + GF_Matrix model_matrix; + +#ifndef GPAC_DISABLE_VRML + /*fog bind stack*/ + GF_List *fogs; + /*navigation bind stack*/ + GF_List *navigations; +#endif + + /*when drawing, signals the mesh is transparent (enables blending)*/ + Bool mesh_is_transparent; + /*when drawing, signals the number of textures used by the mesh*/ + u32 mesh_num_textures; + + /*bounds for TRAVERSE_GET_BOUNDS and background rendering*/ + GF_BBox bbox; + + /*cull flag (used to skip culling of children when parent bbox is completely inside/outside frustum)*/ + u32 cull_flag; + + /*toggle local lights on/off - field is ONLY valid in TRAVERSE_RENDER mode, and local lights + are always set off in reverse order as when set on*/ + Bool local_light_on; + /*current directional ligths contexts - only valid in TRAVERSE_RENDER*/ + GF_List *local_lights; + + /*clip planes in world coords*/ + GF_Plane clip_planes[MAX_USER_CLIP_PLANES]; + u32 num_clip_planes; + + Bool camera_was_dirty; + + /*layer traversal state: + set to the first traversed layer3D when picking + set to the current layer3D traversed when rendering 3D to an offscreen bitmap. This allows other + nodes (typically bindables) seting the layer dirty flags to force a redraw + */ + GF_Node *layer3d; +#endif + + +#ifdef GF_SR_USE_DEPTH + Fixed depth_gain, depth_offset; +#endif + + +#ifdef GF_SR_USE_VIDEO_CACHE + /*set to 1 if cache evaluation can be skipped - this is only set when there is not enough memory + to cache a sub-group, in which case the group cannot be cached (we're caching in display coordinates)*/ + Bool cache_too_small; +#endif +}; + +/* + Audio mixer - MAX GF_AUDIO_MIXER_MAX_CHANNELS CHANNELS SUPPORTED +*/ + +/*max number of channels we support in mixer*/ +#define GF_AUDIO_MIXER_MAX_CHANNELS 24 + +/*the audio object as used by the mixer. All audio nodes need to implement this interface*/ +typedef struct _audiointerface +{ + /*fetch audio data for a given audio delay (~soundcard drift) - if delay is 0 sync should not be performed + (eg intermediate mix) */ + u8 *(*FetchFrame) (void *callback, u32 *size, u32 *planar_stride, u32 audio_delay_ms); + /*release a number of bytes in the indicated frame (ts)*/ + void (*ReleaseFrame) (void *callback, u32 nb_bytes); + /*get media speed*/ + Fixed (*GetSpeed)(void *callback); + /*gets volume for each channel - vol = Fixed[GF_AUDIO_MIXER_MAX_CHANNELS]. returns 1 if volume shall be changed (!= 1.0)*/ + Bool (*GetChannelVolume)(void *callback, Fixed *vol); + /*returns 1 if muted*/ + Bool (*IsMuted)(void *callback); + /*user callback*/ + void *callback; + /*returns 0 if config is not known yet or changed, + otherwise AND IF @for_reconf is set, updates member var below and return TRUE + You may return 0 to force parent user invalidation*/ + Bool (*GetConfig)(struct _audiointerface *ai, Bool for_reconf); + /*updated cfg, or 0 otherwise*/ + u32 chan, afmt, samplerate; + u64 ch_layout; + Bool forced_layout; + //updated at each frame, used if frame fetch returns NULL + Bool is_buffering; + Bool is_eos; +} GF_AudioInterface; + +typedef struct __audiomix GF_AudioMixer; + +/*create mixer - ar is NULL for any sub-mixers, or points to the main audio renderer (mixer outputs to sound driver)*/ +GF_AudioMixer *gf_mixer_new(struct _audio_render *ar); +void gf_mixer_del(GF_AudioMixer *am); +void gf_mixer_remove_all(GF_AudioMixer *am); +void gf_mixer_add_input(GF_AudioMixer *am, GF_AudioInterface *src); +void gf_mixer_remove_input(GF_AudioMixer *am, GF_AudioInterface *src); +void gf_mixer_lock(GF_AudioMixer *am, Bool lockIt); +void gf_mixer_set_max_speed(GF_AudioMixer *am, Double max_speed); + +/*mix inputs in buffer, return number of bytes written to output*/ +u32 gf_mixer_get_output(GF_AudioMixer *am, void *buffer, u32 buffer_size, u32 delay_ms); +/*reconfig all sources if needed - returns TRUE if main audio config changed +NOTE: this is called at each gf_mixer_get_output by the mixer. To call externally for audio hardware +reconfiguration only*/ +Bool gf_mixer_reconfig(GF_AudioMixer *am); +/*retrieves mixer cfg*/ +void gf_mixer_get_config(GF_AudioMixer *am, u32 *outSR, u32 *outCH, u32 *outFMT, u64 *outChCfg); +/*called by audio renderer in case the hardware used a different setup than requested*/ +GF_Err gf_mixer_set_config(GF_AudioMixer *am, u32 outSR, u32 outCH, u32 outFMT, u64 ch_cfg); +Bool gf_mixer_is_src_present(GF_AudioMixer *am, GF_AudioInterface *ifce); +u32 gf_mixer_get_src_count(GF_AudioMixer *am); +GF_Err gf_mixer_force_channel_out(GF_AudioMixer *am, u32 num_channels); +u32 gf_mixer_get_block_align(GF_AudioMixer *am); +Bool gf_mixer_must_reconfig(GF_AudioMixer *am); +Bool gf_mixer_empty(GF_AudioMixer *am); +Bool gf_mixer_buffering(GF_AudioMixer *am); +Bool gf_mixer_is_eos(GF_AudioMixer *am); + +//#define ENABLE_AOUT + +/*the audio renderer*/ +typedef struct _audio_render +{ + GF_Compositor *compositor; + + u32 max_bytes_out, samplerate, bytes_per_samp, nb_bytes_out, buffer_size, nb_buffers; + u64 current_time_sr, time_at_last_config_sr; + GF_FilterPid *aout; + u32 video_ts; + Bool scene_ready; + u32 nb_audio_objects; + + /*startup time, used when no audio output is set*/ + u64 start_time; + /*freeze time, used when no audio output is set*/ + u64 freeze_time; + +/* Bool disable_resync; + Bool disable_multichannel; +*/ + /*frozen time counter if set*/ + Bool Frozen; + + /*system clock compute when audio output is present*/ + u32 current_time, bytes_per_second, time_at_last_config; + //number of bytes requested by sound card since last reconfig + u64 bytes_requested; + + /*final output*/ + GF_AudioMixer *mixer; + Bool need_reconfig; + + u32 config_forced, wait_for_rcfg; + + u32 audio_delay, volume, pan, mute; + + //set when output is not realtime - set to 2 will indicate end of session + u32 non_rt_output; + + Fixed yaw, pitch, roll; + +} GF_AudioRenderer; + +/*creates audio renderer*/ +GF_AudioRenderer *gf_sc_ar_load(GF_Compositor *compositor, u32 init_flags); +/*deletes audio renderer*/ +void gf_sc_ar_del(GF_AudioRenderer *ar); + +enum +{ + GF_SC_AR_PAUSE=0, + GF_SC_AR_RESUME, + GF_SC_AR_RESET_HW_AND_PLAY, +}; +/*control audio renderer*/ +void gf_sc_ar_control(GF_AudioRenderer *ar, u32 CtrlType); +/*set volume and pan*/ +void gf_sc_ar_set_volume(GF_AudioRenderer *ar, u32 Volume); +void gf_sc_ar_set_pan(GF_AudioRenderer *ar, u32 Balance); +/*mute/unmute audio*/ +void gf_sc_ar_mute(GF_AudioRenderer *ar, Bool mute); + +/*gets time in msec - this is the only clock used by the whole ESM system - depends on the audio driver*/ +u32 gf_sc_ar_get_clock(GF_AudioRenderer *ar); +/*reset all input nodes*/ +void gf_sc_ar_reset(GF_AudioRenderer *ar); +/*add audio node*/ +void gf_sc_ar_add_src(GF_AudioRenderer *ar, GF_AudioInterface *source); +/*remove audio node*/ +void gf_sc_ar_remove_src(GF_AudioRenderer *ar, GF_AudioInterface *source); +/*reconfig audio hardware if needed*/ +void gf_sc_ar_send_or_reconfig(GF_AudioRenderer *ar); + +void gf_sc_ar_update_video_clock(GF_AudioRenderer *ar, u32 video_ts); + + +/*the sound node interface for intensity & spatialization*/ +typedef struct _soundinterface +{ + /*gets volume for each channel - vol = Fixed[GF_AUDIO_MIXER_MAX_CHANNELS]. returns 1 if volume shall be changed (!= 1.0) + if NULL channels are always at full intensity*/ + Bool (*GetChannelVolume)(GF_Node *owner, Fixed *vol); + /*node owning the structure*/ + GF_Node *owner; +} GF_SoundInterface; + +/*audio common to AudioClip and AudioSource*/ +typedef struct +{ + GF_Node *owner; + GF_Compositor *compositor; + GF_AudioInterface input_ifce; + /*can be NULL if the audio node generates its output from other input*/ + GF_MediaObject *stream; + /*object speed and intensity*/ + Fixed speed, intensity; + Bool stream_finished; + Bool need_release; + u32 is_open; + Bool is_muted; + Bool register_with_renderer, register_with_parent; + + GF_SoundInterface *snd; +} GF_AudioInput; +/*setup interface with audio renderer - overwrite any functions needed after setup EXCEPT callback object*/ +void gf_sc_audio_setup(GF_AudioInput *ai, GF_Compositor *sr, GF_Node *node); +/*unregister interface from renderer/mixer and stops source - deleteing the interface is the caller responsability*/ +void gf_sc_audio_predestroy(GF_AudioInput *ai); +/*open audio object*/ +GF_Err gf_sc_audio_open(GF_AudioInput *ai, MFURL *url, Double clipBegin, Double clipEnd, Bool lock_timeline); +/*closes audio object*/ +void gf_sc_audio_stop(GF_AudioInput *ai); +/*restarts audio object (cf note in MediaObj)*/ +void gf_sc_audio_restart(GF_AudioInput *ai); + +Bool gf_sc_audio_check_url(GF_AudioInput *ai, MFURL *url); + +/*base grouping audio node (nodes with several audio sources as children)*/ +#define AUDIO_GROUP_NODE \ + GF_AudioInput output; \ + void (*add_source)(struct _audio_group *_this, GF_AudioInput *src); \ + +typedef struct _audio_group +{ + AUDIO_GROUP_NODE +} GF_AudioGroup; + + +/*register audio node with parent audio renderer (mixer or main renderer)*/ +void gf_sc_audio_register(GF_AudioInput *ai, GF_TraverseState *tr_state); +void gf_sc_audio_unregister(GF_AudioInput *ai); + + +#ifndef GPAC_DISABLE_SVG +GF_Err gf_sc_get_mfurl_from_xlink(GF_Node *node, MFURL *mfurl); +Fixed gf_sc_svg_convert_length_to_display(GF_Compositor *sr, SVG_Length *length); +char *gf_scene_resolve_xlink(GF_Node *node, char *the_url); +#endif + +GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *sr); +void compositor_2d_set_user_transform(GF_Compositor *sr, Fixed zoom, Fixed tx, Fixed ty, Bool is_resize) ; +GF_Err compositor_2d_get_video_access(GF_VisualManager *surf); +void compositor_2d_release_video_access(GF_VisualManager *surf); +GF_Rect compositor_2d_update_clipper(GF_TraverseState *tr_state, GF_Rect this_clip, Bool *need_restore, GF_Rect *original, Bool for_layer); +Bool compositor_2d_check_attached(GF_VisualManager *visual); +void compositor_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen); +void compositor_2d_init_callbacks(GF_Compositor *compositor); + +#ifndef GPAC_DISABLE_3D +void compositor_2d_reset_gl_auto(GF_Compositor *compositor); +void compositor_2d_hybgl_flush_video(GF_Compositor *compositor, GF_IRect *area); +void compositor_2d_hybgl_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen_clear); +#endif + +Bool compositor_texture_rectangles(GF_VisualManager *visual, GF_TextureHandler *txh, GF_IRect *clip, GF_Rect *unclip, GF_Window *src, GF_Window *dst, Bool *disable_blit, Bool *has_scale); + +Bool compositor_get_2d_plane_intersection(GF_Ray *ray, SFVec3f *res); + +void compositor_send_resize_event(GF_Compositor *compositor, GF_SceneGraph *subscene, Fixed old_z, Fixed old_tx, Fixed old_ty, Bool is_resize); + +void compositor_set_cache_memory(GF_Compositor *compositor, u32 memory); + +#ifndef GPAC_DISABLE_3D + +GF_Err compositor_3d_set_aspect_ratio(GF_Compositor *sr); +GF_Camera *compositor_3d_get_camera(GF_Compositor *sr); +void compositor_3d_reset_camera(GF_Compositor *sr); +GF_Camera *compositor_layer3d_get_camera(GF_Node *node); +void compositor_layer3d_bind_camera(GF_Node *node, Bool do_bind, u32 nav_value); +void compositor_3d_draw_bitmap(struct _drawable *stack, DrawAspect2D *asp, GF_TraverseState *tr_state, Fixed width, Fixed height, Fixed bmp_scale_x, Fixed bmp_scale_y); + +GF_Err compositor_3d_get_screen_buffer(GF_Compositor *sr, GF_VideoSurface *fb, u32 depth_buffer_mode); +GF_Err compositor_3d_get_offscreen_buffer(GF_Compositor *sr, GF_VideoSurface *fb, u32 view_idx, u32 depth_buffer_mode); +GF_Err compositor_3d_release_screen_buffer(GF_Compositor *sr, GF_VideoSurface *framebuffer); + +void gf_sc_load_opengl_extensions(GF_Compositor *sr, Bool has_gl_context); + +Bool gf_sc_fit_world_to_screen(GF_Compositor *compositor); + +GF_Err compositor_3d_setup_fbo(u32 width, u32 height, u32 *fbo_id, u32 *tx_id, u32 *depth_id); +void compositor_3d_delete_fbo(u32 *fbo_id, u32 *fbo_tx_id, u32 *fbo_depth_id, Bool keep_tx_id); +u32 compositor_3d_get_fbo_pixfmt(); +void compositor_3d_enable_fbo(GF_Compositor *compositor, Bool enable); + +#endif + +Bool gf_sc_exec_event(GF_Compositor *sr, GF_Event *evt); +void gf_sc_get_nodes_bounds(GF_Node *self, GF_ChildNodeItem *children, GF_TraverseState *tr_state, s32 *child_idx); + +Bool gf_sc_exec_event_vrml(GF_Compositor *compositor, GF_Event *ev); + +void gf_sc_visual_register(GF_Compositor *sr, GF_VisualManager *surf); +void gf_sc_visual_unregister(GF_Compositor *sr, GF_VisualManager *surf); +Bool gf_sc_visual_is_registered(GF_Compositor *sr, GF_VisualManager *surf); + +Bool gf_sc_pick_in_clipper(GF_TraverseState *tr_state, GF_Rect *clip); + +void compositor_gradient_update(GF_TextureHandler *txh); +void compositor_set_ar_scale(GF_Compositor *sr, Fixed scaleX, Fixed scaleY); + +/*reset focus if node being deleted has the focus - must be called for each focusable node (internally called for 2D & 3D drawable nodes)*/ +void gf_sc_check_focus_upon_destroy(GF_Node *n); + +void gf_sc_key_navigator_del(GF_Compositor *sr, GF_Node *n); +void gf_sc_change_key_navigator(GF_Compositor *sr, GF_Node *n); +GF_Node *gf_scene_get_keynav(GF_SceneGraph *sg, GF_Node *sensor); +const char *gf_scene_get_service_url(GF_SceneGraph *sg); + + +Bool gf_scene_is_over(GF_SceneGraph *sg); +GF_SceneGraph *gf_scene_enum_extra_scene(GF_SceneGraph *sg, u32 *i); + +Bool gf_scene_is_dynamic_scene(GF_SceneGraph *sg); + +#ifndef GPAC_DISABLE_SVG + +void compositor_svg_build_gradient_texture(GF_TextureHandler *txh); + +/*base routine fo all svg elements: + - check for conditional processing (requiredXXX, ...) + - apply animation and inheritance + + returns 0 if the node shall not be traversed due to conditional processing +*/ +Bool compositor_svg_traverse_base(GF_Node *node, SVGAllAttributes *all_atts, GF_TraverseState *tr_state, SVGPropertiesPointers *backup_props, u32 *backup_flags); +Bool compositor_svg_is_display_off(SVGPropertiesPointers *props); +void compositor_svg_apply_local_transformation(GF_TraverseState *tr_state, SVGAllAttributes *atts, GF_Matrix2D *backup_matrix_2d, GF_Matrix *backup_matrix); +void compositor_svg_restore_parent_transformation(GF_TraverseState *tr_state, GF_Matrix2D *backup_matrix_2d, GF_Matrix *backup_matrix); + +void compositor_svg_traverse_children(GF_ChildNodeItem *children, GF_TraverseState *tr_state); + +Bool compositor_svg_evaluate_conditional(GF_Compositor *compositor, SVGAllAttributes *all_atts); + +/*returns the node associated with the given xlink - this is not always the target node of the xlink structure due +to async restart of animation nodes*/ +GF_Node *compositor_svg_get_xlink_resource_node(GF_Node *node, XMLRI *xlink); + +#endif + +/*Text handling*/ + +/*we identify the edit caret in a text string as this value*/ +#define GF_CARET_CHAR 0x1 + +typedef struct _gf_font GF_Font; + +struct _gf_font +{ + /*fonts are linked within the font manager*/ + GF_Font *next; + /*list of glyphs in the font*/ + GF_Glyph *glyph; + + char *name; + u32 em_size; + u32 styles; + /*font uits in em size*/ + s32 ascent, descent, underline, line_spacing, max_advance_h, max_advance_v; + s32 baseline; + + /*only set for embedded font engines (SVG fonts)*/ + GF_Font *(*get_alias)(void *udta); + GF_Err (*get_glyphs)(void *udta, const char *utf_string, u32 *glyph_ids_buffer, u32 *io_glyph_ids_buffer_size, const char *xml_lang, Bool *is_rtl); + GF_Glyph *(*load_glyph)(void *udta, u32 glyph_name); + void *udta; + + Bool not_loaded; + + struct _gf_ft_mgr *ft_mgr; + + GF_Compositor *compositor; + /*list of spans currently using the font - this is needed to allow for dynamic discard of the font*/ + GF_List *spans; +}; + +enum +{ + /*span direction is horizontal*/ + GF_TEXT_SPAN_HORIZONTAL = 1, + /*span is underlined*/ + GF_TEXT_SPAN_UNDERLINE = 1<<1, + /*span is fliped (coord systems with Y-axis pointing downwards like SVG)*/ + GF_TEXT_SPAN_FLIP = 1<<2, + /*span is in the current text selection*/ + GF_TEXT_SPAN_RIGHT_TO_LEFT = 1<<3, + /*span is in the current text selection*/ + GF_TEXT_SPAN_SELECTED = 1<<4, + /*span is strikeout*/ + GF_TEXT_SPAN_STRIKEOUT = 1<<5 +}; + +typedef struct __text_span +{ + GF_Font *font; + + GF_Glyph **glyphs; + u32 nb_glyphs; + + u32 flags; + + Fixed font_size; + + /*scale to apply to get to requested font size*/ + Fixed font_scale; + GF_Rect bounds; + + /*MPEG-4 span scaling (length & maxExtend)*/ + Fixed x_scale, y_scale; + /*x (resp. y) offset in local coord system. Ignored if per-glyph dx (resp dy) are specified*/ + Fixed off_x, off_y; + + /*per-glyph positioning - when allocated, this is the same number as the glyphs*/ + Fixed *dx, *dy, *rot; + + /*span language*/ +// const char *lang; + + /*span texturing and 3D tools*/ + struct _span_internal *ext; + + /*SVG stuff :(*/ + GF_Node *anchor; + GF_Node *user; +} GF_TextSpan; + +GF_FontManager *gf_font_manager_new(); +void gf_font_manager_del(GF_FontManager *fm); + +GF_Font *gf_font_manager_set_font(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles); +GF_Font *gf_font_manager_set_font_ex(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles, Bool check_only); + +GF_TextSpan *gf_font_manager_create_span(GF_FontManager *fm, GF_Font *font, char *span, Fixed font_size, Bool needs_x_offset, Bool needs_y_offset, Bool needs_rotate, const char *lang, Bool fliped_text, u32 styles, GF_Node *user); +void gf_font_manager_delete_span(GF_FontManager *fm, GF_TextSpan *tspan); + +GF_Err gf_font_manager_register_font(GF_FontManager *fm, GF_Font *font); +GF_Err gf_font_manager_unregister_font(GF_FontManager *fm, GF_Font *font); + +void gf_font_manager_refresh_span_bounds(GF_TextSpan *span); +GF_Path *gf_font_span_create_path(GF_TextSpan *span); + + +void gf_font_spans_draw_2d(GF_List *spans, GF_TraverseState *tr_state, u32 hl_color, Bool force_texture_text, GF_Rect *bounds); +void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspect2D *asp, u32 text_hl, Bool force_texturing); +void gf_font_spans_pick(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state, GF_Rect *node_bounds, Bool use_dom_events, struct _drawable *drawable); +void gf_font_spans_get_selection(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state); + +GF_Font *gf_compositor_svg_set_font(GF_FontManager *fm, char *a_font, u32 styles, Bool check_only); + +/*switches focus node: +@compositor: compositor +@move_prev: finds previous focus rather than next +@focus: new focus if force_focus_type is set +@force_focus_type: 0: focus not forced, 1: focus forced to focus, 2: focus forced to prev/next focusable child of focus node*/ +u32 gf_sc_focus_switch_ring(GF_Compositor *compositor, Bool move_prev, GF_Node *focus, u32 force_focus_type); + +Bool compositor_handle_navigation(GF_Compositor *compositor, GF_Event *ev); + +void compositor_handle_auto_navigation(GF_Compositor *compositor); + +void gf_sc_next_frame_state(GF_Compositor *compositor, u32 state); + + +#ifdef GPAC_USE_TINYGL +void gf_get_tinygl_depth(GF_TextureHandler *txh); +#endif + + +/*TODO - remove this !!*/ +typedef struct +{ + void *udta; + /*called when new video frame is ready to be flushed on screen. time is the terminal global clock in ms*/ + void (*on_video_frame)(void *udta, u32 time); + /*called when video output has been resized*/ + void (*on_video_reconfig)(void *udta, u32 width, u32 height, u8 bpp); +} GF_VideoListener; + +GF_Err gf_sc_set_scene_size(GF_Compositor *compositor, u32 Width, u32 Height, Bool force_size); + +void gf_sc_sys_frame_pending(GF_Compositor *compositor, Double ts_offset, u32 obj_time, GF_Filter *from_filter); + +Bool gf_sc_is_over(GF_Compositor *compositor, GF_SceneGraph *scene_graph); + +/*returns true if scene or current layer accepts tghe requested navigation type, false otherwise*/ +Bool gf_sc_navigation_supported(GF_Compositor *compositor, u32 type); + +u32 gf_sc_check_end_of_scene(GF_Compositor *compositor, Bool skip_interactions); + + +/*add/rem node requiring a call to render without being present in traversed graph (VRML/MPEG-4 protos). +For these nodes, the traverse effect passed will be NULL.*/ +void gf_sc_queue_node_traverse(GF_Compositor *compositor, GF_Node *node); +void gf_sc_unqueue_node_traverse(GF_Compositor *compositor, GF_Node *node); + + +GF_DownloadManager *gf_sc_get_downloader(GF_Compositor *compositor); + +typedef struct +{ + /*od_manager owning service, NULL for services created for remote channels*/ + struct _od_manager *owner; + + /*service url*/ + char *url; + char *url_frag; + GF_Filter *source_filter; + + /*number of attached remote channels ODM (ESD URLs)*/ +// u32 nb_ch_users; + /*number of attached remote ODM (OD URLs)*/ + u32 nb_odm_users; + + /*clock objects. Kept at service level since ESID namespace is the service one*/ + GF_List *clocks; + + Fixed set_speed; + Bool connect_ack; +} GF_SceneNamespace; + + + +/*opens service - performs URL concatenation if parent service specified*/ +GF_SceneNamespace *gf_scene_ns_new(GF_Scene *scene, GF_ObjectManager *owner, const char *url, const char *parent_url); +/*destroy service*/ +void gf_scene_ns_del(GF_SceneNamespace *ns, GF_Scene *scene); + +void gf_scene_ns_connect_object(GF_Scene *scene, GF_ObjectManager *odm, char *serviceURL, char *parent_url, GF_SceneNamespace *parent_ns); + + + +/* + GF_Scene object. This is the structure handling all scene management, mainly: + - list of resources (media objects) used + - scene time management + - reload/seek + - Dynamic scene (without a scene description)* + Each scene registers itself as the private data of its associated scene graph. + + The scene object is also used by Inline nodes and all media resources of type "scene", eg , +*/ +struct _gf_scene +{ + /*root OD of the subscene, ALWAYS namespace of the parent scene*/ + struct _od_manager *root_od; + + /*all sub resources of this scene (eg, list of GF_ObjectManager), namespace of this scene. This includes + both external resources (urls) and ODs sent in MPEG-4 systems*/ + GF_List *resources; + + /*list of GF_MediaObject - these are the links between scene nodes (URL, xlink:href) and media resources. + We need this link because of MPEG-4 Systems, where an OD (media resource) can be removed or replaced by the server + without the scene being modified*/ + GF_List *scene_objects; + + /*list of extra scene graphs (text streams, generic OSDs, ...)*/ + GF_List *extra_scenes; + /*scene graph*/ + GF_SceneGraph *graph; + /*graph state - if not attached, no traversing of inline + 0: not attached + 1: attached + 2: temp graph attached. The temp graph is generated when waiting for the first scene AU to be processed + */ + u32 graph_attached; + + //indicates a valid object is attached to the scene + Bool object_attached; + + /*set to 1 to force all sub-resources to share the timeline of this scene*/ + Bool force_single_timeline; + + /*set to 1 once the forced size has been sez*/ + Bool force_size_set; + + /*callback to call to dispatch SVG MediaEvent - this is a pointer to function only because of linking issues + with static libgpac (avoids depending on QuickJS and OpenGL32 if not needed)*/ + void (*on_media_event)(GF_Scene *scene, u32 type); + + /*duration of inline scene*/ + u64 duration; + + u32 nb_buffering; + u32 nb_rebuffer; + + /*max timeshift of all objects*/ + u32 timeshift_depth; + + /*WorldInfo node or node*/ + void *world_info; + + /*current IRI fragment if any*/ + char *fragment_uri; + + /*secondary resource scene - this is specific to SVG: a secondary resource scene is <use> or <fonturi>, and all resources + identified in a secondary resource must be checked for existence and created in the primary resource*/ + Bool secondary_resource; + + /*needed by some apps in GPAC which manipulate the scene tree in different locations as the resources*/ + char *redirect_xml_base; + + + /*FIXME - Dynamic scenes are only supported through VRML/BIFS nodes, we should add support for SVG scene graph + generation if needed*/ + Bool is_dynamic_scene; + /*for MPEG-2 TS, indicates the current serviceID played from mux*/ + u32 selected_service_id; + + /*URLs of current video, audio and subs (we can't store objects since they may be destroyed when seeking)*/ + SFURL visual_url, audio_url, text_url, dims_url; + + Bool is_tiled_srd; + u32 srd_type; + s32 srd_min_x, srd_max_x, srd_min_y, srd_max_y; + + u32 ambisonic_type; + + Bool end_of_scene; +#ifndef GPAC_DISABLE_VRML + /*list of externproto libraries*/ + GF_List *extern_protos; + + /*togles inline restart - needed because the restart may be triggered from inside the scene or from + parent scene, hence 2 render passes must be used + special value 2 means scene URL changes (for anchor navigation*/ + u32 needs_restart; + + /*URL of the current parent Inline node, only set during traversal*/ + MFURL *current_url; + + + /*list of M_Storage nodes active*/ + GF_List *storages; + + /*list of M_KeyNavigator nodes*/ + GF_List *keynavigators; + + /*list of attached Inline nodes*/ + GF_List *attached_inlines; +#endif + + + Bool disable_hitcoord_notif; + + u32 addon_position, addon_size_factor; + + GF_List *declared_addons; + //set when content is replaced by an addon (DASH PVR mode) + Bool main_addon_selected; + u32 sys_clock_at_main_activation, obj_clock_at_main_activation; + + //0: no pause - 1: paused and trigger pause command to net, 2: only clocks are paused but commands not sent + u32 first_frame_pause_type; + u32 vr_type; + + GF_Compositor *compositor; + + //only for root scene + GF_List *namespaces; + + Bool has_splicing_addons; +}; + +GF_Scene *gf_scene_new(GF_Compositor *compositor, GF_Scene *parentScene); +void gf_scene_del(GF_Scene *scene); + +struct _od_manager *gf_scene_find_odm(GF_Scene *scene, u16 OD_ID); +void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown); + +GF_Scene *gf_scene_get_root_scene(GF_Scene *scene); +Bool gf_scene_is_root(GF_Scene *scene); + +void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shutdown); +/*browse all (media) channels and send buffering info to the app*/ +void gf_scene_buffering_info(GF_Scene *scene, Bool rebuffer_done); +void gf_scene_attach_to_compositor(GF_Scene *scene); +struct _mediaobj *gf_scene_get_media_object(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines); +struct _mediaobj *gf_scene_get_media_object_ex(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines, struct _mediaobj *sync_ref, Bool force_new_if_not_attached, GF_Node *node_ptr, GF_Scene *original_parent_scene); +void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm); +/*updates scene duration based on sub objects*/ +void gf_scene_set_duration(GF_Scene *scene); +/*updates scene timeshift based on sub objects*/ +void gf_scene_set_timeshift_depth(GF_Scene *scene); +/*locate media object by ODID (non dynamic ODs) or URL (dynamic ODs)*/ +struct _mediaobj *gf_scene_find_object(GF_Scene *scene, u16 ODID, char *url); +/*returns scene time in sec - exact meaning of time depends on standard used*/ +Double gf_scene_get_time(void *_is); +/*register extra scene graph for on-screen display*/ +void gf_scene_register_extra_graph(GF_Scene *scene, GF_SceneGraph *extra_scene, Bool do_remove); +/*forces scene size info (without changing pixel metrics) - this may be needed by modules using extra graphs (like timedtext)*/ +void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height); +/*regenerate a scene graph based on available objects - can only be called for dynamic OD streams*/ +void gf_scene_regenerate(GF_Scene *scene); +/*selects given ODM for dynamic scenes*/ +void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm); +/*restarts dynamic scene from given time: scene graph is not reseted, objects are just restarted +instead of closed and reopened. If a media control is present on inline, from_time is overridden by MC range*/ +void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, Bool disable_addon_check); + +void gf_scene_insert_pid(GF_Scene *scene, GF_SceneNamespace *sns, GF_FilterPid *pid, Bool is_in_iod); + +/*exported for compositor: handles filtering of "self" parameter indicating anchor only acts on container inline scene +not root one. Returns 1 if handled (cf user.h, navigate event)*/ +Bool gf_scene_process_anchor(GF_Node *caller, GF_Event *evt); +void gf_scene_force_size_to_video(GF_Scene *scene, GF_MediaObject *mo); + +//check clock status. +//If @check_buffering is 0, returns 1 if all clocks have seen eos, 0 otherwise +//If @check_buffering is 1, returns 1 if no clock is buffering, 0 otheriwse +Bool gf_scene_check_clocks(GF_SceneNamespace *ns, GF_Scene *scene, Bool check_buffering); + +void gf_scene_notify_event(GF_Scene *scene, u32 event_type, GF_Node *n, void *dom_evt, GF_Err code, Bool no_queuing); + +void gf_scene_mpeg4_inline_restart(GF_Scene *scene); +void gf_scene_mpeg4_inline_check_restart(GF_Scene *scene); + + +GF_Node *gf_scene_get_subscene_root(GF_Node *inline_node); + +void gf_scene_select_main_addon(GF_Scene *scene, GF_ObjectManager *odm, Bool set_on, u32 current_clock_time); +void gf_scene_reset_addons(GF_Scene *scene); +void gf_scene_reset_addon(GF_AddonMedia *addon, Bool disconnect); + +#ifndef GPAC_DISABLE_VRML + +/*extern proto fetcher*/ +typedef struct +{ + MFURL *url; + GF_MediaObject *mo; +} GF_ProtoLink; + +/*returns true if the given node DEF name is the url target view (eg blabla#myview)*/ +Bool gf_inline_is_default_viewpoint(GF_Node *node); + +GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url); + +/*restarts inline scene - care has to be taken not to remove the scene while it is traversed*/ +void gf_inline_restart(GF_Scene *scene); + +#endif + +/*compares object URL with another URL - ONLY USE THIS WITH DYNAMIC ODs*/ +Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, u32 obj_hint_type); + +void gf_mo_update_caps(GF_MediaObject *mo); +void gf_mo_update_caps_ex(GF_MediaObject *mo, Bool check_unchanged); + + +const char *gf_scene_get_fragment_uri(GF_Node *node); +void gf_scene_set_fragment_uri(GF_Node *node, const char *uri); + + +/*GF_NET_ASSOCIATED_CONTENT_TIMING*/ +typedef struct +{ + u32 timeline_id; + u32 media_timescale; + u64 media_timestamp; + //for now only used in MPEG-2, so media_pts is in 90khz scale + u64 media_pts; + Bool force_reload; + Bool is_paused; + Bool is_discontinuity; + u64 ntp; +} GF_AssociatedContentTiming; + +typedef struct +{ + //negative values mean "timeline is ready no need for timing message" + s32 timeline_id; + const char *external_URL; + Bool is_announce, is_splicing; + Bool reload_external; + Bool enable_if_defined; + Bool disable_if_defined; + GF_Fraction activation_countdown; + //start and end times of splicing if any + Double splice_start_time, splice_end_time; + Bool splice_time_pts; +} GF_AssociatedContentLocation; + +void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLocation *addon_info); +void gf_scene_notify_associated_media_timeline(GF_Scene *scene, GF_AssociatedContentTiming *addon_time); + + +/*clock*/ +struct _object_clock +{ + u16 clock_id; + GF_Compositor *compositor; + GF_Mutex *mx; + /*no_time_ctrl : set if ANY stream running on this clock has no time control capabilities - this avoids applying + mediaControl and others that would break stream dependencies*/ + Bool clock_init, has_seen_eos, no_time_ctrl; + u32 init_timestamp, start_time, pause_time, nb_paused; + /*the number of streams buffering on this clock*/ + u32 nb_buffering; + /*associated media control if any*/ + struct _media_control *mc; + /*for MC only (no FlexTime)*/ + Fixed speed; + //whenever speed is changed we store the time at this instant + u32 speed_set_time; + s32 audio_delay; + + u32 last_ts_rendered; + u32 service_id; + + //media time in ms corresponding to the init tmiestamp of the clock + u32 media_time_at_init; + Bool has_media_time_shift; + + u32 ocr_discontinuity_time; + //we increment this one at each reset, and ask the filter chain to mark packets with this flag + u32 timeline_id; +}; + +/*destroys clock*/ +void gf_clock_del(GF_Clock *ck); +/*finds a clock by ID or by ES_ID*/ +GF_Clock *gf_clock_find(GF_List *Clocks, u16 clockID, u16 ES_ID); +/*attach clock returns a new clock or the clock this stream (ES_ID) depends on (OCR_ES_ID) +hasOCR indicates whether the stream being attached carries object clock references +@clocks: list of clocks in ES namespace (service) +@is: inline scene to solve clock dependencies +*/ +GF_Clock *gf_clock_attach(GF_List *clocks, GF_Scene *scene, u16 OCR_ES_ID, u16 ES_ID, s32 hasOCR); +/*reset clock (only called by channel owning clock)*/ +void gf_clock_reset(GF_Clock *ck); +/*return clock time in ms*/ +u32 gf_clock_time(GF_Clock *ck); +/*return media time in ms*/ +u32 gf_clock_media_time(GF_Clock *ck); + +/*translates from clock time in ms to media time in ms*/ +u32 gf_clock_to_media_time(GF_Clock *ck, u32 clock_val); + +/*return time in ms since clock started - may be different from clock time when seeking or live*/ +u32 gf_clock_elapsed_time(GF_Clock *ck); + +/*sets clock time - FIXME: drift updates for OCRs*/ +void gf_clock_set_time(GF_Clock *ck, u32 TS); +/*return clock time in ms without drift adjustment - used by audio objects only*/ +u32 gf_clock_real_time(GF_Clock *ck); +/*pause the clock*/ +void gf_clock_pause(GF_Clock *ck); +/*resume the clock*/ +void gf_clock_resume(GF_Clock *ck); +/*returns true if clock started*/ +Bool gf_clock_is_started(GF_Clock *ck); +/*toggles buffering on (clock is paused at the first stream buffering) */ +void gf_clock_buffer_on(GF_Clock *ck); +/*toggles buffering off (clock is paused at the last stream restarting) */ +void gf_clock_buffer_off(GF_Clock *ck); +/*set clock speed scaling factor*/ +void gf_clock_set_speed(GF_Clock *ck, Fixed speed); + +/*set audio delay, i.e. amount of ms to delay non-audio streams +audio is usually send to the sound card quite ahead of time, depending on the output compositor settings*/ +void gf_clock_set_audio_delay(GF_Clock *ck, s32 ms_delay); + + +/*OD manager*/ + +enum +{ + /*flag set if object cannot be time-controloed*/ + GF_ODM_NO_TIME_CTRL = (1<<1), + /*flag set if subscene uses parent scene timeline*/ + GF_ODM_INHERIT_TIMELINE = (1<<2), + /*flag set if object has been redirected*/ + GF_ODM_REMOTE_OD = (1<<3), + /*flag set if object has profile indications*/ + GF_ODM_HAS_PROFILES = (1<<4), + /*flag set if object governs profile of inline subscenes*/ + GF_ODM_INLINE_PROFILES = (1<<5), + /*flag set if object declared by network service, not from OD stream*/ + GF_ODM_NOT_IN_OD_STREAM = (1<<6), + /*flag set if object is an entry point of the network service*/ + GF_ODM_SERVICE_ENTRY = (1<<7), + + /*flag set if object has been started before any start request from the scene*/ + GF_ODM_PREFETCH = (1<<8), + + /*flag set if object has been deleted*/ + GF_ODM_DESTROYED = (1<<9), + /*dynamic flags*/ + + /*flag set if associated subscene must be regenerated*/ + GF_ODM_REGENERATE_SCENE = (1<<10), + + /*flag set for first play request*/ + GF_ODM_INITIAL_BROADCAST_PLAY = (1<<11), + + /*flag set until ODM is setup*/ + GF_ODM_NOT_SETUP = (1<<12), + + /*flag set when ODM is setup*/ + GF_ODM_PAUSED = (1<<13), + + /*flag set when ODM is setup*/ + GF_ODM_PAUSE_QUEUED = (1<<14), + + /*flag indicates the odm is a target passthrough*/ + GF_ODM_PASSTHROUGH = (1<<15), + /*flag indicates the clock is shared between tiles and a play should not trigger a rebuffer*/ + GF_ODM_TILED_SHARED_CLOCK = (1<<16), + /*flag indicates TEMI info is associated with PID*/ + GF_ODM_HAS_TEMI = (1<<17), +}; + +enum +{ + GF_ODM_STATE_STOP, + GF_ODM_STATE_PLAY, +}; + +enum +{ + GF_ODM_ACTION_PLAY, + GF_ODM_ACTION_STOP, + GF_ODM_ACTION_DELETE, + GF_ODM_ACTION_SCENE_DISCONNECT, + GF_ODM_ACTION_SCENE_RECONNECT, + GF_ODM_ACTION_SCENE_INLINE_RESTART, + GF_ODM_ACTION_SETUP +}; + +typedef struct +{ + u32 pid_id; //esid for clock solving + u32 state; + Bool has_seen_eos; + GF_FilterPid *pid; +} GF_ODMExtraPid; + + +struct _od_manager +{ + /*parent scene or NULL for root scene*/ + GF_Scene *parentscene; + /*sub scene for inline or NULL */ + GF_Scene *subscene; + + /*namespace for clocks*/ + GF_SceneNamespace *scene_ns; + + /*clock for this object*/ + GF_Clock *ck; + //one of the pid contributing to this object acts as the clock reference + Bool owns_clock; + + u32 prev_clock_at_discontinuity_plus_one; + + u32 nb_dropped; + + GF_FilterPid *pid; + //object ID for linking with mediaobjects + u32 ID; + u32 pid_id; //esid for clock solving + + //MPEG-4 OD descriptor + GF_ObjectDescriptor *OD; + + //parent service ID as defined from input + u32 ServiceID; + Bool hybrid_layered_coded; + + Bool has_seen_eos; + GF_List *extra_pids; + + Bool clock_inherited; + //0 or 1, except for IOD where we may have several BIFS/OD streams + u32 nb_buffering, nb_rebuffer; + u32 buffer_max_ms, buffer_min_ms, buffer_playout_ms; + Bool blocking_media; + + //internal hash for source allowing to distinguish input PIDs sources + u32 source_id; + + //media type for this object + u32 type, original_oti; + + u32 flags; + + GF_MediaObject *sync_ref; + + /*interface with scene rendering*/ + struct _mediaobj *mo; + + /*number of channels with connection not yet acknowledge*/ + u32 pending_channels; + u32 state; + /* during playback: timing as evaluated by the composition memory or the scene codec - this is the timestamp + media time at clock init*/ + u32 media_current_time; + /*full object duration 0 if unknown*/ + u64 duration; + /* + upon start: media start time as requested by scene compositor (eg not media control) + set to -1 upon stop to postpone stop request + */ + u64 media_start_time; + s64 media_stop_time; + + /*full object timeshift depth in ms, 0 if no timeshift, (u32) -1 is infinity */ + u32 timeshift_depth; + + u32 action_type; + //delay in PID timescale + s64 timestamp_offset; + + Fixed set_speed; + Bool disable_buffer_at_next_play; + +// u32 raw_media_frame_pending; + GF_Semaphore *raw_frame_sema; + +#ifndef GPAC_DISABLE_VRML + /*the one and only media control currently attached to this object*/ + struct _media_control *media_ctrl; + /*the list of media control controlling the object*/ + GF_List *mc_stack; + /*the media sensor(s) attached to this object*/ + GF_List *ms_stack; +#endif + + //only set on root OD of addon subscene, which gather all the hybrid resources + GF_AddonMedia *addon; + //set for objects splicing the main content, indicates the media type (usually in @codec but no codec created for splicing) + u32 splice_addon_mtype; + + //for a regular ODM, this indicates that the current scalable_odm associated + struct _od_manager *upper_layer_odm; + //for a scalable ODM, this indicates the lower layer odm associated + struct _od_manager *lower_layer_odm; + + s32 last_drawn_frame_ntp_diff; + u64 last_drawn_frame_ntp_sender, last_drawn_frame_ntp_receive; + u32 ambi_ch_id; + + const char *redirect_url; + /*0: not set, 1: set , 2: set and disconnect was called to remove the object*/ + u32 skip_disconnect_state; + + Bool ignore_sys; + u64 last_filesize_signaled; +}; + +GF_ObjectManager *gf_odm_new(); +void gf_odm_del(GF_ObjectManager *ODMan); + +/*setup OD*/ +void gf_odm_setup_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, GF_FilterPid *for_pid); + +void gf_odm_setup_remote_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, char *remote_url, Bool for_addon); + +/*disctonnect OD and removes it if desired (otherwise only STOP is propagated)*/ +void gf_odm_disconnect(GF_ObjectManager *odman, u32 do_remove); +/*setup PID attached to this object*/ +GF_Err gf_odm_setup_pid(GF_ObjectManager *odm, GF_FilterPid *pid); + +//if register_only is false, calls gf_odm_setup_object on the given PID +void gf_odm_register_pid(GF_ObjectManager *odm, GF_FilterPid *pid, Bool register_only); + +Bool gf_odm_check_buffering(GF_ObjectManager *odm, GF_FilterPid *pid); + +/*signals end of stream on channels*/ +void gf_odm_on_eos(GF_ObjectManager *odm, GF_FilterPid *pid); + +/*set time shift buffer duration */ +void gf_odm_set_timeshift_depth(GF_ObjectManager *odm, u32 time_shift_ms); + +/*send PLAY request on associated PID*/ +void gf_odm_start(GF_ObjectManager *odm); +/*stop OD streams*/ +void gf_odm_stop(GF_ObjectManager *odm, Bool force_close); +/*send PLAY request to network - needed to properly handle multiplexed inputs +ONLY called by service handler (media manager thread)*/ +void gf_odm_play(GF_ObjectManager *odm); + +/*pause object (mediaControl use only)*/ +void gf_odm_pause(GF_ObjectManager *odm); +/*resume object (mediaControl use only)*/ +void gf_odm_resume(GF_ObjectManager *odm); +/*set object speed*/ +void gf_odm_set_speed(GF_ObjectManager *odm, Fixed speed, Bool adjust_clock_speed); +/*returns the clock of the media stream (video, audio or bifs), NULL otherwise */ +struct _object_clock *gf_odm_get_media_clock(GF_ObjectManager *odm); +/*adds segment descriptors targeted by the URL to the list and sort them - the input list must be empty*/ +void gf_odm_init_segments(GF_ObjectManager *odm, GF_List *list, MFURL *url); +/*returns true if this OD depends on the given clock*/ +Bool gf_odm_shares_clock(GF_ObjectManager *odm, struct _object_clock *ock); + +void gf_odm_update_duration(GF_ObjectManager *odm, GF_FilterPid *pid); + +GF_Segment *gf_odm_find_segment(GF_ObjectManager *odm, char *descName); + +Bool gf_odm_stop_or_destroy(GF_ObjectManager *odm); + +void gf_odm_signal_eos_reached(GF_ObjectManager *odm); + +void gf_odm_reset_media_control(GF_ObjectManager *odm, Bool signal_reset); + +void gf_odm_check_clock_mediatime(GF_ObjectManager *odm); + +void gf_odm_addon_setup(GF_ObjectManager *odm); + + +/*! + * Media Object types + * + * This type provides a hint to network modules which may have to generate an service descriptor on the fly. + * They occur only if objects/services used in the scene are not referenced through ObjectDescriptors (MPEG-4) + * but direct through URL +*/ +enum +{ + /*!service descriptor expected is of undefined type. This should be treated like GF_MEDIA_OBJECT_SCENE*/ + GF_MEDIA_OBJECT_UNDEF = 0, + /*!service descriptor expected is of SCENE type and shall contain a scene stream and OD one if needed*/ + GF_MEDIA_OBJECT_SCENE, + /*!service descriptor expected is of SCENE UPDATES type (animation streams)*/ + GF_MEDIA_OBJECT_UPDATES, + /*!service descriptor expected is of VISUAL type*/ + GF_MEDIA_OBJECT_VIDEO, + /*!service descriptor expected is of AUDIO type*/ + GF_MEDIA_OBJECT_AUDIO, + /*!service descriptor expected is of TEXT type (3GPP/MPEG4)*/ + GF_MEDIA_OBJECT_TEXT, + /*!service descriptor expected is of UserInteraction type (MPEG-4 InputSensor)*/ + GF_MEDIA_OBJECT_INTERACT +}; + +/*! All Media Objects inserted through URLs and not MPEG-4 OD Framework use this ODID*/ +#define GF_MEDIA_EXTERNAL_ID 1050 + +enum +{ + //no connection error, no frames seen in input pipeline + MO_CONNECT_OK=0, + //no connection error, frames seen in input pipeline but no frame yet available for object + MO_CONNECT_BUFFERING, + //explicit source setup failure + MO_CONNECT_FAILED, + //timeout of input pipeline (no frames seen after compositor->timeout ms) + MO_CONNECT_TIMEOUT +}; + +/*GF_MediaObject: link between real object manager and scene. although there is a one-to-one mapping between a +MediaObject and an ObjectManager, we have to keep them separated in order to handle OD remove commands which destroy +ObjectManagers. */ +struct _mediaobj +{ + /*type is as defined in constants.h # GF_MEDIA_OBJECT_* */ + u32 type; + /*one of the above flags*/ + u32 flags; + + + /* private to ESM*/ + + /*media object manager - private to the sync engine*/ + struct _od_manager *odm; + /*OD ID of the object*/ + u32 OD_ID; + /*OD URL for object not using MPEG4 OD urls*/ + MFURL URLs; + /*session join*/ + u32 num_open; + /*shared object restart handling*/ + u32 num_to_restart, num_restart; + Fixed speed; + + //current packet + GF_FilterPacket *pck; + + u32 frame_dur; + u32 last_fetch_time; + Bool first_frame_fetched; + //number of bytes read in the current packet + u32 RenderedLength; + + /*shared object info: if 0 a new frame will be checked, otherwise current is returned*/ + u32 nb_fetch; + /*frame presentation time*/ + u32 timestamp; + /*time in ms until next frame shall be presented*/ + s32 ms_until_next; + s32 ms_until_pres; + /*data frame size*/ + u32 size, framesize; + /*pointer to data frame */ + u8 *frame; + /* Objects implementing the DOM Event Target interface + used to dispatch HTML5 Media and Media Source Events */ + GF_List *evt_targets; + /*pointer to the node responsible for the creation of this media object + ONLY used for scene media type (animationStreams) + Reset upon creation of the decoder. + */ + void *node_ptr; + + Bool is_eos; + Bool config_changed; + + /*currently valid properties of the object*/ + u32 width, height, stride, pixel_ar, pixelformat, bitrate; + Bool is_flipped; + u32 sample_rate, num_channels, afmt, bytes_per_sec; + u64 channel_config; + Bool planar_audio; + u32 srd_x, srd_y, srd_w, srd_h, srd_full_w, srd_full_h; + u32 flip, rotate; + + u32 quality_degradation_hint; + u32 nb_views; + u32 nb_layers; + u32 view_min_x, view_max_x, view_min_y, view_max_y; + GF_FilterFrameInterface *frame_ifce; + + Float c_x, c_y, c_w, c_h; + u32 connect_state; +}; + +GF_MediaObject *gf_mo_new(); + + +/*media access events */ +void gf_odm_service_media_event(GF_ObjectManager *odm, GF_EventType event_type); +void gf_odm_service_media_event_with_download(GF_ObjectManager *odm, GF_EventType event_type, u64 loaded_size, u64 total_size, u32 bytes_per_sec, u32 buffer_level_plus_one, u32 min_buffer_time); + +/*checks the URL and returns the ODID (MPEG-4 od://) or GF_MEDIA_EXTERNAL_ID for all regular URLs*/ +u32 gf_mo_get_od_id(MFURL *url); + +void gf_scene_generate_views(GF_Scene *scene, char *url, char *parent_url); +void gf_scene_generate_mosaic(GF_Scene *scene, char *url, char *parent_path); +//sets pos and size of addon +// size is 1/2 height (0), 1/3 (1) or 1/4 (2) +// pos is bottom-left(0), top-left (1) bottom-right (2) or top-right (3) +void gf_scene_set_addon_layout_info(GF_Scene *scene, u32 position, u32 size_factor); + + +void gf_scene_message(GF_Scene *scene, const char *service, const char *message, GF_Err error); +void gf_scene_message_ex(GF_Scene *scene, const char *service, const char *message, GF_Err error, Bool no_filtering); + +//returns media time in sec for the addon - timestamp_based is set to 1 if no timeline has been found (eg sync is based on direct timestamp comp) +Double gf_scene_adjust_time_for_addon(GF_AddonMedia *addon, Double clock_time, u8 *timestamp_based); +s64 gf_scene_adjust_timestamp_for_addon(GF_AddonMedia *addon, u64 orig_ts_ms); + +/*check if the associated addon has to be restarted, based on the timestamp of the main media (used for scalable addons only). Returns 1 if the addon has been restarted*/ +Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts); + +/*callbacks for scene graph library so that all related ESM nodes are properly instanciated*/ +void gf_scene_node_callback(void *_is, GF_SGNodeCbkType type, GF_Node *node, void *param); + + +void gf_scene_switch_quality(GF_Scene *scene, Bool up); + +//exported for gpac.js, resumes to main content +void gf_scene_resume_live(GF_Scene *subscene); + +enum +{ + //addon to overlay + GF_ADDON_TYPE_ADDITIONAL = 0, + //main content duplicated for PVR purposes + GF_ADDON_TYPE_MAIN, + //scalable decoding - reassembly before the decoder(s) + GF_ADDON_TYPE_SCALABLE, + //multiview reconstruction - reassembly after the decoder(s) + GF_ADDON_TYPE_MULTIVIEW, + //addon used for temporary splicing + GF_ADDON_TYPE_SPLICED, +}; + +struct _gf_addon_media +{ + char *url; + GF_ObjectManager *root_od; + s32 timeline_id; + u32 is_splicing; + //in scene time + Double activation_time; + + Bool enabled; + //object(s) have been started (PLAY command sent) - used to filter out AUs in scalabmle addons + Bool started; + Bool timeline_ready; + + u32 media_timescale; + u64 media_timestamp; + u64 media_pts; + + //in case we detect a loop, we store the value of the mediatime in the loop until we actually loop the content + u32 past_media_timescale; + u64 past_media_timestamp; + u64 past_media_pts, past_media_pts_scaled; + Bool loop_detected; + + u32 addon_type; + + Double splice_start, splice_end; + Bool is_over; + Bool splice_in_pts; + u32 nb_splicing; + Bool min_dts_set; + u64 splice_min_dts; +}; + +void gf_scene_toggle_addons(GF_Scene *scene, Bool show_addons); + + +void gf_scene_set_service_id(GF_Scene *scene, u32 service_id); + + +/*post extended user mouse interaction to terminal + X and Y are point coordinates in the display expressed in 2D coord system top-left (0,0), Y increasing towards bottom + @xxx_but_down: specify whether the mouse button is down(2) or up (1), 0 if unchanged + @wheel: specify current wheel inc (0: unchanged , +1 for one wheel delta forward, -1 for one wheel delta backward) +*/ +/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ +void gf_sc_input_sensor_mouse_input(GF_Compositor *compositor, GF_EventMouse *event); + +/*post extended user key interaction to terminal + @key_code: GPAC DOM code of input key + @hw_code: hardware code of input key + @isKeyUp: set if key is released +*/ +/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ +Bool gf_sc_input_sensor_keyboard_input(GF_Compositor *compositor, u32 key_code, u32 hw_code, Bool isKeyUp); + +/*post extended user character interaction to terminal + @character: unicode character input +*/ +/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ +void gf_sc_input_sensor_string_input(GF_Compositor *compositor, u32 character); + +GF_Err gf_input_sensor_setup_object(GF_ObjectManager *odm, GF_ESD *esd); +void gf_input_sensor_delete(GF_ObjectManager *odm); + +void InitInputSensor(GF_Scene *scene, GF_Node *node); +void InputSensorModified(GF_Node *n); +void InitKeySensor(GF_Scene *scene, GF_Node *node); +void InitStringSensor(GF_Scene *scene, GF_Node *node); + + +GF_Err gf_odm_get_object_info(GF_ObjectManager *odm, GF_MediaInfo *info); + + + +void gf_sc_connect_from_time_ex(GF_Compositor *compositor, const char *URL, u64 startTime, u32 pause_at_first_frame, Bool secondary_scene, const char *parent_path); + +void gf_sc_disconnect(GF_Compositor *compositor); + + +/*restart object and takes care of media control/clock dependencies*/ +void mediacontrol_restart(GF_ObjectManager *odm); +void mediacontrol_pause(GF_ObjectManager *odm); +//resumes object. +//If @resume_to_live is set, will deactivate main addon acting as PVR +void mediacontrol_resume(GF_ObjectManager *odm, Bool resume_to_live); + +Bool MC_URLChanged(MFURL *old_url, MFURL *new_url); + +void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos); + +#ifndef GPAC_DISABLE_VRML + +typedef struct +{ + GF_AudioInput input; + GF_TimeNode time_handle; + Double start_time; + Bool set_duration, failure; +} AudioClipStack; + + +typedef struct +{ + GF_AudioInput input; + GF_TimeNode time_handle; + Bool is_active; + Double start_time; +} AudioSourceStack; + + +/*to do: add preroll support*/ +typedef struct _media_control +{ + M_MediaControl *control; + + /*store current values to detect changes*/ + Double media_start, media_stop; + Fixed media_speed; + Bool enabled; + MFURL url; + + GF_Scene *parent; + /*stream owner*/ + GF_MediaObject *stream; + /*stream owner's clock*/ + GF_Clock *ck; + + u32 changed; + Bool is_init; + Bool paused; + u32 prev_active; + + /*stream object list (segments)*/ + GF_List *seg; + /*current active segment index (ie, controlling the PLAY range of the media)*/ + u32 current_seg; +} MediaControlStack; +void InitMediaControl(GF_Scene *scene, GF_Node *node); +void MC_Modified(GF_Node *node); + +void MC_GetRange(MediaControlStack *ctrl, Double *start_range, Double *end_range); + +/*assign mediaControl for this object*/ +void gf_odm_set_mediacontrol(GF_ObjectManager *odm, struct _media_control *ctrl); +/*get media control ruling the clock the media is running on*/ +struct _media_control *gf_odm_get_mediacontrol(GF_ObjectManager *odm); +/*removes control from OD context*/ +void gf_odm_remove_mediacontrol(GF_ObjectManager *odm, struct _media_control *ctrl); +/*switches control (propagates enable=FALSE), returns 1 if control associated with OD has changed to new one*/ +Bool gf_odm_switch_mediacontrol(GF_ObjectManager *odm, struct _media_control *ctrl); + +/*returns 1 if this is a segment switch, 0 otherwise - takes care of object restart if segment switch*/ +Bool gf_odm_check_segment_switch(GF_ObjectManager *odm); + +typedef struct _media_sensor +{ + M_MediaSensor *sensor; + + GF_Scene *parent; + + GF_List *seg; + Bool is_init; + /*stream owner*/ + GF_MediaObject *stream; + + /*private cache (avoids browsing all sensor*/ + u32 active_seg; +} MediaSensorStack; + +void InitMediaSensor(GF_Scene *scene, GF_Node *node); +void MS_Modified(GF_Node *node); + +void MS_Stop(MediaSensorStack *st); + + +void InitMediaControl(GF_Scene *scene, GF_Node *node); +void MC_Modified(GF_Node *node); + +void InitMediaSensor(GF_Scene *scene, GF_Node *node); +void MS_Modified(GF_Node *node); + +void gf_init_inline(GF_Scene *scene, GF_Node *node); +void gf_inline_on_modified(GF_Node *node); + +void gf_scene_init_storage(GF_Scene *scene, GF_Node *node); + +#endif /*GPAC_DISABLE_VRML*/ + +Bool gf_sc_check_gl_support(GF_Compositor *compositor); +void gf_sc_mo_destroyed(GF_Node *n); +Bool gf_sc_on_event(void *cbck, GF_Event *event); + +#ifdef __cplusplus +} +#endif +#endif /*_COMPOSITOR_DEV_H_*/ + diff --git a/include/gpac/internal/crypt_dev.h b/include/gpac/internal/crypt_dev.h new file mode 100644 index 0000000..63689cc --- /dev/null +++ b/include/gpac/internal/crypt_dev.h @@ -0,0 +1,66 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2000-2012 +* All rights reserved +* +* This file is part of GPAC / Crypto 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_CRYPT_DEV_H_ +#define _GF_CRYPT_DEV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/crypt.h> + +/*private - do not use*/ +struct _gf_crypt_context +{ + GF_CRYPTO_ALGO algo; //single value for now + GF_CRYPTO_MODE mode; //CBC or CTR + + /* Internal context for openSSL or tiny AES*/ + void *context; + + //ptr to encryption function + GF_Err(*_init_crypt) (GF_Crypt *ctx, void*, const void*); + void(*_deinit_crypt) (GF_Crypt *ctx); + void(*_end_crypt) (GF_Crypt *ctx); + void(*_set_key)(GF_Crypt *ctx, void*); + GF_Err(*_crypt) (GF_Crypt *ctx, u8 *buffer, u32 size); + GF_Err(*_decrypt) (GF_Crypt*, u8 *buffer, u32 size); + GF_Err(*_set_state) (GF_Crypt*, const u8 *IV, u32 IV_size); + GF_Err(*_get_state) (GF_Crypt*, u8 *IV, u32 *IV_size); +}; + +#ifdef GPAC_HAS_SSL +GF_Err gf_crypt_open_open_openssl(GF_Crypt* td, GF_CRYPTO_MODE mode); +#else +GF_Err gf_crypt_open_open_tinyaes(GF_Crypt* td, GF_CRYPTO_MODE mode); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_CRYPT_DEV_H_*/ diff --git a/include/gpac/internal/dvb_mpe_dev.h b/include/gpac/internal/dvb_mpe_dev.h new file mode 100644 index 0000000..92735d9 --- /dev/null +++ b/include/gpac/internal/dvb_mpe_dev.h @@ -0,0 +1,265 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Walid B.H - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / MPEG2-TS 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 gf_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 gf_free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef _GF_DVB_MPE_DEV_H_ +#define _GF_DVB_MPE_DEV_H_ + +#include <gpac/dvb_mpe.h> +#include <gpac/internal/reedsolomon.h> + + +#ifndef GPAC_DISABLE_MPEG2TS + + +/*INT object*/ +typedef struct +{ + u32 id; + u32 processing_order; + u32 number_of_descriptor; + GF_List * descriptors; + +} GF_M2TS_INT; + +typedef struct +{ + u32 tag; + u32 length; + u32 network_id; + u32 original_network_id; + u32 ts_id; + u32 service_id; + u32 component_tag; + +} GF_M2TS_LOC_DSCPTR_IP_STREAM; + +typedef struct descriptor_TimeSliceFec +{ + Bool time_slicing; + u8 mpe_fec; + u8 frame_size; + u8 max_burst_duration; + u8 max_average_rate; + u8 time_slice_fec_id; + u8 * id_selector; +} GF_M2TS_DesTimeSliceFec; + +typedef struct +{ + u16 network_id; + u16 original_network_id; + u16 transport_stream_id; + u16 service_id; + u8 component_tag; +} GF_M2TS_DesLocation; + +typedef struct { + u8 type; /* 0 = target_IP_descriptor, 1 = target_IP_address_descriptor */ + u32 address_mask; + u8 address[4]; + u8 slash_mask; + u32 rx_port[10]; /* list of the address port */ +} GF_M2TS_IP_Target; + +typedef struct +{ + GF_List *targets; /* list of IP destination for the IP streams in the platform */ + u32 PID; + Bool stream_info_gathered; + + /* location descriptor only valid for the associated targets */ + GF_M2TS_DesLocation location; + + GF_M2TS_DesTimeSliceFec time_slice_fec; +} GF_M2TS_IP_Stream; + + +/*IP_Platform object*/ +typedef struct __gf_dvb_mpe_ip_platform +{ + /* remaining from INT, to be delete */ + u32 id; + u32 processing_order; + u32 number_of_descriptor; + + u8 *name; /* platform name */ + u8 *provider_name; /* platform provider name */ + + /* location descriptor valid for the whole platform */ + GF_M2TS_DesLocation *location; + + GF_List * ip_streams; + Bool all_info_gathered; + GF_List *socket_struct; + +} GF_M2TS_IP_PLATFORM; + +typedef struct +{ + u8 *data; /* Data */ + u32 u32_version; /* IP version */ + u32 u32_hdr_length; /* header length by piece of 4 bytes */ + u32 u32_total_length; /* the length of the datagram (hdr+payload) in bytes */ + u32 u32_payload_size; /* the length of the payload */ + u32 u32_id_nb; /* the number of the paquet, in case of frag */ + u32 u32_flag; /* if 010 unfrag packet, 100 fragmented packet, check the id_nb to know the packet number. + 0 is the last one */ + u32 u32_frag_offset; /* The offset position of this packet compare to the first packet. unit : 8 bytes */ + u32 u32_TTL; /* (Time To Live) when = 0 , the packet is ignored and error message */ + u32 u32_protocol; /* TCP = 6, UDP = 17, ICMP = 1 */ + u32 u32_crc; + u8 u8_tx_adr[4]; /* source address */ + u8 u8_rx_adr[4]; /* destination address */ + u32 u32_size_option; /* size of the option before payload */ + u32 u32_padding; /* = 1 if where read padding columns */ + u32 u32_sum; + + /* UDP */ + u32 u32_tx_udp_port; /* source port */ + u32 u32_rx_udp_port; /* destination port */ + u32 u32_udp_data_size; + u32 u32_udp_chksm; +} GF_M2TS_IP_Packet; + + + + +#define MPE_ADT_COLS 191 +#define MPE_RS_COLS NPAR + +typedef struct mpe_error_holes +{ + u32 offset; + u32 length; + +} MPE_Error_Holes; + +typedef struct mpe_fec_frame +{ + u32 rows; + u32 col_adt ; + u32 col_rs ; + u8 *p_adt; /* pointer to the application data table*/ + u8 *p_rs; /* pointer to the RS data table*/ + u32 *p_error_adt; + u32 *p_error_rs ; + + u32 capacity_total; + u32 current_offset_adt ; + u32 current_offset_rs; + u32 initialized ; + u8 ADT_done; + u32 PID; + GF_List *mpe_holes; + //u32 erasures [] p_erasures; /*pointer to the error indicators*/ +} MPE_FEC_FRAME; + + +/* Get INT table */ +void gf_m2ts_process_int(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *ip_table, u8 *data, u32 data_size, u32 table_id); + +void section_DSMCC_INT(GF_M2TS_IP_PLATFORM* ip_platform, u8 *data, u32 data_size); + +u32 platform_descriptorDSMCC_INT_UNT (GF_M2TS_IP_PLATFORM* ip_platform,u8 *data); +u32 dsmcc_pto_platform_descriptor_loop (GF_M2TS_IP_PLATFORM* ip_platform,u8 *data); + +u32 descriptorDSMCC_INT_UNT (GF_M2TS_IP_Stream *ip_str,u8 *data); +void descriptorDSMCC_target_IP_address ( GF_M2TS_IP_Stream *ip_str, u8 *data); +u32 dsmcc_pto_descriptor_loop (GF_M2TS_IP_Stream *ip_str, u8 *data); +void descriptorTime_slice_fec_identifier(GF_M2TS_IP_Stream *ip_str, u8 *data); +void gf_m2ts_target_ip( GF_M2TS_IP_Stream* ip_str, u8 *data); +void descriptorLocation(GF_M2TS_IP_Stream *ip_str , u8 *data); + + +void gf_ip_platform_descriptor(GF_M2TS_IP_PLATFORM* ip_platform, u8 *data); +void gf_ip_platform_provider_descriptor(GF_M2TS_IP_PLATFORM* ip_platform,u8 *data); +void gf_m2ts_ip_platform_init(GF_M2TS_IP_PLATFORM * ip_platform); + +u32 gf_m2ts_ipdatagram_reader(u8 *datagram, GF_M2TS_IP_Packet *ip_packet, u32 offset); +void gf_m2ts_process_ipdatagram(MPE_FEC_FRAME *mff,GF_M2TS_Demuxer *ts); +Bool gf_m2ts_compare_ip(u8 rx_ip_address[4], u8 ip_address_bootstrap[4]); + +struct _sock_entry +{ + u32 ipv4_addr; + u16 port; + GF_Socket *sock; + Bool bind_failure; +}; +struct tag_m2ts_section_mpe +{ + ABSTRACT_ES + GF_M2TS_SectionFilter *sec; + + /* if this stream is an MPE section stream, we need: + - a direct access to the timeslice fec descriptor + - an MPE FEC Frame Structure to process RS code */ + GF_M2TS_IP_Stream *ip_platform; + MPE_FEC_FRAME *mff; + +}; + + +void gf_m2ts_process_mpe(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_MPE *mpe, u8 *data, u32 data_size, u8 table_id); +void gf_m2ts_gather_ipdatagram_information(MPE_FEC_FRAME *mff,GF_M2TS_Demuxer *ts); + +void socket_simu(GF_M2TS_IP_Packet *ip_packet, GF_M2TS_Demuxer *ts, Bool yield); + +void gf_m2ts_mpe_send_datagram(GF_M2TS_Demuxer *ts, u32 pid, u8 *data, u32 data_size); + +/* allocate the necessary memory space*/ +u32 init_frame(MPE_FEC_FRAME * mff, u32 rows); + +void getRowFromADT(MPE_FEC_FRAME * mff,u32 index, u8 * adt_row); +void getRowFromRS(MPE_FEC_FRAME * mff,u32 index, u8 * rs_row); +void setRowRS(MPE_FEC_FRAME * mff,u32 index, u8 * p_rs); + +/*return the number of errors and the position of the error in the row*/ +void getErrorPositions(MPE_FEC_FRAME * mff, u32 row, u32 * errPositions); + +void setColRS( MPE_FEC_FRAME * mff, u32 offset, u8 * pds, u32 length ); +void getColRS(MPE_FEC_FRAME * mff, u32 offset, u8 * pds, u32 length); +void setIpDatagram(MPE_FEC_FRAME * mff,u32 offset, u8 * dgram, u32 length ); + +void setErrorIndicator(u32 * data , u32 offset1, u32 length ); +void resetMFF(MPE_FEC_FRAME * mff) ; +u32 getErrasurePositions( MPE_FEC_FRAME *mff , u32 row, u32 *errasures); + +void decode_fec(MPE_FEC_FRAME * mff); + + +// Descriptor tag space/scope... +typedef enum { + MPEG, DVB_SI, + DSMCC_STREAM, DSMCC_CAROUSEL, DSMCC_INT_UNT, MHP_AIT, TVA_RNT +} DTAG_SCOPE; + +void descriptor_PRIVATE (u8 *b, DTAG_SCOPE tag_scope, GF_List * descriptors ); + + +#endif //GPAC_DISABLE_MPEG2TS + +#endif //_GF_DVB_MPE_DEV_H_ diff --git a/include/gpac/internal/ietf_dev.h b/include/gpac/internal/ietf_dev.h new file mode 100644 index 0000000..d8642a6 --- /dev/null +++ b/include/gpac/internal/ietf_dev.h @@ -0,0 +1,517 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / IETF RTP/RTSP/SDP 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_IETF_DEV_H_ +#define _GF_IETF_DEV_H_ + +#include <gpac/ietf.h> + +#ifndef GPAC_DISABLE_STREAMING + +/* + RTP intern +*/ + +typedef struct +{ + /*version of the packet. Must be 2*/ + u8 Version; + /*padding bits at the end of the payload*/ + u8 Padding; + /*number of reports*/ + u8 Count; + /*payload type of RTCP pck*/ + u8 PayloadType; + /*The length of this RTCP packet in 32-bit words minus one including the header and any padding*/ + u16 Length; + /*sync source identifier*/ + u32 SSRC; +} GF_RTCPHeader; + + +typedef struct __PRO_item +{ + struct __PRO_item *next; + u32 pck_seq_num; + void *pck; + u32 size; +} GF_POItem; + +typedef struct __PO +{ + struct __PRO_item *in; + u32 head_seqnum; + u32 Count; + u32 MaxCount; + u32 IsInit; + u32 MaxDelay, LastTime; +} GF_RTPReorder; + +/* creates new RTP reorderer + @MaxCount: forces automatic packet flush. 0 means no flush + @MaxDelay: is the max time in ms the queue will wait for a missing packet +*/ +GF_RTPReorder *gf_rtp_reorderer_new(u32 MaxCount, u32 MaxDelay); +void gf_rtp_reorderer_del(GF_RTPReorder *po); +/*reset the Queue*/ +void gf_rtp_reorderer_reset(GF_RTPReorder *po); + +/*Adds a packet to the queue. Packet Data is memcopied*/ +GF_Err gf_rtp_reorderer_add(GF_RTPReorder *po, const void * pck, u32 pck_size, u32 pck_seqnum); +/*gets the output of the queue. Packet Data IS YOURS to delete*/ +void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size, Bool force_flush); + + +/*the RTP channel with both RTP and RTCP sockets and buffers +each channel is identified by a control string given in RTSP Describe +this control string is used with Darwin +*/ +struct __tag_rtp_channel +{ + /*global transport info for the session*/ + GF_RTSPTransport net_info; + + /*RTP CHANNEL*/ + GF_Socket *rtp; + /*RTCP CHANNEL*/ + GF_Socket *rtcp; + + /*RTP Packet reordering. Turned on/off during initialization. The library forces a 200 ms + max latency at the reordering queue*/ + GF_RTPReorder *po; + + /*RTCP report times*/ + u32 last_report_time; + u32 next_report_time; + + /*NAT keep-alive*/ + u32 last_nat_keepalive_time, nat_keepalive_time_period; + + + /*the seq number of the first packet as signaled by the server if any, or first + RTP SN received (RTP multicast)*/ + u32 rtp_first_SN; + /*the TS of the associated first packet as signaled by the server if any, or first + RTP TS received (RTP multicast)*/ + u32 rtp_time; + /*NPT from the rtp_time*/ + u32 CurrentTime; + /*num loops of pck sn*/ + u32 num_sn_loops; + /*some mapping info - we should support # payloads*/ + u8 PayloadType; + u32 TimeScale; + + /*static buffer for RTP sending*/ + u8 *send_buffer; + u32 send_buffer_size; + u32 pck_sent_since_last_sr; + u32 last_pck_ts; + u32 last_pck_ntp_sec, last_pck_ntp_frac; + u32 num_pck_sent, num_payload_bytes; + u32 forced_ntp_sec, forced_ntp_frac; + + Bool no_auto_rtcp; + /*RTCP info*/ + char *s_name, *s_email, *s_location, *s_phone, *s_tool, *s_note, *s_priv; +// s8 first_rtp_pck; + s8 first_SR; + u32 SSRC; + u32 SenderSSRC; + + u32 last_pck_sn; + /*indicates if a packet loss is detected between current and previous packet*/ + Bool packet_loss; + + char *CName; + + u32 rtcp_bytes_sent; + /*total pck rcv*/ + u32 tot_num_pck_rcv, tot_num_pck_expected; + /*stats since last SR*/ + u32 last_num_pck_rcv, last_num_pck_expected, last_num_pck_loss; + /*jitter compute*/ + u32 Jitter, ntp_init; + s32 last_deviance; + /*NTP of last SR*/ + u32 last_SR_NTP_sec, last_SR_NTP_frac; + /*RTP time at last SR as indicated in SR*/ + u32 last_SR_rtp_time; + /*payload info*/ + u32 total_pck, total_bytes; + + GF_BitStream *bs_r, *bs_w; + Bool no_select; + + gf_rtp_tcp_callback send_interleave; + void *interleave_cbk1, *interleave_cbk2; +}; + +/*gets UTC in the channel RTP timescale*/ +u32 gf_rtp_channel_time(GF_RTPChannel *ch); +/*gets time in 1/65536 seconds (for reports)*/ +u32 gf_rtp_get_report_time(); +/*updates the time for the next report (SR, RR)*/ +void gf_rtp_get_next_report_time(GF_RTPChannel *ch); + + +/* + RTSP intern +*/ + +#define GF_RTSP_DEFAULT_BUFFER 2048 +#define GF_RTSP_VERSION "RTSP/1.0" + +/*macros for RTSP command and response formmating*/ +#define RTSP_WRITE_STEPALLOC 250 + +#define RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, str) \ + if (strlen((const char *) str)+pos >= buf_size) { \ + buf_size += (u32) strlen((const char *) str); \ + buf = (char *) gf_realloc(buf, buf_size); \ + } \ + strcpy(buf+pos, (const char *) str); \ + pos += (u32) strlen((const char *) str); \ + +#define RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, str) \ + if (str){ \ + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, str); \ + } \ + +#define RTSP_WRITE_HEADER(buf, buf_size, pos, type, str) \ + if( str ) { \ + RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, type); \ + RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, ": "); \ + RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, str); \ + RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, "\r\n"); \ + } \ + +#define RTSP_WRITE_INT(buf, buf_size, pos, d, sig) { \ + char temp[50]; \ + if (sig < 0) { \ + sprintf(temp, "%d", d); \ + } else { \ + sprintf(temp, "%d", d); \ + } \ + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp); \ + } + +#define RTSP_WRITE_HEX(buf, buf_size, pos, d, sig) { \ + char temp[50]; \ + sprintf(temp, "%X", d); \ + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp);\ + } + +#define RTSP_WRITE_FLOAT_WITHOUT_CHECK(buf, buf_size, pos, d) { \ + char temp[50]; \ + sprintf(temp, "%.4f", d); \ + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp);\ + } + +#define RTSP_WRITE_FLOAT(buf, buf_size, pos, d) { \ + char temp[50]; \ + sprintf(temp, "%.4f", d); \ + RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, temp); \ + } + + +typedef struct +{ + u8 rtpID; + u8 rtcpID; + void *ch_ptr; +} GF_TCPChan; + +/************************************** + RTSP Session +***************************************/ +struct _tag_rtsp_session +{ + /*service name (extracted from URL) ex: news/latenight.mp4, vod.mp4 ...*/ + char *Service; + /*server name (extracted from URL)*/ + char *Server; + /*server port (extracted from URL)*/ + u16 Port; + + /*if RTSP is on UDP*/ + u8 ConnectionType; + /*TCP interleaving ID*/ + u8 InterID; + /*http tunnel*/ + Bool HasTunnel; + GF_Socket *http; + char HTTP_Cookie[30]; + u32 CookieRadLen; + + /*RTSP CHANNEL*/ + GF_Socket *connection; + u32 SockBufferSize; + /*needs connection*/ + u32 NeedConnection; + + /*the RTSP sequence number*/ + u32 CSeq; + /*this is for aggregated request in order to check SeqNum*/ + u32 NbPending; + + /*RTSP sessionID, arbitrary length, alpha-numeric*/ + const char *last_session_id; + + /*RTSP STATE machine*/ + u32 RTSP_State; + char RTSPLastRequest[40]; + + /*current buffer from TCP if any*/ + u8 *tcp_buffer; + u32 CurrentSize, CurrentPos; + + /*RTSP interleaving*/ + GF_Err (*RTSP_SignalData)(GF_RTSPSession *sess, void *chan, u8 *buffer, u32 bufferSize, Bool IsRTCP); + + /*buffer for pck reconstruction*/ + u8 *rtsp_pck_buf; + u32 rtsp_pck_size; + u32 pck_start, payloadSize; + + /*all RTP channels in an interleaved RTP on RTSP session*/ + GF_List *TCPChannels; + Bool interleaved; +}; + +GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort); + +/*check connection status*/ +GF_Err gf_rtsp_check_connection(GF_RTSPSession *sess); +/*send data on RTSP*/ +GF_Err gf_rtsp_send_data(GF_RTSPSession *sess, u8 *buffer, u32 Size); + +/* + Common RTSP tools +*/ + +/*locate body-start and body size in response/commands*/ +void gf_rtsp_get_body_info(GF_RTSPSession *sess, u32 *body_start, u32 *body_size); +/*read TCP until a full command/response is received*/ +GF_Err gf_rtsp_read_reply(GF_RTSPSession *sess); +/*fill the TCP buffer*/ +GF_Err gf_rtsp_fill_buffer(GF_RTSPSession *sess); +/*force a fill on TCP buffer - used for de-interleaving and TCP-fragmented RTSP messages*/ +GF_Err gf_rtsp_refill_buffer(GF_RTSPSession *sess); +/*parses a transport string and returns a transport structure*/ +GF_RTSPTransport *gf_rtsp_transport_parse(u8 *buffer); +/*parsing of header for com and rsp*/ +GF_Err gf_rtsp_parse_header(u8 *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp); +void gf_rtsp_set_command_value(GF_RTSPCommand *com, char *Header, char *Value); +void gf_rtsp_set_response_value(GF_RTSPResponse *rsp, char *Header, char *Value); +/*deinterleave a data packet*/ +GF_Err gf_rtsp_set_deinterleave(GF_RTSPSession *sess); +/*start session through HTTP tunnel (QTSS)*/ +GF_Err gf_rtsp_http_tunnel_start(GF_RTSPSession *sess, char *UserAgent); + + + + + +/* + RTP -> SL packetization tool + You should ONLY modify the GF_SLHeader while packetizing, all the rest is private + to the tool. + Also note that AU start/end is automatically updated, therefore you should only + set CTS-DTS-OCR-sequenceNumber (which is automatically incremented when splitting a payload) + -padding-idle infos + SL flags are computed on the fly, but you may wish to modify them in case of + packet drop/... at the encoder side + +*/ +struct __tag_rtp_packetizer +{ + /*input packet sl header cfg. modify only if needed*/ + GF_SLHeader sl_header; + u32 nb_aus; + /* + + PRIVATE _ DO NOT TOUCH + */ + +//! @cond Doxygen_Suppress + + /*RTP payload type (RFC type, NOT the RTP hdr payT)*/ + u32 rtp_payt; + /*packetization flags*/ + u32 flags; + /*Path MTU size without 12-bytes RTP header*/ + u32 Path_MTU; + /*max packet duration in RTP TS*/ + u32 max_ptime; + + /*payload type of RTP packets - only one payload type can be used in GPAC*/ + u8 PayloadType; + + /*RTP header of current packet*/ + GF_RTPHeader rtp_header; + + /*RTP packet handling callbacks*/ + void (*OnNewPacket)(void *cbk_obj, GF_RTPHeader *header); + void (*OnPacketDone)(void *cbk_obj, GF_RTPHeader *header); + void (*OnDataReference)(void *cbk_obj, u32 payload_size, u32 offset_from_orig); + void (*OnData)(void *cbk_obj, u8 *data, u32 data_size, Bool is_header); + void *cbk_obj; + + /********************************* + MPEG-4 Generic hinting + *********************************/ + + /*SL to RTP map*/ + GP_RTPSLMap slMap; + /*SL conf and state*/ + GF_SLConfig sl_config; + + /*set to 1 if firstSL in RTP packet*/ + Bool first_sl_in_rtp; + Bool has_AU_header; + /*current info writers*/ + GF_BitStream *pck_hdr, *payload; + + /*AU SN of last au*/ + u32 last_au_sn; + + /*info for the current packet*/ + u32 auh_size, bytesInPacket; + + /********************************* + ISMACryp info + *********************************/ + Bool force_flush, is_encrypted; + u64 IV, first_AU_IV; + char *key_indicator; + + /********************************* + AVC-H264 info + *********************************/ + /*AVC non-IDR flag: set if all NAL in current packet are non-IDR (disposable)*/ + Bool avc_non_idr; + + /********************************* + AC3 info + *********************************/ + /*ac3 ft flags*/ + u8 ac3_ft; + + /********************************* + HEVC-H265 info + *********************************/ + /*HEVC Payload Header. It will be use in case of Aggreation Packet where we must add payload header for packet after having added of NALU to AP*/ + char hevc_payload_hdr[2]; + +//! @endcond + +}; + +/*packetization routines*/ +GF_Err gp_rtp_builder_do_mpeg4(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_amr(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +#ifndef GPAC_DISABLE_AV_PARSERS +GF_Err gp_rtp_builder_do_mpeg12_video(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +#endif +GF_Err gp_rtp_builder_do_mpeg12_audio(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex); +GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_latm(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration); +GF_Err gp_rtp_builder_do_ac3(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_hevc(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +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); + +#define RTP_VVC_AGG_NAL 0x1C //28 +#define RTP_VVC_FRAG_NAL 0x1D //29 + +/*! RTP depacketization tool*/ +struct __tag_rtp_depacketizer +{ + /*! depacketize routine*/ + void (*depacketize)(struct __tag_rtp_depacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size); + + /*! output packet sl header cfg*/ + GF_SLHeader sl_hdr; + + /*! RTP payload type (RFC type, NOT the RTP hdr payT)*/ + u32 payt; + /*! depacketization flags*/ + u32 flags; + + //! RTP static map may be NULL + const GF_RTPStaticMap *static_map; + + /*! callback routine*/ + gf_rtp_packet_cbk on_sl_packet; + /*! callback udta*/ + void *udta; + + /*! SL <-> RTP map*/ + GP_RTPSLMap sl_map; + /*! RTP clock rate*/ + u32 clock_rate; + + //! clip rect X + u32 x; + //! clip rect Y + u32 y; + //! clip rect or full size width + u32 w; + //! clip rect or full size height + u32 h; + + /*! inter-packet reconstruction bitstream (for 3GP text and H264)*/ + GF_BitStream *inter_bs; + + /*! H264/AVC config*/ + u32 h264_pck_mode; + + /*3GP text reassembler state*/ + /*! number of 3GPP text fragments*/ + u8 nb_txt_frag; + /*! current 3GPP text fragments*/ + u8 cur_txt_frag; + /*! current 3GPP text sample desc index*/ + u8 sidx; + /*! 3GPP text total sample text len*/ + u8 txt_len; + /*! number of 3GPP text modifiers*/ + u8 nb_mod_frag; + + /*! ISMACryp scheme*/ + u32 isma_scheme; + /*! ISMACryp key*/ + char *key; +}; + +#endif /*GPAC_DISABLE_STREAMING*/ + +#endif /*_GF_IETF_DEV_H_*/ + diff --git a/include/gpac/internal/isomedia_dev.h b/include/gpac/internal/isomedia_dev.h new file mode 100644 index 0000000..b39a58f --- /dev/null +++ b/include/gpac/internal/isomedia_dev.h @@ -0,0 +1,4695 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / ISO Media File Format 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_ISOMEDIA_DEV_H_ +#define _GF_ISOMEDIA_DEV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/isomedia.h> + + +enum +{ + //internal code type for unknown boxes + GF_ISOM_BOX_TYPE_UNKNOWN = GF_4CC( 'U', 'N', 'K', 'N' ), + + GF_ISOM_BOX_TYPE_CO64 = GF_4CC( 'c', 'o', '6', '4' ), + GF_ISOM_BOX_TYPE_STCO = GF_4CC( 's', 't', 'c', 'o' ), + GF_ISOM_BOX_TYPE_CTTS = GF_4CC( 'c', 't', 't', 's' ), + GF_ISOM_BOX_TYPE_CPRT = GF_4CC( 'c', 'p', 'r', 't' ), + GF_ISOM_BOX_TYPE_KIND = GF_4CC( 'k', 'i', 'n', 'd' ), + GF_ISOM_BOX_TYPE_CHPL = GF_4CC( 'c', 'h', 'p', 'l' ), + GF_ISOM_BOX_TYPE_URL = GF_4CC( 'u', 'r', 'l', ' ' ), + GF_ISOM_BOX_TYPE_URN = GF_4CC( 'u', 'r', 'n', ' ' ), + GF_ISOM_BOX_TYPE_DINF = GF_4CC( 'd', 'i', 'n', 'f' ), + GF_ISOM_BOX_TYPE_DREF = GF_4CC( 'd', 'r', 'e', 'f' ), + GF_ISOM_BOX_TYPE_STDP = GF_4CC( 's', 't', 'd', 'p' ), + GF_ISOM_BOX_TYPE_EDTS = GF_4CC( 'e', 'd', 't', 's' ), + GF_ISOM_BOX_TYPE_ELST = GF_4CC( 'e', 'l', 's', 't' ), + GF_ISOM_BOX_TYPE_UUID = GF_4CC( 'u', 'u', 'i', 'd' ), + GF_ISOM_BOX_TYPE_FREE = GF_4CC( 'f', 'r', 'e', 'e' ), + GF_ISOM_BOX_TYPE_HDLR = GF_4CC( 'h', 'd', 'l', 'r' ), + GF_ISOM_BOX_TYPE_GMHD = GF_4CC( 'g', 'm', 'h', 'd' ), + GF_ISOM_BOX_TYPE_HMHD = GF_4CC( 'h', 'm', 'h', 'd' ), + GF_ISOM_BOX_TYPE_HINT = GF_4CC( 'h', 'i', 'n', 't' ), + GF_ISOM_BOX_TYPE_MDIA = GF_4CC( 'm', 'd', 'i', 'a' ), + GF_ISOM_BOX_TYPE_ELNG = GF_4CC( 'e', 'l', 'n', 'g' ), + GF_ISOM_BOX_TYPE_MDAT = GF_4CC( 'm', 'd', 'a', 't' ), + GF_ISOM_BOX_TYPE_IDAT = GF_4CC( 'i', 'd', 'a', 't' ), + GF_ISOM_BOX_TYPE_MDHD = GF_4CC( 'm', 'd', 'h', 'd' ), + GF_ISOM_BOX_TYPE_MINF = GF_4CC( 'm', 'i', 'n', 'f' ), + GF_ISOM_BOX_TYPE_MOOV = GF_4CC( 'm', 'o', 'o', 'v' ), + GF_ISOM_BOX_TYPE_MVHD = GF_4CC( 'm', 'v', 'h', 'd' ), + GF_ISOM_BOX_TYPE_STSD = GF_4CC( 's', 't', 's', 'd' ), + GF_ISOM_BOX_TYPE_STSZ = GF_4CC( 's', 't', 's', 'z' ), + GF_ISOM_BOX_TYPE_STZ2 = GF_4CC( 's', 't', 'z', '2' ), + GF_ISOM_BOX_TYPE_STBL = GF_4CC( 's', 't', 'b', 'l' ), + GF_ISOM_BOX_TYPE_STSC = GF_4CC( 's', 't', 's', 'c' ), + GF_ISOM_BOX_TYPE_STSH = GF_4CC( 's', 't', 's', 'h' ), + GF_ISOM_BOX_TYPE_SKIP = GF_4CC( 's', 'k', 'i', 'p' ), + GF_ISOM_BOX_TYPE_SMHD = GF_4CC( 's', 'm', 'h', 'd' ), + GF_ISOM_BOX_TYPE_STSS = GF_4CC( 's', 't', 's', 's' ), + GF_ISOM_BOX_TYPE_STTS = GF_4CC( 's', 't', 't', 's' ), + GF_ISOM_BOX_TYPE_TRAK = GF_4CC( 't', 'r', 'a', 'k' ), + GF_ISOM_BOX_TYPE_TKHD = GF_4CC( 't', 'k', 'h', 'd' ), + GF_ISOM_BOX_TYPE_TREF = GF_4CC( 't', 'r', 'e', 'f' ), + GF_ISOM_BOX_TYPE_STRK = GF_4CC( 's', 't', 'r', 'k' ), + 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_UDTA = GF_4CC( 'u', 'd', 't', 'a' ), + GF_ISOM_BOX_TYPE_VMHD = GF_4CC( 'v', 'm', 'h', 'd' ), + GF_ISOM_BOX_TYPE_FTYP = GF_4CC( 'f', 't', 'y', 'p' ), + GF_ISOM_BOX_TYPE_OTYP = GF_4CC( 'o', 't', 'y', 'p' ), + GF_ISOM_BOX_TYPE_PADB = GF_4CC( 'p', 'a', 'd', 'b' ), + GF_ISOM_BOX_TYPE_PDIN = GF_4CC( 'p', 'd', 'i', 'n' ), + GF_ISOM_BOX_TYPE_SDTP = GF_4CC( 's', 'd', 't', 'p' ), + GF_ISOM_BOX_TYPE_CSLG = GF_4CC( 'c', 's', 'l', 'g' ), + + GF_ISOM_BOX_TYPE_SBGP = GF_4CC( 's', 'b', 'g', 'p' ), + GF_ISOM_BOX_TYPE_SGPD = GF_4CC( 's', 'g', 'p', 'd' ), + GF_ISOM_BOX_TYPE_SAIZ = GF_4CC( 's', 'a', 'i', 'z' ), + GF_ISOM_BOX_TYPE_SAIO = GF_4CC( 's', 'a', 'i', 'o' ), + GF_ISOM_BOX_TYPE_MFRA = GF_4CC( 'm', 'f', 'r', 'a' ), + GF_ISOM_BOX_TYPE_MFRO = GF_4CC( 'm', 'f', 'r', 'o' ), + GF_ISOM_BOX_TYPE_TFRA = GF_4CC( 't', 'f', 'r', 'a' ), + GF_ISOM_BOX_TYPE_CSGP = GF_4CC( 'c', 's', 'g', 'p'), + + GF_ISOM_BOX_TYPE_TENC = GF_4CC( 't', 'e', 'n', 'c' ), + + //track group + GF_ISOM_BOX_TYPE_TRGR = GF_4CC( 't', 'r', 'g', 'r' ), + //track group types + GF_ISOM_BOX_TYPE_TRGT = GF_4CC( 't', 'r', 'g', 't' ), + GF_ISOM_BOX_TYPE_MSRC = GF_4CC( 'm', 's', 'r', 'c' ), + GF_ISOM_BOX_TYPE_CSTG = GF_4CC( 'c', 's', 't', 'g' ), + GF_ISOM_BOX_TYPE_STER = GF_4CC( 's', 't', 'e', 'r' ), + + /*Adobe's protection boxes*/ + GF_ISOM_BOX_TYPE_ADKM = GF_4CC( 'a', 'd', 'k', 'm' ), + GF_ISOM_BOX_TYPE_AHDR = GF_4CC( 'a', 'h', 'd', 'r' ), + GF_ISOM_BOX_TYPE_ADAF = GF_4CC( 'a', 'd', 'a', 'f' ), + GF_ISOM_BOX_TYPE_APRM = GF_4CC( 'a', 'p', 'r', 'm' ), + GF_ISOM_BOX_TYPE_AEIB = GF_4CC( 'a', 'e', 'i', 'b' ), + GF_ISOM_BOX_TYPE_AKEY = GF_4CC( 'a', 'k', 'e', 'y' ), + GF_ISOM_BOX_TYPE_FLXS = GF_4CC( 'f', 'l', 'x', 's' ), + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + /*Movie Fragments*/ + GF_ISOM_BOX_TYPE_MVEX = GF_4CC( 'm', 'v', 'e', 'x' ), + GF_ISOM_BOX_TYPE_MEHD = GF_4CC( 'm', 'e', 'h', 'd' ), + GF_ISOM_BOX_TYPE_TREX = GF_4CC( 't', 'r', 'e', 'x' ), + GF_ISOM_BOX_TYPE_TREP = GF_4CC( 't', 'r', 'e', 'p' ), + GF_ISOM_BOX_TYPE_MOOF = GF_4CC( 'm', 'o', 'o', 'f' ), + GF_ISOM_BOX_TYPE_MFHD = GF_4CC( 'm', 'f', 'h', 'd' ), + GF_ISOM_BOX_TYPE_TRAF = GF_4CC( 't', 'r', 'a', 'f' ), + GF_ISOM_BOX_TYPE_TFHD = GF_4CC( 't', 'f', 'h', 'd' ), + GF_ISOM_BOX_TYPE_TRUN = GF_4CC( 't', 'r', 'u', 'n' ), +#ifdef GF_ENABLE_CTRN + GF_ISOM_BOX_TYPE_CTRN = GF_4CC( 'c', 't', 'r', 'n' ), +#endif + +#endif + + + /*MP4 extensions*/ + GF_ISOM_BOX_TYPE_DPND = GF_4CC( 'd', 'p', 'n', 'd' ), + GF_ISOM_BOX_TYPE_IODS = GF_4CC( 'i', 'o', 'd', 's' ), + GF_ISOM_BOX_TYPE_ESDS = GF_4CC( 'e', 's', 'd', 's' ), + GF_ISOM_BOX_TYPE_MPOD = GF_4CC( 'm', 'p', 'o', 'd' ), + GF_ISOM_BOX_TYPE_SYNC = GF_4CC( 's', 'y', 'n', 'c' ), + GF_ISOM_BOX_TYPE_IPIR = GF_4CC( 'i', 'p', 'i', 'r' ), + + GF_ISOM_BOX_TYPE_NMHD = GF_4CC( 'n', 'm', 'h', 'd' ), + GF_ISOM_BOX_TYPE_STHD = GF_4CC( 's', 't', 'h', 'd' ), + /*reseved + GF_ISOM_BOX_TYPE_SDHD = GF_4CC( 's', 'd', 'h', 'd' ), + GF_ISOM_BOX_TYPE_ODHD = GF_4CC( 'o', 'd', 'h', 'd' ), + GF_ISOM_BOX_TYPE_CRHD = GF_4CC( 'c', 'r', 'h', 'd' ), + */ + GF_ISOM_BOX_TYPE_MP4S = GF_4CC( 'm', 'p', '4', 's' ), + GF_ISOM_BOX_TYPE_MP4A = GF_4CC( 'm', 'p', '4', 'a' ), + GF_ISOM_BOX_TYPE_MP4V = GF_4CC( 'm', 'p', '4', 'v' ), + + + /*AVC / H264 extension*/ + GF_ISOM_BOX_TYPE_AVCC = GF_4CC( 'a', 'v', 'c', 'C' ), + GF_ISOM_BOX_TYPE_BTRT = GF_4CC( 'b', 't', 'r', 't' ), + GF_ISOM_BOX_TYPE_M4DS = GF_4CC( 'm', '4', 'd', 's' ), + GF_ISOM_BOX_TYPE_PASP = GF_4CC( 'p', 'a', 's', 'p' ), + GF_ISOM_BOX_TYPE_CLAP = GF_4CC( 'c', 'l', 'a', 'p' ), + GF_ISOM_BOX_TYPE_AVC1 = GF_4CC( 'a', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_AVC2 = GF_4CC( 'a', 'v', 'c', '2' ), + GF_ISOM_BOX_TYPE_AVC3 = GF_4CC( 'a', 'v', 'c', '3' ), + GF_ISOM_BOX_TYPE_AVC4 = GF_4CC( 'a', 'v', 'c', '4' ), + GF_ISOM_BOX_TYPE_SVCC = GF_4CC( 's', 'v', 'c', 'C' ), + GF_ISOM_BOX_TYPE_SVC1 = GF_4CC( 's', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_SVC2 = GF_4CC( 's', 'v', 'c', '2' ), + GF_ISOM_BOX_TYPE_MVCC = GF_4CC( 'm', 'v', 'c', 'C' ), + GF_ISOM_BOX_TYPE_MVC1 = GF_4CC( 'm', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_MVC2 = GF_4CC( 'm', 'v', 'c', '2' ), + GF_ISOM_BOX_TYPE_MHC1 = GF_4CC( 'm', 'h', 'c', '1' ), + GF_ISOM_BOX_TYPE_MHV1 = GF_4CC( 'm', 'h', 'v', '1' ), + + GF_ISOM_BOX_TYPE_HVCC = GF_4CC( 'h', 'v', 'c', 'C' ), + GF_ISOM_BOX_TYPE_HVC1 = GF_4CC( 'h', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_HEV1 = GF_4CC( 'h', 'e', 'v', '1' ), + GF_ISOM_BOX_TYPE_HVT1 = GF_4CC( 'h', 'v', 't', '1' ), + GF_ISOM_BOX_TYPE_MVCI = GF_4CC( 'm', 'v', 'c', 'i' ), + GF_ISOM_BOX_TYPE_MVCG = GF_4CC( 'm', 'v', 'c', 'g' ), + GF_ISOM_BOX_TYPE_VWID = GF_4CC( 'v', 'w', 'i', 'd' ), + + GF_ISOM_BOX_TYPE_HVC2 = GF_4CC( 'h', 'v', 'c', '2' ), + GF_ISOM_BOX_TYPE_HEV2 = GF_4CC( 'h', 'e', 'v', '2' ), + GF_ISOM_BOX_TYPE_LHV1 = GF_4CC( 'l', 'h', 'v', '1' ), + GF_ISOM_BOX_TYPE_LHE1 = GF_4CC( 'l', 'h', 'e', '1' ), + GF_ISOM_BOX_TYPE_LHT1 = GF_4CC( 'l', 'h', 't', '1' ), + + GF_ISOM_BOX_TYPE_LHVC = GF_4CC( 'l', 'h', 'v', 'C' ), + + GF_ISOM_BOX_TYPE_VVC1 = GF_4CC( 'v', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_VVI1 = GF_4CC( 'v', 'v', 'i', '1' ), + GF_ISOM_BOX_TYPE_VVCC = GF_4CC( 'v', 'v', 'c', 'C' ), + GF_ISOM_BOX_TYPE_VVS1 = GF_4CC( 'v', 'v', 's', '1' ), + GF_ISOM_BOX_TYPE_VVNC = GF_4CC( 'v', 'v', 'n', 'C' ), + + GF_ISOM_BOX_TYPE_AV1C = GF_4CC('a', 'v', '1', 'C'), + GF_ISOM_BOX_TYPE_AV01 = GF_4CC('a', 'v', '0', '1'), + + /*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_SMDM = GF_4CC('S', 'm', 'D', 'm'), + GF_ISOM_BOX_TYPE_COLL = GF_4CC('C', 'o', 'L', 'L'), + + /*Opus*/ + GF_ISOM_BOX_TYPE_OPUS = GF_4CC('O', 'p', 'u', 's'), + GF_ISOM_BOX_TYPE_DOPS = GF_4CC('d', 'O', 'p', 's'), + + /*LASeR extension*/ + GF_ISOM_BOX_TYPE_LSRC = GF_4CC( 'l', 's', 'r', 'C' ), + GF_ISOM_BOX_TYPE_LSR1 = GF_4CC( 'l', 's', 'r', '1' ), + + /*3GPP extensions*/ + GF_ISOM_BOX_TYPE_DAMR = GF_4CC( 'd', 'a', 'm', 'r' ), + GF_ISOM_BOX_TYPE_D263 = GF_4CC( 'd', '2', '6', '3' ), + GF_ISOM_BOX_TYPE_DEVC = GF_4CC( 'd', 'e', 'v', 'c' ), + GF_ISOM_BOX_TYPE_DQCP = GF_4CC( 'd', 'q', 'c', 'p' ), + GF_ISOM_BOX_TYPE_DSMV = GF_4CC( 'd', 's', 'm', 'v' ), + GF_ISOM_BOX_TYPE_TSEL = GF_4CC( 't', 's', 'e', 'l' ), + + /* 3GPP Adaptive Streaming extensions */ + GF_ISOM_BOX_TYPE_STYP = GF_4CC( 's', 't', 'y', 'p' ), + GF_ISOM_BOX_TYPE_TFDT = GF_4CC( 't', 'f', 'd', 't' ), + GF_ISOM_BOX_TYPE_SIDX = GF_4CC( 's', 'i', 'd', 'x' ), + GF_ISOM_BOX_TYPE_SSIX = GF_4CC( 's', 's', 'i', 'x' ), + 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' ), + + /*3GPP text / MPEG-4 StreamingText*/ + GF_ISOM_BOX_TYPE_FTAB = GF_4CC( 'f', 't', 'a', 'b' ), + GF_ISOM_BOX_TYPE_TX3G = GF_4CC( 't', 'x', '3', 'g' ), + GF_ISOM_BOX_TYPE_STYL = GF_4CC( 's', 't', 'y', 'l' ), + GF_ISOM_BOX_TYPE_HLIT = GF_4CC( 'h', 'l', 'i', 't' ), + GF_ISOM_BOX_TYPE_HCLR = GF_4CC( 'h', 'c', 'l', 'r' ), + GF_ISOM_BOX_TYPE_KROK = GF_4CC( 'k', 'r', 'o', 'k' ), + GF_ISOM_BOX_TYPE_DLAY = GF_4CC( 'd', 'l', 'a', 'y' ), + GF_ISOM_BOX_TYPE_HREF = GF_4CC( 'h', 'r', 'e', 'f' ), + GF_ISOM_BOX_TYPE_TBOX = GF_4CC( 't', 'b', 'o', 'x' ), + GF_ISOM_BOX_TYPE_BLNK = GF_4CC( 'b', 'l', 'n', 'k' ), + GF_ISOM_BOX_TYPE_TWRP = GF_4CC( 't', 'w', 'r', 'p' ), + + /* ISO Base Media File Format Extensions for MPEG-21 */ + GF_ISOM_BOX_TYPE_META = GF_4CC( 'm', 'e', 't', 'a' ), + GF_ISOM_BOX_TYPE_XML = GF_4CC( 'x', 'm', 'l', ' ' ), + GF_ISOM_BOX_TYPE_BXML = GF_4CC( 'b', 'x', 'm', 'l' ), + GF_ISOM_BOX_TYPE_ILOC = GF_4CC( 'i', 'l', 'o', 'c' ), + GF_ISOM_BOX_TYPE_PITM = GF_4CC( 'p', 'i', 't', 'm' ), + GF_ISOM_BOX_TYPE_IPRO = GF_4CC( 'i', 'p', 'r', 'o' ), + GF_ISOM_BOX_TYPE_INFE = GF_4CC( 'i', 'n', 'f', 'e' ), + GF_ISOM_BOX_TYPE_IINF = GF_4CC( 'i', 'i', 'n', 'f' ), + GF_ISOM_BOX_TYPE_IREF = GF_4CC( 'i', 'r', 'e', 'f' ), + GF_ISOM_BOX_TYPE_ENCA = GF_4CC( 'e', 'n', 'c', 'a' ), + GF_ISOM_BOX_TYPE_ENCV = GF_4CC( 'e', 'n', 'c', 'v' ), + GF_ISOM_BOX_TYPE_RESV = GF_4CC( 'r', 'e', 's', 'v' ), + GF_ISOM_BOX_TYPE_ENCT = GF_4CC( 'e', 'n', 'c', 't' ), + GF_ISOM_BOX_TYPE_ENCS = GF_4CC( 'e', 'n', 'c', 's' ), + GF_ISOM_BOX_TYPE_ENCF = GF_4CC( 'e', 'n', 'c', 'f' ), + GF_ISOM_BOX_TYPE_ENCM = GF_4CC( 'e', 'n', 'c', 'm' ), + GF_ISOM_BOX_TYPE_SINF = GF_4CC( 's', 'i', 'n', 'f' ), + GF_ISOM_BOX_TYPE_RINF = GF_4CC( 'r', 'i', 'n', 'f' ), + GF_ISOM_BOX_TYPE_FRMA = GF_4CC( 'f', 'r', 'm', 'a' ), + GF_ISOM_BOX_TYPE_SCHM = GF_4CC( 's', 'c', 'h', 'm' ), + GF_ISOM_BOX_TYPE_SCHI = GF_4CC( 's', 'c', 'h', 'i' ), + + GF_ISOM_BOX_TYPE_STVI = GF_4CC( 's', 't', 'v', 'i' ), + + + GF_ISOM_BOX_TYPE_METX = GF_4CC( 'm', 'e', 't', 'x' ), + GF_ISOM_BOX_TYPE_METT = GF_4CC( 'm', 'e', 't', 't' ), + + /* ISMA 1.0 Encryption and Authentication V 1.0 */ + GF_ISOM_BOX_TYPE_IKMS = GF_4CC( 'i', 'K', 'M', 'S' ), + GF_ISOM_BOX_TYPE_ISFM = GF_4CC( 'i', 'S', 'F', 'M' ), + GF_ISOM_BOX_TYPE_ISLT = GF_4CC( 'i', 'S', 'L', 'T' ), + + /* Hinting boxes */ + GF_ISOM_BOX_TYPE_RTP_STSD = GF_4CC( 'r', 't', 'p', ' ' ), + GF_ISOM_BOX_TYPE_SRTP_STSD = GF_4CC( 's', 'r', 't', 'p' ), + GF_ISOM_BOX_TYPE_FDP_STSD = GF_4CC( 'f', 'd', 'p', ' ' ), + GF_ISOM_BOX_TYPE_RRTP_STSD = GF_4CC( 'r', 'r', 't', 'p' ), + GF_ISOM_BOX_TYPE_RTCP_STSD = GF_4CC( 'r', 't', 'c', 'p' ), + GF_ISOM_BOX_TYPE_HNTI = GF_4CC( 'h', 'n', 't', 'i' ), + 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' ), + GF_ISOM_BOX_TYPE_NPCK = GF_4CC( 'n', 'p', 'c', 'k' ), + GF_ISOM_BOX_TYPE_TPYL = GF_4CC( 't', 'p', 'y', 'l' ), + GF_ISOM_BOX_TYPE_TPAY = GF_4CC( 't', 'p', 'a', 'y' ), + GF_ISOM_BOX_TYPE_MAXR = GF_4CC( 'm', 'a', 'x', 'r' ), + GF_ISOM_BOX_TYPE_DMED = GF_4CC( 'd', 'm', 'e', 'd' ), + GF_ISOM_BOX_TYPE_DIMM = GF_4CC( 'd', 'i', 'm', 'm' ), + GF_ISOM_BOX_TYPE_DREP = GF_4CC( 'd', 'r', 'e', 'p' ), + GF_ISOM_BOX_TYPE_TMIN = GF_4CC( 't', 'm', 'i', 'n' ), + GF_ISOM_BOX_TYPE_TMAX = GF_4CC( 't', 'm', 'a', 'x' ), + GF_ISOM_BOX_TYPE_PMAX = GF_4CC( 'p', 'm', 'a', 'x' ), + GF_ISOM_BOX_TYPE_DMAX = GF_4CC( 'd', 'm', 'a', 'x' ), + GF_ISOM_BOX_TYPE_PAYT = GF_4CC( 'p', 'a', 'y', 't' ), + GF_ISOM_BOX_TYPE_RELY = GF_4CC( 'r', 'e', 'l', 'y' ), + GF_ISOM_BOX_TYPE_TIMS = GF_4CC( 't', 'i', 'm', 's' ), + GF_ISOM_BOX_TYPE_TSRO = GF_4CC( 't', 's', 'r', 'o' ), + GF_ISOM_BOX_TYPE_SNRO = GF_4CC( 's', 'n', 'r', 'o' ), + GF_ISOM_BOX_TYPE_RTPO = GF_4CC( 'r', 't', 'p', 'o' ), + GF_ISOM_BOX_TYPE_TSSY = GF_4CC( 't', 's', 's', 'y' ), + GF_ISOM_BOX_TYPE_RSSR = GF_4CC( 'r', 's', 's', 'r' ), + GF_ISOM_BOX_TYPE_SRPP = GF_4CC( 's', 'r', 'p', 'p' ), + + //FEC boxes + GF_ISOM_BOX_TYPE_FIIN = GF_4CC( 'f', 'i', 'i', 'n' ), + GF_ISOM_BOX_TYPE_PAEN = GF_4CC( 'p', 'a', 'e', 'n' ), + GF_ISOM_BOX_TYPE_FPAR = GF_4CC( 'f', 'p', 'a', 'r' ), + GF_ISOM_BOX_TYPE_FECR = GF_4CC( 'f', 'e', 'c', 'r' ), + GF_ISOM_BOX_TYPE_SEGR = GF_4CC( 's', 'e', 'g', 'r' ), + GF_ISOM_BOX_TYPE_GITN = GF_4CC( 'g', 'i', 't', 'n' ), + GF_ISOM_BOX_TYPE_FIRE = GF_4CC( 'f', 'i', 'r', 'e' ), + GF_ISOM_BOX_TYPE_FDSA = GF_4CC( 'f', 'd', 's', 'a' ), + GF_ISOM_BOX_TYPE_FDPA = GF_4CC( 'f', 'd', 'p', 'a' ), + GF_ISOM_BOX_TYPE_EXTR = GF_4CC( 'e', 'x', 't', 'r' ), + + /*internal type for track and item references*/ + GF_ISOM_BOX_TYPE_REFT = GF_4CC( 'R', 'E', 'F', 'T' ), + GF_ISOM_BOX_TYPE_REFI = GF_4CC( 'R', 'E', 'F', 'I'), + GF_ISOM_BOX_TYPE_GRPT = GF_4CC( 'G', 'R', 'P', 'T'), + +#ifndef GPAC_DISABLE_ISOM_ADOBE + /* Adobe extensions */ + GF_ISOM_BOX_TYPE_ABST = GF_4CC( 'a', 'b', 's', 't' ), + GF_ISOM_BOX_TYPE_AFRA = GF_4CC( 'a', 'f', 'r', 'a' ), + GF_ISOM_BOX_TYPE_ASRT = GF_4CC( 'a', 's', 'r', 't' ), + GF_ISOM_BOX_TYPE_AFRT = GF_4CC( 'a', 'f', 'r', 't' ), +#endif + + /* Apple extensions */ + + GF_ISOM_BOX_TYPE_ILST = GF_4CC( 'i', 'l', 's', 't' ), + GF_ISOM_BOX_TYPE_iTunesSpecificInfo = GF_4CC( '-', '-', '-', '-' ), + GF_ISOM_BOX_TYPE_DATA = GF_4CC( 'd', 'a', 't', 'a' ), + + GF_ISOM_HANDLER_TYPE_MDIR = GF_4CC( 'm', 'd', 'i', 'r' ), + GF_ISOM_BOX_TYPE_CHAP = GF_4CC( 'c', 'h', 'a', 'p' ), + GF_ISOM_BOX_TYPE_TEXT = GF_4CC( 't', 'e', 'x', 't' ), + + /*OMA (P)DCF boxes*/ + GF_ISOM_BOX_TYPE_OHDR = GF_4CC( 'o', 'h', 'd', 'r' ), + GF_ISOM_BOX_TYPE_GRPI = GF_4CC( 'g', 'r', 'p', 'i' ), + GF_ISOM_BOX_TYPE_MDRI = GF_4CC( 'm', 'd', 'r', 'i' ), + GF_ISOM_BOX_TYPE_ODTT = GF_4CC( 'o', 'd', 't', 't' ), + GF_ISOM_BOX_TYPE_ODRB = GF_4CC( 'o', 'd', 'r', 'b' ), + GF_ISOM_BOX_TYPE_ODKM = GF_4CC( 'o', 'd', 'k', 'm' ), + GF_ISOM_BOX_TYPE_ODAF = GF_4CC( 'o', 'd', 'a', 'f' ), + + /*3GPP DIMS */ + GF_ISOM_BOX_TYPE_DIMS = GF_4CC( 'd', 'i', 'm', 's' ), + GF_ISOM_BOX_TYPE_DIMC = GF_4CC( 'd', 'i', 'm', 'C' ), + GF_ISOM_BOX_TYPE_DIST = GF_4CC( 'd', 'i', 'S', 'T' ), + + + GF_ISOM_BOX_TYPE_AC3 = GF_4CC( 'a', 'c', '-', '3' ), + 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_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' ), + GF_ISOM_BOX_TYPE_DVHE = GF_4CC( 'd', 'v', 'h', 'e' ), + GF_ISOM_BOX_TYPE_DVA1 = GF_4CC( 'd', 'v', 'a', '1' ), + GF_ISOM_BOX_TYPE_DVAV = GF_4CC( 'd', 'v', 'a', 'v' ), + GF_ISOM_BOX_TYPE_DAV1 = GF_4CC( 'd', 'a', 'v', '1' ), + GF_ISOM_BOX_TYPE_MLPA = GF_4CC( 'm', 'l', 'p', 'a' ), + GF_ISOM_BOX_TYPE_DMLP = GF_4CC( 'd', 'm', 'l', 'p' ), + + GF_ISOM_BOX_TYPE_SUBS = GF_4CC( 's', 'u', 'b', 's' ), + + GF_ISOM_BOX_TYPE_RVCC = GF_4CC( 'r', 'v', 'c', 'c' ), + + GF_ISOM_BOX_TYPE_VTTC_CONFIG = GF_4CC( 'v', 't', 't', 'C' ), + GF_ISOM_BOX_TYPE_VTCC_CUE = GF_4CC( 'v', 't', 't', 'c' ), + GF_ISOM_BOX_TYPE_VTTE = GF_4CC( 'v', 't', 't', 'e' ), + GF_ISOM_BOX_TYPE_VTTA = GF_4CC( 'v', 't', 't', 'a' ), + GF_ISOM_BOX_TYPE_CTIM = GF_4CC( 'c', 't', 'i', 'm' ), + GF_ISOM_BOX_TYPE_IDEN = GF_4CC( 'i', 'd', 'e', 'n' ), + GF_ISOM_BOX_TYPE_STTG = GF_4CC( 's', 't', 't', 'g' ), + GF_ISOM_BOX_TYPE_PAYL = GF_4CC( 'p', 'a', 'y', 'l' ), + GF_ISOM_BOX_TYPE_WVTT = GF_4CC( 'w', 'v', 't', 't' ), + + GF_ISOM_BOX_TYPE_STPP = GF_4CC( 's', 't', 'p', 'p' ), + GF_ISOM_BOX_TYPE_SBTT = GF_4CC( 's', 'b', 't', 't' ), + + GF_ISOM_BOX_TYPE_STXT = GF_4CC( 's', 't', 'x', 't' ), + GF_ISOM_BOX_TYPE_TXTC = GF_4CC( 't', 'x', 't', 'C' ), + GF_ISOM_BOX_TYPE_MIME = GF_4CC( 'm', 'i', 'm', 'e' ), + + GF_ISOM_BOX_TYPE_PRFT = GF_4CC( 'p', 'r', 'f', 't' ), + + /* Image File Format Boxes */ + GF_ISOM_BOX_TYPE_ISPE = GF_4CC( 'i', 's', 'p', 'e' ), + GF_ISOM_BOX_TYPE_COLR = GF_4CC( 'c', 'o', 'l', 'r' ), + GF_ISOM_BOX_TYPE_PIXI = GF_4CC( 'p', 'i', 'x', 'i' ), + GF_ISOM_BOX_TYPE_RLOC = GF_4CC( 'r', 'l', 'o', 'c' ), + GF_ISOM_BOX_TYPE_IROT = GF_4CC( 'i', 'r', 'o', 't' ), + GF_ISOM_BOX_TYPE_IMIR = GF_4CC( 'i', 'm', 'i', 'r' ), + GF_ISOM_BOX_TYPE_IPCO = GF_4CC( 'i', 'p', 'c', 'o' ), + GF_ISOM_BOX_TYPE_IPRP = GF_4CC( 'i', 'p', 'r', 'p' ), + GF_ISOM_BOX_TYPE_IPMA = GF_4CC( 'i', 'p', 'm', 'a' ), + GF_ISOM_BOX_TYPE_GRPL = GF_4CC( 'g', 'r', 'p', 'l'), + GF_ISOM_BOX_TYPE_CCST = GF_4CC( 'c', 'c', 's', 't' ), + GF_ISOM_BOX_TYPE_AUXC = GF_4CC( 'a', 'u', 'x', 'C' ), + GF_ISOM_BOX_TYPE_AUXI = GF_4CC( 'a', 'u', 'x', 'i' ), + GF_ISOM_BOX_TYPE_OINF = GF_4CC( 'o', 'i', 'n', 'f' ), + GF_ISOM_BOX_TYPE_TOLS = GF_4CC( 't', 'o', 'l', 's' ), + GF_ISOM_BOX_TYPE_IENC = GF_4CC( 'i', 'e', 'n', 'c' ), + GF_ISOM_BOX_TYPE_IAUX = GF_4CC('i', 'a', 'u', 'x'), + + /* MIAF Boxes */ + GF_ISOM_BOX_TYPE_CLLI = GF_4CC('c', 'l', 'l', 'i'), + GF_ISOM_BOX_TYPE_MDCV = GF_4CC('m', 'd', 'c', 'v'), + + /* AVIF Boxes */ + GF_ISOM_BOX_TYPE_A1LX = GF_4CC('a', '1', 'l', 'x'), + GF_ISOM_BOX_TYPE_A1OP = GF_4CC('a', '1', 'o', 'p'), + + GF_ISOM_BOX_TYPE_ALTR = GF_4CC( 'a', 'l', 't', 'r' ), + + /*ALL INTERNAL BOXES - NEVER WRITTEN TO FILE!!*/ + + /*generic handlers*/ + GF_ISOM_BOX_TYPE_GNRM = GF_4CC( 'G', 'N', 'R', 'M' ), + GF_ISOM_BOX_TYPE_GNRV = GF_4CC( 'G', 'N', 'R', 'V' ), + GF_ISOM_BOX_TYPE_GNRA = GF_4CC( 'G', 'N', 'R', 'A' ), + /*base constructor of all hint formats (currently only RTP uses it)*/ + GF_ISOM_BOX_TYPE_GHNT = GF_4CC( 'g', 'h', 'n', 't' ), + /*for compatibility with old files hinted for DSS - needs special parsing*/ + GF_ISOM_BOX_TYPE_VOID = GF_4CC( 'V', 'O', 'I', 'D' ), + + /*MS Smooth - these are actually UUID boxes*/ + GF_ISOM_BOX_UUID_PSSH = GF_4CC( 'P', 'S', 'S', 'H' ), + GF_ISOM_BOX_UUID_MSSM = GF_4CC( 'M', 'S', 'S', 'M' ), /*Stream Manifest box*/ + GF_ISOM_BOX_UUID_TENC = GF_4CC( 'T', 'E', 'N', 'C' ), + GF_ISOM_BOX_UUID_TFRF = GF_4CC( 'T', 'F', 'R', 'F' ), + GF_ISOM_BOX_UUID_TFXD = GF_4CC( 'T', 'F', 'X', 'D' ), + + GF_ISOM_BOX_TYPE_MP3 = GF_4CC( '.', 'm', 'p', '3' ), + + GF_ISOM_BOX_TYPE_TRIK = GF_4CC( 't', 'r', 'i', 'k' ), + GF_ISOM_BOX_TYPE_BLOC = GF_4CC( 'b', 'l', 'o', 'c' ), + GF_ISOM_BOX_TYPE_AINF = GF_4CC( 'a', 'i', 'n', 'f' ), + + /*JPEG2000*/ + GF_ISOM_BOX_TYPE_MJP2 = GF_4CC('m','j','p','2'), + 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_JP2K = GF_4CC('j','p','2','k'), + + GF_ISOM_BOX_TYPE_JPEG = GF_4CC('j','p','e','g'), + GF_ISOM_BOX_TYPE_PNG = GF_4CC('p','n','g',' '), + + /* apple QT box */ + GF_QT_BOX_TYPE_ALIS = GF_4CC('a','l','i','s'), + GF_QT_BOX_TYPE_LOAD = GF_4CC('l','o','a','d'), + GF_QT_BOX_TYPE_WIDE = GF_4CC('w','i','d','e'), + GF_QT_BOX_TYPE_GMIN = GF_4CC( 'g', 'm', 'i', 'n' ), + GF_QT_BOX_TYPE_TAPT = GF_4CC( 't', 'a', 'p', 't' ), + GF_QT_BOX_TYPE_CLEF = GF_4CC( 'c', 'l', 'e', 'f' ), + GF_QT_BOX_TYPE_PROF = GF_4CC( 'p', 'r', 'o', 'f' ), + GF_QT_BOX_TYPE_ENOF = GF_4CC( 'e', 'n', 'o', 'f' ), + GF_QT_BOX_TYPE_WAVE = GF_4CC('w','a','v','e'), + GF_QT_BOX_TYPE_CHAN = GF_4CC('c','h','a','n'), + GF_QT_BOX_TYPE_TERMINATOR = 0, + GF_QT_BOX_TYPE_ENDA = GF_4CC('e','n','d','a'), + 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_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'), + GF_QT_BOX_TYPE_CHRM = GF_4CC('c','h','r','m'), + + /* from drm_sample.c */ + GF_ISOM_BOX_TYPE_264B = GF_4CC('2','6','4','b'), + GF_ISOM_BOX_TYPE_265B = GF_4CC('2','6','5','b'), + + /*MPEG-H 3D audio*/ + GF_ISOM_BOX_TYPE_MHA1 = GF_4CC('m','h','a','1'), + GF_ISOM_BOX_TYPE_MHA2 = GF_4CC('m','h','a','2'), + GF_ISOM_BOX_TYPE_MHM1 = GF_4CC('m','h','m','1'), + GF_ISOM_BOX_TYPE_MHM2 = GF_4CC('m','h','m','2'), + GF_ISOM_BOX_TYPE_MHAC = GF_4CC('m','h','a','C'), + GF_ISOM_BOX_TYPE_MHAP = GF_4CC('m','h','a','P'), + + GF_ISOM_BOX_TYPE_IPCM = GF_4CC('i','p','c','m'), + GF_ISOM_BOX_TYPE_FPCM = GF_4CC('f','p','c','m'), + GF_ISOM_BOX_TYPE_PCMC = GF_4CC('p','c','m','C'), + + GF_ISOM_BOX_TYPE_CHNL = GF_4CC('c','h','n','l'), + + GF_ISOM_BOX_TYPE_AUXV = GF_4CC('A','U','X','V'), + + GF_ISOM_BOX_TYPE_FLAC = GF_4CC( 'f', 'L', 'a', 'C' ), + GF_ISOM_BOX_TYPE_DFLA = GF_4CC( 'd', 'f', 'L', 'a' ), + + //internal only + GF_QT_SUBTYPE_RAW_AUD = GF_4CC('Q','T','R','A'), + GF_QT_SUBTYPE_RAW_VID = GF_4CC('Q','T','R','V'), + + GF_ISOM_BOX_TYPE_XTRA = GF_4CC( 'X', 't', 'r', 'a' ), + + GF_ISOM_BOX_TYPE_ST3D = GF_4CC( 's', 't', '3', 'd' ), + GF_ISOM_BOX_TYPE_SV3D = GF_4CC( 's', 'v', '3', 'd' ), + GF_ISOM_BOX_TYPE_SVHD = GF_4CC( 's', 'v', 'h', 'd' ), + GF_ISOM_BOX_TYPE_PROJ = GF_4CC( 'p', 'r', 'o', 'j' ), + GF_ISOM_BOX_TYPE_PRHD = GF_4CC( 'p', 'r', 'h', 'd' ), + GF_ISOM_BOX_TYPE_CBMP = GF_4CC( 'c', 'b', 'm', 'p' ), + GF_ISOM_BOX_TYPE_EQUI = GF_4CC( 'e', 'q', 'u', 'i' ), + GF_ISOM_BOX_TYPE_MSHP = GF_4CC( 'm', 's', 'h', 'p' ), + GF_ISOM_BOX_TYPE_MESH = GF_4CC( 'm', 'e', 's', 'h' ), + + + GF_ISOM_BOX_TYPE_AVCE = GF_4CC( 'a', 'v', 'c', 'E' ), + GF_ISOM_BOX_TYPE_HVCE = GF_4CC( 'h', 'v', 'c', 'E' ), + + //opaque data container + GF_ISOM_BOX_TYPE_GDAT = GF_4CC( 'g', 'd', 'a', 't' ), +}; + +enum +{ + //can be safely type-casted to sample entry + GF_ISOM_SAMPLE_ENTRY_GENERIC = 0, + //can be safely type-casted to Video sample entry + GF_ISOM_SAMPLE_ENTRY_VIDEO = GF_4CC('v','i','d','e'), + //can be safely type-casted to Audio sample entry + GF_ISOM_SAMPLE_ENTRY_AUDIO = GF_4CC('a','u','d','i'), + //can be safely type-casted to mpeg systems sample entry + GF_ISOM_SAMPLE_ENTRY_MP4S = GF_4CC('m','p','4','s'), +}; + + +/* bitstream cookies used by isobmff lib*/ +#define GF_ISOM_BS_COOKIE_NO_LOGS 1 +#define GF_ISOM_BS_COOKIE_VISUAL_TRACK (1<<1) +#define GF_ISOM_BS_COOKIE_QT_CONV (1<<2) +#define GF_ISOM_BS_COOKIE_CLONE_TRACK (1<<3) + + +#ifndef GPAC_DISABLE_ISOM + + +#if defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_ADOBE) +#define GPAC_DISABLE_ISOM_ADOBE +#endif + +//internal flags (up to 16) +//if flag is set, position checking of child boxes is ignored +#define GF_ISOM_ORDER_FREEZE 1 +#define GF_ISOM_BOX_COMPRESSED 2 + + /*the default size is 64, cause we need to handle large boxes... + + the child_boxes container is by default NOT created. When parsing a box and sub-boxes are detected, the list is created. + This list is destroyed before calling the final box destructor + This list is automatically taken into account during size() and write() functions, and + gf_isom_box_write shall not be called in XXXX_box_write on any box registered with the child list + + the full box version field is moved in the base box since we also need some internal flags, as u16 to + also be used for audio and video sample entries version field + */ +#define GF_ISOM_BOX \ + u32 type; \ + u64 size; \ + const struct box_registry_entry *registry;\ + GF_List *child_boxes; \ + u16 internal_flags; \ + u16 version; \ + +#define GF_ISOM_FULL_BOX \ + GF_ISOM_BOX \ + u32 flags; \ + +#define GF_ISOM_UUID_BOX \ + GF_ISOM_BOX \ + u8 uuid[16]; \ + u32 internal_4cc; \ + +typedef struct +{ + GF_ISOM_BOX +} GF_Box; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_FullBox; + +typedef struct +{ + GF_ISOM_UUID_BOX +} GF_UUIDBox; + + +#define ISOM_DECL_BOX_ALLOC(__TYPE, __4cc) __TYPE *tmp; \ + GF_SAFEALLOC(tmp, __TYPE); \ + if (tmp==NULL) return NULL; \ + tmp->type = __4cc; + +#define ISOM_DECREASE_SIZE(__ptr, bytes) if (__ptr->size < (bytes) ) {\ + 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), (u32) __ptr->size, (bytes), __FILE__, __LINE__ )); \ + return GF_ISOM_INVALID_FILE; \ + }\ + __ptr->size -= bytes; \ + +#define ISOM_DECREASE_SIZE_GOTO_EXIT(__ptr, bytes) if (__ptr->size < (bytes) ) {\ + 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), (u32) __ptr->size, (bytes), __FILE__, __LINE__ )); \ + e = GF_ISOM_INVALID_FILE; \ + goto exit;\ + }\ + __ptr->size -= bytes; \ + + +#define ISOM_DECREASE_SIZE_NO_ERR(__ptr, bytes) if (__ptr->size < (bytes) ) {\ + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isom] not enough bytes in box %s: %d left, reading %d (file %s, line %d), skipping box\n", gf_4cc_to_str(__ptr->type), (u32) __ptr->size, (bytes), __FILE__, __LINE__ )); \ + return GF_OK; \ + }\ + __ptr->size -= bytes; \ + + +/*constructor*/ +GF_Box *gf_isom_box_new(u32 boxType); +//some boxes may have different syntax based on container. Use this constructor for this case +GF_Box *gf_isom_box_new_ex(u32 boxType, u32 parentType, Bool skip_logs, Bool is_root_box); + +GF_Err gf_isom_box_write(GF_Box *ptr, GF_BitStream *bs); +GF_Err gf_isom_box_read(GF_Box *ptr, GF_BitStream *bs); +void gf_isom_box_del(GF_Box *ptr); +GF_Err gf_isom_box_size(GF_Box *ptr); + +GF_Err gf_isom_clone_box(GF_Box *src, GF_Box **dst); + +GF_Err gf_isom_box_parse(GF_Box **outBox, GF_BitStream *bs); +GF_Err gf_isom_box_array_read(GF_Box *s, GF_BitStream *bs); +GF_Err gf_isom_box_array_read_ex(GF_Box *parent, GF_BitStream *bs, u32 parent_type); + +GF_Err gf_isom_box_parse_ex(GF_Box **outBox, GF_BitStream *bs, u32 parent_type, Bool is_root_box); + +//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 +GF_Err gf_isom_box_write_header(GF_Box *ptr, GF_BitStream *bs); + +//writes box header then version+flags +GF_Err gf_isom_full_box_write(GF_Box *s, GF_BitStream *bs); + +void gf_isom_box_array_reset(GF_List *boxlist); +void gf_isom_box_array_del(GF_List *child_boxes); +GF_Err gf_isom_box_array_write(GF_Box *parent, GF_List *list, GF_BitStream *bs); +GF_Err gf_isom_box_array_size(GF_Box *parent, GF_List *list); + +void gf_isom_check_position(GF_Box *s, GF_Box *child, u32 *pos); +void gf_isom_check_position_list(GF_Box *s, GF_List *childlist, u32 *pos); + +Bool gf_box_valid_in_parent(GF_Box *a, const char *parent_4cc); + +void gf_isom_box_array_del_parent(GF_List **child_boxes, GF_List *boxlist); +void gf_isom_box_array_reset_parent(GF_List **child_boxes, GF_List *boxlist); + +void gf_isom_box_freeze_order(GF_Box *box); + +#define BOX_FIELD_ASSIGN(_field, _box_cast) \ + if (is_rem) {\ + ptr->_field = NULL;\ + return GF_OK;\ + } else {\ + if (ptr->_field) ERROR_ON_DUPLICATED_BOX(a, ptr)\ + ptr->_field = (_box_cast *)a;\ + } + +#define BOX_FIELD_LIST_ASSIGN(_field) \ + if (is_rem) {\ + gf_list_del_item(ptr->_field, a);\ + } else {\ + if (!ptr->_field) ptr->_field = gf_list_new();\ + GF_Err _e = gf_list_add(ptr->_field, a);\ + if (_e) return _e;\ + } + + +void gf_isom_box_remove_from_parent(GF_Box *parent_box, GF_Box *box); + +typedef struct +{ + GF_ISOM_BOX + /*note: the data is NEVER loaded to the mdat in this lib*/ + u64 dataSize; + /* store the file offset when parsing to access the raw data */ + u64 bsOffset; + u8 *data; +} GF_MediaDataBox; + +typedef struct +{ + u64 time; + u64 moof_offset; + u32 traf_number; + u32 trun_number; + u32 sample_number; +} GF_RandomAccessEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOTrackID track_id; + u8 traf_bits; + u8 trun_bits; + u8 sample_bits; + u32 nb_entries; + GF_RandomAccessEntry *entries; +} GF_TrackFragmentRandomAccessBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 container_size; +} GF_MovieFragmentRandomAccessOffsetBox; + +typedef struct +{ + GF_ISOM_BOX + GF_List* tfra_list; + GF_MovieFragmentRandomAccessOffsetBox *mfro; +} GF_MovieFragmentRandomAccessBox; + +typedef struct +{ + GF_ISOM_BOX + u8 *data; + u32 dataSize; + u32 original_4cc; + u32 sai_type, sai_aux_info; + u64 sai_offset; + struct _gf_saio_box *saio_box; +} GF_UnknownBox; + +typedef struct +{ + GF_ISOM_UUID_BOX + u8 *data; + u32 dataSize; +} GF_UnknownUUIDBox; + +u32 gf_isom_solve_uuid_box(u8 *UUID); + +typedef struct +{ + GF_ISOM_FULL_BOX + u64 creationTime; + u64 modificationTime; + u32 timeScale; + u64 duration; + u64 original_duration; + GF_ISOTrackID nextTrackID; + u32 preferredRate; + u16 preferredVolume; + char reserved[10]; + u32 matrixA; + u32 matrixB; + u32 matrixU; + u32 matrixC; + u32 matrixD; + u32 matrixV; + u32 matrixW; + u32 matrixX; + u32 matrixY; + u32 previewTime; + u32 previewDuration; + u32 posterTime; + u32 selectionTime; + u32 selectionDuration; + u32 currentTime; +} GF_MovieHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_Descriptor *descriptor; +} GF_ObjectDescriptorBox; + +/*used for entry list*/ +typedef struct +{ + u64 segmentDuration; + s64 mediaTime; + u32 mediaRate; + Bool was_empty_dur; +} GF_EdtsEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *entryList; +} GF_EditListBox; + +typedef struct +{ + GF_ISOM_BOX + GF_EditListBox *editList; +} GF_EditBox; + + +/*used to classify boxes in the UserData GF_Box*/ +typedef struct +{ + u32 boxType; + u8 uuid[16]; + GF_List *boxes; +} GF_UserDataMap; + +typedef struct +{ + GF_ISOM_BOX + GF_List *recordList; +} GF_UserDataBox; + +typedef struct +{ + GF_ISOM_BOX + GF_MovieHeaderBox *mvhd; + GF_ObjectDescriptorBox *iods; + GF_UserDataBox *udta; +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + struct __tag_mvex_box *mvex; +#endif + /*meta box if any*/ + struct __tag_meta_box *meta; + /*track boxes*/ + GF_List *trackList; + + GF_ISOFile *mov; + + Bool mvex_after_traks; + //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 + u64 file_offset; + +} GF_MovieBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u64 creationTime; + u64 modificationTime; + GF_ISOTrackID trackID; + u32 reserved1; + u64 duration; + u32 reserved2[2]; + u16 layer; + u16 alternate_group; + u16 volume; + u16 reserved3; + s32 matrix[9]; + u32 width, height; + u64 initial_duration; +} GF_TrackHeaderBox; + +typedef struct +{ + GF_ISOM_BOX +} GF_TrackReferenceBox; + + +typedef struct { + GF_ISOM_BOX + GF_List *groups; +} GF_TrackGroupBox; + +typedef struct { + GF_ISOM_FULL_BOX + u32 group_type; + u32 track_group_id; +} GF_TrackGroupTypeBox; + +typedef struct +{ + GF_ISOM_BOX + GF_UserDataBox *udta; + GF_TrackHeaderBox *Header; + struct __tag_media_box *Media; + GF_EditBox *editBox; + GF_TrackReferenceBox *References; + /*meta box if any*/ + struct __tag_meta_box *meta; + GF_TrackGroupBox *groups; + + GF_Box *Aperture; + + GF_MovieBox *moov; + /*private for media padding*/ + u32 padding_bytes; + /*private for editing*/ + Bool is_unpacked; + /*private for checking dependency*/ + u32 originalFile; + u32 originalID; + + //not sure about piff (not supposed to be stored in moov), but senc is in track according to CENC + struct __sample_encryption_box *sample_encryption; + + /*private for SVC/MVC extractors resolution*/ + GF_ISONaluExtractMode extractor_mode; + Bool has_base_layer; + u32 pack_num_samples; + + u64 magic; + u32 index; + u32 nb_base_refs; + +#ifndef GPAC_DISABLE_ISOM_WRITE + u64 first_dts_chunk; + u32 nb_samples_in_cache; + u32 chunk_stsd_idx; + u32 chunk_cache_size; + GF_BitStream *chunk_cache; +#endif + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + //dts value when at refererence fragment start (first frag ever or first after a table reset), usually current segment start + u64 dts_at_seg_start; + //number of samples at refererence fragment start (first frag ever or first after a table reset), usually current segment start + u32 sample_count_at_seg_start; + Bool first_traf_merged; + Bool present_in_scalable_segment; + u32 current_traf_stsd_idx; + + u64 last_tfxd_value; + struct __traf_mss_timeref_box *tfrf; + u64 dts_at_next_frag_start; +#endif +} GF_TrackBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u64 creationTime; + u64 modificationTime; + u32 timeScale; + u64 duration, original_duration; + char packedLanguage[4]; + u16 reserved; +} GF_MediaHeaderBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 reserved1; + u32 handlerType; + u8 reserved2[12]; + char *nameUTF8; + Bool store_counted_string; +} GF_HandlerBox; + +typedef struct __tag_media_box +{ + GF_ISOM_BOX + GF_TrackBox *mediaTrack; + GF_MediaHeaderBox *mediaHeader; + GF_HandlerBox *handler; + struct __tag_media_info_box *information; + u64 BytesMissing; + + //all the following are only used for NALU-based tracks + //NALU reader + GF_BitStream *nalu_parser; + + GF_BitStream *nalu_out_bs; + GF_BitStream *nalu_ps_bs; + u8 *in_sample_buffer; + u32 in_sample_buffer_alloc; + u8 *tmp_nal_copy_buffer; + u32 tmp_nal_copy_buffer_alloc; + + GF_ISOSample *extracted_samp; + GF_BitStream *extracted_bs; + +} GF_MediaBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + char *extended_language; +} GF_ExtendedLanguageBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u64 reserved; +} GF_VideoMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u16 graphics_mode; + u16 op_color_red; + u16 op_color_green; + u16 op_color_blue; + u16 balance; + u16 reserved; +} GF_GenericMediaHeaderInfoBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u16 balance; + u16 reserved; +} GF_SoundMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + /*this is used for us INTERNALLY*/ + u32 subType; + u32 maxPDUSize; + u32 avgPDUSize; + u32 maxBitrate; + u32 avgBitrate; + u32 slidingAverageBitrate; +} GF_HintMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_MPEGMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_SubtitleMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_ODMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_OCRMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_SceneMediaHeaderBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + + u32 width; + u32 height; +} GF_ApertureBox; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_DataReferenceBox; + +typedef struct +{ + GF_ISOM_BOX + GF_DataReferenceBox *dref; +} GF_DataInformationBox; + +#define GF_ISOM_DATAENTRY_FIELDS \ + char *location; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOM_DATAENTRY_FIELDS +} GF_DataEntryBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOM_DATAENTRY_FIELDS +} GF_DataEntryURLBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOM_DATAENTRY_FIELDS +} GF_DataEntryAliasBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOM_DATAENTRY_FIELDS + char *nameURN; +} GF_DataEntryURNBox; + +typedef struct +{ + u32 sampleCount; + u32 sampleDelta; +} GF_SttsEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_SttsEntry *entries; + u32 nb_entries, alloc_size; + +#ifndef GPAC_DISABLE_ISOM_WRITE + /*cache for WRITE*/ + u32 w_currentSampleNum; + u64 w_LastDTS; +#endif + /*cache for READ*/ + u32 r_FirstSampleInEntry; + u32 r_currentEntryIndex; + u64 r_CurrentDTS; + + //stats for read + u32 max_ts_delta; +} GF_TimeToSampleBox; + + +/*TO CHECK - it could be reasonnable to only use 16bits for both count and offset*/ +typedef struct +{ + u32 sampleCount; + s32 decodingOffset; +} GF_DttsEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_DttsEntry *entries; + u32 nb_entries, alloc_size; + +#ifndef GPAC_DISABLE_ISOM_WRITE + u32 w_LastSampleNumber; + /*force one sample per entry*/ + Bool unpack_mode; +#endif + /*Cache for read*/ + u32 r_currentEntryIndex; + u32 r_FirstSampleInEntry; + + s32 max_cts_delta; + u32 sample_num_max_cts_delta; +} GF_CompositionOffsetBox; + + +#define GF_ISOM_SAMPLE_ENTRY_FIELDS \ + GF_ISOM_UUID_BOX \ + u16 dataReferenceIndex; \ + char reserved[ 6 ]; \ + u32 internal_type; \ + +/*base sample entry box - used by some generic media sample descriptions of QT*/ +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS +} GF_SampleEntryBox; + +void gf_isom_sample_entry_init(GF_SampleEntryBox *ptr); +void gf_isom_sample_entry_predestroy(GF_SampleEntryBox *ptr); +GF_Err gf_isom_base_sample_entry_read(GF_SampleEntryBox *ptr, GF_BitStream *bs); + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + /*box type as specified in the file (not this box's type!!)*/ + u32 EntryType; + + u8 *data; + u32 data_size; +} GF_GenericSampleEntryBox; + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + + u32 flags; + u32 timescale; + u32 frame_duration; + u8 frames_per_counter_tick; +} GF_TimeCodeSampleEntryBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u16 text_font; + u16 text_face; + u16 text_size; + u16 text_color_red, text_color_green, text_color_blue; + u16 back_color_red, back_color_green, back_color_blue; + char *font; +} GF_TimeCodeMediaInformationBox; + +typedef struct +{ + GF_ISOM_BOX + + u8 field_count; + u8 field_order; +} GF_FieldInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u32 gama; +} GF_GamaInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u16 chroma; +} GF_ChromaInfoBox; + +typedef struct +{ + u32 label; + u32 flags; + Float coordinates[3]; +} GF_AudioChannelDescription; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u32 layout_tag; + u32 bitmap; + u32 num_audio_description; + GF_AudioChannelDescription *audio_descs; + u8 *ext_data; + u32 ext_data_size; +} GF_ChannelLayoutInfoBox; + + +typedef struct +{ + GF_ISOM_BOX + + u32 preload_start_time; + u32 preload_duration; + u32 preload_flags; + u32 default_hints; +} GF_TrackLoadBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + + GF_AudioChannelLayout layout; +} GF_ChannelLayoutBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ESD *desc; +} GF_ESDBox; + +typedef struct +{ + GF_ISOM_BOX + u32 bufferSizeDB; + u32 maxBitrate; + u32 avgBitrate; +} GF_BitRateBox; + +GF_BitRateBox *gf_isom_sample_entry_get_bitrate(GF_SampleEntryBox *ent, Bool create); + +typedef struct +{ + GF_ISOM_BOX + GF_List *descriptors; +} GF_MPEG4ExtensionDescriptorsBox; + +/*for most MPEG4 media */ +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + GF_ESDBox *esd; + /*used for hinting when extracting the OD stream...*/ + GF_SLConfig *slc; +} GF_MPEGSampleEntryBox; + +typedef struct +{ + GF_ISOM_BOX + u8 *hdr; + u32 hdr_size; +} GF_LASERConfigurationBox; + + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + + GF_LASERConfigurationBox *lsr_config; + GF_MPEG4ExtensionDescriptorsBox *descr; + + /*used for hinting when extracting the OD stream...*/ + GF_SLConfig *slc; +} GF_LASeRSampleEntryBox; + +/*rewrites avcC based on the given esd - this destroys the esd*/ +GF_Err LSR_UpdateESD(GF_LASeRSampleEntryBox *lsr, GF_ESD *esd); + +typedef struct +{ + GF_ISOM_BOX + u32 hSpacing; + u32 vSpacing; +} GF_PixelAspectRatioBox; + +typedef struct +{ + GF_ISOM_BOX + u32 cleanApertureWidthN; + u32 cleanApertureWidthD; + u32 cleanApertureHeightN; + u32 cleanApertureHeightD; + s32 horizOffN; + u32 horizOffD; + s32 vertOffN; + u32 vertOffD; +} GF_CleanApertureBox; + + +typedef struct __ContentLightLevel { + GF_ISOM_BOX + GF_ContentLightLevelInfo clli; +} GF_ContentLightLevelBox; + +typedef struct ___MasteringDisplayColourVolume { + GF_ISOM_BOX + GF_MasteringDisplayColourVolumeInfo mdcv; +} GF_MasteringDisplayColourVolumeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + Bool all_ref_pics_intra; + Bool intra_pred_used; + u32 max_ref_per_pic; + u32 reserved; +} GF_CodingConstraintsBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + char *aux_track_type; +} GF_AuxiliaryTypeInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u16 predefined_rvc_config; + u32 rvc_meta_idx; +} GF_RVCConfigurationBox; + + +typedef struct { + GF_ISOM_BOX + Bool is_jp2; + + u32 colour_type; + u16 colour_primaries; + u16 transfer_characteristics; + u16 matrix_coefficients; + Bool full_range_flag; + u8 *opaque; + u32 opaque_size; + + u8 method, precedence, approx; +} GF_ColourInformationBox; + + +//do NOT extend this structure with boxes, children boxes shall go into the other_box field of the parent +#define GF_ISOM_VISUAL_SAMPLE_ENTRY \ + GF_ISOM_SAMPLE_ENTRY_FIELDS \ + u16 revision; \ + u32 vendor; \ + u32 temporal_quality; \ + u32 spatial_quality; \ + u16 Width, Height; \ + u32 horiz_res, vert_res; \ + u32 entry_data_size; \ + u16 frames_per_sample; \ + char compressor_name[33]; \ + u16 bit_depth; \ + s16 color_table_index; \ + struct __tag_protect_box *rinf; \ + +typedef struct +{ + GF_ISOM_VISUAL_SAMPLE_ENTRY +} GF_VisualSampleEntryBox; + +void gf_isom_video_sample_entry_init(GF_VisualSampleEntryBox *ent); +GF_Err gf_isom_video_sample_entry_read(GF_VisualSampleEntryBox *ptr, GF_BitStream *bs); +#ifndef GPAC_DISABLE_ISOM_WRITE +void gf_isom_video_sample_entry_write(GF_VisualSampleEntryBox *ent, GF_BitStream *bs); +void gf_isom_video_sample_entry_size(GF_VisualSampleEntryBox *ent); +#endif + +GF_Box *gf_isom_box_find_child(GF_List *parent_child_list, u32 code); +void gf_isom_box_del_parent(GF_List **parent_child_list, GF_Box*b); +GF_Box *gf_isom_box_new_parent(GF_List **parent_child_list, u32 code); +Bool gf_isom_box_check_unique(GF_List *children, GF_Box *a); + +typedef struct +{ + GF_ISOM_BOX + GF_AVCConfig *config; +} GF_AVCConfigurationBox; + +typedef struct +{ + GF_ISOM_BOX + GF_HEVCConfig *config; +} GF_HEVCConfigurationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_VVCConfig *config; +} GF_VVCConfigurationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 nal_unit_size; +} GF_VVCNaluConfigurationBox; + + +typedef struct +{ + GF_ISOM_BOX + GF_AV1Config *config; +} GF_AV1ConfigurationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_VPConfig *config; +} GF_VPConfigurationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u16 primaryRChromaticity_x; + u16 primaryRChromaticity_y; + u16 primaryGChromaticity_x; + u16 primaryGChromaticity_y; + u16 primaryBChromaticity_x; + u16 primaryBChromaticity_y; + u16 whitePointChromaticity_x; + u16 whitePointChromaticity_y; + u32 luminanceMax; + u32 luminanceMin; +} GF_SMPTE2086MasteringDisplayMetadataBox; + +typedef struct { + GF_ISOM_FULL_BOX + u16 maxCLL; + u16 maxFALL; +} GF_VPContentLightLevelBox; + +typedef struct { + GF_ISOM_BOX + GF_DOVIDecoderConfigurationRecord DOVIConfig; +} GF_DOVIConfigurationBox; + +/*typedef struct { //extends Box('hvcE') + GF_ISOM_BOX + GF_HEVCConfig HEVCConfig; +} GF_DolbyVisionELHEVCConfigurationBox;*/ + +typedef struct { //extends HEVCSampleEntry('dvhe') + GF_DOVIConfigurationBox config; + //TODO: GF_DolbyVisionELHEVCConfigurationBox ELConfig; // optional +} GF_DolbyVisionHEVCSampleEntry; + +typedef struct +{ + GF_ISOM_BOX + GF_3GPConfig cfg; +} GF_3GPPConfigBox; + +typedef struct +{ + GF_ISOM_BOX + + u32 width, height; + u16 nb_comp; + u8 bpc, Comp, UnkC, IPR; +} GF_J2KImageHeaderBox; + +typedef struct +{ + GF_ISOM_BOX + GF_J2KImageHeaderBox *ihdr; + GF_ColourInformationBox *colr; +} GF_J2KHeaderBox; + +typedef struct +{ + GF_ISOM_VISUAL_SAMPLE_ENTRY + GF_ESDBox *esd; + /*used for Publishing*/ + GF_SLConfig *slc; + + /*avc extensions - we merged with regular 'mp4v' box to handle isma E&A signaling of AVC*/ + GF_AVCConfigurationBox *avc_config; + GF_AVCConfigurationBox *svc_config; + GF_AVCConfigurationBox *mvc_config; + /*hevc extension*/ + GF_HEVCConfigurationBox *hevc_config; + GF_HEVCConfigurationBox *lhvc_config; + /*vvc extension*/ + GF_VVCConfigurationBox *vvc_config; + /*av1 extension*/ + GF_AV1ConfigurationBox *av1_config; + /*vp8-9 extension*/ + GF_VPConfigurationBox *vp_config; + /*jp2k extension*/ + GF_J2KHeaderBox *jp2h; + /*dolbyvision extension*/ + GF_DOVIConfigurationBox *dovi_config; + + /*internally emulated esd*/ + GF_ESD *emul_esd; + + //3GPP + GF_3GPPConfigBox *cfg_3gpp; + + /*iPod's hack*/ + GF_UnknownUUIDBox *ipod_ext; + +} GF_MPEGVisualSampleEntryBox; + +static const u8 GF_ISOM_IPOD_EXT[][16] = { { 0x6B, 0x68, 0x40, 0xF2, 0x5F, 0x24, 0x4F, 0xC5, 0xBA, 0x39, 0xA5, 0x1B, 0xCF, 0x03, 0x23, 0xF3} }; + +Bool gf_isom_is_nalu_based_entry(GF_MediaBox *mdia, GF_SampleEntryBox *_entry); +GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 sampleNumber, GF_MPEGVisualSampleEntryBox *entry); + +/*this is the default visual sdst (to handle unknown media)*/ +typedef struct +{ + GF_ISOM_VISUAL_SAMPLE_ENTRY + /*box type as specified in the file (not this box's type!!)*/ + u32 EntryType; + /*opaque description data (ESDS in MP4, SMI in SVQ3, ...)*/ + u8 *data; + u32 data_size; +} GF_GenericVisualSampleEntryBox; + +enum +{ + GF_ISOM_AUDIO_QTFF_NONE = 0, + //sample entry is QTFF and data in extensions() is NOT valid (conversion done by libisomedia) + GF_ISOM_AUDIO_QTFF_ON_NOEXT, + //sample entry is QTFF and data in extensions() is valid (import from QT) + GF_ISOM_AUDIO_QTFF_ON_EXT_VALID, + GF_ISOM_AUDIO_QTFF_CONVERT_FLAG = 1<<16 +}; + +#define GF_ISOM_AUDIO_SAMPLE_ENTRY \ + GF_ISOM_SAMPLE_ENTRY_FIELDS \ + u16 revision; \ + u32 vendor; \ + u16 channel_count; \ + u16 bitspersample; \ + u16 compression_id; \ + u16 packet_size; \ + u32 qtff_mode; \ + u16 samplerate_hi; \ + u16 samplerate_lo; \ + u8 extensions[36]; \ + + +typedef struct +{ + GF_ISOM_AUDIO_SAMPLE_ENTRY +} GF_AudioSampleEntryBox; + +void gf_isom_audio_sample_entry_init(GF_AudioSampleEntryBox *ptr); +GF_Err gf_isom_audio_sample_entry_read(GF_AudioSampleEntryBox *ptr, GF_BitStream *bs); +#ifndef GPAC_DISABLE_ISOM_WRITE +void gf_isom_audio_sample_entry_write(GF_AudioSampleEntryBox *ptr, GF_BitStream *bs); +void gf_isom_audio_sample_entry_size(GF_AudioSampleEntryBox *ptr); +#endif + +typedef struct +{ + GF_ISOM_BOX + GF_AC3Config cfg; +} GF_AC3ConfigBox; + + + +typedef struct +{ + GF_ISOM_BOX + u32 format_info; + u16 peak_data_rate; +} GF_TrueHDConfigBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 *data; + u32 dataSize; +} GF_FLACConfigBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 stereo_type; +} GF_Stereo3DBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + char *string; +} GF_SphericalVideoInfoBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + s32 yaw; + s32 pitch; + s32 roll; +} GF_ProjectionHeaderBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + //cube map + u32 layout; + s32 padding; + //EQR + u32 bounds_top, bounds_bottom, bounds_left, bounds_right; + //mesh + u32 crc; + s32 encoding_4cc; + +} GF_ProjectionTypeBox; + +typedef struct +{ + GF_ISOM_BOX + + /*OpusSpecificBox*/ + /*u8 version; //1, field included in base box structure */ + u8 OutputChannelCount; //same value as the *Output Channel Count* field in the identification header defined in Ogg Opus [3] + u16 PreSkip; //The value of the PreSkip field shall be at least 80 milliseconds' worth of PCM samples even when removing any number of Opus samples which may or may not contain the priming samples. The PreSkip field is not used for discarding the priming samples at the whole playback at all since it is informative only, and that task falls on the Edit List Box. + u32 InputSampleRate; //The InputSampleRate field shall be set to the same value as the *Input Sample Rate* field in the identification header defined in Ogg Opus + s16 OutputGain; //The OutputGain field shall be set to the same value as the *Output Gain* field in the identification header define in Ogg Opus [3]. Note that the value is stored as 8.8 fixed-point. + u8 ChannelMappingFamily; //The ChannelMappingFamily field shall be set to the same value as the *Channel Mapping Family* field in the identification header defined in Ogg Opus [3]. Note that the value 255 may be used for an alternative to map channels by ISO Base Media native mapping. The details are described in 4.5.1. + + u8 StreamCount; // The StreamCount field shall be set to the same value as the *Stream Count* field in the identification header defined in Ogg Opus [3]. + u8 CoupledCount; // The CoupledCount field shall be set to the same value as the *Coupled Count* field in the identification header defined in Ogg Opus [3]. + u8 ChannelMapping[255]; // The ChannelMapping field shall be set to the same octet string as *Channel Mapping* field in the identi- fication header defined in Ogg Opus [3]. + + /*for internal box use only*/ +// int channels; +} GF_OpusSpecificBox; + +GF_Err gf_isom_opus_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_OpusSpecificBox *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); + +typedef struct +{ + GF_ISOM_BOX + u8 configuration_version; + u8 mha_pl_indication; + u8 reference_channel_layout; + u16 mha_config_size; + char *mha_config; +} GF_MHAConfigBox; + + +typedef struct +{ + GF_ISOM_BOX + u8 num_profiles; + u8 *compat_profiles; +} GF_MHACompatibleProfilesBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 format_flags; + u8 PCM_sample_size; +} GF_PCMConfigBox; + + +typedef struct +{ + GF_ISOM_AUDIO_SAMPLE_ENTRY + //for MPEG4 audio + GF_ESDBox *esd; + GF_SLConfig *slc; + //for 3GPP audio + GF_3GPPConfigBox *cfg_3gpp; + + //for AC3/EC3 audio + GF_AC3ConfigBox *cfg_ac3; + + //for AC3/EC3 audio + GF_TrueHDConfigBox *cfg_mlp; + + //for Opus + GF_OpusSpecificBox *cfg_opus; + + //for MPEG-H audio + GF_MHAConfigBox *cfg_mha; + + //for FLAC + GF_FLACConfigBox *cfg_flac; + +} GF_MPEGAudioSampleEntryBox; + +/*this is the default visual sdst (to handle unknown media)*/ +typedef struct +{ + GF_ISOM_AUDIO_SAMPLE_ENTRY + /*box type as specified in the file (not this box's type!!)*/ + u32 EntryType; + /*opaque description data (ESDS in MP4, ...)*/ + u8 *data; + u32 data_size; +} GF_GenericAudioSampleEntryBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 profile; + u8 level; + u8 pathComponents; + Bool fullRequestHost; + Bool streamType; + u8 containsRedundant; + char *textEncoding; + char *contentEncoding; +} GF_DIMSSceneConfigBox; + +typedef struct +{ + GF_ISOM_BOX + char *content_script_types; +} GF_DIMSScriptTypesBox; + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + GF_DIMSSceneConfigBox *config; + GF_DIMSScriptTypesBox *scripts; +} GF_DIMSSampleEntryBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + char *config; +} GF_TextConfigBox; + +/*base metadata sample entry box for METT, METX, SBTT, STXT and STPP*/ +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + char *content_encoding; //optional + char *mime_type; //for anything except metx + char *xml_namespace; //for metx and sttp only + char *xml_schema_loc; // for metx and sttp only + GF_TextConfigBox *config; //optional for anything except metx and sttp +} GF_MetaDataSampleEntryBox; + + +typedef struct +{ + u8 entry_type; + union { + u32 trackID; + u32 output_view_id; + u32 start_view_id; + }; + union { + u16 tierID; + u16 view_count; + }; +} MVCIEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 multiview_group_id; + u16 num_entries; + MVCIEntry *entries; +} GF_MultiviewGroupBox; + +typedef struct +{ + u8 dep_comp_idc; + u16 ref_view_id; +} ViewIDRefViewEntry; + +typedef struct +{ + u16 view_id; + u16 view_order_index; + u8 texture_in_stream; + u8 texture_in_track; + u8 depth_in_stream; + u8 depth_in_track; + u8 base_view_type; + u16 num_ref_views; + ViewIDRefViewEntry *view_refs; +} ViewIDEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 min_temporal_id; + u8 max_temporal_id; + u16 num_views; + ViewIDEntry *views; +} GF_ViewIdentifierBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_SampleDescriptionBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + /*if this is the compact version, sample size is actually fieldSize*/ + u32 sampleSize; + u32 sampleCount; + u32 alloc_size; + u32 *sizes; + //stats for read + u32 max_size; + u64 total_size; + u32 total_samples; +} GF_SampleSizeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 nb_entries; + u32 alloc_size; + u32 *offsets; +} GF_ChunkOffsetBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 nb_entries; + u32 alloc_size; + u64 *offsets; +} GF_ChunkLargeOffsetBox; + +typedef struct +{ + u32 firstChunk; + u32 nextChunk; + u32 samplesPerChunk; + u32 sampleDescriptionIndex; + u8 isEdited; +} GF_StscEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_StscEntry *entries; + u32 alloc_size, nb_entries; + + /*0-based cache for READ. In WRITE mode, we always have 1 sample per chunk so no need for a cache*/ + u32 currentIndex; + /*first sample number in this chunk*/ + u32 firstSampleInCurrentChunk; + u32 currentChunk; + u32 ghostNumber; + + u32 w_lastSampleNumber; + u32 w_lastChunkNumber; +} GF_SampleToChunkBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 alloc_size, nb_entries; + u32 *sampleNumbers; + /*cache for READ mode (in write we realloc no matter what)*/ + u32 r_LastSyncSample; + /*0-based index in the array*/ + u32 r_LastSampleIndex; +} GF_SyncSampleBox; + +typedef struct +{ + u32 shadowedSampleNumber; + s32 syncSampleNumber; +} GF_StshEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *entries; + /*Cache for read mode*/ + u32 r_LastEntryIndex; + u32 r_LastFoundSample; +} GF_ShadowSyncBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 nb_entries; + u16 *priorities; +} GF_DegradationPriorityBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 SampleCount; + u8 *padbits; +} GF_PaddingBitsBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 sampleCount, sample_alloc; + /*each dep type is packed on 1 byte*/ + u8 *sample_info; +} GF_SampleDependencyTypeBox; + + +typedef struct +{ + u32 subsample_size; + u8 subsample_priority; + u8 discardable; + u32 reserved; +} GF_SubSampleEntry; + +typedef struct +{ + u32 sample_delta; + GF_List *SubSamples; +} GF_SubSampleInfoEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *Samples; +} GF_SubSampleInformationBox; + +Bool gf_isom_get_subsample_types(GF_ISOFile *movie, u32 track, u32 subs_index, u32 *flags); +u32 gf_isom_sample_get_subsample_entry(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 entry_index, GF_SubSampleInfoEntry **sub_sample); +#ifndef GPAC_DISABLE_ISOM_WRITE +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*/ +typedef struct +{ + GF_ISOM_FULL_BOX + + s32 compositionToDTSShift; + s32 leastDecodeToDisplayDelta; + s32 greatestDecodeToDisplayDelta; + s32 compositionStartTime; + s32 compositionEndTime; +} GF_CompositionToDecodeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u32 aux_info_type; + u32 aux_info_type_parameter; + + u8 default_sample_info_size; + u32 sample_count, sample_alloc; + u8 *sample_info_size; + + u32 cached_sample_num; + u32 cached_prev_size; +} GF_SampleAuxiliaryInfoSizeBox; + +typedef struct _gf_saio_box +{ + GF_ISOM_FULL_BOX + + u32 aux_info_type; + u32 aux_info_type_parameter; + + u8 default_sample_info_size; + u32 entry_count; //1 or stco / trun count + u32 entry_alloc; + u64 *offsets; + + u64 offset_first_offset_field; + + u32 total_size; + u8 *cached_data; + + GF_UnknownBox *sai_data; +} GF_SampleAuxiliaryInfoOffsetBox; + +typedef struct +{ + u32 sample_num; + u8 *moof_template; + u32 moof_template_size; + u64 seg_start_plus_one; + u64 sidx_start; + u64 sidx_end; + u64 moof_start; + u64 mdat_end; +} GF_TrafMapEntry; + +typedef struct +{ + u32 nb_entries, nb_alloc; + GF_TrafMapEntry *frag_starts; +} GF_TrafToSampleMap; + +typedef struct +{ + GF_ISOM_BOX + GF_TimeToSampleBox *TimeToSample; + GF_CompositionOffsetBox *CompositionOffset; + GF_CompositionToDecodeBox *CompositionToDecode; + GF_SyncSampleBox *SyncSample; + GF_SampleDescriptionBox *SampleDescription; + GF_SampleSizeBox *SampleSize; + GF_SampleToChunkBox *SampleToChunk; + /*untyped, to handle 32 bits and 64 bits chunkOffsets*/ + GF_Box *ChunkOffset; + GF_ShadowSyncBox *ShadowSync; + GF_DegradationPriorityBox *DegradationPriority; + GF_PaddingBitsBox *PaddingBits; + GF_SampleDependencyTypeBox *SampleDep; + + GF_TrafToSampleMap *traf_map; + + GF_List *sub_samples; + + GF_List *sampleGroups; + GF_List *compactSampleGroups; + GF_List *sampleGroupsDescription; + u32 nb_sgpd_in_stbl; + u32 nb_stbl_boxes; + + GF_List *sai_sizes; + GF_List *sai_offsets; + + u32 MaxSamplePerChunk, MaxChunkSize, MaxChunkDur; + u16 groupID; + u16 trackPriority; + u32 currentEntryIndex; + + Bool no_sync_found; + + u32 r_last_chunk_num, r_last_sample_num, r_last_offset_in_chunk; + u8 patch_piff_psec; +} GF_SampleTableBox; + +GF_Err stbl_AppendTrafMap(GF_SampleTableBox *stbl, Bool is_seg_start, u64 seg_start_offset, u64 frag_start_offset, u8 *moof_template, u32 moof_template_size, u64 sidx_start, u64 sidx_end, u32 nb_pack_samples); + +typedef struct __tag_media_info_box +{ + GF_ISOM_BOX + GF_DataInformationBox *dataInformation; + GF_SampleTableBox *sampleTable; + GF_Box *InfoHeader; + struct __tag_data_map *scalableDataHandler; + struct __tag_data_map *dataHandler; + u32 dataEntryIndex; +} GF_MediaInformationBox; + +GF_Err stbl_SetDependencyType(GF_SampleTableBox *stbl, u32 sampleNumber, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant); +GF_Err stbl_AppendDependencyType(GF_SampleTableBox *stbl, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant); + +typedef struct +{ + GF_ISOM_BOX + u8 *data; + u32 dataSize; + u32 original_4cc; +} GF_FreeSpaceBox; + +typedef struct +{ + GF_ISOM_BOX +} GF_WideBox; /*Apple*/ + +typedef struct +{ + GF_ISOM_FULL_BOX + char packedLanguageCode[4]; + char *notice; +} GF_CopyrightBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + char *schemeURI; + char *value; +} GF_KindBox; + + +typedef struct +{ + char *name; + u64 start_time; +} GF_ChapterEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *list; +} GF_ChapterListBox; + +typedef struct +{ + GF_ISOM_BOX + u32 reference_type; + u32 trackIDCount; + GF_ISOTrackID *trackIDs; +} GF_TrackReferenceTypeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 grouping_type; + u32 group_id; + u32 entity_id_count; + u32 *entity_ids; +} GF_EntityToGroupTypeBox; + +typedef struct +{ + GF_ISOM_BOX + u32 majorBrand; + u32 minorVersion; + u32 altCount; + u32 *altBrand; +} GF_FileTypeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 *rates; + u32 *times; + u32 count; +} GF_ProgressiveDownloadBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 switch_group; + u32 alternate_group; + GF_ISOTrackID sub_track_id; + u64 attribute_count; + u32 *attribute_list; +} GF_SubTrackInformationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 grouping_type; + u16 nb_groups; + u32 *group_description_index; +} GF_SubTrackSampleGroupBox; + +typedef struct +{ + GF_ISOM_BOX + GF_SubTrackInformationBox *info; + GF_Box *strd; +} GF_SubTrackBox; + +/* + 3GPP streaming text boxes +*/ + +typedef struct +{ + GF_ISOM_BOX + u32 entry_count; + GF_FontRecord *fonts; +} GF_FontTableBox; + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS \ + u32 displayFlags; + s8 horizontal_justification; + s8 vertical_justification; + /*ARGB*/ + u32 back_color; + GF_BoxRecord default_box; + GF_StyleRecord default_style; + GF_FontTableBox *font_table; +} GF_Tx3gSampleEntryBox; + +/*Apple specific*/ +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS \ + u32 displayFlags; + u32 textJustification; + char background_color[6], foreground_color[6]; + GF_BoxRecord default_box; + u16 fontNumber; + u16 fontFace; + char reserved1[8]; + u8 reserved2; + u16 reserved3; + char *textName; /*font name*/ +} GF_TextSampleEntryBox; + +typedef struct +{ + GF_ISOM_BOX + u32 entry_count; + GF_StyleRecord *styles; +} GF_TextStyleBox; + +typedef struct +{ + GF_ISOM_BOX + u16 startcharoffset; + u16 endcharoffset; +} GF_TextHighlightBox; + +typedef struct +{ + GF_ISOM_BOX + /*ARGB*/ + u32 hil_color; +} GF_TextHighlightColorBox; + +typedef struct +{ + u32 highlight_endtime; + u16 start_charoffset; + u16 end_charoffset; +} KaraokeRecord; + +typedef struct +{ + GF_ISOM_BOX + u32 highlight_starttime; + u16 nb_entries; + KaraokeRecord *records; +} GF_TextKaraokeBox; + +typedef struct +{ + GF_ISOM_BOX + u32 scroll_delay; +} GF_TextScrollDelayBox; + +typedef struct +{ + GF_ISOM_BOX + u16 startcharoffset; + u16 endcharoffset; + char *URL; + char *URL_hint; +} GF_TextHyperTextBox; + +typedef struct +{ + GF_ISOM_BOX + GF_BoxRecord box; +} GF_TextBoxBox; + +typedef struct +{ + GF_ISOM_BOX + u16 startcharoffset; + u16 endcharoffset; +} GF_TextBlinkBox; + +typedef struct +{ + GF_ISOM_BOX + u8 wrap_flag; +} GF_TextWrapBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 switchGroup; + u32 *attributeList; + u32 attributeListCount; +} GF_TrackSelectionBox; + +/* + MPEG-21 extensions +*/ +typedef struct +{ + GF_ISOM_FULL_BOX + char *xml; +} GF_XMLBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 data_length; + u8 *data; +} GF_BinaryXMLBox; + + +typedef struct +{ + u16 item_ID; + u16 construction_method; + u16 data_reference_index; + u64 base_offset; +#ifndef GPAC_DISABLE_ISOM_WRITE + /*for storage only*/ + u64 original_base_offset; +#endif + GF_List *extent_entries; +} GF_ItemLocationEntry; + +void iloc_entry_del(GF_ItemLocationEntry *location); + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 offset_size; + u8 length_size; + u8 base_offset_size; + u8 index_size; + GF_List *location_entries; +} GF_ItemLocationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u16 item_ID; +} GF_PrimaryItemBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *protection_information; +} GF_ItemProtectionBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u16 item_ID; + u16 item_protection_index; + u32 item_type; + /*zero-terminated strings*/ + char *item_name; + char *content_type; + char *content_encoding; + // needed to actually read the resource file, but not written in the MP21 file. + char *full_path; + // if not 0, full_path is actually the data to write. + u32 data_len; + + u32 tk_id, sample_num, ref_it_id; +} GF_ItemInfoEntryBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *item_infos; +} GF_ItemInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u32 reference_type; + u32 from_item_id; + u32 reference_count; + u32 *to_item_IDs; +} GF_ItemReferenceTypeBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *references; +} GF_ItemReferenceBox; + +typedef struct +{ + GF_ISOM_BOX + u32 data_format; + //if the sample entry is a generic sample entry (data_format==GNRX), this is the underlying 4CC + //otherwise this is 0 + u32 gnr_type; +} GF_OriginalFormatBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 scheme_type; + u32 scheme_version; + char *URI; +} GF_SchemeTypeBox; + +/*ISMACryp specific*/ +typedef struct +{ + GF_ISOM_FULL_BOX + /*zero-terminated string*/ + char *URI; +} GF_ISMAKMSBox; + +/*ISMACryp specific*/ +typedef struct +{ + GF_ISOM_BOX + u64 salt; +} GF_ISMACrypSaltBox; + +/*ISMACryp specific*/ +typedef struct __isma_format_box +{ + GF_ISOM_FULL_BOX + u8 selective_encryption; + u8 key_indicator_length; + u8 IV_length; +} GF_ISMASampleFormatBox; + +typedef struct +{ + GF_ISOM_BOX + GF_ISMAKMSBox *ikms; + GF_ISMASampleFormatBox *isfm; + GF_ISMACrypSaltBox *islt; + struct __oma_kms_box *odkm; + struct __cenc_tenc_box *tenc; + struct __piff_tenc_box *piff_tenc; + struct __adobe_drm_key_management_system_box *adkm; +} GF_SchemeInformationBox; + +typedef struct __tag_protect_box +{ + GF_ISOM_BOX + GF_OriginalFormatBox *original_format; + GF_SchemeTypeBox *scheme_type; + GF_SchemeInformationBox *info; +} GF_ProtectionSchemeInfoBox; +typedef struct __tag_protect_box GF_RestrictedSchemeInfoBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *descriptors; +} GF_IPMPInfoBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_IPMP_ToolList *ipmp_tools; + GF_List *descriptors; +} GF_IPMPControlBox; + +typedef struct { + GF_ISOM_BOX +} GF_ItemPropertyContainerBox; + +typedef struct { + GF_ISOM_BOX + GF_ItemPropertyContainerBox *property_container; + struct __item_association_box *property_association; +} GF_ItemPropertiesBox; + +typedef struct { + GF_ISOM_BOX +} GF_GroupListBox; + +typedef struct __tag_meta_box +{ + GF_ISOM_FULL_BOX + GF_HandlerBox *handler; + GF_PrimaryItemBox *primary_resource; + GF_DataInformationBox *file_locations; + GF_ItemLocationBox *item_locations; + GF_ItemProtectionBox *protections; + GF_ItemInfoBox *item_infos; + GF_IPMPControlBox *IPMP_control; + GF_ItemPropertiesBox *item_props; + GF_ItemReferenceBox *item_refs; + GF_GroupListBox *groups_list; + + u8 use_item_sample_sharing, use_item_item_sharing; +} GF_MetaBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u32 single_view_allowed; + u32 stereo_scheme; + u32 sit_len; + char *stereo_indication_type; +} GF_StereoVideoBox; + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + +/*V2 boxes - Movie Fragments*/ + +typedef struct +{ + GF_ISOM_FULL_BOX + u64 fragment_duration; +} GF_MovieExtendsHeaderBox; + + +typedef struct __tag_mvex_box +{ + GF_ISOM_BOX + GF_List *TrackExList; + GF_List *TrackExPropList; + GF_MovieExtendsHeaderBox *mehd; + GF_ISOFile *mov; +} GF_MovieExtendsBox; + +/*the TrackExtends contains default values for the track fragments*/ +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOTrackID trackID; + u32 def_sample_desc_index; + u32 def_sample_duration; + u32 def_sample_size; + u32 def_sample_flags; + GF_TrackBox *track; + + Bool cannot_use_default; + GF_ISOTrackID inherit_from_traf_id; + + GF_TrackFragmentRandomAccessBox *tfra; +} GF_TrackExtendsBox; + +/*the TrackExtends contains default values for the track fragments*/ +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOTrackID trackID; +} GF_TrackExtensionPropertiesBox; + +/*indicates the seq num of this fragment*/ +typedef struct +{ + GF_ISOM_FULL_BOX + u32 sequence_number; +} GF_MovieFragmentHeaderBox; + +/*MovieFragment is a container IN THE FILE, contains 1 fragment*/ +typedef struct +{ + GF_ISOM_BOX + GF_MovieFragmentHeaderBox *mfhd; + GF_List *TrackList; + GF_List *PSSHs; + GF_ISOFile *mov; + /*offset in the file of moof or mdat (whichever comes first) for this fragment*/ + u64 fragment_offset; + u32 mdat_size; + u8 *mdat; + //when moof box was a compressed moof box, indicates the difference between the uncompressed size and the compressed size + s32 compressed_diff; + + //temp storage of prft box + GF_ISOTrackID reference_track_ID; + u64 ntp, timestamp; + + //emsg to inject before moof, not part of the moof hierarchy ! + + GF_List *emsgs; +} GF_MovieFragmentBox; + + +/*FLAGS for TRAF*/ +enum +{ + GF_ISOM_TRAF_BASE_OFFSET = 0x01, + GF_ISOM_TRAF_SAMPLE_DESC = 0x02, + GF_ISOM_TRAF_SAMPLE_DUR = 0x08, + GF_ISOM_TRAF_SAMPLE_SIZE = 0x10, + GF_ISOM_TRAF_SAMPLE_FLAGS = 0x20, + GF_ISOM_TRAF_DUR_EMPTY = 0x10000, + GF_ISOM_MOOF_BASE_OFFSET = 0x20000, +}; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOTrackID trackID; + /* all the following are optional fields */ + u64 base_data_offset; + u32 sample_desc_index; + u32 def_sample_duration; + u32 def_sample_size; + u32 def_sample_flags; + u32 EmptyDuration; +} GF_TrackFragmentHeaderBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u64 baseMediaDecodeTime; +} GF_TFBaseMediaDecodeTimeBox; + +typedef struct +{ + GF_ISOM_BOX + GF_TrackFragmentHeaderBox *tfhd; + GF_List *TrackRuns; + /*keep a pointer to default flags*/ + GF_TrackExtendsBox *trex; + GF_SampleDependencyTypeBox *sdtp; + +// GF_SubSampleInformationBox *subs; + GF_List *sub_samples; + + GF_List *sampleGroups; + GF_List *compactSampleGroups; + GF_List *sampleGroupsDescription; + + GF_List *sai_sizes; + GF_List *sai_offsets; + + //can be senc or PIFF psec + struct __sample_encryption_box *sample_encryption; + struct __traf_mss_timeext_box *tfxd; /*similar to PRFT but for Smooth Streaming*/ + struct __traf_mss_timeref_box *tfrf; + + /*when data caching is on*/ + u32 DataCache; + GF_TFBaseMediaDecodeTimeBox *tfdt; + + u64 moof_start_in_bs; +#ifdef GF_ENABLE_CTRN + Bool use_ctrn; + Bool use_inherit; +#endif + + u32 interleave_id; + u8 merge_sample_interleave; + u8 use_sample_interleave; + u8 force_new_trun; + u8 IFrameSwitching; + u8 use_sdtp; + u8 truns_first; + u8 truns_v1; + u8 large_tfdt; +} GF_TrackFragmentBox; + +GF_TrackFragmentBox *gf_isom_get_traf(GF_ISOFile *mov, GF_ISOTrackID TrackID); + +/*FLAGS for TRUN : specify what is written in the SampleTable of TRUN*/ +enum +{ + /*common to both trun and ctrn*/ + GF_ISOM_TRUN_DATA_OFFSET = 0x01, + /*trun flags*/ + GF_ISOM_TRUN_FIRST_FLAG = 0x04, + GF_ISOM_TRUN_DURATION = 0x100, + GF_ISOM_TRUN_SIZE = 0x200, + GF_ISOM_TRUN_FLAGS = 0x400, + GF_ISOM_TRUN_CTS_OFFSET = 0x800, + +#ifdef GF_ENABLE_CTRN + /*compact trun flags (not all of them, field indices are stored in trun box)*/ + GF_ISOM_CTRN_FIRST_SAMPLE = 1<<1, //0x00000002 + GF_ISOM_CTRN_DATAOFFSET_16 = 1<<2, //0x00000004 + GF_ISOM_CTRN_CTSO_MULTIPLIER = 1<<3, //0x00000008 + + GF_ISOM_CTRN_INHERIT_CTSO = 1<<4, + GF_ISOM_CTRN_INHERIT_FLAGS = 1<<5, + GF_ISOM_CTRN_INHERIT_SIZE = 1<<6, + GF_ISOM_CTRN_INHERIT_DUR = 1<<7 +#endif + +}; + +typedef struct +{ + u32 Duration; + u32 size; + u32 flags; + s32 CTS_Offset; + + /*internal*/ + u32 SAP_type; + u64 dts; + u32 nb_pack; +} GF_TrunEntry; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 sample_count; + /*the following are optional fields */ + /* unsigned for version 0 */ + s32 data_offset; + + u32 nb_samples, sample_alloc; + /*can be empty*/ + GF_TrunEntry *samples; + + /*only for trun, ignored for ctrn*/ + u32 first_sample_flags; + + /*in write mode with data caching*/ + GF_BitStream *cache; + +#ifdef GF_ENABLE_CTRN + /*the remaining is internal for compact trun*/ + /*use compact mode*/ + Bool use_ctrn; + /*we store the ctrn box flags here rather than in flags and swap when writing/dumping. This avoids overwriting the flags + set by the fragment writer*/ + u32 ctrn_flags; + /*set to default sample duration when writing, parsed from box otherwise. If 0, not used*/ + u32 ctso_multiplier; + u8 ctrn_first_dur, ctrn_first_size, ctrn_first_sample_flags, ctrn_first_ctts; + u8 ctrn_dur, ctrn_size, ctrn_sample_flags, ctrn_ctts; + /*use inherit in write mode- in the current version, only size will be set and all other fields inherited*/ + Bool use_inherit; +#endif + + u32 interleave_id; + u32 first_sample_idx; + u32 *sample_order; +} GF_TrackFragmentRunBox; + +#ifdef GF_ENABLE_CTRN +u32 gf_isom_ctrn_field_size_bits(u32 field_idx); +#endif + +#endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ + + +/*RTP Hint Track Sample Entry*/ +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + u16 HintTrackVersion; + u16 LastCompatibleVersion; + u32 MaxPacketSize; +// GF_List *HintDataTable; + /*this is where we store the current RTP sample in read/write mode*/ + struct __tag_hint_sample *hint_sample; + /*current hint sample in read mode, 1-based (0 is reset)*/ + u32 cur_sample; + u32 pck_sn, ts_offset, ssrc; + GF_TrackReferenceTypeBox *hint_ref; + + //for FEC + u16 partition_entry_ID, FEC_overhead; +} GF_HintSampleEntryBox; + + +typedef struct +{ + GF_ISOM_BOX + u32 subType; + char *sdpText; +} GF_RTPBox; + +typedef struct +{ + GF_ISOM_BOX + char *sdpText; +} GF_SDPBox; + +typedef struct +{ + GF_ISOM_BOX + s32 timeOffset; +} GF_RTPOBox; + +typedef struct +{ + GF_ISOM_BOX + /*contains GF_SDPBox if in track, GF_RTPBox if in movie*/ + GF_Box *SDP; +} GF_HintTrackInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u8 reserved; + u8 preferred; + u8 required; +} GF_RelyHintBox; + +/*********************************************************** + data entry tables for RTP +***********************************************************/ +typedef struct +{ + GF_ISOM_BOX + u32 timeScale; +} GF_TSHintEntryBox; + +typedef struct +{ + GF_ISOM_BOX + u32 TimeOffset; +} GF_TimeOffHintEntryBox; + +typedef struct +{ + GF_ISOM_BOX + u32 SeqOffset; +} GF_SeqOffHintEntryBox; + + + +/*********************************************************** + hint track information boxes for RTP +***********************************************************/ + +/*Total number of bytes that will be sent, including 12-byte RTP headers, but not including any network headers*/ +typedef struct +{ + GF_ISOM_BOX + u64 nbBytes; +} GF_TRPYBox; + +/*32-bits version of trpy used in Darwin*/ +typedef struct +{ + GF_ISOM_BOX + u32 nbBytes; +} GF_TOTLBox; + +/*Total number of network packets that will be sent*/ +typedef struct +{ + GF_ISOM_BOX + u64 nbPackets; +} GF_NUMPBox; + +/*32-bits version of nump used in Darwin*/ +typedef struct +{ + GF_ISOM_BOX + u32 nbPackets; +} GF_NPCKBox; + + +/*Total number of bytes that will be sent, not including 12-byte RTP headers*/ +typedef struct +{ + GF_ISOM_BOX + u64 nbBytes; +} GF_NTYLBox; + +/*32-bits version of tpyl used in Darwin*/ +typedef struct +{ + GF_ISOM_BOX + u32 nbBytes; +} GF_TPAYBox; + +/*Maximum data rate in bits per second.*/ +typedef struct +{ + GF_ISOM_BOX + u32 granularity; + u32 maxDataRate; +} GF_MAXRBox; + + +/*Total number of bytes from the media track to be sent*/ +typedef struct +{ + GF_ISOM_BOX + u64 nbBytes; +} GF_DMEDBox; + +/*Number of bytes of immediate data to be sent*/ +typedef struct +{ + GF_ISOM_BOX + u64 nbBytes; +} GF_DIMMBox; + + +/*Number of bytes of repeated data to be sent*/ +typedef struct +{ + GF_ISOM_BOX + u64 nbBytes; +} GF_DREPBox; + +/*Smallest relative transmission time, in milliseconds. signed integer for smoothing*/ +typedef struct +{ + GF_ISOM_BOX + s32 minTime; +} GF_TMINBox; + +/*Largest relative transmission time, in milliseconds.*/ +typedef struct +{ + GF_ISOM_BOX + s32 maxTime; +} GF_TMAXBox; + +/*Largest packet, in bytes, including 12-byte RTP header*/ +typedef struct +{ + GF_ISOM_BOX + u32 maxSize; +} GF_PMAXBox; + +/*Longest packet duration, in milliseconds*/ +typedef struct +{ + GF_ISOM_BOX + u32 maxDur; +} GF_DMAXBox; + +/*32-bit payload type number, followed by rtpmap payload string */ +typedef struct +{ + GF_ISOM_BOX + u32 payloadCode; + char *payloadString; +} GF_PAYTBox; + + +typedef struct +{ + GF_ISOM_BOX + char *string; +} GF_NameBox; + +typedef struct +{ + GF_ISOM_BOX +} GF_HintInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u8 timestamp_sync; +} GF_TimeStampSynchronyBox; + +typedef struct +{ + GF_ISOM_BOX + u32 ssrc; +} GF_ReceivedSsrcBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 encryption_algorithm_rtp; + u32 encryption_algorithm_rtcp; + u32 integrity_algorithm_rtp; + u32 integrity_algorithm_rtcp; + + GF_SchemeTypeBox *scheme_type; + GF_SchemeInformationBox *info; +} GF_SRTPProcessBox; + +/*Apple extension*/ + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 reserved; + u8 *data; + u32 dataSize; + Bool qt_style; +} GF_DataBox; + +typedef struct +{ + GF_ISOM_BOX + GF_DataBox *data; +} GF_ListItemBox; + +typedef struct +{ + GF_ISOM_BOX +} GF_ItemListBox; + +/*DECE*/ +typedef struct +{ + u8 pic_type; + u8 dependency_level; +} GF_TrickPlayBoxEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 entry_count; + GF_TrickPlayBoxEntry *entries; +} GF_TrickPlayBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 baseLocation[257]; + u8 basePurlLocation[257]; +} GF_BaseLocationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 profile_version; + char *APID; +} GF_AssetInformationBox; + +/*OMA (P)DCF extensions*/ +typedef struct +{ + GF_ISOM_FULL_BOX + u8 EncryptionMethod; + u8 PaddingScheme; + u64 PlaintextLength; + char *ContentID; + char *RightsIssuerURL; + char *TextualHeaders; + u32 TextualHeadersLen; +} GF_OMADRMCommonHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 GKEncryptionMethod; + char *GroupID; + u16 GKLength; + char *GroupKey; +} GF_OMADRMGroupIDBox; + +typedef struct +{ + GF_ISOM_BOX +} GF_OMADRMMutableInformationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + char TransactionID[16]; +} GF_OMADRMTransactionTrackingBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 *oma_ro; + u32 oma_ro_size; +} GF_OMADRMRightsObjectBox; + +/*identical*/ +typedef struct __isma_format_box GF_OMADRMAUFormatBox; + +typedef struct __oma_kms_box +{ + GF_ISOM_FULL_BOX + GF_OMADRMCommonHeaderBox *hdr; + GF_OMADRMAUFormatBox *fmt; +} GF_OMADRMKMSBox; + +typedef struct +{ + Bool reference_type; + u32 reference_size; + u32 subsegment_duration; + Bool starts_with_SAP; + u32 SAP_type; + u32 SAP_delta_time; +} GF_SIDXReference; + +typedef struct __sidx_box +{ + GF_ISOM_FULL_BOX + + u32 reference_ID; + u32 timescale; + u64 earliest_presentation_time; + u64 first_offset; + u32 nb_refs; + GF_SIDXReference *refs; + //for trace only + s32 compressed_diff; +} GF_SegmentIndexBox; + +GF_Err gf_isom_set_fragment_template(GF_ISOFile *movie, u8 *tpl_data, u32 tpl_size, Bool *has_tfdt, GF_SegmentIndexBox **out_sidx); + +typedef struct +{ + u8 level; + u32 range_size; +} GF_SubsegmentRangeInfo; + +typedef struct +{ + u32 range_count; + GF_SubsegmentRangeInfo *ranges; +} GF_SubsegmentInfo; + +typedef struct __ssix_box +{ + GF_ISOM_FULL_BOX + + u32 subsegment_count, subsegment_alloc; + GF_SubsegmentInfo *subsegments; + //for trace only + s32 compressed_diff; +} GF_SubsegmentIndexBox; + +typedef struct +{ + GF_ISOTrackID track_id; + Bool padding_flag; + u8 type; + u32 grouping_type; + u32 grouping_type_parameter; + GF_ISOTrackID sub_track_id; +} GF_LevelAssignment; + +typedef struct __leva_box +{ + GF_ISOM_FULL_BOX + + u32 level_count; + GF_LevelAssignment *levels; +} GF_LevelAssignmentBox; + +typedef struct __pcrInfo_box +{ + GF_ISOM_BOX + u32 subsegment_count; + u64 *pcr_values; +} GF_PcrInfoBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + char *scheme_id_uri; + char *value; + u32 timescale; + u64 presentation_time_delta; + u32 event_duration; + u32 event_id; + u8 *message_data; + u32 message_data_size; +} GF_EventMessageBox; + + +#ifndef GPAC_DISABLE_ISOM_ADOBE + +/*Adobe specific boxes*/ +typedef struct +{ + u64 time; + u64 offset; +} GF_AfraEntry; + +typedef struct +{ + u64 time; + u32 segment; + u32 fragment; + u64 afra_offset; + u64 offset_from_afra; +} GF_GlobalAfraEntry; + +typedef struct __adobe_frag_random_access_box +{ + GF_ISOM_FULL_BOX + Bool long_ids; + Bool long_offsets; + Bool global_entries; + u8 reserved; + u32 time_scale; + u32 entry_count; + GF_List *local_access_entries; + u32 global_entry_count; + GF_List *global_access_entries; +} GF_AdobeFragRandomAccessBox; + +typedef struct __adobe_bootstrap_info_box +{ + GF_ISOM_FULL_BOX + u32 bootstrapinfo_version; + u8 profile; + Bool live; + Bool update; + u8 reserved; + u32 time_scale; + u64 current_media_time; + u64 smpte_time_code_offset; + char *movie_identifier; + u8 server_entry_count; + GF_List *server_entry_table; + u8 quality_entry_count; + GF_List *quality_entry_table; + char *drm_data; + char *meta_data; + //entries in these two lists are NOT registered with the box child_boxes because of the in-between 8 bits !! + u8 segment_run_table_count; + GF_List *segment_run_table_entries; + u8 fragment_run_table_count; + GF_List *fragment_run_table_entries; +} GF_AdobeBootstrapInfoBox; + +typedef struct +{ + u32 first_segment; + u32 fragment_per_segment; +} GF_AdobeSegmentRunEntry; + +typedef struct __adobe_segment_run_table_box +{ + GF_ISOM_FULL_BOX + u8 quality_entry_count; + GF_List *quality_segment_url_modifiers; + u32 segment_run_entry_count; + GF_List *segment_run_entry_table; +} GF_AdobeSegmentRunTableBox; + +typedef struct +{ + u32 first_fragment; + u64 first_fragment_timestamp; + u32 fragment_duration; + u8 discontinuity_indicator; +} GF_AdobeFragmentRunEntry; + +typedef struct __adobe_fragment_run_table_box +{ + GF_ISOM_FULL_BOX + u32 timescale; + u8 quality_entry_count; + GF_List *quality_segment_url_modifiers; + u32 fragment_run_entry_count; + GF_List *fragment_run_entry_table; +} GF_AdobeFragmentRunTableBox; + +#endif /*GPAC_DISABLE_ISOM_ADOBE*/ + + +/*********************************************************** + Sample Groups +***********************************************************/ +typedef struct +{ + u32 sample_count; + u32 group_description_index; +} GF_SampleGroupEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 grouping_type; + u32 grouping_type_parameter; + + u32 entry_count; + GF_SampleGroupEntry *sample_entries; + +} GF_SampleGroupBox; + +typedef struct +{ + u32 length; + u32 sample_count; + u32 *sample_group_description_indices; +} GF_CompactSampleGroupPattern; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 grouping_type; + u32 grouping_type_parameter; + + u32 pattern_count; + GF_CompactSampleGroupPattern *patterns; +} GF_CompactSampleGroupBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 grouping_type; + u32 default_length; + + u32 default_description_index; + GF_List *group_descriptions; +} GF_SampleGroupDescriptionBox; + +/*default entry */ +typedef struct +{ + u32 length; + u8 *data; +} GF_DefaultSampleGroupDescriptionEntry; + +/*VisualRandomAccessEntry - 'rap ' type*/ +typedef struct +{ + u8 num_leading_samples_known; + u8 num_leading_samples; +} GF_VisualRandomAccessEntry; + +/*RollRecoveryEntry - 'roll' and prol type*/ +typedef struct +{ + s16 roll_distance; +} GF_RollRecoveryEntry; + +/*TemporalLevelEntry - 'tele' type*/ +typedef struct +{ + Bool level_independently_decodable; +} GF_TemporalLevelEntry; + +/*SAPEntry - 'sap ' type*/ +typedef struct +{ + Bool dependent_flag; + u8 SAP_type; +} GF_SAPEntry; + +/*SAPEntry - 'sync' type*/ +typedef struct +{ + u8 NALU_type; +} GF_SYNCEntry; + +/*Operating Points Information - 'oinf' type*/ +typedef struct +{ + u16 scalability_mask; + GF_List* profile_tier_levels; + GF_List* operating_points; + GF_List* dependency_layers; +} GF_OperatingPointsInformation; + +GF_OperatingPointsInformation *gf_isom_oinf_new_entry(); +void gf_isom_oinf_del_entry(void *entry); +GF_Err gf_isom_oinf_read_entry(void *entry, GF_BitStream *bs); +GF_Err gf_isom_oinf_write_entry(void *entry, GF_BitStream *bs); +u32 gf_isom_oinf_size_entry(void *entry); +Bool gf_isom_get_oinf_info(GF_ISOFile *file, u32 trackNumber, GF_OperatingPointsInformation **ptr); + + +/*Operating Points Information - 'oinf' type*/ +typedef struct +{ + u8 layer_id; + u8 min_TemporalId; + u8 max_TemporalId; + u8 sub_layer_presence_flags; +} LHVCLayerInfoItem; + +typedef struct +{ + GF_List* num_layers_in_track; +} GF_LHVCLayerInformation; + +GF_LHVCLayerInformation *gf_isom_linf_new_entry(); +void gf_isom_linf_del_entry(void *entry); +GF_Err gf_isom_linf_read_entry(void *entry, GF_BitStream *bs); +GF_Err gf_isom_linf_write_entry(void *entry, GF_BitStream *bs); +u32 gf_isom_linf_size_entry(void *entry); +Bool gf_isom_get_linf_info(GF_ISOFile *file, u32 trackNumber, GF_LHVCLayerInformation **ptr); + + +#define MAX_LHEVC_LAYERS 64 + +typedef struct +{ + u8 general_profile_space, general_tier_flag, general_profile_idc, general_level_idc; + u32 general_profile_compatibility_flags; + u64 general_constraint_indicator_flags; +} LHEVC_ProfileTierLevel; + +typedef struct +{ + u8 ptl_idx; + u8 layer_id; + Bool is_outputlayer, is_alternate_outputlayer; +} LHEVC_LayerInfo; + +typedef struct +{ + u16 output_layer_set_idx; + u8 max_temporal_id; + u8 layer_count; + LHEVC_LayerInfo layers_info[MAX_LHEVC_LAYERS]; + u16 minPicWidth, minPicHeight, maxPicWidth, maxPicHeight; + u8 maxChromaFormat, maxBitDepth; + Bool frame_rate_info_flag, bit_rate_info_flag; + u16 avgFrameRate; + u8 constantFrameRate; + u32 maxBitRate, avgBitRate; +} LHEVC_OperatingPoint; + + +typedef struct +{ + u8 dependent_layerID; + u8 num_layers_dependent_on; + u8 dependent_on_layerID[MAX_LHEVC_LAYERS]; + u8 dimension_identifier[16]; +} LHEVC_DependentLayer; + +typedef struct +{ + u8 subpic_id_len_minus1; + u16 subpic_id_bit_pos; + u8 start_code_emul_flag; + u8 pps_sps_subpic_id_flag; + u8 xps_id; +} GF_VVCSubpicIDRewritingInfo; + +/*SubpictureOrder - 'spor' type*/ +typedef struct +{ + Bool subpic_id_info_flag; + u16 num_subpic_ref_idx; + u16 *subp_track_ref_idx; + GF_VVCSubpicIDRewritingInfo spinfo; +} GF_SubpictureOrderEntry; + +/*SubpictureLayoutMap - 'sulm' type*/ +typedef struct +{ + u32 groupID_info_4cc; + u32 nb_entries; + u16 *groupIDs; +} GF_SubpictureLayoutMapEntry; + +/* + CENC stuff +*/ + +/*CENCSampleEncryptionGroupEntry - 'seig' type*/ +typedef struct +{ + u8 crypt_byte_block, skip_byte_block; + u8 IsProtected; + u8 *key_info; + u32 key_info_size; +} GF_CENCSampleEncryptionGroupEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + + bin128 SystemID; + u32 KID_count; + bin128 *KIDs; + u32 private_data_size; + u8 *private_data; + u8 moof_defined; +} GF_ProtectionSystemHeaderBox; + +typedef struct __cenc_tenc_box +{ + GF_ISOM_FULL_BOX + + u8 crypt_byte_block, skip_byte_block; + u8 isProtected; + + //single key + u8 key_info[37]; +} GF_TrackEncryptionBox; + +typedef struct __piff_tenc_box +{ + GF_ISOM_UUID_BOX + /*u8 version; field in included in base box version */ + u32 flags; + + u32 AlgorithmID; + u8 key_info[20]; +} GF_PIFFTrackEncryptionBox; + +typedef struct +{ + GF_ISOM_UUID_BOX + /*u8 version; field in included in base box version */ + u32 flags; + + bin128 SystemID; + u32 private_data_size; + u8 *private_data; +} GF_PIFFProtectionSystemHeaderBox; + + +typedef struct __sample_encryption_box +{ + GF_ISOM_UUID_BOX + /*u8 version; field in included in base box version */ + u32 flags; + + //0: regular senc, 1: PIFF PSEC, 2: MS senc with version 1 (not compatible with ISOBMFF senc v1) + u32 piff_type; + + GF_List *samp_aux_info; /*GF_CENCSampleAuxInfo*/ + u64 bs_offset; + Bool load_needed; + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + /*pointer to container traf*/ + GF_TrackFragmentBox *traf; +#endif + /*pointer to associated saio*/ + GF_SampleAuxiliaryInfoSizeBox *cenc_saiz; + GF_SampleAuxiliaryInfoOffsetBox *cenc_saio; + + + u32 AlgorithmID; + u8 IV_size; + bin128 KID; + +} GF_SampleEncryptionBox; + +typedef struct __traf_mss_timeext_box +{ + GF_ISOM_UUID_BOX + /*u8 version; field in included in base box version */ + u32 flags; + + u64 absolute_time_in_track_timescale; + u64 fragment_duration_in_track_timescale; +} GF_MSSTimeExtBox; + +typedef struct +{ + u64 absolute_time_in_track_timescale; + u64 fragment_duration_in_track_timescale; +} GF_MSSTimeEntry; + +typedef struct __traf_mss_timeref_box +{ + GF_ISOM_UUID_BOX + /*u8 version; field in included in base box version */ + u32 flags; + + u32 frags_count; + GF_MSSTimeEntry *frags; + +} GF_MSSTimeRefBox; + + + +GF_SampleEncryptionBox *gf_isom_create_piff_psec_box(u8 version, u32 flags, u32 AlgorithmID, u8 IV_size, bin128 KID); +GF_SampleEncryptionBox * gf_isom_create_samp_enc_box(u8 version, u32 flags); + +void gf_isom_cenc_get_default_info_internal(GF_TrackBox *trak, u32 sampleDescriptionIndex, u32 *container_type, Bool *default_IsEncrypted, u8 *crypt_byte_block, u8 *skip_byte_block, const u8 **key_info, u32 *key_info_size); + + +GF_Err gf_isom_get_sample_cenc_info_internal(GF_TrackBox *trak, +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + GF_TrackFragmentBox *traf, +#else + void *traf, +#endif + GF_SampleEncryptionBox *senc, u32 sample_number, Bool *IsEncrypted, u8 *crypt_byte_block, u8 *skip_byte_block, const u8 **key_info, u32 *key_info_size); + + +GF_Err senc_Parse(GF_BitStream *bs, GF_TrackBox *trak, +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + GF_TrackFragmentBox *traf, +#else + void *traf, +#endif + GF_SampleEncryptionBox *ptr); + + +/* + Boxes for Adobe's protection scheme +*/ +typedef struct __adobe_enc_info_box +{ + GF_ISOM_FULL_BOX + char *enc_algo; /*spec: The encryption algorithm shall be 'AES-CBC'*/ + u8 key_length; +} GF_AdobeEncryptionInfoBox; + +typedef struct __adobe_flash_access_params_box +{ + GF_ISOM_BOX + u8 *metadata; /*base-64 encoded metadata used by the DRM client to retrieve decrypted key*/ +} GF_AdobeFlashAccessParamsBox; + +typedef struct __adobe_key_info_box +{ + GF_ISOM_FULL_BOX + GF_AdobeFlashAccessParamsBox * params; /*spec: APSParamsBox will no longer be produced by conformaing applications*/ +} GF_AdobeKeyInfoBox; + +typedef struct __adobe_std_enc_params_box +{ + GF_ISOM_FULL_BOX + GF_AdobeEncryptionInfoBox *enc_info; + GF_AdobeKeyInfoBox *key_info; +} GF_AdobeStdEncryptionParamsBox; + +typedef struct __adobe_drm_header_box +{ + GF_ISOM_FULL_BOX + GF_AdobeStdEncryptionParamsBox *std_enc_params; + //AdobeSignatureBox *signature; /*AdobeSignatureBox is not described*/ +} GF_AdobeDRMHeaderBox; + + +typedef struct __adobe_drm_au_format_box +{ + GF_ISOM_FULL_BOX + u8 selective_enc; + u8 IV_length; +} GF_AdobeDRMAUFormatBox; + +typedef struct __adobe_drm_key_management_system_box +{ + GF_ISOM_FULL_BOX + GF_AdobeDRMHeaderBox *header; + GF_AdobeDRMAUFormatBox *au_format; +} GF_AdobeDRMKeyManagementSystemBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOTrackID refTrackID; + u64 ntp, timestamp; +} GF_ProducerReferenceTimeBox; + +/* Image File Format Structures */ +typedef struct { + GF_ISOM_FULL_BOX + u32 image_width; + u32 image_height; +} GF_ImageSpatialExtentsPropertyBox; + +typedef struct { + GF_ISOM_FULL_BOX + u8 num_channels; + u8 *bits_per_channel; +} GF_PixelInformationPropertyBox; + +typedef struct { + GF_ISOM_FULL_BOX + u32 horizontal_offset; + u32 vertical_offset; +} GF_RelativeLocationPropertyBox; + +typedef struct { + GF_ISOM_BOX + u8 angle; +} GF_ImageRotationBox; + +typedef struct { + GF_ISOM_BOX + u8 axis; +} GF_ImageMirrorBox; + +typedef struct +{ + u8 essential; + u32 index; +} GF_ItemPropertyAssociationSlot; + +typedef struct { + u32 item_id; + GF_ItemPropertyAssociationSlot *associations; + u32 nb_associations; +} GF_ItemPropertyAssociationEntry; + +typedef struct __item_association_box { + GF_ISOM_FULL_BOX + GF_List *entries; +} GF_ItemPropertyAssociationBox; + +typedef struct { + GF_ISOM_BOX + u8 large_size; + u32 layer_size[3]; +} GF_AV1LayeredImageIndexingPropertyBox; + +typedef struct { + GF_ISOM_BOX + u8 op_index; +} GF_AV1OperatingPointSelectorPropertyBox; + + +typedef struct { + GF_ISOM_FULL_BOX + char *aux_urn; + u32 data_size; + u8 *data; +} GF_AuxiliaryTypePropertyBox; + +typedef struct { + GF_ISOM_FULL_BOX + u8 skip_byte_block, crypt_byte_block; + u8 *key_info; + u32 key_info_size; +} GF_ItemEncryptionPropertyBox; + + +typedef struct { + GF_ISOM_FULL_BOX + u32 aux_info_type; + u32 aux_info_parameter; +} GF_AuxiliaryInfoPropertyBox; + +typedef struct { + GF_ISOM_FULL_BOX + + GF_OperatingPointsInformation *oinf; +} GF_OINFPropertyBox; + + +typedef struct { + GF_ISOM_FULL_BOX + + u16 target_ols_index; +} GF_TargetOLSPropertyBox; + +/*flute hint track boxes*/ +typedef struct +{ + u16 block_count; + u32 block_size; +} FilePartitionEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 itemID; + u16 packet_payload_size; + u8 FEC_encoding_ID; + u16 FEC_instance_ID; + u16 max_source_block_length; + u16 encoding_symbol_length; + u16 max_number_of_encoding_symbols; + char *scheme_specific_info; + u32 nb_entries; + FilePartitionEntry *entries; +} FilePartitionBox; + +typedef struct +{ + u32 item_id; + u32 symbol_count; +} FECReservoirEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 nb_entries; + FECReservoirEntry *entries; +} FECReservoirBox; + +typedef struct +{ + u32 nb_groups; + u32 *group_ids; + u32 nb_channels; + u32 *channels; +} SessionGroupEntry; + +typedef struct +{ + GF_ISOM_BOX + u16 num_session_groups; + SessionGroupEntry *session_groups; +} FDSessionGroupBox; + +typedef struct +{ + u32 group_id; + char *name; +} GroupIdNameEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u16 nb_entries; + GroupIdNameEntry *entries; +} GroupIdToNameBox; + + +typedef struct +{ + u32 item_id; + u32 symbol_count; +} FileReservoirEntry; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 nb_entries; + FileReservoirEntry *entries; +} FileReservoirBox; + +typedef struct +{ + GF_ISOM_BOX + FilePartitionBox *blocks_and_symbols; + FECReservoirBox *FEC_symbol_locations; + FileReservoirBox *File_symbol_locations; +} FDPartitionEntryBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_List *partition_entries; + FDSessionGroupBox *session_info; + GroupIdToNameBox *group_id_to_name; +} FDItemInformationBox; + + + +typedef struct +{ + char *name; + u32 flags; + u16 prop_type; + + u32 prop_size; + u8 *prop_value; //most of the time, utf16 with trailing \0\0 +} GF_XtraTag; + +typedef struct +{ + GF_ISOM_BOX + + GF_List *tags; +} GF_XtraBox; + + +/* + Data Map (media storage) stuff +*/ + +/*regular file IO*/ +#define GF_ISOM_DATA_FILE 0x01 +/*External file object. Needs implementation*/ +#define GF_ISOM_DATA_FILE_EXTERN 0x03 +/*regular memory IO*/ +#define GF_ISOM_DATA_MEM 0x04 + +/*Data Map modes*/ +enum +{ + /*read mode*/ + GF_ISOM_DATA_MAP_READ = 1, + /*write mode*/ + GF_ISOM_DATA_MAP_WRITE = 2, + /*the following modes are just ways of signaling extended functionalities + edit mode, to make sure the file is here, set to GF_ISOM_DATA_MAP_READ afterwards*/ + GF_ISOM_DATA_MAP_EDIT = 3, + /*read-only access to the movie file: we create a file mapping object + mode is set to GF_ISOM_DATA_MAP_READ afterwards*/ + GF_ISOM_DATA_MAP_READ_ONLY = 4, + /*write-only access at the end of the movie - only used for movie fragments concatenation*/ + GF_ISOM_DATA_MAP_CAT = 5, +}; + +/*this is the DataHandler structure each data handler has its own bitstream*/ +#define GF_ISOM_BASE_DATA_HANDLER \ + u8 type; \ + u64 curPos; \ + u8 mode; \ + GF_BitStream *bs;\ + u64 last_read_offset;\ + char *szName; + +typedef struct __tag_data_map +{ + GF_ISOM_BASE_DATA_HANDLER +} GF_DataMap; + +typedef struct +{ + GF_ISOM_BASE_DATA_HANDLER + FILE *stream; + Bool is_stdout; + Bool last_acces_was_read; +#ifndef GPAC_DISABLE_ISOM_WRITE + char *temp_file; +#endif + GF_Blob *blob; +} GF_FileDataMap; + +/*file mapping handler. used if supported, only on read mode for complete files (not in file download)*/ +typedef struct +{ + GF_ISOM_BASE_DATA_HANDLER + char *name; + u64 file_size; + u8 *byte_map; + u64 byte_pos; +} GF_FileMappingDataMap; + +GF_Err gf_isom_datamap_new(const char *location, const char *parentPath, u8 mode, GF_DataMap **outDataMap); +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); + +/*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); + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_DataMap *gf_isom_fdm_new_temp(const char *sTempPath); +#endif + +#ifndef GPAC_DISABLE_ISOM_WRITE +u64 gf_isom_datamap_get_offset(GF_DataMap *map); +GF_Err gf_isom_datamap_add_data(GF_DataMap *ptr, u8 *data, u32 dataSize); +#endif + +void gf_isom_datamap_flush(GF_DataMap *map); + +/* + Movie stuff +*/ + + +/*time def for MP4/QT/MJ2K files*/ +#define GF_ISOM_MAC_TIME_OFFSET 2082844800 + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +#define GF_ISOM_FORMAT_FRAG_FLAGS(pad, sync, deg) ( ( (pad) << 17) | ( ( !(sync) ) << 16) | (deg) ); +#define GF_ISOM_GET_FRAG_PAD(flag) ( (flag) >> 17) & 0x7 +#define GF_ISOM_GET_FRAG_SYNC(flag) ( ! ( ( (flag) >> 16) & 0x1)) +#define GF_ISOM_GET_FRAG_DEG(flag) (flag) & 0x7FFF + +#define GF_ISOM_GET_FRAG_LEAD(flag) ( (flag) >> 26) & 0x3 +#define GF_ISOM_GET_FRAG_DEPENDS(flag) ( (flag) >> 24) & 0x3 +#define GF_ISOM_GET_FRAG_DEPENDED(flag) ( (flag) >> 22) & 0x3 +#define GF_ISOM_GET_FRAG_REDUNDANT(flag) ( (flag) >> 20) & 0x3 + +#define GF_ISOM_GET_FRAG_DEPEND_FLAGS(lead, depends, depended, redundant) ( (lead<<26) | (depends<<24) | (depended<<22) | (redundant<<20) ) +#define GF_ISOM_RESET_FRAG_DEPEND_FLAGS(flags) flags = flags & 0xFFFFF + +GF_TrackExtendsBox *GetTrex(GF_MovieBox *moov, GF_ISOTrackID TrackID); +#endif + +enum +{ + GF_ISOM_FRAG_WRITE_READY = 0x01, + GF_ISOM_FRAG_READ_DEBUG = 0x02, +}; + + +/*this is our movie object*/ +struct __tag_isom { + /*the last fatal error*/ + GF_Err LastError; + /*the original filename*/ + char *fileName; + /*the original file in read/edit, and also used in fragments mode + once the first moov has been written + Nota: this API doesn't allow fragments BEFORE the MOOV in order + to make easily parsable files (note there could be some data (mdat) before + the moov*/ + GF_DataMap *movieFileMap; + +#ifndef GPAC_DISABLE_ISOM_WRITE + /*the final file name*/ + char *finalName; + /*the file where we store edited samples (for READ_WRITE and WRITE mode only)*/ + GF_DataMap *editFileMap; + /*the interleaving time for dummy mode (in movie TimeScale)*/ + u32 interleavingTime; + GF_ISOTrackID last_created_track_id; +#endif + + GF_ISOOpenMode openMode; + u8 storageMode; + /*if true 3GPP text streams are read as MPEG-4 StreamingText*/ + u8 convert_streaming_text; + u8 is_jp2; + u8 force_co64; + u8 disable_odf_translate; + u8 disable_brand_rewrite; + u64 next_flush_chunk_time; + Bool keep_utc; + /*main boxes for fast access*/ + /*moov*/ + GF_MovieBox *moov; + /*our MDAT box (one and only one when we store the file)*/ + GF_MediaDataBox *mdat; + /*file brand (since v2, NULL means mp4 v1)*/ + GF_FileTypeBox *brand; + /*original file type box if any*/ + GF_Box *otyp; + + /*progressive download info*/ + GF_ProgressiveDownloadBox *pdin; + /*meta box if any*/ + GF_MetaBox *meta; + + s64 read_byte_offset; + u64 bytes_removed; + + GF_ISOCompressMode compress_mode; + u32 compress_flags; + + void (*progress_cbk)(void *udta, u64 nb_done, u64 nb_total); + void *progress_cbk_udta; + + u32 FragmentsFlags; +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + u32 NextMoofNumber; + /*active fragment*/ + GF_MovieFragmentBox *moof; + /*in WRITE mode, this is the current MDAT where data is written*/ + /*in READ mode this is the last valid file position before a gf_isom_box_read failed*/ + u64 current_top_box_start; + u64 segment_start; + + GF_List *moof_list; + Bool use_segments, moof_first, append_segment, styp_written, force_moof_base_offset; + + GF_List *emsgs; + + /*used when building single-indexed self initializing media segments*/ + GF_SegmentIndexBox *root_sidx; + u64 root_sidx_offset; + u32 root_sidx_index; + Bool dyn_root_sidx; + GF_SubsegmentIndexBox *root_ssix; + + Bool is_index_segment; + + GF_BitStream *segment_bs; + /* 0: no moof found yet, 1: 1 moof found, 2: next moof found */ + Bool single_moof_mode; + u32 single_moof_state; + + Bool sample_groups_in_traf; + Bool force_sidx_v1; + + /* optional mfra box used in write mode */ + GF_MovieFragmentRandomAccessBox *mfra; + + Bool store_traf_map; + Bool signal_frag_bounds; + u64 sidx_start_offset, sidx_end_offset; + u64 styp_start_offset; + u64 mdat_end_offset; + GF_Box *seg_ssix, *seg_styp; + + u32 sidx_pts_store_alloc, sidx_pts_store_count; + u64 *sidx_pts_store, *sidx_pts_next_store; + + GF_SegmentIndexBox *main_sidx; + u64 main_sidx_end_pos; + + Bool has_pssh_moof; +#endif + GF_ProducerReferenceTimeBox *last_producer_ref_time; + + /*this contains ALL the root boxes excepts fragments*/ + GF_List *TopBoxes; + + /*default track for sync of MPEG4 streams - this is the first accessed stream without OCR info - only set in READ mode*/ + s32 es_id_default_sync; + + Bool is_smooth; + + GF_Err (*on_block_out)(void *usr_data, u8 *block, u32 block_size); + GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert); + void *on_block_out_usr_data; + u32 on_block_out_block_size; + + //in block disptach mode we don't have the full file, keep the position + u64 fragmented_file_pos; + u8 *block_buffer; + u32 block_buffer_size; + + u32 nb_box_init_seg; + + Bool no_inplace_rewrite; + u32 padding; + u64 original_moov_offset, original_meta_offset, first_data_toplevel_offset, first_data_toplevel_size; +}; + +/*time function*/ +u64 gf_isom_get_mp4time(); +/*set the last error of the file. if file is NULL, set the static error (used for IO errors*/ +void gf_isom_set_last_error(GF_ISOFile *the_file, GF_Err error); +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(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); +u32 gf_isom_get_tracknum_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID); +/*open a movie*/ +GF_ISOFile *gf_isom_open_file(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir); +/*close and delete a movie*/ +void gf_isom_delete_movie(GF_ISOFile *mov); +void gf_isom_meta_restore_items_ref(GF_ISOFile *file, GF_MetaBox *meta); + +GF_MetaBox *gf_isom_get_meta(GF_ISOFile *file, Bool root_meta, u32 track_num); + +/*StreamDescription reconstruction Functions*/ +GF_Err GetESD(GF_MovieBox *moov, GF_ISOTrackID trackID, u32 StreamDescIndex, GF_ESD **outESD); +GF_Err GetESDForTime(GF_MovieBox *moov, GF_ISOTrackID trackID, u64 CTS, GF_ESD **outESD); +GF_Err Media_GetSampleDesc(GF_MediaBox *mdia, u32 SampleDescIndex, GF_SampleEntryBox **out_entry, u32 *dataRefIndex); +GF_Err Media_GetSampleDescIndex(GF_MediaBox *mdia, u64 DTS, u32 *sampleDescIndex); +/*get esd for given sample desc - + @true_desc_only: if true doesn't emulate desc and returns native ESD, + otherwise emulates if needed/possible (TimedText) and return a hard copy of the desc +*/ +GF_Err Media_GetESD(GF_MediaBox *mdia, u32 sampleDescIndex, GF_ESD **esd, Bool true_desc_only); +Bool Track_IsMPEG4Stream(u32 HandlerType); +Bool IsMP4Description(u32 entryType); +/*Find a reference of a given type*/ +GF_Err Track_FindRef(GF_TrackBox *trak, u32 ReferenceType, GF_TrackReferenceTypeBox **dpnd); +/*Time and sample*/ +GF_Err GetMediaTime(GF_TrackBox *trak, Bool force_non_empty, u64 movieTime, u64 *MediaTime, s64 *SegmentStartTime, s64 *MediaOffset, u8 *useEdit, u64 *next_edit_start_plus_one); +GF_Err Media_GetSample(GF_MediaBox *mdia, u32 sampleNumber, GF_ISOSample **samp, u32 *sampleDescriptionIndex, Bool no_data, u64 *out_offset); +GF_Err Media_CheckDataEntry(GF_MediaBox *mdia, u32 dataEntryIndex); +GF_Err Media_FindSyncSample(GF_SampleTableBox *stbl, u32 searchFromTime, u32 *sampleNumber, u8 mode); +GF_Err Media_RewriteODFrame(GF_MediaBox *mdia, GF_ISOSample *sample); +GF_Err Media_FindDataRef(GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex); +Bool Media_IsSelfContained(GF_MediaBox *mdia, u32 StreamDescIndex); + +typedef enum +{ + ISOM_DREF_MIXED = 0, + ISOM_DREF_SELF, + ISOM_DREF_EXT, +} GF_ISOMDataRefAllType; +GF_ISOMDataRefAllType Media_SelfContainedType(GF_MediaBox *mdia); + +GF_TrackBox *GetTrackbyID(GF_MovieBox *moov, GF_ISOTrackID TrackID); + +/*check the TimeToSample for the given time and return the Sample number +if the entry is not found, return the closest sampleNumber in prevSampleNumber and 0 in sampleNumber +if the DTS required is after all DTSs in the list, set prevSampleNumber and SampleNumber to 0 +useCTS specifies that we're looking for a composition time +*/ +GF_Err stbl_findEntryForTime(GF_SampleTableBox *stbl, u64 DTS, u8 useCTS, u32 *sampleNumber, u32 *prevSampleNumber); +/*Reading of the sample tables*/ +GF_Err stbl_GetSampleSize(GF_SampleSizeBox *stsz, u32 SampleNumber, u32 *Size); +GF_Err stbl_GetSampleCTS(GF_CompositionOffsetBox *ctts, u32 SampleNumber, s32 *CTSoffset); +GF_Err stbl_GetSampleDTS(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS); +GF_Err stbl_GetSampleDTS_and_Duration(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS, u32 *duration); + +/*find a RAP or set the prev / next RAPs if vars are passed*/ +GF_Err stbl_GetSampleRAP(GF_SyncSampleBox *stss, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP); +/*same as above but only look for open-gop RAPs and GDR (roll)*/ +GF_Err stbl_SearchSAPs(GF_SampleTableBox *stbl, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP); +GF_Err stbl_GetSampleInfos(GF_SampleTableBox *stbl, u32 sampleNumber, u64 *offset, u32 *chunkNumber, u32 *descIndex, GF_StscEntry **scsc_entry); +GF_Err stbl_GetSampleShadow(GF_ShadowSyncBox *stsh, u32 *sampleNumber, u32 *syncNum); +GF_Err stbl_GetPaddingBits(GF_PaddingBitsBox *padb, u32 SampleNumber, u8 *PadBits); +GF_Err stbl_GetSampleDepType(GF_SampleDependencyTypeBox *stbl, u32 SampleNumber, u32 *isLeading, u32 *dependsOn, u32 *dependedOn, u32 *redundant); + + +/*unpack sample2chunk and chunk offset so that we have 1 sample per chunk (edition mode only)*/ +GF_Err stbl_UnpackOffsets(GF_SampleTableBox *stbl); +GF_Err stbl_unpackCTS(GF_SampleTableBox *stbl); +GF_Err SetTrackDuration(GF_TrackBox *trak); +GF_Err Media_SetDuration(GF_TrackBox *trak); + +/*rewrites 3GP samples desc as MPEG-4 ESD*/ +GF_Err gf_isom_get_ttxt_esd(GF_MediaBox *mdia, GF_ESD **out_esd); +/*inserts TTU header - only used when conversion to StreamingText is on*/ +GF_Err gf_isom_rewrite_text_sample(GF_ISOSample *samp, u32 sampleDescriptionIndex, u32 sample_dur); + +GF_UserDataMap *udta_getEntry(GF_UserDataBox *ptr, u32 box_type, bin128 *uuid); + + +GF_Err gf_isom_set_sample_group_description_internal(GF_ISOFile *movie, u32 track, u32 sample_number, u32 grouping_type, u32 grouping_type_parameter, void *data, u32 data_size, Bool check_access); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +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); + +GF_Err WriteToFile(GF_ISOFile *movie, Bool for_fragments); +GF_Err Track_SetStreamDescriptor(GF_TrackBox *trak, u32 StreamDescriptionIndex, u32 DataReferenceIndex, GF_ESD *esd, u32 *outStreamIndex); +u8 RequestTrack(GF_MovieBox *moov, GF_ISOTrackID TrackID); +/*Track-Media setup*/ +GF_Err NewMedia(GF_MediaBox **mdia, u32 MediaType, u32 TimeScale); +GF_Err Media_ParseODFrame(GF_MediaBox *mdia, const GF_ISOSample *sample, GF_ISOSample **od_samp); +GF_Err Media_AddSample(GF_MediaBox *mdia, u64 data_offset, const GF_ISOSample *sample, u32 StreamDescIndex, u32 syncShadowNumber); +GF_Err Media_CreateDataRef(GF_ISOFile *file, GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex); +GF_Err Media_SetDrefURL(GF_DataEntryURLBox *dref_entry, const char *origName, const char *finalName); + +/*update a media sample. ONLY in edit mode*/ +GF_Err Media_UpdateSample(GF_MediaBox *mdia, u32 sampleNumber, GF_ISOSample *sample, Bool data_only); +GF_Err Media_UpdateSampleReference(GF_MediaBox *mdia, u32 sampleNumber, GF_ISOSample *sample, u64 data_offset); +/*addition in the sample tables*/ +GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration, u32 nb_pack_samples); +GF_Err stbl_AddCTS(GF_SampleTableBox *stbl, u32 sampleNumber, s32 CTSoffset); +GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size, u32 nb_pack_samples); +GF_Err stbl_AddRAP(GF_SyncSampleBox *stss, u32 sampleNumber); +GF_Err stbl_AddShadow(GF_ShadowSyncBox *stsh, u32 sampleNumber, u32 shadowNumber); +GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset, u32 nb_pack_samples); +/*NB - no add for padding, this is done only through SetPaddingBits*/ + +GF_Err stbl_AddSampleFragment(GF_SampleTableBox *stbl, u32 sampleNumber, u16 size); + +/*update of the sample table +all these functions are called in edit and we always have 1 sample per chunk*/ +GF_Err stbl_SetChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u64 offset); +GF_Err stbl_SetSampleCTS(GF_SampleTableBox *stbl, u32 sampleNumber, s32 offset); +GF_Err stbl_SetSampleSize(GF_SampleSizeBox *stsz, u32 SampleNumber, u32 size); +GF_Err stbl_SetSampleRAP(GF_SyncSampleBox *stss, u32 SampleNumber, u8 isRAP); +GF_Err stbl_SetSyncShadow(GF_ShadowSyncBox *stsh, u32 sampleNumber, u32 syncSample); +GF_Err stbl_SetPaddingBits(GF_SampleTableBox *stbl, u32 SampleNumber, u8 bits); +/*for adding fragmented samples*/ +GF_Err stbl_SampleSizeAppend(GF_SampleSizeBox *stsz, u32 data_size); +/*writing of the final chunk info in edit mode*/ +GF_Err stbl_SetChunkAndOffset(GF_SampleTableBox *stbl, u32 sampleNumber, u32 StreamDescIndex, GF_SampleToChunkBox *the_stsc, GF_Box **the_stco, u64 data_offset, Bool forceNewChunk, u32 nb_samp); +/*EDIT LIST functions*/ +GF_EdtsEntry *CreateEditEntry(u64 EditDuration, u64 MediaTime, u32 MediaRate, u8 EditMode); + +GF_Err stbl_SetRedundant(GF_SampleTableBox *stbl, u32 sampleNumber); +GF_Err stbl_AddRedundant(GF_SampleTableBox *stbl, u32 sampleNumber); + +/*REMOVE functions*/ +GF_Err stbl_RemoveDTS(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples, u32 LastAUDefDuration); +GF_Err stbl_RemoveCTS(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples); +GF_Err stbl_RemoveSize(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples); +GF_Err stbl_RemoveChunk(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples); +GF_Err stbl_RemoveRAP(GF_SampleTableBox *stbl, u32 sampleNumber); +GF_Err stbl_RemoveShadow(GF_SampleTableBox *stbl, u32 sampleNumber); +GF_Err stbl_RemovePaddingBits(GF_SampleTableBox *stbl, u32 SampleNumber); +GF_Err stbl_RemoveRedundant(GF_SampleTableBox *stbl, u32 SampleNumber, u32 nb_samples); +GF_Err stbl_RemoveSubSample(GF_SampleTableBox *stbl, u32 SampleNumber); +GF_Err stbl_RemoveSampleGroup(GF_SampleTableBox *stbl, u32 SampleNumber); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +GF_Err gf_isom_close_fragments(GF_ISOFile *movie); +#endif + +GF_Err gf_isom_flush_sidx(GF_ISOFile *movie, u32 sidx_max_size, Bool force_v1); + +#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); + +GF_DefaultSampleGroupDescriptionEntry * 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); + +GF_Err GetNextMediaTime(GF_TrackBox *trak, u64 movieTime, u64 *OutMovieTime); +GF_Err GetPrevMediaTime(GF_TrackBox *trak, u64 movieTime, u64 *OutMovieTime); + +Bool IsHintTrack(GF_TrackBox *trak); +Bool CheckHintFormat(GF_TrackBox *trak, u32 HintType); +u32 GetHintFormat(GF_TrackBox *trak); + +/*locate a box by its type or UUID*/ +GF_ItemListBox *gf_isom_locate_box(GF_List *list, u32 boxType, bin128 UUID); + +GF_Err moov_on_child_box(GF_Box *ptr, GF_Box *a, Bool is_rem); +GF_Err trak_on_child_box(GF_Box *ptr, GF_Box *a, Bool is_rem); +GF_Err mvex_on_child_box(GF_Box *ptr, GF_Box *a, Bool is_rem); +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 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); +GF_Err sdtp_box_read(GF_Box *s, GF_BitStream *bs); +GF_Err dinf_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem); +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); + +/*rewrites avcC based on the given esd - this destroys the esd*/ +GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd); +void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *mdia); +void AVC_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *avc); +void HEVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *hevc, GF_MediaBox *mdia); +void HEVC_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *hevc); +void VP9_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *vp9, GF_MediaBox *mdia); +void VP9_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *vp9); +void AV1_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *av1, GF_MediaBox *mdia); +void AV1_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *av1); +GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, GF_ISOTrackID trackID, u16 *outRefIndex); +Bool gf_isom_cenc_has_saiz_saio_track(GF_SampleTableBox *stbl, u32 scheme_type); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +Bool gf_isom_cenc_has_saiz_saio_traf(GF_TrackFragmentBox *traf, u32 scheme_type); +void gf_isom_cenc_set_saiz_saio(GF_SampleEncryptionBox *senc, GF_SampleTableBox *stbl, GF_TrackFragmentBox *traf, u32 len, Bool saio_32bits, Bool use_mkey); +#endif +GF_Err gf_isom_cenc_merge_saiz_saio(GF_SampleEncryptionBox *senc, GF_SampleTableBox *stbl, u32 sample_number, u64 offset, u32 len); + +void gf_isom_parse_trif_info(const u8 *data, u32 size, u32 *id, u32 *independent, Bool *full_picture, u32 *x, u32 *y, u32 *w, u32 *h); + +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); + + +/*! CENC auxiliary info*/ +typedef struct __cenc_sample_aux_info +{ + u8 *cenc_data; + u32 cenc_data_size; + /*! flag set if sample is clear - it MUST NOT be written to file*/ + u8 isNotProtected; + + /*! key info, for dump only (not valid otherwise)*/ + const u8 *key_info; + u32 key_info_size; +} GF_CENCSampleAuxInfo; + + +/*! destroys a CENC sample auxiliary structure +\param samp_aux_info the target auxiliary buffer +*/ +void gf_isom_cenc_samp_aux_info_del(GF_CENCSampleAuxInfo *samp_aux_info); + + +#ifndef GPAC_DISABLE_ISOM_HINTING + +/* + Hinting stuff +*/ + +/***************************************************** + RTP Data Entries +*****************************************************/ + +typedef struct +{ + u8 sender_current_time_present; + u8 expected_residual_time_present; + u8 session_close_bit; + u8 object_close_bit; + u16 transport_object_identifier; +} GF_LCTheaderTemplate; + +typedef struct +{ + u8 header_extension_type; + u8 content[3]; + u32 data_length; + u8 *data; +} GF_LCTheaderExtension; + +typedef struct +{ + GF_ISOM_BOX + + GF_LCTheaderTemplate info; + u16 header_ext_count; + GF_LCTheaderExtension *headers; + + GF_List *constructors; +} GF_FDpacketBox; + + +typedef struct +{ + GF_ISOM_BOX + + u8 FEC_encoding_ID; + u16 FEC_instance_ID; + u16 source_block_number; + u16 encoding_symbol_ID; +} GF_FECInformationBox; + + +typedef struct +{ + GF_ISOM_BOX + //not registered with child list !! + GF_FECInformationBox *feci; + u32 data_length; + u8 *data; +} GF_ExtraDataBox; + + +#define GF_ISMO_BASE_DTE_ENTRY \ + u8 source; + +typedef struct +{ + GF_ISMO_BASE_DTE_ENTRY +} GF_GenericDTE; + +typedef struct +{ + GF_ISMO_BASE_DTE_ENTRY +} GF_EmptyDTE; + +typedef struct +{ + GF_ISMO_BASE_DTE_ENTRY + u8 dataLength; + char data[14]; +} GF_ImmediateDTE; + +typedef struct +{ + GF_ISMO_BASE_DTE_ENTRY + s8 trackRefIndex; + u32 sampleNumber; + u16 dataLength; + u32 byteOffset; + u16 bytesPerComp; + u16 samplesPerComp; +} GF_SampleDTE; + +typedef struct +{ + GF_ISMO_BASE_DTE_ENTRY + s8 trackRefIndex; + u32 streamDescIndex; + u16 dataLength; + u32 byteOffset; + u32 reserved; +} GF_StreamDescDTE; + +GF_GenericDTE *NewDTE(u8 type); +void DelDTE(GF_GenericDTE *dte); +GF_Err ReadDTE(GF_GenericDTE *dte, GF_BitStream *bs); +GF_Err WriteDTE(GF_GenericDTE *dte, GF_BitStream *bs); +GF_Err OffsetDTE(GF_GenericDTE *dte, u32 offset, u32 HintSampleNumber); + +/***************************************************** + RTP Sample +*****************************************************/ + +/*data cache when reading*/ +typedef struct __tag_hint_data_cache +{ + GF_ISOSample *samp; + GF_TrackBox *trak; + u32 sample_num; +} GF_HintDataCache; + +typedef struct __tag_hint_sample +{ + //for samples deriving from box + GF_ISOM_BOX + + /*contains 4cc of hint track sample entry*/ + u32 hint_subtype; + u16 packetCount; + u16 reserved; + GF_List *packetTable; + u8 *AdditionalData; + u32 dataLength; + /*used internally for hinting*/ + u64 TransmissionTime; + /*for read only, used to store samples fetched while building packets*/ + GF_List *sample_cache; + + //for dump + GF_ISOTrackID trackID; + u32 sampleNumber; + + GF_ExtraDataBox *extra_data; +} GF_HintSample; + +GF_HintSample *gf_isom_hint_sample_new(u32 ProtocolType); +void gf_isom_hint_sample_del(GF_HintSample *ptr); +GF_Err gf_isom_hint_sample_read(GF_HintSample *ptr, GF_BitStream *bs, u32 sampleSize); +GF_Err gf_isom_hint_sample_write(GF_HintSample *ptr, GF_BitStream *bs); +u32 gf_isom_hint_sample_size(GF_HintSample *ptr); + + +/***************************************************** + Hint Packets (generic packet for future protocol support) +*****************************************************/ +#define GF_ISOM_BASE_PACKET \ + u32 hint_subtype, sampleNumber; \ + GF_ISOTrackID trackID;\ + s32 relativeTransTime; + + +typedef struct +{ + GF_ISOM_BASE_PACKET +} GF_HintPacket; + +GF_HintPacket *gf_isom_hint_pck_new(u32 HintType); +void gf_isom_hint_pck_del(GF_HintPacket *ptr); +GF_Err gf_isom_hint_pck_read(GF_HintPacket *ptr, GF_BitStream *bs); +GF_Err gf_isom_hint_pck_write(GF_HintPacket *ptr, GF_BitStream *bs); +u32 gf_isom_hint_pck_size(GF_HintPacket *ptr); +GF_Err gf_isom_hint_pck_offset(GF_HintPacket *ptr, u32 offset, u32 HintSampleNumber); +GF_Err gf_isom_hint_pck_add_dte(GF_HintPacket *ptr, GF_GenericDTE *dte, u8 AtBegin); +/*get the size of the packet AS RECONSTRUCTED BY THE SERVER (without CSRC)*/ +u32 gf_isom_hint_pck_length(GF_HintPacket *ptr); + +/*the RTP packet*/ +typedef struct +{ + GF_ISOM_BASE_PACKET + + /*RTP Header*/ + u8 P_bit; + u8 X_bit; + u8 M_bit; + /*on 7 bits */ + u8 payloadType; + u16 SequenceNumber; + /*Hinting flags*/ + u8 B_bit; + u8 R_bit; + /*ExtraInfos TLVs - not really used */ + GF_List *TLV; + /*DataTable - contains the DTEs...*/ + GF_List *DataTable; +} GF_RTPPacket; + +GF_RTPPacket *gf_isom_hint_rtp_new(); +void gf_isom_hint_rtp_del(GF_RTPPacket *ptr); +GF_Err gf_isom_hint_rtp_read(GF_RTPPacket *ptr, GF_BitStream *bs); +GF_Err gf_isom_hint_rtp_write(GF_RTPPacket *ptr, GF_BitStream *bs); +u32 gf_isom_hint_rtp_size(GF_RTPPacket *ptr); +GF_Err gf_isom_hint_rtp_offset(GF_RTPPacket *ptr, u32 offset, u32 HintSampleNumber); +u32 gf_isom_hint_rtp_length(GF_RTPPacket *ptr); + + +/*the RTP packet*/ +typedef struct +{ + GF_ISOM_BASE_PACKET + + //RTCP report + u8 Version, Padding, Count, PayloadType; + u32 length; + u8 *data; +} GF_RTCPPacket; + +GF_RTCPPacket *gf_isom_hint_rtcp_new(); +void gf_isom_hint_rtcp_del(GF_RTCPPacket *ptr); +GF_Err gf_isom_hint_rtcp_read(GF_RTCPPacket *ptr, GF_BitStream *bs); +GF_Err gf_isom_hint_rtcp_write(GF_RTCPPacket *ptr, GF_BitStream *bs); +u32 gf_isom_hint_rtcp_size(GF_RTCPPacket *ptr); +u32 gf_isom_hint_rtcp_length(GF_RTCPPacket *ptr); + + +#endif + + +struct _3gpp_text_sample +{ + char *text; + u32 len; + + GF_TextStyleBox *styles; + /*at most one of these*/ + GF_TextHighlightColorBox *highlight_color; + GF_TextScrollDelayBox *scroll_delay; + GF_TextBoxBox *box; + GF_TextWrapBox *wrap; + + GF_List *others; + GF_TextKaraokeBox *cur_karaoke; +}; + +GF_TextSample *gf_isom_parse_text_sample(GF_BitStream *bs); + +struct _generic_subtitle_sample +{ + char *text; + u32 len; +}; +GF_GenericSubtitleSample *gf_isom_parse_generic_subtitle_sample(GF_BitStream *bs); +GF_GenericSubtitleSample *gf_isom_parse_generic_subtitle_sample_from_data(u8 *data, u32 dataLength); + + +/*do not throw fatal errors if boxes are duplicated, just warn and remove extra ones*/ +#define ERROR_ON_DUPLICATED_BOX(__abox, __parent) { \ + char __ptype[GF_4CC_MSIZE];\ + strcpy(__ptype, gf_4cc_to_str(__parent->type) );\ + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] extra box %s found in %s, deleting\n", gf_4cc_to_str(__abox->type), __ptype)); \ + gf_isom_box_del_parent(& (__parent->child_boxes), __abox);\ + return GF_OK;\ + } + + +#ifndef GPAC_DISABLE_VTT + +GF_ISOSample *gf_isom_webvtt_to_sample(void *samp); + +typedef struct +{ + GF_ISOM_BOX + char *string; +} GF_StringBox; + +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + GF_StringBox *config; +} GF_WebVTTSampleEntryBox; + +GF_WebVTTSampleEntryBox *gf_webvtt_isom_get_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex); + +#endif /* GPAC_DISABLE_VTT */ + +//exported for sgpd comparison in traf merge +void sgpd_write_entry(u32 grouping_type, void *entry, GF_BitStream *bs); +Bool gf_isom_box_equal(GF_Box *a, GF_Box *b); +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); + +void gf_isom_registry_disable(u32 boxCode, Bool disable); + +/*Apple extensions*/ +GF_Box *gf_isom_get_meta_extensions(GF_ISOFile *mov, Bool for_xtra); + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Box *gf_isom_create_meta_extensions(GF_ISOFile *mov, Bool for_xtra); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +#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); +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 + +GF_Box *boxstring_new_with_data(u32 type, const char *string, GF_List **parent); + +GF_Err gf_isom_read_null_terminated_string(GF_Box *s, GF_BitStream *bs, u64 size, char **out_str); + +GF_Err MergeTrack(GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_MovieFragmentBox *moof, u64 moof_offset, s32 compresed_diff, u64 *cumulated_offset); + + +#endif //GPAC_DISABLE_ISOM + +#ifdef __cplusplus +} +#endif + +#endif //_GF_ISOMEDIA_DEV_H_ + diff --git a/include/gpac/internal/laser_dev.h b/include/gpac/internal/laser_dev.h new file mode 100644 index 0000000..ea78add --- /dev/null +++ b/include/gpac/internal/laser_dev.h @@ -0,0 +1,344 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / LASeR codec 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_LASER_DEV_H_ +#define _GF_LASER_DEV_H_ + +#include <gpac/laser.h> +#include <gpac/maths.h> + +#ifndef GPAC_DISABLE_LASER + +/*per_stream config support*/ +typedef struct +{ + GF_LASERConfig cfg; + u16 ESID; +} LASeRStreamInfo; + +typedef struct +{ + /*colors can be encoded on up to 16 bits per comp*/ + u16 r, g, b; +} LSRCol; + +struct __tag_laser_codec +{ + GF_BitStream *bs; + GF_SceneGraph *sg; + GF_Err last_error; + + /*all attached streams*/ + GF_List *streamInfo; + + LASeRStreamInfo *info; + Fixed res_factor/*2^-coord_res*/; + /*duplicated from config*/ + u8 scale_bits; + u8 coord_bits; + u16 time_resolution; + u16 color_scale; + + LSRCol *col_table; + u32 nb_cols; + /*computed dynamically*/ + u32 colorIndexBits; + GF_List *font_table; + u32 fontIndexBits; + + u32 privateData_id_index, privateTag_index; + + /*decoder only*/ + Double (*GetSceneTime)(void *cbk); + void *cbk; + + /*sameElement coding*/ + SVG_Element *prev_g; + SVG_Element *prev_line; + SVG_Element *prev_path; + SVG_Element *prev_polygon; + SVG_Element *prev_rect; + SVG_Element *prev_text; + SVG_Element *prev_use; + GF_Node *current_root; + + /*0: normal playback, store script content + 1: memory decoding of scene, decompress script into commands + */ + Bool memory_dec; + + Bool has_conditionnals; + + GF_List *deferred_hrefs; + GF_List *deferred_anims; + GF_List *deferred_listeners; + + char *cache_dir, *service_name; + GF_List *unresolved_commands; +}; + +s32 gf_lsr_anim_type_from_attribute(u32 tag); +s32 gf_lsr_anim_type_to_attribute(u32 tag); +s32 gf_lsr_rare_type_from_attribute(u32 tag); +s32 gf_lsr_rare_type_to_attribute(u32 tag); +u32 gf_lsr_same_rare(SVGAllAttributes *elt_atts, SVGAllAttributes *base_atts); + + +/*transform*/ +#define RARE_TRANSFORM 47 + +enum +{ + LSR_EVT_abort = 0, + LSR_EVT_accessKey = 1, + LSR_EVT_activate = 2, + LSR_EVT_activatedEvent = 3, + LSR_EVT_beginEvent = 4, + LSR_EVT_click = 5, + LSR_EVT_deactivatedEvent = 6, + LSR_EVT_endEvent = 7, + LSR_EVT_error = 8, + LSR_EVT_executionTime = 9, + LSR_EVT_focusin = 10, + LSR_EVT_focusout = 11, + LSR_EVT_keydown = 12, + LSR_EVT_keyup = 13, + LSR_EVT_load = 14, + LSR_EVT_longAccessKey = 15, + LSR_EVT_mousedown = 16, + LSR_EVT_mousemove = 17, + LSR_EVT_mouseout = 18, + LSR_EVT_mouseover = 19, + LSR_EVT_mouseup = 20, + LSR_EVT_pause = 21, + LSR_EVT_pausedEvent = 22, + LSR_EVT_play = 23, + LSR_EVT_repeatEvent = 24, + LSR_EVT_repeatKey = 25, + LSR_EVT_resize = 26, + LSR_EVT_resumedEvent = 27, + LSR_EVT_scroll = 28, + LSR_EVT_shortAccessKey = 29, + LSR_EVT_textinput = 30, + LSR_EVT_unload = 31, + LSR_EVT_zoom = 32 +}; + +u32 dom_to_lsr_key(u32 dom_k); + + +#define LSR_UPDATE_TYPE_ROTATE 76 +#define LSR_UPDATE_TYPE_SCALE 79 +#define LSR_UPDATE_TYPE_SVG_HEIGHT 94 +#define LSR_UPDATE_TYPE_SVG_WIDTH 95 +#define LSR_UPDATE_TYPE_TEXT_CONTENT 107 +#define LSR_UPDATE_TYPE_TRANSFORM 108 +#define LSR_UPDATE_TYPE_TRANSLATION 110 + + +/*LASeR commands code*/ +enum +{ + LSR_UPDATE_ADD = 0, + LSR_UPDATE_CLEAN, + LSR_UPDATE_DELETE, + LSR_UPDATE_INSERT, + LSR_UPDATE_NEW_SCENE, + LSR_UPDATE_REFRESH_SCENE, + LSR_UPDATE_REPLACE, + LSR_UPDATE_RESTORE, + LSR_UPDATE_SAVE, + LSR_UPDATE_SEND_EVENT, + LSR_UPDATE_EXTEND, + LSR_UPDATE_TEXT_CONTENT +}; + +/*Code point Path code*/ +enum +{ + LSR_PATH_COM_C = 0, + LSR_PATH_COM_H, + LSR_PATH_COM_L, + LSR_PATH_COM_M, + LSR_PATH_COM_Q, + LSR_PATH_COM_S, + LSR_PATH_COM_T, + LSR_PATH_COM_V, + LSR_PATH_COM_Z, + LSR_PATH_COM_c, + LSR_PATH_COM_h, + LSR_PATH_COM_l, + LSR_PATH_COM_m, + LSR_PATH_COM_q, + LSR_PATH_COM_s, + LSR_PATH_COM_t, + LSR_PATH_COM_v, + LSR_PATH_COM_z +}; + + + + +enum +{ + LSR_SCENE_CONTENT_MODEL_a = 0, + LSR_SCENE_CONTENT_MODEL_animate, + LSR_SCENE_CONTENT_MODEL_animateColor, + LSR_SCENE_CONTENT_MODEL_animateMotion, + LSR_SCENE_CONTENT_MODEL_animateTransform, + LSR_SCENE_CONTENT_MODEL_audio, + LSR_SCENE_CONTENT_MODEL_circle, + LSR_SCENE_CONTENT_MODEL_defs, + LSR_SCENE_CONTENT_MODEL_desc, + LSR_SCENE_CONTENT_MODEL_ellipse, + LSR_SCENE_CONTENT_MODEL_foreignObject, + LSR_SCENE_CONTENT_MODEL_g, + LSR_SCENE_CONTENT_MODEL_image, + LSR_SCENE_CONTENT_MODEL_line, + LSR_SCENE_CONTENT_MODEL_linearGradient, + LSR_SCENE_CONTENT_MODEL_metadata, + LSR_SCENE_CONTENT_MODEL_mpath, + LSR_SCENE_CONTENT_MODEL_path, + LSR_SCENE_CONTENT_MODEL_polygon, + LSR_SCENE_CONTENT_MODEL_polyline, + LSR_SCENE_CONTENT_MODEL_radialGradient, + LSR_SCENE_CONTENT_MODEL_rect, + LSR_SCENE_CONTENT_MODEL_sameg, + LSR_SCENE_CONTENT_MODEL_sameline, + LSR_SCENE_CONTENT_MODEL_samepath, + LSR_SCENE_CONTENT_MODEL_samepathfill, + LSR_SCENE_CONTENT_MODEL_samepolygon, + LSR_SCENE_CONTENT_MODEL_samepolygonfill, + LSR_SCENE_CONTENT_MODEL_samepolygonstroke, + LSR_SCENE_CONTENT_MODEL_samepolyline, + LSR_SCENE_CONTENT_MODEL_samepolylinefill, + LSR_SCENE_CONTENT_MODEL_samepolylinestroke, + LSR_SCENE_CONTENT_MODEL_samerect, + LSR_SCENE_CONTENT_MODEL_samerectfill, + LSR_SCENE_CONTENT_MODEL_sametext, + LSR_SCENE_CONTENT_MODEL_sametextfill, + LSR_SCENE_CONTENT_MODEL_sameuse, + LSR_SCENE_CONTENT_MODEL_script, + LSR_SCENE_CONTENT_MODEL_set, + LSR_SCENE_CONTENT_MODEL_stop, + LSR_SCENE_CONTENT_MODEL_switch, + LSR_SCENE_CONTENT_MODEL_text, + LSR_SCENE_CONTENT_MODEL_title, + LSR_SCENE_CONTENT_MODEL_tspan, + LSR_SCENE_CONTENT_MODEL_use, + LSR_SCENE_CONTENT_MODEL_video, + LSR_SCENE_CONTENT_MODEL_listener, + LSR_SCENE_CONTENT_MODEL_conditional, + LSR_SCENE_CONTENT_MODEL_cursorManager, + LSR_SCENE_CONTENT_MODEL_element_any, + LSR_SCENE_CONTENT_MODEL_privateContainer, + LSR_SCENE_CONTENT_MODEL_rectClip, + LSR_SCENE_CONTENT_MODEL_selector, + LSR_SCENE_CONTENT_MODEL_simpleLayout, + LSR_SCENE_CONTENT_MODEL_textContent, + LSR_SCENE_CONTENT_MODEL_extension, +}; + +enum +{ + LSR_UPDATE_CONTENT_MODEL_a = 0, + LSR_UPDATE_CONTENT_MODEL_animate, + LSR_UPDATE_CONTENT_MODEL_animateColor, + LSR_UPDATE_CONTENT_MODEL_animateMotion, + LSR_UPDATE_CONTENT_MODEL_animateTransform, + LSR_UPDATE_CONTENT_MODEL_audio, + LSR_UPDATE_CONTENT_MODEL_circle, + LSR_UPDATE_CONTENT_MODEL_defs, + LSR_UPDATE_CONTENT_MODEL_desc, + LSR_UPDATE_CONTENT_MODEL_ellipse, + LSR_UPDATE_CONTENT_MODEL_foreignObject, + LSR_UPDATE_CONTENT_MODEL_g, + LSR_UPDATE_CONTENT_MODEL_image, + LSR_UPDATE_CONTENT_MODEL_line, + LSR_UPDATE_CONTENT_MODEL_linearGradient, + LSR_UPDATE_CONTENT_MODEL_metadata, + LSR_UPDATE_CONTENT_MODEL_mpath, + LSR_UPDATE_CONTENT_MODEL_path, + LSR_UPDATE_CONTENT_MODEL_polygon, + LSR_UPDATE_CONTENT_MODEL_polyline, + LSR_UPDATE_CONTENT_MODEL_radialGradient, + LSR_UPDATE_CONTENT_MODEL_rect, + LSR_UPDATE_CONTENT_MODEL_script, + LSR_UPDATE_CONTENT_MODEL_set, + LSR_UPDATE_CONTENT_MODEL_stop, + LSR_UPDATE_CONTENT_MODEL_svg, + LSR_UPDATE_CONTENT_MODEL_switch, + LSR_UPDATE_CONTENT_MODEL_text, + LSR_UPDATE_CONTENT_MODEL_title, + LSR_UPDATE_CONTENT_MODEL_tspan, + LSR_UPDATE_CONTENT_MODEL_use, + LSR_UPDATE_CONTENT_MODEL_video, + LSR_UPDATE_CONTENT_MODEL_listener, +}; + +enum +{ + LSR_UPDATE_CONTENT_MODEL2_conditional = 0, + LSR_UPDATE_CONTENT_MODEL2_cursorManager, + LSR_UPDATE_CONTENT_MODEL2_extend, + LSR_UPDATE_CONTENT_MODEL2_private, + LSR_UPDATE_CONTENT_MODEL2_rectClip, + LSR_UPDATE_CONTENT_MODEL2_selector, + LSR_UPDATE_CONTENT_MODEL2_simpleLayout, +}; + +/*just to remember them, not implemented yet*/ +enum +{ + LSR_SVG12_EXT_animation = 0, + LSR_SVG12_EXT_discard, + LSR_SVG12_EXT_font, + LSR_SVG12_EXT_font_face, + LSR_SVG12_EXT_font_face_src, + LSR_SVG12_EXT_font_face_uri, + LSR_SVG12_EXT_glyph, + LSR_SVG12_EXT_handler, + LSR_SVG12_EXT_hkern, + LSR_SVG12_EXT_missingGlyph, + LSR_SVG12_EXT_prefetch, + LSR_SVG12_EXT_solidColor, + LSR_SVG12_EXT_tBreak, + LSR_SVG12_EXT_textArea, +}; + +/*just to remember them, not implemented yet*/ +enum +{ + LSR_AMD1_EXT_animateScroll = 0, + LSR_AMD1_EXT_setScroll, + LSR_AMD1_EXT_streamSource, + LSR_AMD1_EXT_updateSource, +}; + +#endif /*GPAC_DISABLE_LASER*/ + +#endif + diff --git a/include/gpac/internal/m3u8.h b/include/gpac/internal/m3u8.h new file mode 100644 index 0000000..93d5a52 --- /dev/null +++ b/include/gpac/internal/m3u8.h @@ -0,0 +1,144 @@ +/** + * GPAC - Multimedia Framework C SDK + * + * Authors: Pierre Souchay - Jean Le Feuvre - Romain Bouqueau + * Copyright (c) Telecom ParisTech 2010-2021 + * All rights reserved + * + * This file is part of GPAC + * + * 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 M3U8_PLAYLIST_H +#define M3U8_PLAYLIST_H + +#include <gpac/tools.h> +#include <gpac/list.h> + + +#define M3U8_UNKNOWN_MIME_TYPE "unknown" + +/** + * Basic Media structure + */ +typedef struct s_media { + int i; //unused: C requires that a struct or union has at least one member +} Media; + +/** + * The playlist contains a list of elements to play + */ +struct s_playList { + int current_media_seq; + int media_seq_min; + int media_seq_max; + int discontinuity; + double target_duration; + double computed_duration; + Bool is_ended; + GF_List *elements; /*PlaylistElement*/ +}; +typedef struct s_playList Playlist; + +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_MediaType { + MEDIA_TYPE_UNKNOWN = 0, + MEDIA_TYPE_AUDIO = 0x100000, + MEDIA_TYPE_VIDEO = 0x200000, + MEDIA_TYPE_SUBTITLES = 0x300000, + MEDIA_TYPE_CLOSED_CAPTIONS = 0x400000 +} MediaType; + +/** + * The Structure containing the playlist element + */ +struct s_playlistElement { + MediaType media_type; + double duration_info; + u64 byte_range_start, byte_range_end; + int bandwidth, width, height, low_lat_chunk, independent_chunk; + char *title; + char *codecs; + char *language; + char *audio_group; + char *video_group; + char *url; + char *init_segment_url; + u64 init_byte_range_start, init_byte_range_end; + //informative UTC start time + u64 utc_start_time; + u32 discontinuity; + PlaylistElementDRMMethod drm_method; + char *key_uri; + bin128 key_iv; + GF_Err load_error; + PlaylistElementType element_type; + union { + Playlist playlist; + Media media; + } element; +}; +typedef struct s_playlistElement PlaylistElement; + +struct s_stream { + int stream_id; //may be a real PROGRAM_ID, or a converted GROUP_ID with GROUP_ID_TO_PROGRAM_ID + GF_List *variants; /*PlaylistElement*/ + double computed_duration; +}; +typedef struct s_stream Stream; + +/** + * The root playlist, can contains several PlaylistElements structures + */ +struct s_masterPlaylist { + GF_List *streams; /*Stream*/ + int current_stream; + Bool playlist_needs_refresh; + Bool independent_segments, low_latency; +}; +typedef struct s_masterPlaylist MasterPlaylist; + + +/** + * Parse the given m3u8 playlist file +\param file The file from cache to parse +\param playlist The playlist to fill. If argument is null, and file is valid, playlist will be allocated +\param baseURL The base URL of the playlist +\param GF_OK if playlist valid + */ +GF_Err gf_m3u8_parse_master_playlist(const char *file, MasterPlaylist **playlist, const char *baseURL); + +/** + * Parse the given playlist file as a subplaylist of an existing playlist +\param file The file from cache to parse +\param playlist The playlist to fill. +\param baseURL base URL of the playlist +\param in_program in which the playlist is parsed +\param sub_playlist existing subplaylist element in the playlist in which the playlist is parsed +\param is_master set to true to indicate if this is the root playlist +\param GF_OK if playlist valid + */ +GF_Err gf_m3u8_parse_sub_playlist(const char *file, MasterPlaylist **playlist, const char *baseURL, Stream *in_program, PlaylistElement *sub_playlist, Bool is_master); + +/** + * Deletes the given MasterPlaylist and all of its sub elements + */ +GF_Err gf_m3u8_master_playlist_del(MasterPlaylist **playlist); + +#endif /* M3U8_PLAYLIST_H */ diff --git a/include/gpac/internal/media_dev.h b/include/gpac/internal/media_dev.h new file mode 100644 index 0000000..f722dd9 --- /dev/null +++ b/include/gpac/internal/media_dev.h @@ -0,0 +1,1135 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * 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_MEDIA_DEV_H_ +#define _GF_MEDIA_DEV_H_ + +#include <gpac/media_tools.h> +#include <gpac/mpeg4_odf.h> + +#ifndef GPAC_DISABLE_STREAMING +#include <gpac/ietf.h> +#endif + +#ifndef GPAC_DISABLE_ISOM +void gf_media_get_sample_average_infos(GF_ISOFile *file, u32 Track, u32 *avgSize, u32 *MaxSize, u32 *TimeDelta, u32 *maxCTSDelta, u32 *const_duration, u32 *bandwidth); +#endif + + +#ifndef GPAC_DISABLE_MEDIA_IMPORT +GF_Err gf_import_message(GF_MediaImporter *import, GF_Err e, char *format, ...); +#endif /*GPAC_DISABLE_MEDIA_IMPORT*/ + + +typedef struct +{ + Bool override; + u16 colour_primaries, transfer_characteristics, matrix_coefficients; + Bool full_range; +} COLR; + + +#ifndef GPAC_DISABLE_AV_PARSERS + +u32 gf_latm_get_value(GF_BitStream *bs); + +#define GF_SVC_SSPS_ID_SHIFT 16 + +/*returns 0 if not a start code, or size of start code (3 or 4 bytes). If start code, bitstream +is positionned AFTER start code*/ +u32 gf_media_nalu_is_start_code(GF_BitStream *bs); + +/*returns size of chunk between current and next startcode (excluding startcode sizes), 0 if no more startcodes (eos)*/ +u32 gf_media_nalu_next_start_code_bs(GF_BitStream *bs); + +/*return nb bytes from current data until the next start code and set the size of the next start code (3 or 4 bytes) +returns data_len if no startcode found and sets sc_size to 0 (last nal in payload)*/ +u32 gf_media_nalu_next_start_code(const u8 *data, u32 data_len, u32 *sc_size); + +u32 gf_media_nalu_emulation_bytes_remove_count(const u8 *buffer, u32 nal_size); +u32 gf_media_nalu_remove_emulation_bytes(const u8 *buffer_src, u8 *buffer_dst, u32 nal_size); + +u32 gf_bs_read_ue(GF_BitStream *bs); +s32 gf_bs_read_se(GF_BitStream *bs); +void gf_bs_write_ue(GF_BitStream *bs, u32 num); +void gf_bs_write_se(GF_BitStream *bs, s32 num); + + +enum +{ + /*SPS has been parsed*/ + AVC_SPS_PARSED = 1, + /*SPS has been declared to the upper layer*/ + AVC_SPS_DECLARED = 1<<1, + /*SUB-SPS has been parsed*/ + AVC_SUBSPS_PARSED = 1<<2, + /*SUB-SPS has been declared to the upper layer*/ + AVC_SUBSPS_DECLARED = 1<<3, + /*SPS extension has been parsed*/ + AVC_SPS_EXT_DECLARED = 1<<4, +}; + +typedef struct +{ + u8 cpb_removal_delay_length_minus1; + u8 dpb_output_delay_length_minus1; + u8 time_offset_length; + /*to be eventually completed by other hrd members*/ +} AVC_HRD; + +typedef struct +{ + s32 timing_info_present_flag; + u32 num_units_in_tick; + u32 time_scale; + s32 fixed_frame_rate_flag; + + Bool aspect_ratio_info_present_flag; + u32 par_num, par_den; + + Bool overscan_info_present_flag; + Bool video_signal_type_present_flag; + u8 video_format; + Bool video_full_range_flag; + + Bool colour_description_present_flag; + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + Bool nal_hrd_parameters_present_flag; + Bool vcl_hrd_parameters_present_flag; + Bool low_delay_hrd_flag; + AVC_HRD hrd; + + Bool pic_struct_present_flag; + + /*to be eventually completed by other vui members*/ +} AVC_VUI; + +typedef struct +{ + u32 left; + u32 right; + u32 top; + u32 bottom; + +} AVC_CROP; + + +typedef struct +{ + s32 profile_idc; + s32 level_idc; + s32 prof_compat; + s32 log2_max_frame_num; + u32 poc_type, poc_cycle_length; + s32 log2_max_poc_lsb; + s32 delta_pic_order_always_zero_flag; + s32 offset_for_non_ref_pic, offset_for_top_to_bottom_field; + Bool frame_mbs_only_flag; + Bool mb_adaptive_frame_field_flag; + u32 max_num_ref_frames; + Bool gaps_in_frame_num_value_allowed_flag; + u8 chroma_format; + u8 luma_bit_depth_m8; + u8 chroma_bit_depth_m8; + u32 ChromaArrayType; + + s16 offset_for_ref_frame[256]; + + u32 width, height; + + Bool vui_parameters_present_flag; + AVC_VUI vui; + AVC_CROP crop; + + /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 sent*/ + u32 state; + + u32 sbusps_crc; + + /*for SVC stats during import*/ + u32 nb_ei, nb_ep, nb_eb; +} AVC_SPS; + +typedef struct +{ + s32 id; /* used to compare pps when storing SVC PSS */ + s32 sps_id; + Bool entropy_coding_mode_flag; + s32 pic_order_present; /* pic_order_present_flag*/ + s32 redundant_pic_cnt_present; /* redundant_pic_cnt_present_flag */ + u32 slice_group_count; /* num_slice_groups_minus1 + 1*/ + u32 mb_slice_group_map_type; + u32 pic_size_in_map_units_minus1; + u32 slice_group_change_rate_minus1; + /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 sent*/ + u32 status; + Bool weighted_pred_flag; + u8 weighted_bipred_idc; + Bool deblocking_filter_control_present_flag; + u32 num_ref_idx_l0_default_active_minus1, num_ref_idx_l1_default_active_minus1; +} AVC_PPS; + +typedef struct +{ + s32 idr_pic_flag; + u8 temporal_id, priority_id, dependency_id, quality_id; +} SVC_NALUHeader; + +typedef struct +{ + u8 nal_ref_idc, nal_unit_type, field_pic_flag, bottom_field_flag; + u32 frame_num, idr_pic_id, poc_lsb, slice_type; + s32 delta_poc_bottom; + s32 delta_poc[2]; + s32 redundant_pic_cnt; + + s32 poc; + u32 poc_msb, poc_msb_prev, poc_lsb_prev, frame_num_prev; + s32 frame_num_offset, frame_num_offset_prev; + + AVC_SPS *sps; + AVC_PPS *pps; + SVC_NALUHeader svc_nalhdr; +} AVCSliceInfo; + + +typedef struct +{ + u32 frame_cnt; + u8 exact_match_flag; + u8 broken_link_flag; + u8 changing_slice_group_idc; + u8 valid; +} AVCSeiRecoveryPoint; + +typedef struct +{ + u8 pic_struct; + /*to be eventually completed by other pic_timing members*/ +} AVCSeiPicTiming; + +typedef struct +{ + Bool rpu_flag; +} AVCSeiItuTT35DolbyVision; + +typedef struct +{ + AVCSeiRecoveryPoint recovery_point; + AVCSeiPicTiming pic_timing; + AVCSeiItuTT35DolbyVision dovi; + /*to be eventually completed by other sei*/ +} AVCSei; + +typedef struct +{ + AVC_SPS sps[32]; /* 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*/ + + AVC_PPS pps[255]; + + AVCSliceInfo s_info; + AVCSei sei; + + Bool is_svc; + u8 last_nal_type_parsed; + s8 last_ps_idx; +} AVCState; + +typedef struct +{ + u32 NALUnitHeader; + u8 track_ref_index; + s8 sample_offset; + u32 data_offset; + u32 data_length; +} SVC_Extractor; + + +/*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); +/*return pps ID or -1 if error*/ +s32 gf_avc_read_pps(const u8 *pps_data, u32 pps_size, AVCState *avc); +s32 gf_avc_read_pps_bs(GF_BitStream *bs, AVCState *avc); + +/*is slice containing intra MB only*/ +Bool gf_avc_slice_is_intra(AVCState *avc); +/*parses NALU, updates avc state and returns: + 1 if NALU part of new frame + 0 if NALU part of prev frame + -1 if bitstream error +*/ +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); + +#ifndef GPAC_DISABLE_ISOM + +GF_Err gf_media_get_color_info(GF_ISOFile *file, u32 track, u32 sampleDescriptionIndex, u32 *colour_type, u16 *colour_primaries, u16 *transfer_characteristics, u16 *matrix_coefficients, Bool *full_range_flag); + +/*! VUI modification parameters*/ +typedef struct +{ + /*! if true, the structure members will be updated to the actual values written or present in bitstream. If still -1, info was not written in bitstream*/ + Bool update; + /*! pixel aspect ratio num + a value of 0 in ar_num or ar_den removes PAR + a value of -1 in ar_num or ar_den keeps PAR from bitstream + positive values change PAR + */ + s32 ar_num; + /*! pixel aspect ratio den*/ + s32 ar_den; + + //if set all video info is removed + Bool remove_video_info; + //new fullrange, -1 to use info from bitstream + s32 fullrange; + //new vidformat flag, -1 to use info from bitstream + s32 video_format; + //new color primaries flag, -1 to use info from bitstream + s32 color_prim; + //new color transfer characteristics flag, -1 to use info from bitstream + s32 color_tfc; + //new color matrix flag, -1 to use info from bitstream + s32 color_matrix; +} GF_VUIInfo; + +GF_Err gf_avc_change_vui(GF_AVCConfig *avcc, GF_VUIInfo *vui_info); + +//shortucts for the above for API compatibility +GF_Err gf_avc_change_par(GF_AVCConfig *avcc, s32 ar_n, s32 ar_d); +GF_Err gf_avc_change_color(GF_AVCConfig *avcc, s32 fullrange, s32 vidformat, s32 colorprim, s32 transfer, s32 colmatrix); + +GF_Err gf_hevc_change_vui(GF_HEVCConfig *hvcc, GF_VUIInfo *vui); +//shortcut for the above for API compatibility +GF_Err gf_hevc_change_par(GF_HEVCConfig *hvcc, s32 ar_n, s32 ar_d); +GF_Err gf_hevc_change_color(GF_HEVCConfig *hvcc, s32 fullrange, s32 vidformat, s32 colorprim, s32 transfer, s32 colmatrix); +#endif + +GF_Err gf_vvc_change_vui(GF_VVCConfig *cfg, GF_VUIInfo *vui); +//shortcut for the above for API compatibility +GF_Err gf_vvc_change_par(GF_VVCConfig *cfg, s32 ar_n, s32 ar_d); +GF_Err gf_vvc_change_color(GF_VVCConfig *cfg, s32 fullrange, s32 vidformat, s32 colorprim, s32 transfer, s32 colmatrix); + + +typedef struct +{ + Bool profile_present_flag, level_present_flag, tier_flag; + u8 profile_space; + u8 profile_idc; + u32 profile_compatibility_flag; + u8 level_idc; +} HEVC_SublayerPTL; + +typedef struct +{ + u8 profile_space, tier_flag, profile_idc, level_idc; + u32 profile_compatibility_flag; + Bool general_progressive_source_flag; + Bool general_interlaced_source_flag; + Bool general_non_packed_constraint_flag; + Bool general_frame_only_constraint_flag; + u64 general_reserved_44bits; + + HEVC_SublayerPTL sub_ptl[8]; +} HEVC_ProfileTierLevel; + +typedef struct +{ + u32 num_negative_pics; + u32 num_positive_pics; + s32 delta_poc[16]; +} HEVC_ReferencePictureSets; + +typedef struct +{ + s32 id, vps_id; + /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 stored*/ + u32 state; + u32 crc; + u32 width, height; + + HEVC_ProfileTierLevel ptl; + + u8 chroma_format_idc; + Bool cw_flag ; + u32 cw_left, cw_right, cw_top, cw_bottom; + u8 bit_depth_luma; + u8 bit_depth_chroma; + u8 log2_max_pic_order_cnt_lsb; + Bool separate_colour_plane_flag; + + u32 max_CU_width, max_CU_height, max_CU_depth; + u32 bitsSliceSegmentAddress; + + u32 num_short_term_ref_pic_sets, num_long_term_ref_pic_sps; + HEVC_ReferencePictureSets rps[64]; + + + Bool aspect_ratio_info_present_flag, long_term_ref_pics_present_flag, temporal_mvp_enable_flag, sample_adaptive_offset_enabled_flag; + u8 sar_idc; + u16 sar_width, sar_height; + Bool has_timing_info; + u32 num_units_in_tick, time_scale; + Bool poc_proportional_to_timing_flag; + u32 num_ticks_poc_diff_one_minus1; + + Bool video_full_range_flag; + Bool colour_description_present_flag; + u8 colour_primaries, transfer_characteristic, matrix_coeffs; + u32 rep_format_idx; + + u8 sps_ext_or_max_sub_layers_minus1, max_sub_layers_minus1, update_rep_format_flag, sub_layer_ordering_info_present_flag, scaling_list_enable_flag, infer_scaling_list_flag, scaling_list_ref_layer_id, scaling_list_data_present_flag, asymmetric_motion_partitions_enabled_flag, pcm_enabled_flag, strong_intra_smoothing_enable_flag, vui_parameters_present_flag; + u32 log2_diff_max_min_luma_coding_block_size; + u32 log2_min_transform_block_size, log2_min_luma_coding_block_size, log2_max_transform_block_size; + u32 max_transform_hierarchy_depth_inter, max_transform_hierarchy_depth_intra; + + u8 pcm_sample_bit_depth_luma_minus1, pcm_sample_bit_depth_chroma_minus1, pcm_loop_filter_disable_flag; + u32 log2_min_pcm_luma_coding_block_size_minus3, log2_diff_max_min_pcm_luma_coding_block_size; + u8 overscan_info_present, overscan_appropriate, video_signal_type_present_flag, video_format; + + u8 chroma_loc_info_present_flag; + u32 chroma_sample_loc_type_top_field, chroma_sample_loc_type_bottom_field; + + u8 neutra_chroma_indication_flag, field_seq_flag, frame_field_info_present_flag; + u8 default_display_window_flag; + u32 left_offset, right_offset, top_offset, bottom_offset; + u8 hrd_parameters_present_flag; +} HEVC_SPS; + +typedef struct +{ + s32 id; + u32 sps_id; + /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 stored*/ + u32 state; + u32 crc; + + Bool dependent_slice_segments_enabled_flag, tiles_enabled_flag, uniform_spacing_flag; + u32 num_extra_slice_header_bits, num_ref_idx_l0_default_active, num_ref_idx_l1_default_active; + Bool slice_segment_header_extension_present_flag, output_flag_present_flag, lists_modification_present_flag, cabac_init_present_flag; + Bool weighted_pred_flag, weighted_bipred_flag, slice_chroma_qp_offsets_present_flag, deblocking_filter_override_enabled_flag, loop_filter_across_slices_enabled_flag, entropy_coding_sync_enabled_flag; + Bool loop_filter_across_tiles_enabled_flag; + s32 pic_init_qp_minus26; + u32 num_tile_columns, num_tile_rows; + u32 column_width[22], row_height[20]; + + Bool sign_data_hiding_flag, constrained_intra_pred_flag, transform_skip_enabled_flag, cu_qp_delta_enabled_flag, transquant_bypass_enable_flag; + u32 diff_cu_qp_delta_depth, pic_cb_qp_offset, pic_cr_qp_offset; + + 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; + +} HEVC_PPS; + +typedef struct RepFormat +{ + u32 chroma_format_idc; + u32 pic_width_luma_samples; + u32 pic_height_luma_samples; + u32 bit_depth_luma; + u32 bit_depth_chroma; + u8 separate_colour_plane_flag; +} HEVC_RepFormat; + +typedef struct +{ + u16 avg_bit_rate, max_bit_rate, avg_pic_rate; + u8 constant_pic_rate_idc; +} HEVC_RateInfo; + + +#define MAX_LHVC_LAYERS 4 +#define MAX_NUM_LAYER_SETS 1024 +typedef struct +{ + s32 id; + /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 stored*/ + u32 state; + s32 bit_pos_vps_extensions; + u32 crc; + Bool vps_extension_found; + u32 max_layers, max_sub_layers, max_layer_id, num_layer_sets; + Bool temporal_id_nesting; + HEVC_ProfileTierLevel ptl; + + HEVC_SublayerPTL sub_ptl[8]; + //this is not parsed yet (in VPS VUI) + HEVC_RateInfo rates[8]; + + + u32 scalability_mask[16]; + u32 dimension_id[MAX_LHVC_LAYERS][16]; + u32 layer_id_in_nuh[MAX_LHVC_LAYERS]; + u32 layer_id_in_vps[MAX_LHVC_LAYERS]; + + u8 num_profile_tier_level, num_output_layer_sets; + u32 profile_level_tier_idx[MAX_LHVC_LAYERS]; + HEVC_ProfileTierLevel ext_ptl[MAX_LHVC_LAYERS]; + + u32 num_rep_formats; + HEVC_RepFormat rep_formats[16]; + u32 rep_format_idx[16]; + Bool base_layer_internal_flag, base_layer_available_flag; + u8 num_layers_in_id_list[MAX_NUM_LAYER_SETS]; + u8 direct_dependency_flag[MAX_LHVC_LAYERS][MAX_LHVC_LAYERS]; + Bool output_layer_flag[MAX_LHVC_LAYERS][MAX_LHVC_LAYERS]; + u8 profile_tier_level_idx[MAX_LHVC_LAYERS][MAX_LHVC_LAYERS]; + Bool alt_output_layer_flag[MAX_LHVC_LAYERS]; + u8 num_necessary_layers[MAX_LHVC_LAYERS]; + Bool necessary_layers_flag[MAX_LHVC_LAYERS][MAX_LHVC_LAYERS]; + u8 LayerSetLayerIdList[MAX_LHVC_LAYERS][MAX_LHVC_LAYERS]; + u8 LayerSetLayerIdListMax[MAX_LHVC_LAYERS]; //the highest value in LayerSetLayerIdList[i] +} HEVC_VPS; + +typedef struct +{ + AVCSeiRecoveryPoint recovery_point; + AVCSeiPicTiming pic_timing; + AVCSeiItuTT35DolbyVision dovi; +} HEVC_SEI; + +typedef struct +{ + u8 nal_unit_type; + u32 frame_num, poc_lsb, slice_type, header_size_with_emulation; + + s32 redundant_pic_cnt; + + s32 poc; + u32 poc_msb, poc_msb_prev, poc_lsb_prev, frame_num_prev; + s32 frame_num_offset, frame_num_offset_prev; + + Bool dependent_slice_segment_flag; + Bool first_slice_segment_in_pic_flag; + u32 slice_segment_address; + u8 prev_layer_id_plus1; + + //bit offset of the num_entry_point (if present) field + s32 entry_point_start_bits; + u64 header_size_bits; + //byte offset of the payload start (after byte alignment) + s32 payload_start_offset; + + s32 slice_qp_delta_start_bits; + s32 slice_qp_delta; + + HEVC_SPS *sps; + HEVC_PPS *pps; +} HEVCSliceInfo; + +typedef struct _hevc_state +{ + //set by user + Bool full_slice_header_parse; + + //all other vars set by parser + + HEVC_SPS sps[16]; /* 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*/ + + HEVC_PPS pps[64]; + + HEVC_VPS vps[16]; + + HEVCSliceInfo s_info; + HEVC_SEI 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_data[4]; + u8 mdcv_data[24]; + u8 clli_valid, mdcv_valid; +} HEVCState; + +typedef struct hevc_combine{ + Bool is_hevccombine, first_slice_segment; + s32 buffer_header_src_alloc; // because payload_start_offset is s32, otherwhise it's an u32 + u8 *buffer_header_src; +}Combine; + +enum +{ + GF_HEVC_SLICE_TYPE_B = 0, + GF_HEVC_SLICE_TYPE_P = 1, + GF_HEVC_SLICE_TYPE_I = 2, +}; +s32 gf_hevc_read_vps(u8 *data, u32 size, HEVCState *hevc); +s32 gf_hevc_read_vps_bs(GF_BitStream *bs, HEVCState *hevc); +s32 gf_hevc_read_sps(u8 *data, u32 size, HEVCState *hevc); +s32 gf_hevc_read_sps_bs(GF_BitStream *bs, HEVCState *hevc); +s32 gf_hevc_read_pps(u8 *data, u32 size, HEVCState *hevc); +s32 gf_hevc_read_pps_bs(GF_BitStream *bs, HEVCState *hevc); +s32 gf_hevc_parse_nalu(u8 *data, u32 size, HEVCState *hevc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); +Bool gf_hevc_slice_is_intra(HEVCState *hevc); +Bool gf_hevc_slice_is_IDR(HEVCState *hevc); +//parses VPS and rewrites data buffer after removing VPS extension +s32 gf_hevc_read_vps_ex(u8 *data, u32 *size, HEVCState *hevc, Bool remove_extensions); + +void gf_hevc_parse_ps(GF_HEVCConfig* hevccfg, HEVCState* hevc, u32 nal_type); +s32 gf_hevc_parse_nalu_bs(GF_BitStream *bs, HEVCState *hevc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); + +GF_Err gf_hevc_get_sps_info_with_state(HEVCState *hevc_state, u8 *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); + +/*parses HEVC SEI and fill state accordingly*/ +void gf_hevc_parse_sei(char* buffer, u32 nal_size, HEVCState *hevc); + + + +enum +{ + GF_VVC_SLICE_TYPE_B = 0, + GF_VVC_SLICE_TYPE_P = 1, + GF_VVC_SLICE_TYPE_I = 2, + GF_VVC_SLICE_TYPE_UNKNOWN = 10, +}; + +#define VVC_MAX_REF_PICS 29 + +#define VVC_RPL_ST 0 +#define VVC_RPL_LT 1 +#define VVC_RPL_IL 2 + +typedef struct +{ + u32 num_ref_entries; + u32 nb_short_term_pictures, nb_long_term_pictures, nb_inter_layer_pictures; + + u8 ref_pic_type[VVC_MAX_REF_PICS]; +// u32 ref_pic_id[VVC_MAX_REF_PICS]; +// s32 poc[VVC_MAX_REF_PICS]; +// u32 nb_active_pics; +// u8 delta_poc_msb_present[VVC_MAX_REF_PICS]; +// s32 delta_poc_msb_cycle_lt[VVC_MAX_REF_PICS]; + u8 ltrp_in_header_flag; +// u32 inter_layer_ref_pic_id[VVC_MAX_REF_PICS]; +} VVC_RefPicList; + +#define MAX_TILE_COLS 30 +#define MAX_TILE_ROWS 33 + +typedef struct +{ + s32 id; + u32 vps_id; + u8 state; + + //all flags needed for further PPS , picture header or slice header parsing + u8 max_sublayers, chroma_format_idc, log2_ctu_size, sps_ptl_dpb_hrd_params_present_flag; + u8 gdr_enabled, ref_pic_resampling, res_change_in_clvs, explicit_scaling_list_enabled_flag; + u8 virtual_boundaries_enabled_flag, virtual_boundaries_present_flag, joint_cbcr_enabled_flag; + u8 dep_quant_enabled_flag, sign_data_hiding_enabled_flag, transform_skip_enabled_flag; + u8 ph_num_extra_bits, sh_num_extra_bits, partition_constraints_override_enabled_flag; + u8 alf_enabled_flag, ccalf_enabled_flag, lmcs_enabled_flag, long_term_ref_pics_flag, inter_layer_prediction_enabled_flag; + u8 weighted_pred_flag, weighted_bipred_flag, temporal_mvp_enabled_flag, mmvd_fullpel_only_enabled_flag, bdof_control_present_in_ph_flag; + u8 dmvr_control_present_in_ph_flag, prof_control_present_in_ph_flag, sao_enabled_flag, idr_rpl_present_flag; + u8 entry_point_offsets_present_flag, entropy_coding_sync_enabled_flag; + + u8 conf_window; + u32 cw_left, cw_right, cw_top, cw_bottom; + + //subpic info, not fully implemented yet + u8 subpic_info_present, independent_subpic_flags, subpic_same_size, subpicid_mapping_explicit, subpicid_mapping_present; + u32 nb_subpics; //up to 600 + u32 subpicid_len; + + Bool has_timing_info; + u32 num_units_in_tick, time_scale; + u32 width, height; + + u32 bitdepth; + + //POC compute + u8 log2_max_poc_lsb, poc_msb_cycle_flag; + u32 poc_msb_cycle_len; + + //reference picture lists + u32 num_ref_pic_lists[2]; + VVC_RefPicList rps[2][64]; + + //VUI + u8 aspect_ratio_info_present_flag; + u8 sar_idc; + u16 sar_width, sar_height; + + u8 overscan_info_present_flag; + u8 video_signal_type_present_flag; + u8 video_format; + u8 video_full_range_flag; + + u8 colour_description_present_flag; + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + //SPS range extensions, not yet parsed + u8 ts_residual_coding_rice_present_in_sh_flag, reverse_last_sig_coeff_enabled_flag; +} VVC_SPS; + +typedef struct +{ + s32 id; + u32 sps_id; + u8 state; + //all flags needed for further picture header or slice header parsing + u8 mixed_nal_types, output_flag_present_flag, no_pic_partition_flag, subpic_id_mapping_present_flag, rect_slice_flag; + u8 alf_info_in_ph_flag, rpl_info_in_ph_flag, cu_qp_delta_enabled_flag, cu_chroma_qp_offset_list_enabled_flag, weighted_pred_flag; + u8 weighted_bipred_flag, wp_info_in_ph_flag, qp_delta_info_in_ph_flag, sao_info_in_ph_flag, dbf_info_in_ph_flag; + u8 deblocking_filter_disabled_flag, deblocking_filter_override_enabled_flag, chroma_tool_offsets_present_flag; + u8 slice_chroma_qp_offsets_present_flag, picture_header_extension_present_flag, rpl1_idx_present_flag; + u8 cabac_init_present_flag, slice_header_extension_present_flag, single_slice_per_subpic_flag; + + u32 num_ref_idx_default_active[2]; + u32 num_tiles_in_pic, num_tile_rows, num_tile_cols, slice_address_len, num_slices_in_pic; + + u32 width, height; + u8 conf_window; + u32 cw_left, cw_right, cw_top, cw_bottom; + + //tile info + u32 tile_rows_height_ctb[MAX_TILE_ROWS]; + u32 tile_cols_width_ctb[MAX_TILE_COLS]; + u32 pic_width_in_ctbsY, pic_height_in_ctbsY; +} VVC_PPS; + +#define VVC_MAX_LAYERS 4 +#define VVC_MAX_NUM_LAYER_SETS 1024 + + +typedef struct +{ + u8 profile_present_flag, level_present_flag; + u8 sublayer_level_idc; + +} VVC_SublayerPTL; + +typedef struct +{ + u8 pt_present; + u8 ptl_max_tid; + + u8 general_profile_idc, general_tier_flag, general_level_idc, frame_only_constraint, multilayer_enabled; + VVC_SublayerPTL sub_ptl[8]; + + u8 num_sub_profiles; + u32 sub_profile_idc[255]; + + u8 gci_present; + //holds 81 bits, the last byte contains the remainder (low bit set, not high) + u8 gci[12]; +} VVC_ProfileTierLevel; + +typedef struct +{ + s32 id; + u8 state; + + HEVC_RateInfo rates[8]; + u32 max_layers, max_sub_layers; + Bool all_layers_independent, each_layer_is_ols; + u32 max_layer_id; //, num_layer_sets; + + u16 num_ptl; //max 256 + VVC_ProfileTierLevel ptl[256]; +} VVC_VPS; + +typedef struct +{ + u8 nal_unit_type; + u32 frame_num, poc_lsb, slice_type; + + u8 poc_msb_cycle_present_flag; + s32 poc; + u32 poc_msb, poc_msb_cycle, poc_msb_prev, poc_lsb_prev, frame_num_prev; + + VVC_SPS *sps; + VVC_PPS *pps; + + u8 picture_header_in_slice_header_flag, inter_slice_allowed_flag, intra_slice_allowed_flag; + u8 irap_or_gdr_pic; + u8 non_ref_pic; + u8 gdr_pic; + u32 gdr_recovery_count; + u8 recovery_point_valid, lmcs_enabled_flag, explicit_scaling_list_enabled_flag, temporal_mvp_enabled_flag; + + u8 prev_layer_id_plus1; + u8 compute_poc_defer; + + //picture header RPL state + VVC_RefPicList ph_rpl[2]; + s32 ph_rpl_idx[2]; + + //slive RPL state + VVC_RefPicList rpl[2]; + s32 rpl_idx[2]; + + //slice header size in bytes + u32 payload_start_offset; +} 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*/ + + //-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; + s32 last_parsed_aps_id; + + VVC_SPS sps[16]; + VVC_PPS pps[64]; + VVC_VPS vps[16]; + + VVCSliceInfo s_info; + + //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_data[4]; + u8 mdcv_data[24]; + u8 clli_valid, mdcv_valid; +} VVCState; + +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); +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); + +void gf_vvc_parse_ps(GF_VVCConfig* hevccfg, VVCState* vvc, u32 nal_type); + + +GF_Err gf_media_parse_ivf_file_header(GF_BitStream *bs, u32 *width, u32*height, u32 *codec_fourcc, u32 *timebase_num, u32 *timebase_den, u32 *num_frames); + + + +#define VP9_MAX_FRAMES_IN_SUPERFRAME 16 + +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_sizes[VP9_MAX_FRAMES_IN_SUPERFRAME], u32 *superframe_index_size); + + + +#define AV1_MAX_TILE_ROWS 64 +#define AV1_MAX_TILE_COLS 64 + +typedef enum { + AV1_KEY_FRAME = 0, + AV1_INTER_FRAME = 1, + AV1_INTRA_ONLY_FRAME = 2, + AV1_SWITCH_FRAME = 3, +} AV1FrameType; + +typedef struct +{ + //offset in bytes after first byte of obu, including its header + u32 obu_start_offset; + u32 size; +} AV1Tile; + +typedef struct +{ + Bool is_first_frame; + Bool seen_frame_header, seen_seq_header; + Bool key_frame, show_frame; + AV1FrameType frame_type; + GF_List *header_obus, *frame_obus; /*GF_AV1_OBUArrayEntry*/ + AV1Tile tiles[AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS]; + u32 nb_tiles_in_obu; + u8 refresh_frame_flags; + u8 order_hint; + u8 allow_high_precision_mv; + u8 show_existing_frame, frame_to_show_map_idx; + //indicates the size of the uncompressed_header syntax element. This is set back to 0 at the next OBU parsing + u16 uncompressed_header_bytes; +} AV1StateFrame; + +#define AV1_NUM_REF_FRAMES 8 + +typedef struct +{ + s32 coefs[AV1_NUM_REF_FRAMES][6]; +} AV1GMParams; + +typedef struct +{ + /*importing options*/ + Bool keep_temporal_delim; + /*parser config*/ + //if set only header frames are stored + Bool skip_frames; + //if set, frame OBUs are not pushed to the frame_obus OBU list but are written in the below bitstream + Bool mem_mode; + /*bitstream object for mem mode - this bitstream is NOT destroyed by gf_av1_reset_state(state, GF_TRUE) */ + GF_BitStream *bs; + Bool unframed; + u8 *frame_obus; + u32 frame_obus_alloc; + + /*general sequence information*/ + Bool frame_id_numbers_present_flag; + Bool reduced_still_picture_header; + Bool decoder_model_info_present_flag; + u16 OperatingPointIdc; + u32 width, height, UpscaledWidth; + u32 sequence_width, sequence_height; + u32 tb_num, tb_den; + + Bool use_128x128_superblock; + u8 frame_width_bits_minus_1, frame_height_bits_minus_1; + u8 equal_picture_interval; + u8 delta_frame_id_length_minus_2; + u8 additional_frame_id_length_minus_1; + u8 seq_force_integer_mv; + u8 seq_force_screen_content_tools; + Bool enable_superres; + Bool enable_order_hint; + Bool enable_cdef; + Bool enable_restoration; + Bool enable_warped_motion; + u8 OrderHintBits; + Bool enable_ref_frame_mvs; + Bool film_grain_params_present; + u8 buffer_delay_length; + u8 frame_presentation_time_length; + u32 buffer_removal_time_length; + u8 operating_points_count; + u8 decoder_model_present_for_this_op[32]; + u8 operating_point_idc[32]; + + u32 tileRows, tileCols, tileRowsLog2, tileColsLog2; + u8 tile_size_bytes; /*coding tile header size*/ + Bool separate_uv_delta_q; + + /*Needed for RFC6381*/ + Bool still_picture; + u8 bit_depth; + Bool color_description_present_flag; + u8 color_primaries, transfer_characteristics, matrix_coefficients; + Bool color_range; + + /*AV1 config record - shall not be null when parsing - this is NOT destroyed by gf_av1_reset_state(state, GF_TRUE) */ + GF_AV1Config *config; + + /*OBU parsing state, reset at each obu*/ + Bool obu_has_size_field, obu_extension_flag; + u8 temporal_id, spatial_id; + ObuType obu_type; + + /*inter-frames state */ + u8 RefOrderHint[AV1_NUM_REF_FRAMES]; + u8 RefValid[AV1_NUM_REF_FRAMES]; + u8 OrderHints[AV1_NUM_REF_FRAMES]; + + AV1GMParams GmParams; + AV1GMParams PrevGmParams; + AV1GMParams SavedGmParams[AV1_NUM_REF_FRAMES]; + u8 RefFrameType[AV1_NUM_REF_FRAMES]; + + u32 RefUpscaledWidth[AV1_NUM_REF_FRAMES]; + u32 RefFrameHeight[AV1_NUM_REF_FRAMES]; + + /*frame parsing state*/ + AV1StateFrame frame_state; + + /*layer sizes for AVIF a1lx*/ + u32 layer_size[4]; + + + u8 clli_data[4]; + u8 mdcv_data[24]; + u8 clli_valid, mdcv_valid; + +} AV1State; + +GF_Err aom_av1_parse_temporal_unit_from_section5(GF_BitStream *bs, AV1State *state); +GF_Err aom_av1_parse_temporal_unit_from_annexb(GF_BitStream *bs, AV1State *state); +GF_Err aom_av1_parse_temporal_unit_from_ivf(GF_BitStream *bs, AV1State *state); + +/*may return GF_BUFFER_TOO_SMALL if not enough bytes*/ +GF_Err gf_media_parse_ivf_frame_header(GF_BitStream *bs, u64 *frame_size, u64 *pts); + +Bool gf_media_probe_ivf(GF_BitStream *bs); +Bool gf_media_aom_probe_annexb(GF_BitStream *bs); + +/*parses one 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 obu_hdr_size OBU header size +\param state the frame parser +*/ +GF_Err gf_av1_parse_obu(GF_BitStream *bs, ObuType *obu_type, u64 *obu_size, u32 *obu_hdr_size, AV1State *state); + +Bool av1_is_obu_header(ObuType obu_type); + +/*! init av1 frame parsing state +\param state the frame parser +*/ +void gf_av1_init_state(AV1State *state); + +/*! reset av1 frame parsing state - this does not destroy the structure. +\param state the frame parser +\param is_destroy if TRUE, destroy internal reference picture lists +*/ +void gf_av1_reset_state(AV1State *state, Bool is_destroy); + +u64 gf_av1_leb128_read(GF_BitStream *bs, u8 *opt_Leb128Bytes); +u32 gf_av1_leb128_size(u64 value); +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 +{ + u32 picture_size; + u16 deprecated_number_of_slices; + u8 log2_desired_slice_size_in_mb; +} GF_ProResPictureInfo; + +typedef struct +{ + u32 frame_size; + u32 frame_identifier; + u16 frame_hdr_size; + u8 version; + u32 encoder_id; + u16 width; + u16 height; + u8 chroma_format; + u8 interlaced_mode; + u8 aspect_ratio_information; + u8 framerate_code; + u8 color_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + u8 alpha_channel_type; + u8 load_luma_quant_matrix, load_chroma_quant_matrix; + u8 luma_quant_matrix[8][8]; + u8 chroma_quant_matrix[8][8]; + + u8 nb_pic; //1 or 2 + //for now we don't parse this +// GF_ProResPictureInfo pictures[2]; +} GF_ProResFrameInfo; + +GF_Err gf_media_prores_parse_bs(GF_BitStream *bs, GF_ProResFrameInfo *prores_frame); + +#endif /*GPAC_DISABLE_AV_PARSERS*/ + +typedef struct +{ + u8 rate_idx; + u8 pck_size; +} QCPRateTable; + + +#if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_STREAMING) + +GP_RTPPacketizer *gf_rtp_packetizer_create_and_init_from_file(GF_ISOFile *file, + u32 TrackNum, + void *cbk_obj, + void (*OnNewPacket)(void *cbk, GF_RTPHeader *header), + void (*OnPacketDone)(void *cbk, GF_RTPHeader *header), + void (*OnDataReference)(void *cbk, u32 payload_size, u32 offset_from_orig), + void (*OnData)(void *cbk, u8 *data, u32 data_size, Bool is_head), + u32 Path_MTU, + u32 max_ptime, + u32 default_rtp_rate, + u32 flags, + u8 PayloadID, + Bool copy_media, + 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); + +#endif + + +#ifndef GPAC_DISABLE_VTT + +typedef struct _webvtt_parser GF_WebVTTParser; +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, + 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 *)); +GF_Err gf_webvtt_parser_parse(GF_WebVTTParser *parser); +void gf_webvtt_parser_del(GF_WebVTTParser *parser); +void gf_webvtt_parser_suspend(GF_WebVTTParser *vttparser); +void gf_webvtt_parser_restart(GF_WebVTTParser *parser); + +#include <gpac/webvtt.h> +void gf_webvtt_parser_cue_callback(GF_WebVTTParser *parser, void (*on_cue_read)(void *, GF_WebVTTCue *), void *udta); +GF_Err gf_webvtt_merge_cues(GF_WebVTTParser *parser, u64 start, GF_List *cues); +GF_Err gf_webvtt_parser_finalize(GF_WebVTTParser *parser, u64 duration); + +void gf_webvtt_sample_del(GF_WebVTTSample * samp); +u64 gf_webvtt_sample_get_start(GF_WebVTTSample * samp); +u64 gf_webvtt_sample_get_end(GF_WebVTTSample * samp); + + + +#ifndef GPAC_DISABLE_ISOM +GF_Err gf_webvtt_dump_header(FILE *dump, GF_ISOFile *file, u32 track, Bool box_mode, u32 index); +GF_Err gf_webvtt_parser_dump_done(GF_WebVTTParser *parser, u32 duration); +#endif /* GPAC_DISABLE_ISOM */ + +#endif /* GPAC_DISABLE_VTT */ + + +#define M4V_VO_START_CODE 0x00 +#define M4V_VOL_START_CODE 0x20 +#define M4V_VOP_START_CODE 0xB6 +#define M4V_VISOBJ_START_CODE 0xB5 +#define M4V_VOS_START_CODE 0xB0 +#define M4V_GOV_START_CODE 0xB3 +#define M4V_UDTA_START_CODE 0xB2 + + +#define M2V_PIC_START_CODE 0x00 +#define M2V_SEQ_START_CODE 0xB3 +#define M2V_EXT_START_CODE 0xB5 +#define M2V_GOP_START_CODE 0xB8 + + +#endif /*_GF_MEDIA_DEV_H_*/ + diff --git a/include/gpac/internal/mesh.h b/include/gpac/internal/mesh.h new file mode 100644 index 0000000..ce4e699 --- /dev/null +++ b/include/gpac/internal/mesh.h @@ -0,0 +1,317 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Scene Compositor 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_MESH_H_ +#define _GF_MESH_H_ + +#include <gpac/scenegraph_vrml.h> +#include <gpac/path2d.h> +#include <gpac/mediaobject.h> + +/*by default we store each color on 32 bit rather than 4 floats (128 bits)*/ + +//#define MESH_USE_SFCOLOR + +#ifdef MESH_USE_SFCOLOR +#define MESH_MAKE_COL(_argb) _argb +#define MESH_GET_COLOR(_argb, _vertex) _argb = (_vertex).color; +#else +#define MESH_MAKE_COL(_argb) GF_COL_ARGB(FIX2INT(255*(_argb.alpha)), FIX2INT(255*(_argb.blue)), FIX2INT(255*(_argb.green)), FIX2INT(255*(_argb.red))) +#define MESH_GET_COLOR(_argb, _vertex) { _argb.alpha = INT2FIX(GF_COL_A((_vertex).color))/255; _argb.red = INT2FIX(GF_COL_R((_vertex).color))/255; _argb.green = INT2FIX(GF_COL_G((_vertex).color))/255; _argb.blue = INT2FIX(GF_COL_B((_vertex).color))/255; } +#endif + +/*by default we store normals as signed bytes rather than floats*/ + +//#define MESH_USE_FIXED_NORMAL + +#ifdef MESH_USE_FIXED_NORMAL +#define MESH_SET_NORMAL(_vertex, _nor) _vertex.normal = _nor; +#define MESH_GET_NORMAL(_nor, _vertex) _nor = _vertex.normal; +#define MESH_NORMAL_UNIT FIX_ONE +#else + +typedef struct +{ + s8 x, y, z; + s8 __dummy; +} SFVec3f_bytes; + +#define MESH_NORMAL_UNIT 1 + +#ifdef GPAC_FIXED_POINT +#define MESH_SET_NORMAL(_vertex, _nor) { SFVec3f_bytes __nor; __nor.x = (s8) FIX2INT(_nor.x*100); __nor.y = (s8) FIX2INT(_nor.y*100); __nor.z = (s8) FIX2INT(_nor.z*100); __nor.__dummy=0; _vertex.normal = __nor; } +#define MESH_GET_NORMAL(_nor, _vertex) { (_nor).x = INT2FIX(_vertex.normal.x); (_nor).y = INT2FIX(_vertex.normal.y); (_nor).z = INT2FIX(_vertex.normal.z); gf_vec_norm(&(_nor)); } +#else +#define MESH_SET_NORMAL(_vertex, _nor) { SFVec3f_bytes __nor; __nor.x = (s8) (_nor.x*100); __nor.y = (s8) (_nor.y*100); __nor.z = (s8) (_nor.z*100); __nor.__dummy=0; _vertex.normal = __nor; } +#define MESH_GET_NORMAL(_nor, _vertex) { (_nor).x = _vertex.normal.x; (_nor).y = _vertex.normal.y; (_nor).z = _vertex.normal.z; gf_vec_norm(&(_nor)); } +#endif + +#endif + +typedef struct +{ + /*position*/ + SFVec3f pos; + /*texture coordinates*/ + SFVec2f texcoords; + /*normal*/ +#ifdef MESH_USE_FIXED_NORMAL + SFVec3f normal; +#else + SFVec3f_bytes normal; +#endif + /*color if used by mesh object*/ +#ifdef MESH_USE_SFCOLOR + SFColorRGBA color; +#else + u32 color; +#endif +} GF_Vertex; + +/*memory offset in bytes from start of vertex to texcoords = 3 * 4bytes*/ +#define MESH_TEX_OFFSET 12 +/*memory offset in bytes from start of vertex to normal = 5 * 4bytes*/ +#define MESH_NORMAL_OFFSET 20 +/*memory offset in bytes from start of vertex to color - platform dependent*/ +#ifdef MESH_USE_FIXED_NORMAL +/*3+2+3 * 4*/ +#define MESH_COLOR_OFFSET 32 +#else +/*3+2 * 4 + 4 (3 + 1 byte alignment)*/ +#define MESH_COLOR_OFFSET 24 +#endif + +/*mesh type used*/ +enum +{ + /*default: triangles described by indices (nb triangles = nb indices / 3) */ + MESH_TRIANGLES = 0, + /*point set: indices is meaningless*/ + MESH_POINTSET, + /*line set: lines described by indices (nb lines = nb indices / 2) */ + MESH_LINESET, +}; + +/*mesh flags*/ +enum +{ + /*vertex.color is used*/ + MESH_HAS_COLOR = 1, + /*mesh is 2D: normal should be ignored and a global normal set to 0 0 1*/ + MESH_IS_2D = 1<<1, + /*mesh has no texture coords - disable texturing*/ + MESH_NO_TEXTURE = 1<<2, + /*mesh faces are clockwise*/ + MESH_IS_CW = 1<<3, + /*mesh is solid (back face culling + 2 side lighting)*/ + MESH_IS_SOLID = 1<<4, + /*mesh has smoothed normals*/ + MESH_IS_SMOOTHED = 1<<5, + /*vertex.color is used with alpha channel*/ + MESH_HAS_ALPHA = 1<<6, + /*flag only used by VRgeometry proto*/ + MESH_WAS_VISIBLE = 1<<7, +}; + +/*indexes as used in glDrawElements - note that integer type is not allowed with oglES*/ +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) +#define IDX_TYPE u16 +#else +#define IDX_TYPE u32 +#endif + +/*mesh object used by all 2D/3D primitives. */ +typedef struct __gf_mesh +{ + /*vertex list*/ + u32 v_count, v_alloc; + GF_Vertex *vertices; + /*triangle indexes*/ + u32 i_count, i_alloc; + IDX_TYPE *indices; + + /*one of the above type*/ + u32 mesh_type; + + /*one of the above flags*/ + u32 flags; + + /*bounds info: bounding box and bounding sphere radius*/ + GF_BBox bounds; + + /*aabb tree of the mesh if any*/ + struct __AABBNode *aabb_root; + /*triangle indexes used in AABB tree - order may be different than the one in mesh->indices*/ + IDX_TYPE *aabb_indices; +// u32 aabb_nb_index; + + u32 vbo; + u32 vbo_idx; + Bool vbo_dirty, vbo_dynamic; +} GF_Mesh; + +GF_Mesh *new_mesh(); +void mesh_free(GF_Mesh *mesh); +/*reset mesh*/ +void mesh_reset(GF_Mesh *mesh); +/*recompute mesh bounds*/ +void mesh_update_bounds(GF_Mesh *mesh); +/*adds new vertex*/ +void mesh_set_vertex_vx(GF_Mesh *mesh, GF_Vertex *vx); +/*adds new vertex (exported for tesselator only)*/ +void mesh_set_vertex(GF_Mesh *mesh, Fixed x, Fixed y, Fixed z, Fixed nx, Fixed ny, Fixed nz, Fixed u, Fixed v); +/*adds an index (exported for tesselator only)*/ +void mesh_set_index(GF_Mesh *mesh, u32 idx); +/*adds an point & associated color, normal set to NULL*/ +void mesh_set_point(GF_Mesh *mesh, Fixed x, Fixed y, Fixed z, SFColorRGBA col); +/*adds an index (exported for tesselator only)*/ +void mesh_set_triangle(GF_Mesh *mesh, u32 id1, u32 id2, u32 id3); +/*make dest mesh the clone of orig*/ +void mesh_clone(GF_Mesh *dest, GF_Mesh *orig); +/*recompute all normals*/ +void mesh_recompute_normals(GF_Mesh *mesh); +/*generate texture coordinate - ONLY LOCAL MODES SUPPORTED FOR NOW*/ +void mesh_generate_tex_coords(GF_Mesh *mesh, GF_Node *__texCoords); + +/*inserts a box (lines only) of size 1.0 1.0 1.0*/ +void mesh_new_unit_bbox(GF_Mesh *mesh); + +/*insert base primitives - low res indicates less subdivision steps for circles (cone, cylinder, ellipse, sphere)*/ +void mesh_new_rectangle(GF_Mesh *mesh, SFVec2f size, SFVec2f *orig, Bool flip); +void mesh_new_rectangle_ex(GF_Mesh *mesh, SFVec2f size, SFVec2f *orig, u32 flip, u32 rotate); +void mesh_new_ellipse(GF_Mesh *mesh, Fixed a_dia, Fixed b_dia, Bool low_res); +void mesh_new_box(GF_Mesh *mesh, SFVec3f size); +void mesh_new_cylinder(GF_Mesh *mesh, Fixed height, Fixed radius, Bool bottom, Bool side, Bool top, Bool low_res); +void mesh_new_cone(GF_Mesh *mesh, Fixed height, Fixed radius, Bool bottom, Bool side, Bool low_res); + + +typedef struct +{ + Fixed min_phi; + Fixed max_phi; + Fixed min_theta; + Fixed max_theta; +} GF_MeshSphereAngles; +/*create a new sphere of the given radius. If angles is set, mesh is a partial sphere but tx coords still range from 0,0 to 1,1*/ +void mesh_new_sphere(GF_Mesh *mesh, Fixed radius, Bool low_res, GF_MeshSphereAngles *angles); +/*inserts ILS/ILS2D and IFS2D outline when not filled*/ +void mesh_new_ils(GF_Mesh *mesh, GF_Node *__coord, MFInt32 *coordIndex, GF_Node *__color, MFInt32 *colorIndex, Bool colorPerVertex, Bool do_close); +/*inserts IFS2D*/ +void mesh_new_ifs2d(GF_Mesh *mesh, GF_Node *ifs2d); +/*inserts IFS*/ +void mesh_new_ifs(GF_Mesh *mesh, GF_Node *ifs); +/*inserts PS/PS2D*/ +void mesh_new_ps(GF_Mesh *mesh, GF_Node *__coord, GF_Node *__color); +/*inserts ElevationGrid*/ +void mesh_new_elevation_grid(GF_Mesh *mesh, GF_Node *eg); +/*inserts Extrusion*/ +void mesh_new_extrusion(GF_Mesh *mesh, GF_Node *ext); +/*builds mesh from path, performing tesselation if desired*/ +void mesh_from_path(GF_Mesh *mesh, GF_Path *path); +/*builds mesh for outline of the given path*/ +void mesh_get_outline(GF_Mesh *mesh, GF_Path *path); +/*constructs an extrusion from given path - mesh is reseted, txcoords computed from path bounds +@thespine: spine line +@creaseAngle: creaseAngle for normal smoothing, 0 for no smoothing +begin_cap, end_cap: indicates whether start/end faces shall be added +@spine_ori: orientation at spine points +@spine_scale: scale at spine points +@tx_along_spine: if set, texture coords are generated so that the texture is mapped on the side, +otherwise the same txcoords are used all along the extrusion spine +*/ +void mesh_extrude_path(GF_Mesh *mesh, GF_Path *path, MFVec3f *thespine, Fixed creaseAngle, Bool begin_cap, Bool end_cap, MFRotation *spine_ori, MFVec2f *spine_scale, Bool tx_along_spine); +/*special extension of the above: APPENDS an extrusion from given path - mesh is NOT reseted, txcoords are computed based on min_cx, min_cy, width_cx, width_cy*/ +void mesh_extrude_path_ext(GF_Mesh *mesh, GF_Path *path, MFVec3f *thespine, Fixed creaseAngle, Fixed min_cx, Fixed min_cy, Fixed width_cx, Fixed width_cy, Bool begin_cap, Bool end_cap, MFRotation *spine_ori, MFVec2f *spine_scale, Bool tx_along_spine); + +/*returns 1 if intersection and set outPoint to closest intersection, 0 otherwise*/ +Bool gf_mesh_intersect_ray(GF_Mesh *mesh, GF_Ray *r, SFVec3f *outPoint, SFVec3f *outNormal, SFVec2f *outTexCoords); +/*returns 1 if any face is less than min_dist from pos, with collision point on closest face (towards pos)*/ +Bool gf_mesh_closest_face(GF_Mesh *mesh, SFVec3f pos, Fixed min_dist, SFVec3f *outPoint); + + + + +/*AABB tree node (exported for bounds drawing)*/ +typedef struct __AABBNode +{ + /*bbox*/ + SFVec3f min, max; + /*sorted indices in mesh indices list*/ + IDX_TYPE *indices; + /*nb triangles*/ + u32 nb_idx; + /*children nodes, NULL if leaf*/ + struct __AABBNode *pos, *neg; +} AABBNode; + +/*tree construction modes*/ +enum +{ + /*AABB tree is not used*/ + AABB_NONE, + /*longest box axis is used to divide an AABB node*/ + AABB_LONGEST, + /*keep tree well-balanced*/ + AABB_BALANCED, + /*best axis is use: test largest, then middle, then smallest axis*/ + AABB_BEST_AXIS, + /*use variance to pick axis*/ + AABB_SPLATTER, + /*fifty/fifty point split*/ + AABB_FIFTY, +}; + +void gf_mesh_build_aabbtree(GF_Mesh *mesh); + + +/* + * tesselation functions + */ + +/*appends given face (and tesselate if needed) to the mesh. Only vertices are used in the face +indices are ignored. +partially implemented on ogl-ES*/ +void TesselateFaceMesh(GF_Mesh *mesh, GF_Mesh *face); + +#ifdef GPAC_HAS_GLU +/*converts 2D path into a polygon - these are only partially implemented when using oglES +for_outline: + 0, regular odd/even windining rule with texCoords + 1, zero-non-zero windining rule without texCoords + 2, zero-non-zero windining rule with texCoords +*/ +void gf_mesh_tesselate_path(GF_Mesh *mesh, GF_Path *path, u32 outline_style); + +/*appends given face (and tesselate if needed) to the mesh. Only vertices are used in the face +indices are ignored. +Same as TesselateFaceMesh + faces info to determine where are the polygons in the face - used by extruder only +*/ +void TesselateFaceMeshComplex(GF_Mesh *dest, GF_Mesh *orig, u32 nbFaces, u32 *ptsPerFaces); + +#endif + +#endif /*_GF_MESH_H_*/ + diff --git a/include/gpac/internal/odf_dev.h b/include/gpac/internal/odf_dev.h new file mode 100644 index 0000000..cc14eea --- /dev/null +++ b/include/gpac/internal/odf_dev.h @@ -0,0 +1,364 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / MPEG-4 ObjectDescriptor 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_OD_DEV_H_ +#define _GF_OD_DEV_H_ + +#include <gpac/mpeg4_odf.h> + +/*read-write OD formatted strings*/ +GF_Err gf_odf_read_url_string(GF_BitStream *bs, char **string, u32 *readBytes); +GF_Err gf_odf_write_url_string(GF_BitStream *bs, char *string); +u32 gf_odf_size_url_string(char *string); + +/*descriptors base functions*/ +GF_Descriptor *gf_odf_create_descriptor(u8 tag); +GF_Err gf_odf_delete_descriptor(GF_Descriptor *desc); +GF_Err gf_odf_parse_descriptor(GF_BitStream *bs, GF_Descriptor **desc, u32 *size); +GF_Err gf_odf_read_descriptor(GF_BitStream *bs, GF_Descriptor *desc, u32 DescSize); +GF_Err gf_odf_write_base_descriptor(GF_BitStream *bs, u8 tag, u32 size); +GF_Err gf_odf_write_descriptor(GF_BitStream *bs, GF_Descriptor *desc); +GF_Err gf_odf_size_descriptor(GF_Descriptor *desc, u32 *outSize); +GF_Err gf_odf_delete_descriptor_list(GF_List *descList); +GF_Err gf_odf_write_descriptor_list(GF_BitStream *bs, GF_List *descList); +GF_Err gf_odf_write_descriptor_list_filter(GF_BitStream *bs, GF_List *descList, u8 tag_only); +GF_Err gf_odf_size_descriptor_list(GF_List *descList, u32 *outSize); + +/*handle lazy bitstreams where SizeOfInstance is always encoded on 4 bytes*/ +s32 gf_odf_size_field_size(u32 size_desc); + +GF_Err DumpDescList(GF_List *list, FILE *trace, u32 indent, const char *ListName, Bool XMTDump, Bool no_skip_empty); + +/*IPMPX tools*/ +u32 gf_ipmpx_array_size(GF_BitStream *bs, u32 *array_size); +void gf_ipmpx_write_array(GF_BitStream *bs, u8 *data, u32 data_len); + +/*QoS qualifiers base functions*/ +GF_Err gf_odf_parse_qos_qual(GF_BitStream *bs, GF_QoS_Default **qos_qual, u32 *qos_size); +void gf_odf_delete_qos_qual(GF_QoS_Default *qos); +GF_Err gf_odf_size_qos_qual(GF_QoS_Default *qos); +GF_Err gf_odf_write_qos_qual(GF_BitStream *bs, GF_QoS_Default *qos); + +GF_Descriptor *gf_odf_new_iod(); +GF_Descriptor *gf_odf_new_esd(); +GF_Descriptor *gf_odf_new_dcd(); +GF_Descriptor *gf_odf_new_slc(u8 predef); +GF_Descriptor *gf_odf_new_cc(); +GF_Descriptor *gf_odf_new_cc_date(); +GF_Descriptor *gf_odf_new_cc_name(); +GF_Descriptor *gf_odf_new_ci(); +GF_Descriptor *gf_odf_new_default(); +GF_Descriptor *gf_odf_new_esd_inc(); +GF_Descriptor *gf_odf_new_esd_ref(); +GF_Descriptor *gf_odf_new_exp_text(); +GF_Descriptor *gf_odf_new_pl_ext(); +GF_Descriptor *gf_odf_new_ipi_ptr(); +GF_Descriptor *gf_odf_new_ipmp(); +GF_Descriptor *gf_odf_new_ipmp_ptr(); +GF_Descriptor *gf_odf_new_kw(); +GF_Descriptor *gf_odf_new_lang(); +GF_Descriptor *gf_odf_new_isom_iod(); +GF_Descriptor *gf_odf_new_isom_od(); +GF_Descriptor *gf_odf_new_od(); +GF_Descriptor *gf_odf_new_oci_date(); +GF_Descriptor *gf_odf_new_oci_name(); +GF_Descriptor *gf_odf_new_pl_idx(); +GF_Descriptor *gf_odf_new_qos(); +GF_Descriptor *gf_odf_new_rating(); +GF_Descriptor *gf_odf_new_reg(); +GF_Descriptor *gf_odf_new_short_text(); +GF_Descriptor *gf_odf_new_smpte_camera(); +GF_Descriptor *gf_odf_new_sup_cid(); +GF_Descriptor *gf_odf_new_segment(); +GF_Descriptor *gf_odf_new_mediatime(); +GF_Descriptor *gf_odf_new_ipmp_tool_list(); +GF_Descriptor *gf_odf_new_ipmp_tool(); +GF_Descriptor *gf_odf_new_muxinfo(); +GF_Descriptor *gf_odf_New_ElemMask(); +GF_Descriptor *gf_odf_new_bifs_cfg(); +GF_Descriptor *gf_odf_new_ui_cfg(); +GF_Descriptor *gf_odf_new_laser_cfg(); +GF_Descriptor *gf_odf_new_auxvid(); + + +GF_Err gf_odf_del_iod(GF_InitialObjectDescriptor *iod); +GF_Err gf_odf_del_esd(GF_ESD *esd); +GF_Err gf_odf_del_dcd(GF_DecoderConfig *dcd); +GF_Err gf_odf_del_slc(GF_SLConfig *sl); +GF_Err gf_odf_del_cc(GF_CCDescriptor *ccd); +GF_Err gf_odf_del_cc_date(GF_CC_Date *cdd); +GF_Err gf_odf_del_cc_name(GF_CC_Name *cnd); +GF_Err gf_odf_del_ci(GF_CIDesc *cid); +GF_Err gf_odf_del_default(GF_DefaultDescriptor *dd); +GF_Err gf_odf_del_esd_inc(GF_ES_ID_Inc *esd_inc); +GF_Err gf_odf_del_esd_ref(GF_ES_ID_Ref *esd_ref); +GF_Err gf_odf_del_exp_text(GF_ExpandedTextual *etd); +GF_Err gf_odf_del_pl_ext(GF_PLExt *pld); +GF_Err gf_odf_del_ipi_ptr(GF_IPIPtr *ipid); +GF_Err gf_odf_del_ipmp(GF_IPMP_Descriptor *ipmp); +GF_Err gf_odf_del_ipmp_ptr(GF_IPMPPtr *ipmpd); +GF_Err gf_odf_del_kw(GF_KeyWord *kwd); +GF_Err gf_odf_del_lang(GF_Language *ld); +GF_Err gf_odf_del_isom_iod(GF_IsomInitialObjectDescriptor *iod); +GF_Err gf_odf_del_isom_od(GF_IsomObjectDescriptor *od); +GF_Err gf_odf_del_od(GF_ObjectDescriptor *od); +GF_Err gf_odf_del_oci_date(GF_OCI_Data *ocd); +GF_Err gf_odf_del_oci_name(GF_OCICreators *ocn); +GF_Err gf_odf_del_pl_idx(GF_PL_IDX *plid); +GF_Err gf_odf_del_qos(GF_QoS_Descriptor *qos); +GF_Err gf_odf_del_rating(GF_Rating *rd); +GF_Err gf_odf_del_reg(GF_Registration *reg); +GF_Err gf_odf_del_short_text(GF_ShortTextual *std); +GF_Err gf_odf_del_smpte_camera(GF_SMPTECamera *cpd); +GF_Err gf_odf_del_sup_cid(GF_SCIDesc *scid); +GF_Err gf_odf_del_segment(GF_Segment *sd); +GF_Err gf_odf_del_mediatime(GF_MediaTime *mt); +GF_Err gf_odf_del_ipmp_tool_list(GF_IPMP_ToolList *ipmptl); +GF_Err gf_odf_del_ipmp_tool(GF_IPMP_Tool *ipmp); +GF_Err gf_odf_del_muxinfo(GF_MuxInfo *mi); +GF_Err gf_odf_del_bifs_cfg(GF_BIFSConfig *desc); +GF_Err gf_odf_del_ui_cfg(GF_UIConfig *desc); +GF_Err gf_odf_del_laser_cfg(GF_LASERConfig *desc); +GF_Err gf_odf_del_auxvid(GF_AuxVideoDescriptor *ld); +GF_Err gf_odf_del_ElemMask(GF_ElementaryMask *desc); + +GF_Err gf_odf_read_iod(GF_BitStream *bs, GF_InitialObjectDescriptor *iod, u32 DescSize); +GF_Err gf_odf_read_esd(GF_BitStream *bs, GF_ESD *esd, u32 DescSize); +GF_Err gf_odf_read_dcd(GF_BitStream *bs, GF_DecoderConfig *dcd, u32 DescSize); +GF_Err gf_odf_read_slc(GF_BitStream *bs, GF_SLConfig *sl, u32 DescSize); +GF_Err gf_odf_read_cc(GF_BitStream *bs, GF_CCDescriptor *ccd, u32 DescSize); +GF_Err gf_odf_read_cc_date(GF_BitStream *bs, GF_CC_Date *cdd, u32 DescSize); +GF_Err gf_odf_read_cc_name(GF_BitStream *bs, GF_CC_Name *cnd, u32 DescSize); +GF_Err gf_odf_read_ci(GF_BitStream *bs, GF_CIDesc *cid, u32 DescSize); +GF_Err gf_odf_read_default(GF_BitStream *bs, GF_DefaultDescriptor *dd, u32 DescSize); +GF_Err gf_odf_read_esd_inc(GF_BitStream *bs, GF_ES_ID_Inc *esd_inc, u32 DescSize); +GF_Err gf_odf_read_esd_ref(GF_BitStream *bs, GF_ES_ID_Ref *esd_ref, u32 DescSize); +GF_Err gf_odf_read_exp_text(GF_BitStream *bs, GF_ExpandedTextual *etd, u32 DescSize); +GF_Err gf_odf_read_pl_ext(GF_BitStream *bs, GF_PLExt *pld, u32 DescSize); +GF_Err gf_odf_read_ipi_ptr(GF_BitStream *bs, GF_IPIPtr *ipid, u32 DescSize); +GF_Err gf_odf_read_ipmp(GF_BitStream *bs, GF_IPMP_Descriptor *ipmp, u32 DescSize); +GF_Err gf_odf_read_ipmp_ptr(GF_BitStream *bs, GF_IPMPPtr *ipmpd, u32 DescSize); +GF_Err gf_odf_read_kw(GF_BitStream *bs, GF_KeyWord *kwd, u32 DescSize); +GF_Err gf_odf_read_lang(GF_BitStream *bs, GF_Language *ld, u32 DescSize); +GF_Err gf_odf_read_isom_iod(GF_BitStream *bs, GF_IsomInitialObjectDescriptor *iod, u32 DescSize); +GF_Err gf_odf_read_isom_od(GF_BitStream *bs, GF_IsomObjectDescriptor *od, u32 DescSize); +GF_Err gf_odf_read_od(GF_BitStream *bs, GF_ObjectDescriptor *od, u32 DescSize); +GF_Err gf_odf_read_oci_date(GF_BitStream *bs, GF_OCI_Data *ocd, u32 DescSize); +GF_Err gf_odf_read_oci_name(GF_BitStream *bs, GF_OCICreators *ocn, u32 DescSize); +GF_Err gf_odf_read_pl_idx(GF_BitStream *bs, GF_PL_IDX *plid, u32 DescSize); +GF_Err gf_odf_read_qos(GF_BitStream *bs, GF_QoS_Descriptor *qos, u32 DescSize); +GF_Err gf_odf_read_rating(GF_BitStream *bs, GF_Rating *rd, u32 DescSize); +GF_Err gf_odf_read_reg(GF_BitStream *bs, GF_Registration *reg, u32 DescSize); +GF_Err gf_odf_read_short_text(GF_BitStream *bs, GF_ShortTextual *std, u32 DescSize); +GF_Err gf_odf_read_smpte_camera(GF_BitStream *bs, GF_SMPTECamera *cpd, u32 DescSize); +GF_Err gf_odf_read_sup_cid(GF_BitStream *bs, GF_SCIDesc *scid, u32 DescSize); +GF_Err gf_odf_read_segment(GF_BitStream *bs, GF_Segment *sd, u32 DescSize); +GF_Err gf_odf_read_mediatime(GF_BitStream *bs, GF_MediaTime *mt, u32 DescSize); +GF_Err gf_odf_read_ipmp_tool_list(GF_BitStream *bs, GF_IPMP_ToolList *ipmptl, u32 DescSize); +GF_Err gf_odf_read_ipmp_tool(GF_BitStream *bs, GF_IPMP_Tool *ipmp, u32 DescSize); +GF_Err gf_odf_read_auxvid(GF_BitStream *bs, GF_AuxVideoDescriptor *ld, u32 DescSize); + +GF_Err gf_odf_size_iod(GF_InitialObjectDescriptor *iod, u32 *outSize); +GF_Err gf_odf_size_esd(GF_ESD *esd, u32 *outSize); +GF_Err gf_odf_size_dcd(GF_DecoderConfig *dcd, u32 *outSize); +GF_Err gf_odf_size_slc(GF_SLConfig *sl, u32 *outSize); +GF_Err gf_odf_size_cc(GF_CCDescriptor *ccd, u32 *outSize); +GF_Err gf_odf_size_cc_date(GF_CC_Date *cdd, u32 *outSize); +GF_Err gf_odf_size_cc_name(GF_CC_Name *cnd, u32 *outSize); +GF_Err gf_odf_size_ci(GF_CIDesc *cid, u32 *outSize); +GF_Err gf_odf_size_default(GF_DefaultDescriptor *dd, u32 *outSize); +GF_Err gf_odf_size_esd_inc(GF_ES_ID_Inc *esd_inc, u32 *outSize); +GF_Err gf_odf_size_esd_ref(GF_ES_ID_Ref *esd_ref, u32 *outSize); +GF_Err gf_odf_size_exp_text(GF_ExpandedTextual *etd, u32 *outSize); +GF_Err gf_odf_size_pl_ext(GF_PLExt *pld, u32 *outSize); +GF_Err gf_odf_size_ipi_ptr(GF_IPIPtr *ipid, u32 *outSize); +GF_Err gf_odf_size_ipmp(GF_IPMP_Descriptor *ipmp, u32 *outSize); +GF_Err gf_odf_size_ipmp_ptr(GF_IPMPPtr *ipmpd, u32 *outSize); +GF_Err gf_odf_size_kw(GF_KeyWord *kwd, u32 *outSize); +GF_Err gf_odf_size_lang(GF_Language *ld, u32 *outSize); +GF_Err gf_odf_size_isom_iod(GF_IsomInitialObjectDescriptor *iod, u32 *outSize); +GF_Err gf_odf_size_isom_od(GF_IsomObjectDescriptor *od, u32 *outSize); +GF_Err gf_odf_size_od(GF_ObjectDescriptor *od, u32 *outSize); +GF_Err gf_odf_size_oci_date(GF_OCI_Data *ocd, u32 *outSize); +GF_Err gf_odf_size_oci_name(GF_OCICreators *ocn, u32 *outSize); +GF_Err gf_odf_size_pl_idx(GF_PL_IDX *plid, u32 *outSize); +GF_Err gf_odf_size_qos(GF_QoS_Descriptor *qos, u32 *outSize); +GF_Err gf_odf_size_rating(GF_Rating *rd, u32 *outSize); +GF_Err gf_odf_size_reg(GF_Registration *reg, u32 *outSize); +GF_Err gf_odf_size_short_text(GF_ShortTextual *std, u32 *outSize); +GF_Err gf_odf_size_smpte_camera(GF_SMPTECamera *cpd, u32 *outSize); +GF_Err gf_odf_size_sup_cid(GF_SCIDesc *scid, u32 *outSize); +GF_Err gf_odf_size_segment(GF_Segment *sd, u32 *outSize); +GF_Err gf_odf_size_mediatime(GF_MediaTime *mt, u32 *outSize); +GF_Err gf_odf_size_muxinfo(GF_MuxInfo *mi, u32 *outSize); +GF_Err gf_odf_size_ipmp_tool_list(GF_IPMP_ToolList *ipmptl, u32 *outSize); +GF_Err gf_odf_size_ipmp_tool(GF_IPMP_Tool *ipmp, u32 *outSize); +GF_Err gf_odf_size_auxvid(GF_AuxVideoDescriptor *ld, u32 *outSize); + +GF_Err gf_odf_write_iod(GF_BitStream *bs, GF_InitialObjectDescriptor *iod); +GF_Err gf_odf_write_esd(GF_BitStream *bs, GF_ESD *esd); +GF_Err gf_odf_write_dcd(GF_BitStream *bs, GF_DecoderConfig *dcd); +GF_Err gf_odf_write_slc(GF_BitStream *bs, GF_SLConfig *sl); +GF_Err gf_odf_write_cc(GF_BitStream *bs, GF_CCDescriptor *ccd); +GF_Err gf_odf_write_cc_date(GF_BitStream *bs, GF_CC_Date *cdd); +GF_Err gf_odf_write_cc_name(GF_BitStream *bs, GF_CC_Name *cnd); +GF_Err gf_odf_write_ci(GF_BitStream *bs, GF_CIDesc *cid); +GF_Err gf_odf_write_default(GF_BitStream *bs, GF_DefaultDescriptor *dd); +GF_Err gf_odf_write_esd_inc(GF_BitStream *bs, GF_ES_ID_Inc *esd_inc); +GF_Err gf_odf_write_esd_ref(GF_BitStream *bs, GF_ES_ID_Ref *esd_ref); +GF_Err gf_odf_write_exp_text(GF_BitStream *bs, GF_ExpandedTextual *etd); +GF_Err gf_odf_write_pl_ext(GF_BitStream *bs, GF_PLExt *pld); +GF_Err gf_odf_write_ipi_ptr(GF_BitStream *bs, GF_IPIPtr *ipid); +GF_Err gf_odf_write_ipmp(GF_BitStream *bs, GF_IPMP_Descriptor *ipmp); +GF_Err gf_odf_write_ipmp_ptr(GF_BitStream *bs, GF_IPMPPtr *ipmpd); +GF_Err gf_odf_write_kw(GF_BitStream *bs, GF_KeyWord *kwd); +GF_Err gf_odf_write_lang(GF_BitStream *bs, GF_Language *ld); +GF_Err gf_odf_write_isom_iod(GF_BitStream *bs, GF_IsomInitialObjectDescriptor *iod); +GF_Err gf_odf_write_isom_od(GF_BitStream *bs, GF_IsomObjectDescriptor *od); +GF_Err gf_odf_write_od(GF_BitStream *bs, GF_ObjectDescriptor *od); +GF_Err gf_odf_write_oci_date(GF_BitStream *bs, GF_OCI_Data *ocd); +GF_Err gf_odf_write_oci_name(GF_BitStream *bs, GF_OCICreators *ocn); +GF_Err gf_odf_write_pl_idx(GF_BitStream *bs, GF_PL_IDX *plid); +GF_Err gf_odf_write_qos(GF_BitStream *bs, GF_QoS_Descriptor *qos); +GF_Err gf_odf_write_rating(GF_BitStream *bs, GF_Rating *rd); +GF_Err gf_odf_write_reg(GF_BitStream *bs, GF_Registration *reg); +GF_Err gf_odf_write_short_text(GF_BitStream *bs, GF_ShortTextual *std); +GF_Err gf_odf_write_smpte_camera(GF_BitStream *bs, GF_SMPTECamera *cpd); +GF_Err gf_odf_write_sup_cid(GF_BitStream *bs, GF_SCIDesc *scid); +GF_Err gf_odf_write_segment(GF_BitStream *bs, GF_Segment *sd); +GF_Err gf_odf_write_mediatime(GF_BitStream *bs, GF_MediaTime *mt); +GF_Err gf_odf_write_muxinfo(GF_BitStream *bs, GF_MuxInfo *mi); +GF_Err gf_odf_write_ipmp_tool_list(GF_BitStream *bs, GF_IPMP_ToolList *ipmptl); +GF_Err gf_odf_write_ipmp_tool(GF_BitStream *bs, GF_IPMP_Tool *ipmp); +GF_Err gf_odf_write_auxvid(GF_BitStream *bs, GF_AuxVideoDescriptor *ld); + +GF_Descriptor *gf_odf_new_text_cfg(); +GF_Err gf_odf_del_text_cfg(GF_TextConfig *desc); +GF_Descriptor *gf_odf_new_tx3g(); +GF_Err gf_odf_del_tx3g(GF_TextSampleDescriptor *sd); + +/*our commands base functions*/ +GF_ODCom *gf_odf_create_command(u8 tag); +GF_Err gf_odf_delete_command(GF_ODCom *com); +GF_Err gf_odf_parse_command(GF_BitStream *bs, GF_ODCom **com, u32 *com_size); +GF_Err gf_odf_read_command(GF_BitStream *bs, GF_ODCom *com, u32 gf_odf_size_command); +GF_Err gf_odf_write_command(GF_BitStream *bs, GF_ODCom *com); + +GF_ODCom *gf_odf_new_od_remove(); +GF_ODCom *gf_odf_new_od_update(); +GF_ODCom *gf_odf_new_esd_update(); +GF_ODCom *gf_odf_new_esd_remove(); +GF_ODCom *gf_odf_new_ipmp_update(); +GF_ODCom *gf_odf_new_ipmp_remove(); +GF_ODCom *gf_odf_new_base_command(); + +GF_Err gf_odf_del_od_remove(GF_ODRemove *ODRemove); +GF_Err gf_odf_del_od_update(GF_ODUpdate *ODUpdate); +GF_Err gf_odf_del_esd_update(GF_ESDUpdate *ESDUpdate); +GF_Err gf_odf_del_esd_remove(GF_ESDRemove *ESDRemove); +GF_Err gf_odf_del_ipmp_update(GF_IPMPUpdate *IPMPDUpdate); +GF_Err gf_odf_del_ipmp_remove(GF_IPMPRemove *IPMPDRemove); +GF_Err gf_odf_del_base_command(GF_BaseODCom *bcRemove); + +GF_Err gf_odf_read_od_remove(GF_BitStream *bs, GF_ODRemove *odRem, u32 gf_odf_size_command); +GF_Err gf_odf_read_od_update(GF_BitStream *bs, GF_ODUpdate *odUp, u32 gf_odf_size_command); +GF_Err gf_odf_read_esd_update(GF_BitStream *bs, GF_ESDUpdate *esdUp, u32 gf_odf_size_command); +GF_Err gf_odf_read_esd_remove(GF_BitStream *bs, GF_ESDRemove *esdRem, u32 gf_odf_size_command); +GF_Err gf_odf_read_ipmp_update(GF_BitStream *bs, GF_IPMPUpdate *ipmpUp, u32 gf_odf_size_command); +GF_Err gf_odf_read_ipmp_remove(GF_BitStream *bs, GF_IPMPRemove *ipmpRem, u32 gf_odf_size_command); +GF_Err gf_odf_read_base_command(GF_BitStream *bs, GF_BaseODCom *bcRem, u32 gf_odf_size_command); + +GF_Err gf_odf_size_od_remove(GF_ODRemove *odRem, u32 *outSize); +GF_Err gf_odf_size_od_update(GF_ODUpdate *odUp, u32 *outSize); +GF_Err gf_odf_size_esd_update(GF_ESDUpdate *esdUp, u32 *outSize); +GF_Err gf_odf_size_esd_remove(GF_ESDRemove *esdRem, u32 *outSize); +GF_Err gf_odf_size_ipmp_update(GF_IPMPUpdate *ipmpUp, u32 *outSize); +GF_Err gf_odf_size_ipmp_remove(GF_IPMPRemove *ipmpRem, u32 *outSize); +GF_Err gf_odf_size_base_command(GF_BaseODCom *bcRem, u32 *outSize); + +GF_Err gf_odf_write_od_remove(GF_BitStream *bs, GF_ODRemove *odRem); +GF_Err gf_odf_write_od_update(GF_BitStream *bs, GF_ODUpdate *odUp); +GF_Err gf_odf_write_esd_update(GF_BitStream *bs, GF_ESDUpdate *esdUp); +GF_Err gf_odf_write_esd_remove(GF_BitStream *bs, GF_ESDRemove *esdRem); +GF_Err gf_odf_write_ipmp_update(GF_BitStream *bs, GF_IPMPUpdate *ipmpUp); +GF_Err gf_odf_write_ipmp_remove(GF_BitStream *bs, GF_IPMPRemove *ipmpRem); +GF_Err gf_odf_write_base_command(GF_BitStream *bs, GF_BaseODCom *bcRem); + + + +/*dumping*/ +GF_Err gf_odf_dump_iod(GF_InitialObjectDescriptor *iod, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_esd(GF_ESD *esd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_dcd(GF_DecoderConfig *dcd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_slc(GF_SLConfig *sl, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_cc(GF_CCDescriptor *ccd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_cc_date(GF_CC_Date *cdd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_cc_name(GF_CC_Name *cnd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ci(GF_CIDesc *cid, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_default(GF_DefaultDescriptor *dd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_esd_inc(GF_ES_ID_Inc *esd_inc, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_esd_ref(GF_ES_ID_Ref *esd_ref, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_exp_text(GF_ExpandedTextual *etd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_pl_ext(GF_PLExt *pld, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipi_ptr(GF_IPIPtr *ipid, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipmp(GF_IPMP_Descriptor *ipmp, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipmp_ptr(GF_IPMPPtr *ipmpd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_kw(GF_KeyWord *kwd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_lang(GF_Language *ld, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_isom_iod(GF_IsomInitialObjectDescriptor *iod, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_isom_od(GF_IsomObjectDescriptor *od, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_od(GF_ObjectDescriptor *od, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_oci_date(GF_OCI_Data *ocd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_oci_name(GF_OCICreators *ocn, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_pl_idx(GF_PL_IDX *plid, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_qos(GF_QoS_Descriptor *qos, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_rating(GF_Rating *rd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_reg(GF_Registration *reg, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_short_text(GF_ShortTextual *std, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_smpte_camera(GF_SMPTECamera *cpd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_sup_cid(GF_SCIDesc *scid, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_segment(GF_Segment *sd, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_mediatime(GF_MediaTime *mt, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_muxinfo(GF_MuxInfo *mi, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_bifs_cfg(GF_BIFSConfig *dsi, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_laser_cfg(GF_LASERConfig *dsi, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ui_cfg(GF_UIConfig *dsi, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_txtcfg(GF_TextConfig *desc, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipmp_tool_list(GF_IPMP_ToolList *tl, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipmp_tool(GF_IPMP_Tool*t, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_aux_vid(GF_AuxVideoDescriptor *ld, FILE *trace, u32 indent, Bool XMTDump); + + +GF_Err gf_odf_dump_od_update(GF_ODUpdate *com, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_od_remove(GF_ODRemove *com, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_esd_update(GF_ESDUpdate *com, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_esd_remove(GF_ESDRemove *com, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipmp_update(GF_IPMPUpdate *com, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_ipmp_remove(GF_IPMPRemove *com, FILE *trace, u32 indent, Bool XMTDump); +GF_Err gf_odf_dump_base_command(GF_BaseODCom *com, FILE *trace, u32 indent, Bool XMTDump); + +#endif /*_GF_OD_DEV_H_*/ + diff --git a/include/gpac/internal/odf_parse_common.h b/include/gpac/internal/odf_parse_common.h new file mode 100644 index 0000000..4b0b514 --- /dev/null +++ b/include/gpac/internal/odf_parse_common.h @@ -0,0 +1,51 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / MPEG-4 ObjectDescriptor 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_OD_PARSE_COMMON_H_ +#define _GF_OD_PARSE_COMMON_H_ +#include <gpac/setup.h> + +#define GET_U8(field) { u32 d; if (strstr(val, "0x")) { ret += sscanf(val, "%x", &d); if (ret) field = (u8) d; } else { ret += sscanf(val, "%u", &d); if (ret) field = (u8) d; } } +#define GET_U16(field) { u16 d; if (strstr(val, "0x")) { ret += sscanf(val, "%hx", &d); if (ret) field = d; } else { ret += sscanf(val, "%hu", &d); if (ret) field = d; } } +#define GET_S16(field) { s16 d; if (strstr(val, "0x")) { ret += sscanf(val, "%hx", (u16*)&d); if (ret) field = d; } else { ret += sscanf(val, "%hd", &d); if (ret) field = d; } } +#define GET_U32(field) { u32 d; if (strstr(val, "0x")) { ret += sscanf(val, "%x", &d); if (ret) field = d; } else { ret += sscanf(val, "%ud", &d); if (ret) field = d; } } +#define GET_S32(field) { s32 d; if (strstr(val, "0x")) { ret += sscanf(val, "%x", (u32*)&d); if (ret) field = d; } else { ret += sscanf(val, "%d", &d); if (ret) field = d; } } +#define GET_BOOL(field) { ret = 1; field = (!stricmp(val, "true") || !strcmp(val, "1")) ? GF_TRUE : GF_FALSE; } +#define GET_U64(field) { u64 d; if (strstr(val, "0x")) { ret += sscanf(val, LLX, &d); if (ret) field = d; } else { ret += sscanf(val, LLU, &d); if (ret) field = d; } } + +#define GET_DOUBLE(field) { Float v; ret = 1; sscanf(val, "%f", &v); field = (Double) v;} +#define GET_STRING(field) { \ + ret = 1;\ + (field) = gf_strdup(val); \ + if (field) { \ + if (val[0] == '"') strcpy((field), val+1); \ + if ((field)[strlen(field)-1] == '"') (field)[strlen(field)-1] = 0;\ + }\ + } + + +#endif /* _GF_OD_PARSE_COMMON_H_ */ + diff --git a/include/gpac/internal/ogg.h b/include/gpac/internal/ogg.h new file mode 100644 index 0000000..87824e2 --- /dev/null +++ b/include/gpac/internal/ogg.h @@ -0,0 +1,213 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: code raw [Vorbis] packets into framed OggSquish stream and + decode Ogg streams back into raw packets + + note: The CRC code is directly derived from public domain code by + Ross Williams (ross@guest.adelaide.edu.au). See docs/framing.html + for details. + + ********************************************************************/ + + +#ifndef _GF_OGG_H_ +#define _GF_OGG_H_ + +#include <gpac/tools.h> + +#ifndef GPAC_DISABLE_OGG + +/*DON'T CLASH WITH OFFICIAL OGG IF ALREADY INCLUDED*/ +#ifndef _OGG_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + s32 endbyte; + s32 endbit; + + u8 *buffer; + u8 *ptr; + s32 storage; +} oggpack_buffer; + +/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ + +typedef struct { + u8 *header; + s32 header_len; + u8 *body; + s32 body_len; +} ogg_page; + +/* ogg_stream_state contains the current encode/decode state of a logical + Ogg bitstream **********************************************************/ + +typedef struct { + unsigned char *body_data; /* bytes from packet bodies */ + s32 body_storage; /* storage elements allocated */ + s32 body_fill; /* elements stored; fill mark */ + s32 body_returned; /* elements of fill returned */ + + + s32 *lacing_vals; /* The values that will go to the segment table */ + s64 *granule_vals; /* granulepos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + s32 lacing_storage; + s32 lacing_fill; + s32 lacing_packet; + s32 lacing_returned; + + unsigned char header[282]; /* working space for header encode */ + s32 header_fill; + + s32 e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + s32 b_o_s; /* set after we've written the initial page + of a logical bitstream */ + s32 serialno; + s32 pageno; + s64 packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ + s64 granulepos; + +} ogg_stream_state; + +/* ogg_packet is used to encapsulate the data and metadata belonging + to a single raw Ogg/Vorbis packet *************************************/ + +typedef struct { + u8 *packet; + s32 bytes; + s32 b_o_s; + s32 e_o_s; + + s64 granulepos; + + s64 packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ +} ogg_packet; + +typedef struct { + u8 *data; + s32 storage; + s32 fill; + s32 returned; + + s32 unsynced; + s32 headerbytes; + s32 bodybytes; +} ogg_sync_state; + + + +/* Ogg BITSTREAM PRIMITIVES: bitstream ************************/ + +void oggpack_writeinit(oggpack_buffer *b); +void oggpack_writetrunc(oggpack_buffer *b,s32 bits); +void oggpack_writealign(oggpack_buffer *b); +void oggpack_writecopy(oggpack_buffer *b,void *source,s32 bits); +void oggpack_reset(oggpack_buffer *b); +void oggpack_writeclear(oggpack_buffer *b); +void oggpack_readinit(oggpack_buffer *b,u8 *buf,s32 bytes); +void oggpack_write(oggpack_buffer *b,u32 value,s32 bits); +s32 oggpack_look(oggpack_buffer *b,s32 bits); +s32 oggpack_look1(oggpack_buffer *b); +void oggpack_adv(oggpack_buffer *b,s32 bits); +void oggpack_adv1(oggpack_buffer *b); +s32 oggpack_read(oggpack_buffer *b,s32 bits); +s32 oggpack_read1(oggpack_buffer *b); +s32 oggpack_bytes(oggpack_buffer *b); +s32 oggpack_bits(oggpack_buffer *b); +u8 *oggpack_get_buffer(oggpack_buffer *b); + +void oggpackB_writeinit(oggpack_buffer *b); +void oggpackB_writetrunc(oggpack_buffer *b,s32 bits); +void oggpackB_writealign(oggpack_buffer *b); +void oggpackB_writecopy(oggpack_buffer *b,void *source,s32 bits); +void oggpackB_reset(oggpack_buffer *b); +void oggpackB_writeclear(oggpack_buffer *b); +void oggpackB_readinit(oggpack_buffer *b,u8 *buf,s32 bytes); +void oggpackB_write(oggpack_buffer *b,u32 value,s32 bits); +s32 oggpackB_look(oggpack_buffer *b,s32 bits); +s32 oggpackB_look1(oggpack_buffer *b); +void oggpackB_adv(oggpack_buffer *b,s32 bits); +void oggpackB_adv1(oggpack_buffer *b); +s32 oggpackB_read(oggpack_buffer *b,s32 bits); +s32 oggpackB_read1(oggpack_buffer *b); +s32 oggpackB_bytes(oggpack_buffer *b); +s32 oggpackB_bits(oggpack_buffer *b); +u8 *oggpackB_get_buffer(oggpack_buffer *b); + +/* Ogg BITSTREAM PRIMITIVES: encoding **************************/ + +s32 ogg_stream_packetin(ogg_stream_state *os, ogg_packet *op); +s32 ogg_stream_pageout(ogg_stream_state *os, ogg_page *og); +s32 ogg_stream_flush(ogg_stream_state *os, ogg_page *og); + +/* Ogg BITSTREAM PRIMITIVES: decoding **************************/ + +s32 ogg_sync_init(ogg_sync_state *oy); +s32 ogg_sync_clear(ogg_sync_state *oy); +s32 ogg_sync_reset(ogg_sync_state *oy); +s32 ogg_sync_destroy(ogg_sync_state *oy); + +u8 *ogg_sync_buffer(ogg_sync_state *oy, s32 size); +s32 ogg_sync_wrote(ogg_sync_state *oy, s32 bytes); +s32 ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); +s32 ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); +s32 ogg_stream_pagein(ogg_stream_state *os, ogg_page *og); +s32 ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op); +s32 ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op); + +/* Ogg BITSTREAM PRIMITIVES: general ***************************/ + +s32 ogg_stream_init(ogg_stream_state *os,s32 serialno); +s32 ogg_stream_clear(ogg_stream_state *os); +s32 ogg_stream_reset(ogg_stream_state *os); +s32 ogg_stream_reset_serialno(ogg_stream_state *os,s32 serialno); +s32 ogg_stream_destroy(ogg_stream_state *os); +s32 ogg_stream_eos(ogg_stream_state *os); +void ogg_page_checksum_set(ogg_page *og); +s32 ogg_page_version(ogg_page *og); +s32 ogg_page_continued(ogg_page *og); +s32 ogg_page_bos(ogg_page *og); +s32 ogg_page_eos(ogg_page *og); +s64 ogg_page_granulepos(ogg_page *og); +s32 ogg_page_serialno(ogg_page *og); +s32 ogg_page_pageno(ogg_page *og); +s32 ogg_page_packets(ogg_page *og); + +void ogg_packet_clear(ogg_packet *op); + + +#ifdef __cplusplus +} +#endif + + +#endif /*_OGG_H*/ + +#endif /*GPAC_DISABLE_OGG*/ + +#endif /*_GF_OGG_H_*/ + diff --git a/include/gpac/internal/reedsolomon.h b/include/gpac/internal/reedsolomon.h new file mode 100644 index 0000000..5d6f2ab --- /dev/null +++ b/include/gpac/internal/reedsolomon.h @@ -0,0 +1,78 @@ +/* Reed Solomon Coding for glyphs + * + * (c) Henry Minsky (hqm@ua.com), Universal Access Inc. (1991-1996) + * + * + */ + +/**************************************************************** + + Below is NPAR, the only compile-time parameter you should have to + modify. + + It is the number of parity bytes which will be appended to + your data to create a codeword. + + Note that the maximum codeword size is 255, so the + sum of your message length plus parity should be less than + or equal to this maximum limit. + + In practice, you will get slooow error correction and decoding + if you use more than a reasonably small number of parity bytes. + (say, 10 or 20) + + ****************************************************************/ +#ifndef _ECC_H_ +#define _ECC_H_ +#define NPAR 64 + +/****************************************************************/ + +#define TRUE 1 +#define FALSE 0 + +typedef unsigned long BIT32; +typedef unsigned short BIT16; + +/* **************************************************************** */ + +/* Maximum degree of various polynomials. */ +#define MAXDEG (NPAR*2) + +/*************************************/ +/* Encoder parity bytes */ +extern int pBytes[MAXDEG]; + +/* Decoder syndrome bytes */ +extern int synBytes[MAXDEG]; + +/* print debugging info */ +extern int RS_DEBUG; + +/* Reed Solomon encode/decode routines */ +void initialize_ecc (void); +int check_syndrome (void); +void decode_data (unsigned char data[], int nbytes); +void encode_data (unsigned char msg[], int nbytes, unsigned char dst[]); + + +/* galois arithmetic tables */ +extern int gexp[]; +extern int glog[]; + +void init_galois_tables (void); +int ginv(int elt); +int gmult(int a, int b); + + +/* Error location routines */ +int correct_errors_erasures (unsigned char codeword[], int csize,int nerasures, int erasures[]); + +/* polynomial arithmetic */ +void add_polys(int dst[], int src[]) ; +void scale_poly(int k, int poly[]); +void mult_polys(int dst[], int p1[], int p2[]); + +void copy_poly(int dst[], int src[]); +void zero_poly(int poly[]); +#endif //_ECC_H_ diff --git a/include/gpac/internal/scenegraph_dev.h b/include/gpac/internal/scenegraph_dev.h new file mode 100644 index 0000000..d8f2a10 --- /dev/null +++ b/include/gpac/internal/scenegraph_dev.h @@ -0,0 +1,960 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Scene Graph 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_SCENEGRAPH_DEV_H_ +#define _GF_SCENEGRAPH_DEV_H_ + +/*defined this macro to enable cyclic render*/ +#define GF_CYCLIC_TRAVERSE_ON + +/*defined this macro to enable scene replacement from inside (through conditional)*/ +//#define GF_SELF_REPLACE_ENABLE + +/*for vrml base types, ROUTEs and PROTOs*/ +#include <gpac/scenegraph_vrml.h> + +#include <gpac/scenegraph_svg.h> + +#include <gpac/thread.h> + +#ifdef __cplusplus +extern "C" { +#endif + + + +void gf_node_setup(GF_Node *p, u32 tag); + +typedef struct _parent_list +{ + struct _parent_list *next; + GF_Node *node; +} GF_ParentList; + + +/*internal flag reserved for NodeID*/ +#define GF_NODE_IS_DEF 0x80000000 // 1<<31 +/*internal flag reserved for activate/deactivate*/ +#define GF_NODE_IS_DEACTIVATED 0x40000000 // 1<<30 +/*internal flag reserved for node with scripting bindings*/ +#define GF_NODE_HAS_BINDING 0x20000000 // 1<<29 + +#ifdef GF_CYCLIC_TRAVERSE_ON +#define GF_NODE_IN_TRAVERSE 0x10000000 // 1<<28 +#define GF_NODE_INTERNAL_FLAGS 0xF0000000 +#else +#define GF_NODE_INTERNAL_FLAGS 0xE0000000 +#endif + +struct _node_interactive_ext +{ + /*routes on eventOut, ISed routes, ... for VRML-based scene graphs + THIS IS DYNAMICALLY CREATED*/ + GF_List *routes; + +#ifdef GPAC_HAS_QJS + /*JS bindings if any - THIS IS DYNAMICALLY CREATED + This speeds up field modif notification (script bindings are listed here)*/ + struct _node_js_binding *js_binding; +#endif + +#ifndef GPAC_DISABLE_SVG + /*event listeners - THIS IS DYNAMICALLY CREATED*/ + GF_DOMEventTarget *dom_evt; + + /* SVG animations are registered in the target node - THIS IS DYNAMICALLY CREATED*/ + GF_List *animations; +#endif + +}; + +typedef struct _nodepriv +{ + /*node type*/ + u16 tag; + /*number of instances of this node in the graph*/ + u16 num_instances; + /*node flags*/ + u32 flags; + /*scenegraph holding the node*/ + struct __tag_scene_graph *scenegraph; + + /*user defined callback function */ + void (*UserCallback)(struct _base_node *node, void *render_stack, Bool node_destroy); + /*user defined stack*/ + void *UserPrivate; + + /*list of all parent nodes (needed to invalidate parent tree)*/ + GF_ParentList *parents; + + /*holder for all interactive stuff - THIS IS DYNAMICALLY CREATED*/ + struct _node_interactive_ext *interact; +} NodePriv; + + +typedef struct __tag_node_id +{ + struct __tag_node_id *next; + GF_Node *node; + + /*node ID*/ + u32 NodeID; + /*node def name*/ + char *NodeName; +} NodeIDedItem; + +typedef struct +{ + char *name; + char *qname; + u32 xmlns_id; +} GF_XMLNS; + +struct __tag_scene_graph +{ + /*used to discriminate between node and scenegraph*/ + u64 __reserved_null; + + /*all DEF nodes (explicit)*/ + NodeIDedItem *id_node, *id_node_last; + + /*pointer to the root node*/ + GF_Node *RootNode; + + /*nodes exported from this scene graph*/ + GF_List *exported_nodes; + + + /*user private data*/ + void *userpriv; + + /*callback routines*/ + /*node callback*/ + gf_sg_node_init_callback NodeCallback; + /*real scene time callback*/ + Double (*GetSceneTime)(void *userpriv); + + /*parent scene if any*/ + struct __tag_scene_graph *parent_scene; + + /*size info and pixel metrics - this is not used internally, however it helps when rendering + and decoding modules don't know each-other (as in MPEG4)*/ + u32 width, height; + Bool usePixelMetrics; + + Bool modified; + + /*application interface for javascript*/ + gf_sg_script_action script_action; + void *script_action_cbck; + + /*script loader*/ + void (*script_load)(GF_Node *node); + /*callback to JS upon node modif*/ + void (*on_node_modified)(struct __tag_scene_graph *sg, GF_Node *node, GF_FieldInfo *info, GF_Node *script); + +#ifdef GF_SELF_REPLACE_ENABLE + /*to detect replace scene from within conditionals*/ + Bool graph_has_been_reset; +#endif + + + /*namespaces list. This list is used while parsing/dumping the tree to store the hierarchy of xmlns attributes in subtrees. + It is a stack of GF_XMLNS structures pushed/popped at each element*/ + GF_List *ns; + + /*temp storage for name conversions*/ + char szNameBuffer[100]; + +#ifndef GPAC_DISABLE_VRML + + /*all routes available*/ + GF_List *Routes; + + /*when a proto is instantiated it creates its own scene graph. BIFS/VRML specify that the namespace is the same + (eg cannot reuse a NodeID or route name/ID), but this could be done differently by some other stds + if NULL this is the main scenegraph*/ + struct _proto_instance *pOwningProto; + + /*all first-level protos of the graph (the only ones that can be instantiated in this graph)*/ + GF_List *protos; + /*all first-level protos of the graph not currently registered - memory handling of graph only*/ + GF_List *unregistered_protos; + + /*routes to be activated (cascade model). This is used at the top-level graph only (eg + proto routes use that too, ecept ISed fields). It is the app responsability to + correctly connect or browse scene graphs connected through Inline*/ + GF_List *routes_to_activate; + + /*since events may trigger deletion of objects we use a 2 step delete*/ + GF_List *routes_to_destroy; + + u32 simulation_tick; + + GF_SceneGraph *(*GetExternProtoLib)(void *userpriv, MFURL *lib_url); + + u32 max_defined_route_id; + + /*global qp used in BIFS coding*/ + GF_Node *global_qp; +#endif + + +#ifndef GPAC_DISABLE_SVG + /*use stack as used in the dom_fire_event - this is only valid during an event fire, and may be NULL*/ + GF_List *use_stack; + Bool abort_bubbling; + + + GF_Mutex *dom_evt_mx; + GF_DOMEventTarget *dom_evt; + u32 nb_evts_focus; + u32 nb_evts_mouse; + u32 nb_evts_key; + u32 nb_evts_ui; + u32 nb_evts_text; + u32 nb_evts_smil; + u32 nb_evts_mutation; + u32 nb_evts_laser; + u32 nb_evts_media; + u32 nb_evts_svg; + GF_DOMEventCategory dom_evt_filter; + + GF_List *xlink_hrefs; + GF_List *smil_timed_elements; + GF_List *modified_smil_timed_elements; + Bool update_smil_timing; + + /*listeners to add*/ + GF_List *listeners_to_add; + +#ifdef GPAC_HAS_QJS + struct __tag_svg_script_ctx *svg_js; + struct __tag_html_media_script_ctx *html_media_js; +#endif + +#endif + +#ifdef GPAC_HAS_QJS + GF_List *scripts; + /* + Note about reference counter + + A DOM document (<=> scenegraph) may be created through javascript, and the JS object having created the + document may be destroyed while the document is still in use. Moreover with XMLHttpRequest, the + "associated" doc is re-created at each request, but the script may still refer to the original document. + Since the document doesn't have a fixed owner, a reference counter is use and the scenegraph is kept alive + until the last object using it is destroyed. + + If this counter is set to 0 when creating DOM Elements/..., this means the scenegraph is hold by an external + entity (typically the player), and cannot be destroyed from the scripting engine + */ + u32 reference_count; + /*DOM nodes*/ + GF_List *objects; + struct _dom_js_data *js_data; + Bool trigger_gc; + + u32 (*get_element_class)(GF_Node *n); + u32 (*get_document_class)(GF_SceneGraph *n); + struct __gf_filter_session *attached_session; +#endif +}; + +void gf_sg_parent_setup(GF_Node *pNode); +void gf_sg_parent_reset(GF_Node *pNode); + +void gf_node_changed_internal(GF_Node *node, GF_FieldInfo *field, Bool notify_scripts); + +void gf_node_dirty_parent_graph(GF_Node *node); + + +/*BASE node (GF_Node) destructor*/ +void gf_node_free(GF_Node *node); + +/*node destructor dispatcher: redirects destruction for each graph type: VRML/MPEG4, X3D, SVG...)*/ +void gf_node_del(GF_Node *node); + +/*creates an undefined GF_Node - for parsing only*/ +GF_Node *gf_sg_new_base_node(); + +#ifndef GPAC_DISABLE_VRML + +struct _route +{ + u8 is_setup; + /*set to 1 for proto IS fields*/ + u8 IS_route; + /*set to 1 for JS route to fun*/ + u8 script_route; + + u32 ID; + char *name; + + /*scope of this route*/ + GF_SceneGraph *graph; + u32 lastActivateTime; + + GF_Node *FromNode; + GF_FieldInfo FromField; + + GF_Node *ToNode; + GF_FieldInfo ToField; +}; + +void gf_sg_route_unqueue(GF_SceneGraph *sg, GF_Route *r); +/*returns TRUE if route modified destination node*/ +Bool gf_sg_route_activate(GF_Route *r); +void gf_sg_route_queue(GF_SceneGraph *pSG, GF_Route *r); +void gf_sg_destroy_routes(GF_SceneGraph *sg); +void gf_sg_route_setup(GF_Route *r); + + +/*MPEG4 def*/ +GF_Node *gf_sg_mpeg4_node_new(u32 NodeTag); +u32 gf_sg_mpeg4_node_get_child_ndt(GF_Node *node); +GF_Err gf_sg_mpeg4_node_get_field_index(GF_Node *node, u32 inField, u8 code_mode, u32 *fieldIndex); +GF_Err gf_sg_mpeg4_node_get_field(GF_Node *node, GF_FieldInfo *field); +u32 gf_sg_mpeg4_node_get_field_count(GF_Node *node, u8 code_mode); +void gf_sg_mpeg4_node_del(GF_Node *node); +const char *gf_sg_mpeg4_node_get_class_name(u32 NodeTag); +Bool gf_sg_mpeg4_node_get_aq_info(GF_Node *node, u32 FieldIndex, u8 *QType, u8 *AType, Fixed *b_min, Fixed *b_max, u32 *QT13_bits); +s32 gf_sg_mpeg4_node_get_field_index_by_name(GF_Node *node, char *name); + +#ifndef GPAC_DISABLE_X3D +/*X3D def*/ +GF_Node *gf_sg_x3d_node_new(u32 NodeTag); +GF_Err gf_sg_x3d_node_get_field(GF_Node *node, GF_FieldInfo *field); +u32 gf_sg_x3d_node_get_field_count(GF_Node *node); +void gf_sg_x3d_node_del(GF_Node *node); +const char *gf_sg_x3d_node_get_class_name(u32 NodeTag); +s32 gf_sg_x3d_node_get_field_index_by_name(GF_Node *node, char *name); +Bool gf_x3d_get_node_type(u32 NDT_Tag, u32 NodeTag); +#endif + + +/*VRML/X3D types*/ +void gf_sg_mfint32_del(MFInt32 par); +void gf_sg_mffloat_del(MFFloat par); +void gf_sg_mfdouble_del(MFDouble par); +void gf_sg_mfbool_del(MFBool par); +void gf_sg_mfcolor_del(MFColor par); +void gf_sg_mfcolorrgba_del(MFColorRGBA par); +void gf_sg_mfrotation_del(MFRotation par); +void gf_sg_mfstring_del(MFString par); +void gf_sg_mftime_del(MFTime par); +void gf_sg_mfvec2f_del(MFVec2f par); +void gf_sg_mfvec3f_del(MFVec3f par); +void gf_sg_mfvec4f_del(MFVec4f par); +void gf_sg_mfvec2d_del(MFVec2d par); +void gf_sg_mfvec3d_del(MFVec3d par); +void gf_sg_sfimage_del(SFImage im); +void gf_sg_sfstring_del(SFString par); +void gf_sg_mfscript_del(MFScript sc); +void gf_sg_sfcommand_del(SFCommandBuffer cb); +void gf_sg_sfurl_del(SFURL url); + void gf_sg_mfattrref_del(MFAttrRef par); + +Bool gf_sg_vrml_node_init(GF_Node *node); +Bool gf_sg_vrml_node_changed(GF_Node *node, GF_FieldInfo *field); + + +// +// MF Fields tools +// WARNING: MF / SF Nodes CANNOT USE THESE FUNCTIONS +// + +//return the size (in bytes) of fixed fields (buffers are handled as a char ptr , 1 byte) +u32 gf_sg_vrml_get_sf_size(u32 FieldType); + +/*returns field type from its name*/ +u32 gf_sg_field_type_by_name(char *fieldType); + +/*clones the command in another graph - needed for uncompressed conditional in protos +if force_clone is not set and the target graph is the same as the command graph, nodes are just registered +with the new commands rather than cloned*/ +GF_Command *gf_sg_vrml_command_clone(GF_Command *com, GF_SceneGraph *inGraph, Bool force_clone); + + +/* + Proto node + +*/ + +/*field interface to codec. This is used to do the node decoding, index translation +and all QP/BIFS Anim parsing. */ +struct _protofield +{ + u8 EventType; + u8 FieldType; + /*if UseName, otherwise fieldN*/ + char *FieldName; + + /*default field value*/ + void *def_value; + + GF_Node *def_sfnode_value; + GF_ChildNodeItem *def_mfnode_value; + + /*coding indexes*/ + u32 IN_index, OUT_index, DEF_index, ALL_index; + + /*Quantization*/ + u32 QP_Type, hasMinMax; + void *qp_min_value, *qp_max_value; + /*this is for QP=13 only*/ + u32 NumBits; + + /*Animation*/ + u32 Anim_Type; + + void *userpriv; + void (*OnDelete)(void *ptr); +}; + +GF_ProtoFieldInterface *gf_sg_proto_new_field_interface(u32 FieldType); + +/*set QP and anim info for a proto field (BIFS allows for that in proto coding)*/ +GF_Err gf_bifs_proto_field_set_aq_info(GF_ProtoFieldInterface *field, u32 QP_Type, u32 hasMinMax, u32 QPSFType, void *qp_min_value, void *qp_max_value, u32 QP13_NumBits); + +/*proto field instance. since it is useless to duplicate all coding info, names and the like +we separate proto declaration and proto instanciation*/ +typedef struct +{ + u8 EventType; + u8 FieldType; + u8 has_been_accessed; + void *field_pointer; + void (*on_event_in)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ +} GF_ProtoField; + + +struct _proto +{ + /*1 - Prototype interface*/ + u32 ID; + char *Name; + GF_List *proto_fields; + + /*pointer to parent scene graph*/ + struct __tag_scene_graph *parent_graph; + /*pointer to proto scene graph*/ + struct __tag_scene_graph *sub_graph; + + /*2 - proto implementation as declared in the bitstream*/ + GF_List *node_code; + + /*num fields*/ + u32 NumIn, NumOut, NumDef, NumDyn; + + void *userpriv; + void (*OnDelete)(void *ptr); + + /*URL of extern proto lib (if none, URL is empty)*/ + MFURL ExternProto; + + /*list of instances*/ + GF_List *instances; +}; + +/*proto field API*/ +u32 gf_sg_proto_get_num_fields(GF_Node *node, u8 code_mode); +GF_Err gf_sg_proto_get_field(GF_Proto *proto, GF_Node *node, GF_FieldInfo *field); + +enum +{ + GF_SG_PROTO_LOADED = 1, + GF_SG_PROTO_IS_GROUPING = 2, + GF_SG_PROTO_HARDCODED = 4, +}; + +typedef struct _proto_instance +{ + /*this is a node*/ + BASE_NODE + + /*Prototype interface for coding and field addressing*/ + GF_Proto *proto_interface; + + /*proto implementation at run-time (aka the state of the nodes may differ across + different instances of the proto)*/ + GF_List *fields; + + /*a proto doesn't have one root SFnode but a collection of nodes for implementation*/ + GF_List *node_code; + + /*node for proto rendering, first of all declared nodes*/ + GF_Node *RenderingNode; + + /*in case the PROTO is destroyed*/ + char *proto_name; + + /*scripts are loaded once all IS routes are activated and node code is loaded*/ + GF_List *scripts_to_load; + + u32 flags; +} GF_ProtoInstance; + +/*destroy proto*/ +void gf_sg_proto_del_instance(GF_ProtoInstance *inst); +GF_Err gf_sg_proto_get_field_index(GF_ProtoInstance *proto, u32 index, u32 code_mode, u32 *all_index); +Bool gf_sg_proto_get_aq_info(GF_Node *Node, u32 FieldIndex, u8 *QType, u8 *AType, Fixed *b_min, Fixed *b_max, u32 *QT13_bits); +GF_Err gf_sg_proto_get_field_ind_static(GF_Node *Node, u32 inField, u8 IndexMode, u32 *allField); +GF_Node *gf_sg_proto_create_node(GF_SceneGraph *scene, GF_Proto *proto, GF_ProtoInstance *from_inst); +void gf_sg_proto_instantiate(GF_ProtoInstance *proto_node); + +/*get tag of first node in proto code - used for validation only*/ +u32 gf_sg_proto_get_root_tag(GF_Proto *proto); + + +/*to call when a proto field has been modified (at creation or through commands, modifications through events +are handled internally). +node can be the proto instance or a node from the proto code +this will call NodeChanged if needed, forward to proto/node or trigger any route if needed*/ +void gf_sg_proto_propagate_event(GF_Node *node, u32 fieldIndex, GF_Node *from_node); + +s32 gf_sg_proto_get_field_index_by_name(GF_Proto *proto, GF_Node *node, char *name); + +GF_Node *gf_vrml_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *inst_id_suffix); + +#endif /*GPAC_DISABLE_VRML*/ + + +/*specialized node unregister for Memory Commands - checks if the node(s) used in the command have been destroyed +during the reset. If so don't attempt to unregister the node*/ +GF_Err gf_node_try_destroy(GF_SceneGraph *sg, GF_Node *pNode, GF_Node *parentNode); + +#ifndef GPAC_DISABLE_SVG + + +/* reset functions for SVG types */ +void gf_svg_reset_iri(GF_SceneGraph *sg, XMLRI*iri); +/* delete functions for SVG types */ +void gf_svg_delete_paint (GF_SceneGraph *sg, SVG_Paint *paint); +void gf_smil_delete_times (GF_List *l); +/*for keyTimes, keyPoints and keySplines*/ +void gf_smil_delete_key_types (GF_List *l); + +u32 gf_node_get_attribute_count(GF_Node *node); +GF_Err gf_node_get_attribute_info(GF_Node *node, GF_FieldInfo *info) ; + + +/*SMIL anim tools*/ + +typedef struct __xlink_attrip_ptrs { + XMLRI *href; + SVG_ContentType *type; + SVG_String *title; + XMLRI *arcrole; + XMLRI *role; + SVG_String *show; + SVG_String *actuate; +} XLinkAttributesPointers; + +typedef struct __smil_time_attrip_ptrs { + SMIL_Times *begin, *end; + SVG_Clock *clipBegin, *clipEnd; + SMIL_Duration *dur; + SMIL_RepeatCount *repeatCount; + SMIL_Duration *repeatDur; + SMIL_Restart *restart; + SMIL_Fill *fill; + SMIL_Duration *max; + SMIL_Duration *min; + struct _smil_timing_rti *runtime; /* contains values for runtime handling of the SMIL timing */ +} SMILTimingAttributesPointers; + +typedef struct __smil_sync_attrip_ptrs { + SMIL_SyncBehavior *syncBehavior, *syncBehaviorDefault; + SMIL_SyncTolerance *syncTolerance, *syncToleranceDefault; + SVG_Boolean *syncMaster; + XMLRI *syncReference; +} SMILSyncAttributesPointers; + +typedef struct __smil_anim_attrip_ptrs { + SMIL_AttributeName *attributeName; + SMIL_AttributeType *attributeType; + SMIL_AnimateValue *to, *by, *from; + SMIL_AnimateValues *values; + SMIL_CalcMode *calcMode; + SMIL_Accumulate *accumulate; + SMIL_Additive *additive; + SMIL_KeySplines *keySplines; + SMIL_KeyTimes *keyTimes; + SVG_TransformType *type; + SVG_Boolean *lsr_enabled; + + SMIL_KeyPoints *keyPoints; + SVG_String *origin; + SVG_Rotate *rotate; + SVG_PathData *path; +} SMILAnimationAttributesPointers; + + +typedef struct { + GF_DOM_BASE_NODE + + /*shortcuts for xlink, anim, timing attributes*/ + XLinkAttributesPointers *xlinkp; + SMILAnimationAttributesPointers *animp; + SMILTimingAttributesPointers *timingp; +} SVGTimedAnimBaseElement; + +GF_Err gf_node_animation_add(GF_Node *node, void *animation); +GF_Err gf_node_animation_del(GF_Node *node); +u32 gf_node_animation_count(GF_Node *node); +void *gf_node_animation_get(GF_Node *node, u32 i); +Bool gf_svg_is_inherit(GF_FieldInfo *a); +Bool gf_svg_is_current_color(GF_FieldInfo *a); + +void gf_svg_reset_animate_values(SMIL_AnimateValues anim_values, GF_SceneGraph *sg); + +Bool gf_svg_is_timing_tag(u32 tag); +Bool gf_svg_is_animation_tag(u32 tag); +u32 gf_svg_get_modification_flags(SVG_Element *n, GF_FieldInfo *info); + +Bool gf_svg_resolve_smil_times(GF_Node *anim, void *event_base_element, GF_List *smil_times, Bool is_end, const char *node_name); + + +/* SMIL Timing structures */ +/* status of an SMIL timed element */ +enum { + SMIL_STATUS_WAITING_TO_BEGIN = 0, + SMIL_STATUS_ACTIVE, + SMIL_STATUS_POST_ACTIVE, + SMIL_STATUS_FROZEN, + SMIL_STATUS_DONE +}; + +typedef struct { + u32 activation_cycle; + u32 nb_iterations; + + /* for the case where min > simple duration*/ + Bool min_active; + + /* negative values mean indefinite */ + Double begin, + end, + simple_duration, + active_duration, + repeat_duration; + +} SMIL_Interval; + +struct _smil_timing_rti +{ + GF_Node *timed_elt; + SMILTimingAttributesPointers *timingp; + + Double scene_time; + Fixed normalized_simple_time; + Bool force_reevaluation; + + /* SMIL element life-cycle status */ + u8 status; + + SMIL_Interval *current_interval; + SMIL_Interval *next_interval; + + /* Evaluation of animations is postponed until tree traversal, so that inherit values can be computed + Other timed elements (audio, video, animation) are evaluated directly and do not require + scene tree traversal.*/ + Bool postpone; + + void (*evaluate)(struct _smil_timing_rti *rti, Fixed normalized_simple_time, GF_SGSMILTimingEvalState state); + GF_SGSMILTimingEvalState evaluate_status; + +#if 0 + /* is called only when the timed element is active */ + void (*activation)(struct _smil_timing_rti *rti, Fixed normalized_simple_time); + + /* is called (possibly many times) when the timed element is frozen */ + void (*freeze)(struct _smil_timing_rti *rti, Fixed normalized_simple_time); + + /* is called (only once) when the timed element is restored */ + void (*restore)(struct _smil_timing_rti *rti, Fixed normalized_simple_time); + + /* is called only when the timed element is inactive and receives a fraction event, the second parameter is ignored */ + void (*fraction_activation)(struct _smil_timing_rti *rti, Fixed normalized_simple_time); +#endif + /* simulated normalized simple time */ + Fixed fraction; + + Bool paused; + Double media_duration; + + /* shortcut when this rti corresponds to an animation */ + struct _smil_anim_rti *rai; +}; + +void gf_smil_timing_init_runtime_info(GF_Node *timed_elt); +void gf_smil_timing_delete_runtime_info(GF_Node *timed_elt, SMIL_Timing_RTI *rti); +Fixed gf_smil_timing_get_normalized_simple_time(SMIL_Timing_RTI *rti, Double scene_time, Bool *force_end); +/*returns 1 if an animation changed a value in the rendering tree */ +s32 gf_smil_timing_notify_time(SMIL_Timing_RTI *rti, Double scene_time); + + +/* SMIL Animation Structures */ +/* This structure is used per animated attribute, + it contains: + - all the animations applying to the same attribute, + - the specified value before any inheritance has been applied nor any animation started + (as specified in the SVG document), + - the presentation value passed from one animation to the next one, at the same level in the tree + - a boolean indicating if the animated attribute is in fact a property + + and if the attribute is a property: + - a pointer to presentation value passed from the previous level in the tree + - a pointer to the value of the color property (for handling of 'currentColor'), from previous level in the tree + - the location of the attribute in the elt structure when it was created + (used for fast comparison of SVG properties when animating from/to/by/values/... inherited values) +*/ +typedef struct { + GF_List *anims; + GF_FieldInfo specified_value; + GF_FieldInfo presentation_value; + Bool is_property; + GF_FieldInfo parent_presentation_value; + GF_FieldInfo current_color_value; + void *orig_dom_ptr; + /* flag set by any animation to inform other animations that there base value has changed */ + Bool presentation_value_changed; + /* flag used for rendering */ + u32 dirty_flags; + Bool dirty_parents; +} SMIL_AttributeAnimations; + +/* This structure is per animation element, + it holds the result of the animation and + some info to make animation computation faster */ +typedef struct _smil_anim_rti { + SMIL_AttributeAnimations *owner; + + Bool is_first_anim; + + /* animation element */ + GF_Node *anim_elt; + SMILAnimationAttributesPointers *animp; + SMILTimingAttributesPointers *timingp; + XLinkAttributesPointers *xlinkp; + + /* in case of animateTransform without from or to, the underlying value is the identity transform */ + GF_Matrix2D identity; + GF_FieldInfo default_transform_value; + + /* result of the animation */ + GF_FieldInfo interpolated_value; + + /* last value of the animation, used in accumulation phase */ + /* normally the far pointer in the last specified value is a pointer to a real attribute value, + and there's no need to allocate a new value. Except if the last specified value is the last + point in a path (animateMotion) in which case we allocate a matrix as last spec value, + which we need to delete (see animate-elem-202-t.svg). This is signaled if rai->path is not NULL*/ + GF_FieldInfo last_specified_value; + + /* temporary value needed when the type of + the key values is different from the target attribute type */ + GF_FieldInfo tmp_value; + + /* the number of values in animations should be constant (unless updated with LASeR commands) + we can store them to avoid computing them at each cycle */ + u32 values_count; + u32 key_times_count; + u32 key_points_count; + u32 key_splines_count; + + + /* In change detection mode, we test previous animation parameters to determine + if a new evaluation of the animation will produce a different result. + The result of these test is stored in interpolated_value_changed */ + Bool change_detection_mode; + Bool interpolated_value_changed; + s32 previous_key_index; + u32 previous_keytime_index; + Fixed previous_coef; + s32 previous_iteration; + Bool anim_done; + + GF_Path *path; + u8 rotate; + GF_PathIterator *path_iterator; + Fixed length; + +} SMIL_Anim_RTI; + +void gf_smil_anim_init_node(GF_Node *node); +void gf_smil_anim_init_discard(GF_Node *node); +void gf_smil_anim_init_runtime_info(GF_Node *node); +void gf_smil_anim_delete_runtime_info(SMIL_Anim_RTI *rai); +void gf_smil_anim_delete_animations(GF_Node *e); +void gf_smil_anim_remove_from_target(GF_Node *anim, GF_Node *target); +GF_Node *gf_smil_anim_get_target(GF_Node *e); + +void gf_sg_handle_dom_event(GF_Node *hdl, GF_DOM_Event *event, GF_Node *observer); +void gf_smil_setup_events(GF_Node *node); + +void gf_smil_anim_reset_variables(SMIL_Anim_RTI *rai); +void gf_smil_anim_set_anim_runtime_in_timing(GF_Node *n); + +void gf_smil_timing_pause(GF_Node *node); +void gf_smil_timing_resume(GF_Node *node); + +#endif + + +typedef struct _gf_vrml_script_priv GF_ScriptPriv; + + +/*setup script stack*/ +void gf_sg_script_init(GF_Node *node); +/*get script field*/ +GF_Err gf_sg_script_get_field(GF_Node *node, GF_FieldInfo *info); +/*get effective field count per event mode*/ +u32 gf_sg_script_get_num_fields(GF_Node *node, u8 IndexMode); +/*translate field index from inMode to ALL mode*/ +GF_Err gf_sg_script_get_field_index(GF_Node *Node, u32 inField, u8 IndexMode, u32 *allField); +/*create dynamic fields in the clone*/ +GF_Err gf_sg_script_prepare_clone(GF_Node *dest, GF_Node *orig); + +struct _scriptfield +{ + u32 eventType; + u32 fieldType; + char *name; + + s32 IN_index, OUT_index, DEF_index; + u32 ALL_index; + + //real field + void *pField; + + Double last_route_time; + Bool activate_event_out; + s32 magic; +}; + + +#ifdef GPAC_HAS_QJS + + +#include <gpac/download.h> +#include <gpac/network.h> + +void gf_js_vrml_flush_event_out(GF_Node *node, GF_ScriptPriv *priv); + + +enum +{ + GF_DOM_EXC_INDEX_SIZE_ERR = 1, + GF_DOM_EXC_DOMSTRING_SIZE_ERR = 2, + GF_DOM_EXC_HIERARCHY_REQUEST_ERR = 3, + GF_DOM_EXC_WRONG_DOCUMENT_ERR = 4, + GF_DOM_EXC_INVALID_CHARACTER_ERR = 5, + GF_DOM_EXC_NO_DATA_ALLOWED_ERR = 6, + GF_DOM_EXC_NO_MODIFICATION_ALLOWED_ERR = 7, + GF_DOM_EXC_NOT_FOUND_ERR = 8, + GF_DOM_EXC_NOT_SUPPORTED_ERR = 9, + GF_DOM_EXC_INUSE_ATTRIBUTE_ERR = 10, + GF_DOM_EXC_INVALID_STATE_ERR = 11, + GF_DOM_EXC_SYNTAX_ERR = 12, + GF_DOM_EXC_INVALID_MODIFICATION_ERR = 13, + GF_DOM_EXC_NAMESPACE_ERR = 14, + GF_DOM_EXC_INVALID_ACCESS_ERR = 15, + GF_DOM_EXC_VALIDATION_ERR = 16, + GF_DOM_EXC_TYPE_MISMATCH_ERR = 17, + GF_DOM_EXC_SECURITY_ERR = 18, + GF_DOM_EXC_NETWORK_ERR = 19, + GF_DOM_EXC_ABORT_ERR = 20, + GF_DOM_EXC_URL_MISMATCH_ERR = 21, + GF_DOM_EXC_QUOTA_EXCEEDED_ERR = 22, + GF_DOM_EXC_TIMEOUT_ERR = 23, + GF_DOM_EXC_INVALID_NODE_TYPE_ERR = 24, + GF_DOM_EXC_DATA_CLONE_ERR = 25, + +}; + +void gf_sg_handle_dom_event_for_vrml(GF_Node *hdl, GF_DOM_Event *event, GF_Node *observer); + +Bool gf_sg_javascript_initialized(); +#endif /*GPAC_HAS_QJS*/ + +SVG_Element *gf_svg_create_node(u32 tag); +Bool gf_svg_node_init(GF_Node *node); +void gf_svg_node_del(GF_Node *node); +Bool gf_svg_node_changed(GF_Node *node, GF_FieldInfo *field); +const char *gf_xml_get_element_name(GF_Node *node); + +SVGAttribute *gf_node_create_attribute_from_datatype(u32 data_type, u32 attribute_tag); + +GF_Err gf_node_get_attribute_by_name(GF_Node *node, char *name, u32 xmlns_code, Bool create_if_not_found, Bool set_default, GF_FieldInfo *field); +void *gf_svg_get_property_pointer_from_tag(SVGPropertiesPointers *output_property_context, u32 prop_tag); +void *gf_svg_get_property_pointer(SVG_Element *elt, void *input_attribute, + SVGPropertiesPointers *output_property_context); + +Bool gf_svg_is_property(GF_Node *node, GF_FieldInfo *target_attribute); + +/*exported for LASeR paring*/ +u32 svg_parse_point(SVG_Point *p, char *value_string); + +/*activates node. This is used by LASeR:activate and whenever a node is inserted in the scene +through DOM*/ +GF_Err gf_node_activate(GF_Node *node); +/*deactivates node. This is used by LASeR:deactivate and whenever a node is removed from the scene +through DOM*/ +GF_Err gf_node_deactivate(GF_Node *node); + +/*post a listener to be added - this is only used by LASeR:activate and DOM.addEventListener. This +is to ensure that when a node is processing an event creating a new listener on this node, this listener +will not be triggered*/ +void gf_sg_listener_post_add(GF_Node *obs, GF_Node *listener); +/*process all pending add_listener request*/ +void gf_dom_listener_process_add(GF_SceneGraph *sg); +void gf_dom_listener_reset_deferred(GF_SceneGraph *sg); + + +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_Err gf_dom_listener_del(GF_Node *listener, GF_DOMEventTarget *target); + +GF_DOMHandler *gf_dom_listener_build_ex(GF_Node *node, u32 event_type, u32 event_parameter, GF_Node *handler, GF_Node **out_listener); + +void gf_dom_event_dump_listeners(GF_Node *n, FILE *f); +void gf_dom_event_remove_all_listeners(GF_DOMEventTarget *event_target, GF_SceneGraph *sg); +void gf_dom_event_target_del(GF_DOMEventTarget *target); +GF_Err gf_dom_event_remove_listener_from_parent(GF_DOMEventTarget *event_target, GF_Node *listener); + +/* returns associated DOMEventtarget for an HTML/SVG media element, or NULL otherwise*/ +GF_DOMEventTarget *gf_dom_event_get_target_from_node(GF_Node *n); + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_SCENEGRAPH_DEV_H_*/ + diff --git a/include/gpac/internal/swf_dev.h b/include/gpac/internal/swf_dev.h new file mode 100644 index 0000000..5b7dc4a --- /dev/null +++ b/include/gpac/internal/swf_dev.h @@ -0,0 +1,410 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Scene Management 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_SWF_DEV_H_ +#define _GF_SWF_DEV_H_ + +#include <gpac/scene_manager.h> +#include <gpac/color.h> +#include <gpac/media_tools.h> + + +#ifndef GPAC_DISABLE_SWF_IMPORT + + +#define SWF_TWIP_SCALE (1/20.0f) + + +typedef struct SWFReader SWFReader; +typedef struct SWFSound SWFSound; +typedef struct SWFText SWFText; +typedef struct SWFEditText SWFEditText; +typedef struct SWF_Button SWF_Button; +typedef struct SWFShape SWFShape; +typedef struct SWFFont SWFFont; +typedef struct SWFAction SWFAction; + +enum +{ + SWF_PLACE , + SWF_REPLACE, + SWF_MOVE, +}; + +/*display list item (one per layer only)*/ +typedef struct +{ + GF_Matrix2D mat; + GF_ColorMatrix cmat; + u32 depth; + u32 char_id; +} DispShape; + +struct SWFReader +{ + GF_SceneLoader *load; + + FILE *input; + + char *inputName; + char *localPath; + /*file header*/ + u32 length; + char *mem; + u32 frame_rate; + u32 frame_count; + Fixed width, height; + Bool has_interact, no_as; + Bool empty_frame; + + /*copy of the swf import flags*/ + u32 flags; + + /*bit reader*/ + GF_BitStream *bs; + GF_Err ioerr; + + u32 current_frame; + + /*current tag*/ + u32 tag, size; + + GF_List *display_list; + u32 max_depth; + + /*defined fonts*/ + GF_List *fonts; + + /*define sounds*/ + GF_List *sounds; + + /*the one and only sound stream for current timeline*/ + SWFSound *sound_stream; + + /*when creating sprites: + 1- all BIFS AUs in sprites are random access + 2- depth is ignored in Sprites + */ + u32 current_sprite_id; + + /*the parser can decide to remove nearly aligned pppoints in lineTo sequences*/ + /*flatten limit - 0 means no flattening*/ + Fixed flat_limit; + /*number of points removed*/ + u32 flatten_points; + + u8 *jpeg_hdr; + u32 jpeg_hdr_size; + + + /*callback functions for translator*/ + GF_Err (*set_backcol)(SWFReader *read, u32 xrgb); + GF_Err (*show_frame)(SWFReader *read); + + /*checks if display list is large enough - returns 1 if yes, 0 otherwise (and allocate space)*/ + Bool (*allocate_depth)(SWFReader *read, u32 depth); + GF_Err (*place_obj)(SWFReader *read, u32 depth, u32 ID, u32 prev_id, u32 type, GF_Matrix2D *mat, GF_ColorMatrix *cmat, GF_Matrix2D *prev_mat, GF_ColorMatrix *prev_cmat); + GF_Err (*remove_obj)(SWFReader *read, u32 depth, u32 ID); + + GF_Err (*define_shape)(SWFReader *read, SWFShape *shape, SWFFont *parent_font, Bool last_sub_shape); + GF_Err (*define_sprite)(SWFReader *read, u32 nb_frames); + GF_Err (*define_text)(SWFReader *read, SWFText *text); + GF_Err (*define_edit_text)(SWFReader *read, SWFEditText *text); + /*@button is NULL to signal end of button declaration, non-null otherwise. "action" callback will be + called in between*/ + GF_Err (*define_button)(SWFReader *read, SWF_Button *button); + + GF_Err (*setup_image)(SWFReader *read, u32 ID, char *fileName); + /*called whenever a sound is found. For soundstreams, called twice, once on the header (declaration), + and one on the first soundstream block for offset signaling*/ + GF_Err (*setup_sound)(SWFReader *read, SWFSound *snd, Bool soundstream_first_block); + GF_Err (*start_sound)(SWFReader *read, SWFSound *snd, Bool stop); + /*performs an action, returns 0 if action not supported*/ + Bool (*action)(SWFReader *read, SWFAction *act); + + void (*finalize)(SWFReader *read); + + + /* <BIFS conversion state> */ + + /*all simple appearances (no texture)*/ + GF_List *apps; + + GF_List *buttons; + + /*current BIFS stream*/ + GF_StreamContext *bifs_es; + GF_AUContext *bifs_au; + + GF_StreamContext *bifs_dict_es; + GF_AUContext *bifs_dict_au; + + /*for sound insert*/ + GF_Node *root; + + /*current OD AU*/ + GF_StreamContext *od_es; + GF_AUContext *od_au; + + GF_Node *cur_shape; + u16 prev_od_id, prev_es_id; + + u32 wait_frame; + SWF_Button *btn; + GF_List *btn_over, *btn_not_over, *btn_active, *btn_not_active; + + /* </BIFS conversion state> */ + + /* SVG conversion state */ + Bool print_stream_header; + Bool print_frame_header; + u32 frame_header_offset; + char *svg_data; + u32 svg_data_size; + Bool svg_shape_started; + /* end of SVG conversion state */ + + /* MP4/SVG user */ + void *user; + GF_Err (*add_sample)(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap); + GF_Err (*add_header)(void *user, const u8 *data, u32 length, Bool isHeader); + + FILE *svg_file; +}; + + +void swf_report(SWFReader *read, GF_Err e, char *format, ...); +SWFFont *swf_find_font(SWFReader *read, u32 fontID); +GF_Err swf_parse_sprite(SWFReader *read); +GF_Err swf_parse_tag(SWFReader *read); + +#ifndef GPAC_DISABLE_VRML +GF_Err swf_to_bifs_init(SWFReader *read); +#endif + +#ifndef GPAC_DISABLE_SVG +GF_Err swf_to_svg_init(SWFReader *read, u32 flags, Float angle); +GF_Err swf_svg_write_text_sample(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap); +GF_Err swf_svg_write_text_header(void *user, const u8 *data, u32 length, Bool isHeader); +#endif + + +SWFReader *gf_swf_reader_new(const char *path, const char *filename); +GF_Err gf_swf_read_header(SWFReader *read); +void gf_swf_reader_del(SWFReader *read); +GF_Err gf_swf_get_duration(SWFReader *read, u32 *frame_rate, u32 *frame_count); + +GF_Err gf_swf_reader_set_user_mode(SWFReader *read, void *user, + GF_Err (*add_sample)(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap), + GF_Err (*add_header)(void *user, const u8 *data, u32 length, Bool isheader)); + +typedef struct +{ + Fixed x, y; + Fixed w, h; +} SWFRec; + +typedef struct +{ + /*0: not defined, otherwise index of shape*/ + u32 nbType; + /*0: moveTo, 1: lineTo, 2: quad curveTo*/ + u32 *types; + SFVec2f *pts; + u32 nbPts; + /*used by SWF->BIFS for IndexedCurveSet*/ + u32 *idx; +} SWFPath; + +typedef struct +{ + u32 type; + u32 solid_col; + u32 nbGrad; + u32 *grad_col; + u8 *grad_ratio; + GF_Matrix2D mat; + u32 img_id; + Fixed width; + + SWFPath *path; +} SWFShapeRec; + +struct SWFShape +{ + GF_List *fill_left, *fill_right, *lines; + u32 ID; + SWFRec rc; +}; + +/*SWF font object*/ +struct SWFFont +{ + u32 fontID; + u32 nbGlyphs; + GF_List *glyphs; + + /*the following may all be overridden by a DefineFontInfo*/ + + /*index -> glyph code*/ + u16 *glyph_codes; + /*index -> glyph advance*/ + s16 *glyph_adv; + + /*font flags (SWF 3.0)*/ + Bool has_layout; + Bool has_shiftJIS; + Bool is_unicode, is_ansi; + Bool is_bold, is_italic; + s16 ascent, descent, leading; + + /*font familly*/ + char *fontName; +}; + +/*chunk of text with the same aspect (font, col)*/ +typedef struct +{ + u32 fontID; + u32 col; + /*font size*/ + u32 fontSize; + /*origin point in local metrics*/ + Fixed orig_x, orig_y; + + u32 nbGlyphs; + u32 *indexes; + Fixed *dx; +} SWFGlyphRec; + +struct SWFText +{ + u32 ID; + GF_Matrix2D mat; + GF_List *text; +}; + +struct SWFEditText +{ + u32 ID; + char *init_value; + SWFRec bounds; + Bool word_wrap, multiline, password, read_only, auto_size, no_select, html, outlines, has_layout, border; + u32 color; + Fixed max_length, font_height; + u32 fontID; + + u32 align; + Fixed left, right, indent, leading; +}; + + +enum +{ + SWF_SND_UNCOMP = 0, + SWF_SND_ADPCM, + SWF_SND_MP3 +}; + +struct SWFSound +{ + u32 ID; + u8 format; + /*0: 5.5k - 1: 11k - 2: 22k - 3: 44k*/ + u8 sound_rate; + u8 bits_per_sample; + Bool stereo; + u16 sample_count; + u32 frame_delay_ms; + + /*IO*/ + FILE *output; + char *szFileName; + + /*set when sound is setup (OD inserted)*/ + Bool is_setup; +}; + +typedef struct +{ + /*interaction states*/ + Bool hitTest, down, over, up; + u32 character_id; + u16 depth; + GF_Matrix2D mx; + GF_ColorMatrix cmx; + Bool skip; +} SWF_ButtonRecord; + + +struct SWF_Button +{ + u32 count; + SWF_ButtonRecord buttons[40]; + u32 ID; +}; + +/*AS codes.*/ +enum +{ + GF_SWF_AS3_GOTO_FRAME, + GF_SWF_AS3_GET_URL, + GF_SWF_AS3_NEXT_FRAME, + GF_SWF_AS3_PREV_FRAME, + GF_SWF_AS3_PLAY, + GF_SWF_AS3_STOP, + GF_SWF_AS3_TOGGLE_QUALITY, + GF_SWF_AS3_STOP_SOUNDS, + GF_SWF_AS3_WAIT_FOR_FRAME, + GF_SWF_AS3_SET_TARGET, + GF_SWF_AS3_GOTO_LABEL, +}; + +enum +{ + GF_SWF_COND_IDLE_TO_OVERDOWN = 1, + GF_SWF_COND_OUTDOWN_TO_IDLE = 1<<1, + GF_SWF_COND_OUTDOWN_TO_OVERDOWN = 1<<2, + GF_SWF_COND_OVERDOWN_TO_OUTDOWN = 1<<3, + GF_SWF_COND_OVERDOWN_TO_OUTUP = 1<<4, + GF_SWF_COND_OVERUP_TO_OVERDOWN = 1<<5, + GF_SWF_COND_OVERUP_TO_IDLE = 1<<6, + GF_SWF_COND_IDLE_TO_OVERUP = 1<<7, + GF_SWF_COND_OVERDOWN_TO_IDLE = 1<<8, +}; + +struct SWFAction +{ + u32 type; + u32 frame_number; + u32 button_mask, button_key; + /*target (geturl/set_target), label (goto_frame)*/ + char *target; + char *url; +}; + +#endif /*GPAC_DISABLE_SWF_IMPORT*/ + +#endif /*_GF_SWF_DEV_H_*/ diff --git a/include/gpac/internal/vobsub.h b/include/gpac/internal/vobsub.h new file mode 100644 index 0000000..3db65b8 --- /dev/null +++ b/include/gpac/internal/vobsub.h @@ -0,0 +1,75 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Copyright (c) by Falco (Ivan Vecera) 2006 + * 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_VOBSUB_H_ +#define _GF_VOBSUB_H_ + +#include <gpac/tools.h> +#include <gpac/list.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define VOBSUBIDXVER 7 + +typedef struct _tag_vobsub_pos +{ + u64 filepos; + u64 start; + u64 stop; +} vobsub_pos; + +typedef struct _tag_vobsub_lang +{ + u32 id; + char *name; + GF_List *subpos; + u32 idx, current; + Bool is_seek; + u64 last_dts; +} vobsub_lang; + +typedef struct _tag_vobsub_file +{ + u32 width; + u32 height; + u8 palette[16][4]; + u32 num_langs; + vobsub_lang langs[32]; +} vobsub_file; + +s32 vobsub_lang_name(u16 id); +char *vobsub_lang_id(char *name); +GF_Err vobsub_read_idx(FILE *file, vobsub_file *vobsub, int *version); +void vobsub_free(vobsub_file *vobsub); +GF_Err vobsub_get_subpic_duration(u8 *data, u32 psize, u32 dsize, u32 *duration); +GF_Err vobsub_packetize_subpicture(FILE *fsub, u64 pts, u8 *data, u32 dataSize); + +#ifdef __cplusplus +} +#endif + +#endif /* _GF_VOBSUB_H_ */ diff --git a/include/gpac/iso639.h b/include/gpac/iso639.h new file mode 100644 index 0000000..d8a6bb6 --- /dev/null +++ b/include/gpac/iso639.h @@ -0,0 +1,89 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC + * + * 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_ISO_639_H +#define _GF_ISO_639_H + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/iso639.h> +\brief Language codes helper tools. + */ + +/*! +\addtogroup lang_grp +\brief Language codes helper tools + +This section documents the language codes used in GPAC, based in ISO 639 or RFC 5646. + +@{ +*/ + +#include <gpac/setup.h> + +/*! +Gets number of supported language codes +\return the number of supported language codes +*/ +u32 gf_lang_get_count(); +/*! +Finds language by name or code +\param lang_or_rfc_5646_code the langauage name, ISO 639 code or RFC 5646 code +\return the index of the language, or -1 if not supported +*/ +s32 gf_lang_find(const char *lang_or_rfc_5646_code); + +/*! +Gets the langauge name for the given index +\param lang_idx the langauge 0-based IDX +\return the name of the language +*/ +const char *gf_lang_get_name(u32 lang_idx); + +/*! +Gets the 2 character code for the given index +\param lang_idx the langauge 0-based IDX +\return the 2 character code of the language +*/ +const char *gf_lang_get_2cc(u32 lang_idx); + +/*! +Gets the 3 character code for the given index +\param lang_idx the langauge 0-based IDX +\return the 3 character code of the language +*/ +const char *gf_lang_get_3cc(u32 lang_idx); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/gpac/isomedia.h b/include/gpac/isomedia.h new file mode 100644 index 0000000..cb4b5bb --- /dev/null +++ b/include/gpac/isomedia.h @@ -0,0 +1,6834 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / ISO Media File Format 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_ISOMEDIA_H_ +#define _GF_ISOMEDIA_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/isomedia.h> +\brief ISOBMFF parsing and writing library. +*/ + +/*! +\addtogroup iso_grp ISO Base Media File +\brief ISOBMF, 3GPP, AVC and HEVC file format utilities. + +This section documents the reading and writing of ISOBMF (MP4, 3GPP, AVC, HEVC HEIF ...) +The library supports: +- regular movie read +- regular movie write +- fragmented movie and movie segments read +- fragmented movie and movie segments write +- QT support +- Sample descriptions for most common media found in such files (audio, video, text and subtitles) +- Meta and HEIF read +- Meta and HEIF write +- Common Encryption ISMA E&A and OMA DRM support +- MPEG-4 Systems extensions + + +All the READ function in this API can be used in EDIT/WRITE mode. +However, some unexpected errors or values may happen in that case, depending +on how much modifications you made (timing, track with 0 samples, ...). +On the other hand, none of the EDIT/WRITE functions will work in READ mode. + +The output structure of a edited file will sometimes be different +from the original file, but the media-data and meta-data will be identical. +The only change happens in the file media-data container(s) during edition + +When editing the file, you MUST set the final name of the modified file +to something different. This API doesn't allow file overwriting. + +@{ + */ + +#include <gpac/tools.h> + + +/*! Track reference types + +Some track may depend on other tracks for several reasons. They reference these tracks through the following Reference Types +*/ +enum +{ + /*! ref type for the OD track dependencies*/ + GF_ISOM_REF_OD = GF_4CC( 'm', 'p', 'o', 'd' ), + /*! ref type for stream dependencies*/ + GF_ISOM_REF_DECODE = GF_4CC( 'd', 'p', 'n', 'd' ), + /*! ref type for OCR (Object Clock Reference) dependencies*/ + GF_ISOM_REF_OCR = GF_4CC( 's', 'y', 'n', 'c' ), + /*! ref type for IPI (Intellectual Property Information) dependencies*/ + GF_ISOM_REF_IPI = GF_4CC( 'i', 'p', 'i', 'r' ), + /*! this track describes the referenced tr*/ + GF_ISOM_REF_META = GF_4CC( 'c', 'd', 's', 'c' ), + /*! ref type for Hint tracks*/ + GF_ISOM_REF_HINT = GF_4CC( 'h', 'i', 'n', 't' ), + /*! ref type for QT Chapter tracks*/ + GF_ISOM_REF_CHAP = GF_4CC( 'c', 'h', 'a', 'p' ), + /*! ref type for the SVC and SHVC base tracks*/ + GF_ISOM_REF_BASE = GF_4CC( 's', 'b', 'a', 's' ), + /*! ref type for the SVC and SHVC extractor reference tracks*/ + GF_ISOM_REF_SCAL = GF_4CC( 's', 'c', 'a', 'l' ), + /*! ref type for the SHVC tile base tracks*/ + GF_ISOM_REF_TBAS = GF_4CC( 't', 'b', 'a', 's' ), + /*! ref type for the SHVC tile tracks*/ + GF_ISOM_REF_SABT = GF_4CC( 's', 'a', 'b', 't' ), + /*! ref type for the SHVC oinf track*/ + GF_ISOM_REF_OREF = GF_4CC( 'o', 'r', 'e', 'f' ), + /*! this track uses fonts carried/defined in the referenced track*/ + GF_ISOM_REF_FONT = GF_4CC( 'f', 'o', 'n', 't' ), + /*! this track depends on the referenced hint track, i.e., it should only be used if the referenced hint track is used.*/ + GF_ISOM_REF_HIND = GF_4CC( 'h', 'i', 'n', 'd' ), + /*! this track contains auxiliary depth video information for the referenced video track*/ + GF_ISOM_REF_VDEP = GF_4CC( 'v', 'd', 'e', 'p' ), + /*! this track contains auxiliary parallax video information for the referenced video track*/ + GF_ISOM_REF_VPLX = GF_4CC( 'v', 'p', 'l', 'x' ), + /*! this track contains subtitle, timed text or overlay graphical information for the referenced track or any track in the alternate group to which the track belongs, if any*/ + GF_ISOM_REF_SUBT = GF_4CC( 's', 'u', 'b', 't' ), + /*! thumbnail track*/ + GF_ISOM_REF_THUMB = GF_4CC( 't', 'h', 'm', 'b' ), + /*DRC*/ + /*! additional audio track*/ + GF_ISOM_REF_ADDA = GF_4CC( 'a', 'd', 'd', 'a' ), + /*! DRC metadata*/ + GF_ISOM_REF_ADRC = GF_4CC( 'a', 'd', 'r', 'c' ), + /*! item->track location*/ + GF_ISOM_REF_ILOC = GF_4CC( 'i', 'l', 'o', 'c' ), + /*! AVC dep stream*/ + GF_ISOM_REF_AVCP = GF_4CC( 'a', 'v', 'c', 'p' ), + /*! AVC switch to*/ + GF_ISOM_REF_SWTO = GF_4CC( 's', 'w', 't', 'o' ), + /*! AVC switch from*/ + GF_ISOM_REF_SWFR = GF_4CC( 's', 'w', 'f', 'r' ), + + /*! Time code*/ + GF_ISOM_REF_TMCD = GF_4CC( 't', 'm', 'c', 'd' ), + /*! Structural dependency*/ + GF_ISOM_REF_CDEP = GF_4CC( 'c', 'd', 'e', 'p' ), + /*! transcript*/ + GF_ISOM_REF_SCPT = GF_4CC( 's', 'c', 'p', 't' ), + /*! nonprimary source description*/ + GF_ISOM_REF_SSRC = GF_4CC( 's', 's', 'r', 'c' ), + /*! layer audio track dependency*/ + GF_ISOM_REF_LYRA = GF_4CC( 'l', 'y', 'r', 'a' ), + /*! File Delivery Item Information Extension */ + GF_ISOM_REF_FDEL = GF_4CC( 'f', 'd', 'e', 'l' ), +#ifdef GF_ENABLE_CTRN + /*! Track fragment inherit */ + GF_ISOM_REF_TRIN = GF_4CC( 't', 'r', 'i', 'n' ), +#endif + + /*! Item auxiliary reference */ + GF_ISOM_REF_AUXR = GF_4CC( 'a', 'u', 'x', 'r' ), + + /*! ref type for the VVC subpicture tracks*/ + GF_ISOM_REF_SUBPIC = GF_4CC( 's', 'u', 'b', 'p' ), +}; + +/*! Track Edit list type*/ +typedef enum { + /*! empty segment in the track (no media for this segment)*/ + GF_ISOM_EDIT_EMPTY = 0x00, + /*! dwelled segment in the track (one media sample for this segment)*/ + GF_ISOM_EDIT_DWELL = 0x01, + /*! normal segment in the track*/ + GF_ISOM_EDIT_NORMAL = 0x02 +} GF_ISOEditType; + +/*! Generic Media Types (YOU HAVE TO USE ONE OF THESE TYPES FOR COMPLIANT ISO MEDIA FILES)*/ +enum +{ + /*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_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' ), + GF_ISOM_MEDIA_TEXT = GF_4CC( 't', 'e', 'x', 't' ), + /*subtitle code point used on ipod - same as text*/ + GF_ISOM_MEDIA_SUBT = GF_4CC( 's', 'b', 't', 'l' ), + GF_ISOM_MEDIA_SUBPIC = GF_4CC( 's', 'u', 'b', 'p' ), + GF_ISOM_MEDIA_MPEG_SUBT = GF_4CC( 's', 'u', 'b', 't' ), + /*closed caption track types for QT/ProRes*/ + GF_ISOM_MEDIA_CLOSED_CAPTION = GF_4CC( 'c', 'l', 'c', 'p' ), + /*timecode metadata for QT/ProRes*/ + GF_ISOM_MEDIA_TIMECODE = GF_4CC( 't', 'm', 'c', 'd' ), + /*MPEG-4 media types*/ + GF_ISOM_MEDIA_OD = GF_4CC( 'o', 'd', 's', 'm' ), + GF_ISOM_MEDIA_OCR = GF_4CC( 'c', 'r', 's', 'm' ), + GF_ISOM_MEDIA_SCENE = GF_4CC( 's', 'd', 's', 'm' ), + GF_ISOM_MEDIA_MPEG7 = GF_4CC( 'm', '7', 's', 'm' ), + GF_ISOM_MEDIA_OCI = GF_4CC( 'o', 'c', 's', 'm' ), + GF_ISOM_MEDIA_IPMP = GF_4CC( 'i', 'p', 's', 'm' ), + GF_ISOM_MEDIA_MPEGJ = GF_4CC( 'm', 'j', 's', 'm' ), + /*GPAC-defined, for any track using MPEG-4 systems signaling but with undefined streaml types*/ + GF_ISOM_MEDIA_ESM = GF_4CC( 'g', 'e', 's', 'm' ), + /*DIMS media type (same as scene but with a different mediaInfo)*/ + GF_ISOM_MEDIA_DIMS = GF_4CC( 'd', 'i', 'm', 's' ), + /*SWF file embedded in media track*/ + GF_ISOM_MEDIA_FLASH = GF_4CC( 'f', 'l', 's', 'h' ), + /*QTVR track*/ + GF_ISOM_MEDIA_QTVR = GF_4CC( 'q', 't', 'v', 'r' ), + GF_ISOM_MEDIA_JPEG = GF_4CC( 'j', 'p', 'e', 'g' ), + GF_ISOM_MEDIA_JP2 = GF_4CC( 'j', 'p', '2', ' ' ), + GF_ISOM_MEDIA_PNG = GF_4CC( 'p', 'n', 'g', ' ' ), +}; + + +/*! specific media sub-types - you shall make sure the media sub type is what you expect*/ +enum +{ + /*reserved, internal use in the lib. Indicates the track complies to MPEG-4 system + specification, and the usual OD framework tools may be used*/ + GF_ISOM_SUBTYPE_MPEG4 = GF_4CC( 'M', 'P', 'E', 'G' ), + + /*reserved, internal use in the lib. Indicates the track is of GF_ISOM_SUBTYPE_MPEG4 + but it is encrypted.*/ + GF_ISOM_SUBTYPE_MPEG4_CRYP = GF_4CC( 'E', 'N', 'C', 'M' ), + + /*AVC/H264 media type - not listed as an MPEG-4 type, ALTHOUGH this library automatically remaps + GF_AVCConfig to MPEG-4 ESD*/ + GF_ISOM_SUBTYPE_AVC_H264 = GF_4CC( 'a', 'v', 'c', '1' ), + GF_ISOM_SUBTYPE_AVC2_H264 = GF_4CC( 'a', 'v', 'c', '2' ), + GF_ISOM_SUBTYPE_AVC3_H264 = GF_4CC( 'a', 'v', 'c', '3' ), + GF_ISOM_SUBTYPE_AVC4_H264 = GF_4CC( 'a', 'v', 'c', '4' ), + GF_ISOM_SUBTYPE_SVC_H264 = GF_4CC( 's', 'v', 'c', '1' ), + GF_ISOM_SUBTYPE_MVC_H264 = GF_4CC( 'm', 'v', 'c', '1' ), + + /*HEVC media type*/ + GF_ISOM_SUBTYPE_HVC1 = GF_4CC( 'h', 'v', 'c', '1' ), + GF_ISOM_SUBTYPE_HEV1 = GF_4CC( 'h', 'e', 'v', '1' ), + GF_ISOM_SUBTYPE_HVC2 = GF_4CC( 'h', 'v', 'c', '2' ), + GF_ISOM_SUBTYPE_HEV2 = GF_4CC( 'h', 'e', 'v', '2' ), + GF_ISOM_SUBTYPE_LHV1 = GF_4CC( 'l', 'h', 'v', '1' ), + GF_ISOM_SUBTYPE_LHE1 = GF_4CC( 'l', 'h', 'e', '1' ), + GF_ISOM_SUBTYPE_HVT1 = GF_4CC( 'h', 'v', 't', '1' ), + + /*VVC media types*/ + GF_ISOM_SUBTYPE_VVC1 = GF_4CC( 'v', 'v', 'c', '1' ), + GF_ISOM_SUBTYPE_VVI1 = GF_4CC( 'v', 'v', 'i', '1' ), + GF_ISOM_SUBTYPE_VVS1 = GF_4CC( 'v', 'v', 's', '1' ), + GF_ISOM_SUBTYPE_VVCN = GF_4CC( 'v', 'v', 'c', 'N' ), + + /*AV1 media type*/ + GF_ISOM_SUBTYPE_AV01 = GF_4CC('a', 'v', '0', '1'), + + /*Opus media type*/ + GF_ISOM_SUBTYPE_OPUS = GF_4CC('O', 'p', 'u', 's'), + GF_ISOM_SUBTYPE_FLAC = GF_4CC( 'f', 'L', 'a', 'C' ), + + /* VP */ + GF_ISOM_SUBTYPE_VP08 = GF_4CC('v', 'p', '0', '8'), + GF_ISOM_SUBTYPE_VP09 = GF_4CC('v', 'p', '0', '9'), + GF_ISOM_SUBTYPE_VP10 = GF_4CC('v', 'p', '1', '0'), + + /* Dolby Vision */ + GF_ISOM_SUBTYPE_DVHE = GF_4CC('d', 'v', 'h', 'e'), + GF_ISOM_SUBTYPE_DVH1 = GF_4CC('d', 'v', 'h', '1'), + GF_ISOM_SUBTYPE_DVA1 = GF_4CC('d', 'v', 'a', '1'), + GF_ISOM_SUBTYPE_DVAV = GF_4CC('d', 'v', 'a', 'v'), + GF_ISOM_SUBTYPE_DAV1 = GF_4CC('d', 'a', 'v', '1'), + + /*3GPP(2) extension subtypes*/ + GF_ISOM_SUBTYPE_3GP_H263 = GF_4CC( 's', '2', '6', '3' ), + GF_ISOM_SUBTYPE_3GP_AMR = GF_4CC( 's', 'a', 'm', 'r' ), + GF_ISOM_SUBTYPE_3GP_AMR_WB = GF_4CC( 's', 'a', 'w', 'b' ), + GF_ISOM_SUBTYPE_3GP_EVRC = GF_4CC( 's', 'e', 'v', 'c' ), + GF_ISOM_SUBTYPE_3GP_QCELP = GF_4CC( 's', 'q', 'c', 'p' ), + GF_ISOM_SUBTYPE_3GP_SMV = GF_4CC( 's', 's', 'm', 'v' ), + + /*3GPP DIMS*/ + GF_ISOM_SUBTYPE_3GP_DIMS = GF_4CC( 'd', 'i', 'm', 's' ), + + GF_ISOM_SUBTYPE_AC3 = GF_4CC( 'a', 'c', '-', '3' ), + GF_ISOM_SUBTYPE_EC3 = GF_4CC( 'e', 'c', '-', '3' ), + GF_ISOM_SUBTYPE_MP3 = GF_4CC( '.', 'm', 'p', '3' ), + GF_ISOM_SUBTYPE_MLPA = GF_4CC( 'm', 'l', 'p', 'a' ), + + GF_ISOM_SUBTYPE_MP4A = GF_4CC( 'm', 'p', '4', 'a' ), + GF_ISOM_SUBTYPE_MP4S = GF_4CC( 'm', 'p', '4', 's' ), + + GF_ISOM_SUBTYPE_LSR1 = GF_4CC( 'l', 's', 'r', '1' ), + GF_ISOM_SUBTYPE_WVTT = GF_4CC( 'w', 'v', 't', 't' ), + GF_ISOM_SUBTYPE_STXT = GF_4CC( 's', 't', 'x', 't' ), + GF_ISOM_SUBTYPE_STPP = GF_4CC( 's', 't', 'p', 'p' ), + GF_ISOM_SUBTYPE_SBTT = GF_4CC( 's', 'b', 't', 't' ), + GF_ISOM_SUBTYPE_METT = GF_4CC( 'm', 'e', 't', 't' ), + GF_ISOM_SUBTYPE_METX = GF_4CC( 'm', 'e', 't', 'x' ), + GF_ISOM_SUBTYPE_TX3G = GF_4CC( 't', 'x', '3', 'g' ), + GF_ISOM_SUBTYPE_TEXT = GF_4CC( 't', 'e', 'x', 't' ), + + + GF_ISOM_SUBTYPE_RTP = GF_4CC( 'r', 't', 'p', ' ' ), + GF_ISOM_SUBTYPE_SRTP = GF_4CC( 's', 'r', 't', 'p' ), + GF_ISOM_SUBTYPE_RRTP = GF_4CC( 'r', 'r', 't', 'p' ), + GF_ISOM_SUBTYPE_RTCP = GF_4CC( 'r', 't', 'c', 'p' ), + GF_ISOM_SUBTYPE_FLUTE = GF_4CC( 'f', 'd', 'p', ' ' ), + + /* Apple XDCAM */ + GF_ISOM_SUBTYPE_XDVB = GF_4CC( 'x', 'd', 'v', 'b' ), + + GF_ISOM_SUBTYPE_H263 = GF_4CC( 'h', '2', '6', '3' ), + + GF_ISOM_SUBTYPE_JPEG = GF_4CC( 'j', 'p', 'e', 'g' ), + GF_ISOM_SUBTYPE_PNG = GF_4CC( 'p', 'n', 'g', ' ' ), + GF_ISOM_SUBTYPE_MJP2 = GF_4CC( 'm', 'j', 'p', '2' ), + GF_ISOM_SUBTYPE_JP2K = GF_4CC('j','p','2','k'), + + GF_ISOM_SUBTYPE_MH3D_MHA1 = GF_4CC( 'm', 'h', 'a', '1' ), + GF_ISOM_SUBTYPE_MH3D_MHA2 = GF_4CC( 'm', 'h', 'a', '2' ), + GF_ISOM_SUBTYPE_MH3D_MHM1 = GF_4CC( 'm', 'h', 'm', '1' ), + GF_ISOM_SUBTYPE_MH3D_MHM2 = GF_4CC( 'm', 'h', 'm', '2' ), + + GF_ISOM_SUBTYPE_IPCM = GF_4CC( 'i', 'p', 'c', 'm' ), + GF_ISOM_SUBTYPE_FPCM = GF_4CC( 'f', 'p', 'c', 'm' ), + + /* on-screen colours */ + GF_ISOM_SUBTYPE_NCLX = GF_4CC( 'n', 'c', 'l', 'x' ), + GF_ISOM_SUBTYPE_NCLC = GF_4CC( 'n', 'c', 'l', 'c' ), + GF_ISOM_SUBTYPE_PROF = GF_4CC( 'p', 'r', 'o', 'f' ), + GF_ISOM_SUBTYPE_RICC = GF_4CC( 'r', 'I', 'C', 'C' ), + + /* QT audio codecs */ + //this one is also used for 24bit RGB + GF_QT_SUBTYPE_RAW = GF_4CC('r','a','w',' '), + GF_QT_SUBTYPE_TWOS = GF_4CC('t','w','o','s'), + GF_QT_SUBTYPE_SOWT = GF_4CC('s','o','w','t'), + GF_QT_SUBTYPE_FL32 = GF_4CC('f','l','3','2'), + GF_QT_SUBTYPE_FL64 = GF_4CC('f','l','6','4'), + GF_QT_SUBTYPE_IN24 = GF_4CC('i','n','2','4'), + GF_QT_SUBTYPE_IN32 = GF_4CC('i','n','3','2'), + GF_QT_SUBTYPE_ULAW = GF_4CC('u','l','a','w'), + GF_QT_SUBTYPE_ALAW = GF_4CC('a','l','a','w'), + GF_QT_SUBTYPE_ADPCM = GF_4CC(0x6D,0x73,0x00,0x02), + GF_QT_SUBTYPE_IMA_ADPCM = GF_4CC(0x6D,0x73,0x00,0x11), + GF_QT_SUBTYPE_DVCA = GF_4CC('d','v','c','a'), + GF_QT_SUBTYPE_QDMC = GF_4CC('Q','D','M','C'), + GF_QT_SUBTYPE_QDMC2 = GF_4CC('Q','D','M','2'), + GF_QT_SUBTYPE_QCELP = GF_4CC('Q','c','l','p'), + GF_QT_SUBTYPE_kMP3 = GF_4CC(0x6D,0x73,0x00,0x55), + + /* QT video codecs */ + GF_QT_SUBTYPE_C608 = GF_4CC( 'c', '6', '0', '8' ), + GF_QT_SUBTYPE_APCH = GF_4CC( 'a', 'p', 'c', 'h' ), + GF_QT_SUBTYPE_APCO = GF_4CC( 'a', 'p', 'c', 'o' ), + GF_QT_SUBTYPE_APCN = GF_4CC( 'a', 'p', 'c', 'n' ), + GF_QT_SUBTYPE_APCS = GF_4CC( 'a', 'p', 'c', 's' ), + GF_QT_SUBTYPE_AP4X = GF_4CC( 'a', 'p', '4', 'x' ), + GF_QT_SUBTYPE_AP4H = GF_4CC( 'a', 'p', '4', 'h' ), + GF_QT_SUBTYPE_YUYV = GF_4CC('y','u','v','2'), + GF_QT_SUBTYPE_UYVY = GF_4CC('2','v','u','y'), + GF_QT_SUBTYPE_YUV444 = GF_4CC('v','3','0','8'), + GF_QT_SUBTYPE_YUVA444 = GF_4CC('v','4','0','8'), + GF_QT_SUBTYPE_YUV422_10 = GF_4CC('v','2','1','0'), + GF_QT_SUBTYPE_YUV444_10 = GF_4CC('v','4','1','0'), + GF_QT_SUBTYPE_YUV422_16 = GF_4CC('v','2','1','6'), + GF_QT_SUBTYPE_YUV420 = GF_4CC('j','4','2','0'), + GF_QT_SUBTYPE_I420 = GF_4CC('I','4','2','0'), + GF_QT_SUBTYPE_IYUV = GF_4CC('I','Y','U','V'), + GF_QT_SUBTYPE_YV12 = GF_4CC('y','v','1','2'), + GF_QT_SUBTYPE_YVYU = GF_4CC('Y','V','Y','U'), + GF_QT_SUBTYPE_RGBA = GF_4CC('R','G','B','A'), + GF_QT_SUBTYPE_ABGR = GF_4CC('A','B','G','R'), + + GF_ISOM_SUBTYPE_FFV1 = GF_4CC( 'F', 'F', 'V', '1' ), + + GF_ISOM_ITEM_TYPE_AUXI = GF_4CC('a', 'u', 'x', 'i'), + + GF_QT_SUBTYPE_TMCD = GF_4CC( 't', 'm', 'c', 'd' ), + +}; + + + + +/*! direction for sample search (including SyncSamples search) +Function using search allways specify the desired time in composition (presentation) time + (Sample N-1) DesiredTime (Sample N) +*/ +typedef enum +{ + /*! FORWARD: will give the next sample given the desired time (eg, N)*/ + GF_ISOM_SEARCH_FORWARD = 1, + /*! BACKWARD: will give the previous sample given the desired time (eg, N-1)*/ + GF_ISOM_SEARCH_BACKWARD = 2, + /*! SYNCFORWARD: will search from the desired point in time for a sync sample if any. If no sync info, behaves as FORWARD*/ + GF_ISOM_SEARCH_SYNC_FORWARD = 3, + /*! SYNCBACKWARD: will search till the desired point in time for a sync sample if any. If no sync info, behaves as BACKWARD*/ + GF_ISOM_SEARCH_SYNC_BACKWARD = 4, + /*! SYNCSHADOW: use the sync shadow information to retrieve the sample. If no SyncShadow info, behave as SYNCBACKWARD + \warning deprecated in ISOBMFF*/ + GF_ISOM_SEARCH_SYNC_SHADOW = 5 +} GF_ISOSearchMode; + +/*! Predefined File Brand codes (MPEG-4 and JPEG2000)*/ +enum +{ + /*file complying to the generic ISO Media File (base specification ISO/IEC 14496-12) + this is the default brand when creating a new movie*/ + GF_ISOM_BRAND_ISOM = GF_4CC( 'i', 's', 'o', 'm' ), + /*file complying to the generic ISO Media File (base specification ISO/IEC 14496-12) + Meta extensions*/ + GF_ISOM_BRAND_ISO2 = GF_4CC( 'i', 's', 'o', '2' ), + /*file complying to ISO/IEC 14496-1 2001 edition. A .mp4 file without a brand + is equivalent to a file compatible with this brand*/ + GF_ISOM_BRAND_MP41 = GF_4CC( 'm', 'p', '4', '1' ), + /*file complying to ISO/IEC 14496-14 (MP4 spec)*/ + GF_ISOM_BRAND_MP42 = GF_4CC( 'm', 'p', '4', '2' ), + /*file complying to ISO/IEC 15444-3 (JPEG2000) without profile restriction*/ + GF_ISOM_BRAND_MJP2 = GF_4CC( 'm', 'j', 'p', '2' ), + /*file complying to ISO/IEC 15444-3 (JPEG2000) with simple profile restriction*/ + GF_ISOM_BRAND_MJ2S = GF_4CC( 'm', 'j', '2', 's' ), + /*old versions of 3GPP spec (without timed text)*/ + GF_ISOM_BRAND_3GP4 = GF_4CC('3', 'g', 'p', '4'), + GF_ISOM_BRAND_3GP5 = GF_4CC('3', 'g', 'p', '5'), + /*final version of 3GPP file spec*/ + GF_ISOM_BRAND_3GP6 = GF_4CC('3', 'g', 'p', '6'), + /*generci 3GPP file (several audio tracks, etc..)*/ + GF_ISOM_BRAND_3GG5 = GF_4CC('3', 'g', 'g', '5'), + GF_ISOM_BRAND_3GG6 = GF_4CC('3', 'g', 'g', '6'), + /*3GPP2 file spec*/ + GF_ISOM_BRAND_3G2A = GF_4CC('3', 'g', '2', 'a'), + /*AVC file spec*/ + GF_ISOM_BRAND_AVC1 = GF_4CC('a', 'v', 'c', '1'), + /* file complying to ISO/IEC 21000-9:2005 (MPEG-21 spec)*/ + GF_ISOM_BRAND_MP21 = GF_4CC('m', 'p', '2', '1'), + /*file complying to the generic ISO Media File (base specification ISO/IEC 14496-12) + support for version 1*/ + GF_ISOM_BRAND_ISO4 = GF_4CC( 'i', 's', 'o', '4' ), + /* Image File Format */ + GF_ISOM_BRAND_HEIF = GF_4CC('h', 'e', 'i', 'f'), + GF_ISOM_BRAND_MIF1 = GF_4CC('m', 'i', 'f', '1'), + GF_ISOM_BRAND_HEIC = GF_4CC('h', 'e', 'i', 'c'), + GF_ISOM_BRAND_HEIM = GF_4CC('h', 'e', 'i', 'm'), + GF_ISOM_BRAND_AVIF = GF_4CC('a', 'v', 'i', 'f'), + GF_ISOM_BRAND_AVCI = GF_4CC('a', 'v', 'c', 'i'), + GF_ISOM_BRAND_VVIC = GF_4CC('v', 'v', 'i', 'c'), + + /*other iso media brands */ + GF_ISOM_BRAND_ISO1 = GF_4CC( 'i', 's', 'o', '1' ), + GF_ISOM_BRAND_ISO3 = GF_4CC( 'i', 's', 'o', '3' ), + GF_ISOM_BRAND_ISO5 = GF_4CC( 'i', 's', 'o', '5' ), + GF_ISOM_BRAND_ISO6 = GF_4CC( 'i', 's', 'o', '6' ), + GF_ISOM_BRAND_ISO7 = GF_4CC( 'i', 's', 'o', '7' ), + GF_ISOM_BRAND_ISO8 = GF_4CC( 'i', 's', 'o', '8' ), + GF_ISOM_BRAND_ISO9 = GF_4CC( 'i', 's', 'o', '9' ), + GF_ISOM_BRAND_ISOA = GF_4CC( 'i', 's', 'o', 'a' ), + + /* QT brand*/ + GF_ISOM_BRAND_QT = GF_4CC( 'q', 't', ' ', ' ' ), + + /* JPEG 2000 Image (.JP2) [ISO 15444-1] */ + GF_ISOM_BRAND_JP2 = GF_4CC( 'j', 'p', '2', ' ' ), + + /* MPEG-4 (.MP4) for SonyPSP */ + GF_ISOM_BRAND_MSNV = GF_4CC( 'M', 'S', 'N', 'V' ), + /* Apple iTunes AAC-LC (.M4A) Audio */ + GF_ISOM_BRAND_M4A = GF_4CC( 'M', '4', 'A', ' ' ), + /* Apple iTunes Video (.M4V) Video */ + GF_ISOM_BRAND_M4V = GF_4CC( 'M', '4', 'V', ' ' ), + + GF_ISOM_BRAND_HVCE = GF_4CC( 'h', 'v', 'c', 'e' ), + GF_ISOM_BRAND_HVCI = GF_4CC( 'h', 'v', 'c', 'i' ), + GF_ISOM_BRAND_HVTI = GF_4CC( 'h', 'v', 't', 'i' ), + + + GF_ISOM_BRAND_AV01 = GF_4CC( 'a', 'v', '0', '1'), + + GF_ISOM_BRAND_OPUS = GF_4CC( 'O', 'p', 'u', 's'), + + GF_ISOM_BRAND_ISMA = GF_4CC( 'I', 'S', 'M', 'A' ), + + /* dash related brands (ISO/IEC 23009-1) */ + GF_ISOM_BRAND_DASH = GF_4CC('d','a','s','h'), + /* Media Segment conforming to the DASH Self-Initializing Media Segment format type */ + GF_ISOM_BRAND_DSMS = GF_4CC('d','s','m','s'), + /* Media Segment conforming to the general format type */ + GF_ISOM_BRAND_MSDH = GF_4CC('m','s','d','h'), + /* Media Segment conforming to the Indexed Media Segment format type */ + GF_ISOM_BRAND_MSIX = GF_4CC('m','s','i','x'), + /* Representation Index Segment used to index MPEG-2 TS based Media Segments */ + GF_ISOM_BRAND_RISX = GF_4CC('r','i','s','x'), + /* last Media Segment indicator for ISO base media file format */ + GF_ISOM_BRAND_LMSG = GF_4CC('l','m','s','g'), + /* Single Index Segment used to index MPEG-2 TS based Media Segments */ + GF_ISOM_BRAND_SISX = GF_4CC('s','i','s','x'), + /* Subsegment Index Segment used to index MPEG-2 TS based Media Segments */ + GF_ISOM_BRAND_SSSS = GF_4CC('s','s','s','s'), + + /* CMAF brand */ + GF_ISOM_BRAND_CMFC = GF_4CC('c','m','f','c'), + /* CMAF brand with neg ctts */ + GF_ISOM_BRAND_CMF2 = GF_4CC('c','m','f','2'), + + /* from ismacryp.c */ + /* OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A) */ + GF_ISOM_BRAND_ODCF = GF_4CC('o','d','c','f'), + /* OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C) */ + GF_ISOM_BRAND_OPF2 = GF_4CC('o','p','f','2'), + + /* compressed brand*/ + GF_ISOM_BRAND_COMP = GF_4CC( 'c', 'o', 'm', 'p' ), + GF_ISOM_BRAND_ISOC = GF_4CC( 'i', 's', 'o', 'C' ), + +}; + +/*! sample roll information type*/ +typedef enum +{ + /*! no roll info associated*/ + GF_ISOM_SAMPLE_ROLL_NONE=0, + /*! roll info describes a roll operation*/ + GF_ISOM_SAMPLE_ROLL, + /*! roll info describes an audio preroll*/ + GF_ISOM_SAMPLE_PREROLL, + /*! roll info describes audio preroll but is not set for this sample*/ + GF_ISOM_SAMPLE_PREROLL_NONE +} GF_ISOSampleRollType; + +#ifndef GPAC_DISABLE_ISOM + +#include <gpac/mpeg4_odf.h> + +/*! isomedia file*/ +typedef struct __tag_isom GF_ISOFile; + +/*! a track ID value - just a 32 bit value but typedefed for API safety*/ +typedef u32 GF_ISOTrackID; + +/*! @} */ + +/*! +\addtogroup isosample_grp ISO Sample +\ingroup iso_grp + +Media sample for ISOBMFF API. +@{ +*/ + +/*! Random Access Point flag*/ +typedef enum { + /*! redundant sync shadow - only set when reading sample*/ + RAP_REDUNDANT = -1, + /*! not rap*/ + RAP_NO = 0, + /*! sync point (IDR)*/ + RAP = 1, + /*! sync point (IDR)*/ + SAP_TYPE_1 = 1, + /*! sync point (IDR)*/ + SAP_TYPE_2 = 2, + /*! RAP, OpenGOP*/ + SAP_TYPE_3 = 3, + /*! RAP, roll info (GDR or audio preroll)*/ + SAP_TYPE_4 = 4, +} GF_ISOSAPType; + +/*! media sample object*/ +typedef struct +{ + /*! data size*/ + u32 dataLength; + /*! data with padding if requested*/ + u8 *data; + /*! decoding time*/ + u64 DTS; + /*! relative offset for composition if needed*/ + s32 CTS_Offset; + /*! SAP type*/ + 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; +} GF_ISOSample; + + +/*! creates a new empty sample +\return the newly allocated ISO sample*/ +GF_ISOSample *gf_isom_sample_new(); + +/*! delete a sample. +\note The buffer content will be destroyed by default. If you wish to keep the buffer, set dataLength to 0 in the sample before deleting it +the pointer is set to NULL after deletion +\param samp pointer to the target ISO sample +*/ +void gf_isom_sample_del(GF_ISOSample **samp); + + +/*! @} */ + +/*! +\addtogroup isogen_grp Generic API +\ingroup iso_grp + +Generic API functions +@{ +*/ + +/*! Movie file opening modes */ +typedef enum +{ + /*! Opens file for dumping: same as read-only but keeps all movie fragments info untouched*/ + GF_ISOM_OPEN_READ_DUMP = 0, + /*! Opens a file in READ ONLY mode*/ + GF_ISOM_OPEN_READ, + /*! Opens a file in WRITE ONLY mode. Media Data is captured on the fly and storage mode is always flat (moov at end). + In this mode, the editing functions are disabled.*/ + GF_ISOM_OPEN_WRITE, + /*! Opens an existing file in EDIT mode*/ + GF_ISOM_OPEN_EDIT, + /*! Creates a new file in EDIT mode*/ + GF_ISOM_WRITE_EDIT, + /*! Opens an existing file and keep fragment information*/ + GF_ISOM_OPEN_KEEP_FRAGMENTS, + /*! Opens an existing file in READ ONLY mode but enables most of the file edit functions except fragmentation + Samples may be added to the file in this mode, they will be stored in memory + */ + GF_ISOM_OPEN_READ_EDIT, +} GF_ISOOpenMode; + +/*! indicates if target file is an IsoMedia file +\param fileName the target local file name or path to probe, gmem:// or gfio:// resource +\return 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, 0 otherwise +*/ +u32 gf_isom_probe_file(const char *fileName); + +/*! indicates if target file is an IsoMedia file +\param fileName the target local file name or path to probe, gmem:// or gfio:// resource +\param start_range the offset in the file to start probing from +\param end_range the offset in the file at which probing shall stop +\return 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, 4 if empty or no file, 0 otherwise +*/ +u32 gf_isom_probe_file_range(const char *fileName, u64 start_range, u64 end_range); + +/*! indicates if target file is an IsoMedia file +\param inBuf the buffer to probe +\param inSize the sizeo of the buffer to probe +\returns 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, 0 otherwise (non recognized or too short) +*/ +u32 gf_isom_probe_data(const u8*inBuf, u32 inSize); + +/*! opens an isoMedia File. +If fileName is NULL data will be written in memory ; write with gf_isom_write() ; use gf_isom_get_bs() to get the data ; use gf_isom_delete() to delete the internal data. +\param fileName name of the file to open, , gmem:// or gfio:// resource. The special name "_gpac_isobmff_redirect" is used to indicate that segment shall be written to a memory buffer passed to callback function set through \ref gf_isom_set_write_callback. +\param OpenMode file opening mode +\param tmp_dir for the 2 edit modes only, specifies a location for temp file. If NULL, the library will use the default libgpac temporary file management schemes. +\return the created ISO file if no error +*/ +GF_ISOFile *gf_isom_open(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir); + +/*! closes the file, write it if new/edited - equivalent to gf_isom_write()+gf_isom_delete() +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_close(GF_ISOFile *isom_file); + +/*! deletes the movie without saving it +\param isom_file the target ISO file +*/ +void gf_isom_delete(GF_ISOFile *isom_file); + +/*! 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 +the last error is a FatalError, and is not always set if a bad +param is specified... +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_last_error(GF_ISOFile *isom_file); + +/*! gets the mode of an open file +\param isom_file the target ISO file +\return open mode of the file +*/ +u8 gf_isom_get_mode(GF_ISOFile *isom_file); + +/*! checks if file is J2K image +\param isom_file the target ISO file +\return GF_TRUE if file is a j2k image, GF_FALSE otherwise +*/ +Bool gf_isom_is_JPEG2000(GF_ISOFile *isom_file); + + +/*! checks if a given four character code matches a known video handler type (vide, auxv, pict, ...) +\param mtype the four character code to check +\return GF_TRUE if the type is a video media type*/ +Bool gf_isom_is_video_handler_type(u32 mtype); + +/*! gets number of implemented boxes in (including the internal unknown box wrapper). +\note There can be several times the same type returned due to variation of the box (versions or flags) +\return number of implemented boxes +*/ +u32 gf_isom_get_num_supported_boxes(); + +/*! gets four character code of box given its index. Index 0 is GPAC internal unknown box handler +\param idx 0-based index of the box +\return four character code of the box +*/ +u32 gf_isom_get_supported_box_type(u32 idx); + +#ifndef GPAC_DISABLE_ISOM_DUMP + +/*! prints default box syntax of box given its index. Index 0 is GPAC internal unknown box handler +\param idx 0-based index of the box +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_supported_box(u32 idx, FILE * trace); + +#endif + +/*! @} */ + +/*! +\addtogroup isoread_grp ISOBMFF Reading +\ingroup iso_grp + +ISOBMF file reading +@{ +*/ + +/*! checks if moov box is before any mdat box +\param isom_file the target ISO file +\return GF_TRUE if if moov box is before any mdat box, GF_FALSE otherwise +*/ +Bool gf_isom_moov_first(GF_ISOFile *isom_file); + +/*! when reading a file, indicates that file data is missing the indicated bytes +\param isom_file the target ISO file +\param byte_offset number of bytes not present at the beginning of the file +\return error if any +*/ +GF_Err gf_isom_set_byte_offset(GF_ISOFile *isom_file, s64 byte_offset); + + +/*! opens a movie that can be uncomplete in READ_ONLY mode +to use for http streaming & co + +start_range and end_range restricts the media byte range in the URL (used by DASH) +if 0 or end_range<=start_range, the entire URL is used when parsing + +If the url indicates a gfio or gmem resource, the file can be played from the associated underlying buffer. For gmem, you must call \ref gf_isom_refresh_fragmented and gf_isom_set_removed_bytes whenever the underlying buffer is modified. + +\param fileName the name of the local file or cache to open, gmem:// or gfio:// +\param start_range only loads starting from indicated byte range +\param end_range loading stops at indicated byte range +\param enable_frag_templates loads fragment and segment boundaries in an internal table +\param isom_file pointer set to the opened file if success +\param BytesMissing is set to the predicted number of bytes missing for the file to be loaded +Note that if the file is not optimized for streaming, this number is not accurate +If the movie is successfully loaded (isom_file non-NULL), BytesMissing is zero +\return error if any +*/ +GF_Err gf_isom_open_progressive(const char *fileName, u64 start_range, u64 end_range, Bool enable_frag_templates, GF_ISOFile **isom_file, u64 *BytesMissing); + + +/*! same as \ref gf_isom_open_progressive but allows fetching the incomplete box type + +\param fileName the name of the local file or cache to open +\param start_range only loads starting from indicated byte range +\param end_range loading stops at indicated byte range +\param enable_frag_templates loads fragment and segment boundaries in an internal table +\param isom_file pointer set to the opened file if success +\param BytesMissing is set to the predicted number of bytes missing for the file to be loaded +\param topBoxType is set to the 4CC of the incomplete top-level box found - may be NULL +Note that if the file is not optimized for streaming, this number is not accurate +If the movie is successfully loaded (isom_file non-NULL), BytesMissing is zero +\return error if any +*/ +GF_Err gf_isom_open_progressive_ex(const char *fileName, u64 start_range, u64 end_range, Bool enable_frag_templates, GF_ISOFile **isom_file, u64 *BytesMissing, u32 *topBoxType); + +/*! retrieves number of bytes missing. +if requesting a sample fails with error GF_ISOM_INCOMPLETE_FILE, use this function +to get the number of bytes missing to retrieve the sample +\param isom_file the target ISO file +\param trackNumber the desired track to query +\return the number of bytes missing to fetch the sample +*/ +u64 gf_isom_get_missing_bytes(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a file has movie info (moov box with tracks & dynamic media). Some files may just use +the base IsoMedia structure without "moov" container +\param isom_file the target ISO file +\return GF_TRUE if the file has a movie, GF_FALSE otherwise +*/ +Bool gf_isom_has_movie(GF_ISOFile *isom_file); + +/*! gets number of tracks +\param isom_file the target ISO file +\return the number of tracks in the movie, or -1 if error*/ +u32 gf_isom_get_track_count(GF_ISOFile *isom_file); + +/*! gets the movie timescale +\param isom_file the target ISO file +\return the timescale of the movie, 0 if error*/ +u32 gf_isom_get_timescale(GF_ISOFile *isom_file); + +/*! gets the movie duration computed based on media samples and edit lists +\param isom_file the target ISO file +\return the computed duration of the movie, 0 if error*/ +u64 gf_isom_get_duration(GF_ISOFile *isom_file); + +/*! gets the original movie duration as written in the file, regardless of the media data +\param isom_file the target ISO file +\return the duration of the movie*/ +u64 gf_isom_get_original_duration(GF_ISOFile *isom_file); + +/*! gets the creation info of the movie +\param isom_file the target ISO file +\param creationTime set to the creation time of the movie +\param modificationTime set to the modification time of the movie +\return error if any +*/ +GF_Err gf_isom_get_creation_time(GF_ISOFile *isom_file, u64 *creationTime, u64 *modificationTime); + + +/*! gets the creation info of the movie +\param isom_file the target ISO file +\param trackNumber the target track +\param creationTime set to the creation time of the movie +\param modificationTime set to the modification time of the movie +\return error if any +*/ +GF_Err gf_isom_get_track_creation_time(GF_ISOFile *isom_file, u32 trackNumber, u64 *creationTime, u64 *modificationTime); + +/*! gets the ID of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return the trackID of the track, or 0 if error*/ +GF_ISOTrackID gf_isom_get_track_id(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets a track number by its ID +\param isom_file the target ISO file +\param trackID the target track ID +\return the track number of the track, or 0 if error*/ +u32 gf_isom_get_track_by_id(GF_ISOFile *isom_file, GF_ISOTrackID trackID); + +/*! gets the track original ID (before cloning from another file) +\param isom_file the target ISO file +\param trackNumber the target track +\return the original trackID of the track, or 0 if error*/ +GF_ISOTrackID gf_isom_get_track_original_id(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the enable flag of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return 0: not enabled, 1: enabled, 2: error +*/ +u8 gf_isom_is_track_enabled(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets track flags +\param isom_file the target ISO file +\param trackNumber the target track +\return the track flags +*/ +u32 gf_isom_get_track_flags(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the track duration +\param isom_file the target ISO file +\param trackNumber the target track +\return the track duration in movie timescale, or 0 if error*/ +u64 gf_isom_get_track_duration(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the media type (audio, video, etc) of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return the media type four character code of the media*/ +u32 gf_isom_get_media_type(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets media subtype of a sample description entry +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the media type four character code of the given sample description*/ +u32 gf_isom_get_media_subtype(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the composition time (media time) given the absolute time in the Movie +\param isom_file the target ISO file +\param trackNumber the target track +\param movieTime desired time in movie timescale +\param mediaTime is set to 0 if the media is not playing at that time (empty time segment) +\return error if any*/ +GF_Err gf_isom_get_media_time(GF_ISOFile *isom_file, u32 trackNumber, u32 movieTime, u64 *mediaTime); + +/*! gets the number of sample descriptions in the media - a media can have several stream descriptions (eg different codec configurations, different protetcions, different visual sizes). +\param isom_file the target ISO file +\param trackNumber the target track +\return the number of sample descriptions +*/ +u32 gf_isom_get_sample_description_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the stream description index for a given time in media time +\param isom_file the target ISO file +\param trackNumber the target track +\param for_time the desired time in media timescale +\return the sample description index, or 0 if error or if empty*/ +u32 gf_isom_get_sample_description_index(GF_ISOFile *isom_file, u32 trackNumber, u64 for_time); + +/*! checks if a sample stream description is self-contained (samples located in the file) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return GF_TRUE if samples referring to the given stream description are present in the file, GF_FALSE otherwise*/ +Bool gf_isom_is_self_contained(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the media duration (without edit) based on sample table +\param isom_file the target ISO file +\param trackNumber the target track +\return the media duration, 0 if no samples +*/ +u64 gf_isom_get_media_duration(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the original media duration (without edit) as indicated in the file +\param isom_file the target ISO file +\param trackNumber the target track +\return the media duration +*/ +u64 gf_isom_get_media_original_duration(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the media timescale +\param isom_file the target ISO file +\param trackNumber the target track +\return the timeScale of the media +*/ +u32 gf_isom_get_media_timescale(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets media chunking information for non-fragmented files +\param isom_file the target ISO file +\param trackNumber the target track +\param dur_min set to minimum chunk duration in media timescale (optional, can be NULL) +\param dur_avg set to average chunk duration in media timescale (optional, can be NULL) +\param dur_max set to maximum chunk duration in media timescale (optional, can be NULL) +\param size_min set to smallest chunk size in bytes (optional, can be NULL) +\param size_avg set to average chunk size in bytes (optional, can be NULL) +\param size_max set to largest chunk size in bytes (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_chunks_infos(GF_ISOFile *isom_file, u32 trackNumber, u32 *dur_min, u32 *dur_avg, u32 *dur_max, u32 *size_min, u32 *size_avg, u32 *size_max); + +/*! gets the handler name. The outName must be: +\param isom_file the target ISO file +\param trackNumber the target track +\param outName set to the handler name (must be non NULL) +\return error if any +*/ +GF_Err gf_isom_get_handler_name(GF_ISOFile *isom_file, u32 trackNumber, const char **outName); + +/*! checks if the data reference for the given track and sample description is valid and supported +(a data Reference allows to construct a file without integrating the media data, however this library only handles local storage external data references) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return error if any +*/ +GF_Err gf_isom_check_data_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the location of the data. If both outURL and outURN are set to NULL, the data is in this file +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param outURL set to URL value of the data reference +\param outURN set to URN value of the data reference +\return error if any +*/ +GF_Err gf_isom_get_data_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **outURL, const char **outURN); + +/*! gets the number of samples in a track +\param isom_file the target ISO file +\param trackNumber the target track +\return the number of samples, or 0 if error*/ +u32 gf_isom_get_sample_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the constant sample size for samples of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return constant size of samples or 0 if size not constant +*/ +u32 gf_isom_get_constant_sample_size(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the constant sample duration for samples of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return constant duration of samples, or 0 if duration not constant*/ +u32 gf_isom_get_constant_sample_duration(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets max audio sample packing in a single ISOSample. +This is mostly used when processing raw audio tracks, for which extracting samples per samples would be too time consuming +\param isom_file the target ISO file +\param trackNumber the target track +\param pack_num_samples the target number of samples to pack in one ISOSample +\return GF_TRUE if packing was successfull, GF_FALSE otherwise (non constant size and non constant duration) +*/ +Bool gf_isom_enable_raw_pack(GF_ISOFile *isom_file, u32 trackNumber, u32 pack_num_samples); + +/*! gets the total media data size of a track (whether in the file or not) +\param isom_file the target ISO file +\param trackNumber the target track +\return total amount of media bytes in track +*/ +u64 gf_isom_get_media_data_size(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets sample padding bytes when reading a sample +It may be desired to fetch samples with a bigger allocated buffer than their real size, in case the decoder +reads more data than available. This sets the amount of extra bytes to allocate when reading samples from this track +\note The dataLength of the sample does NOT include padding +\param isom_file the target ISO file +\param trackNumber the target track +\param padding_bytes the amount of bytes to add at the end of a sample data buffer +\return error if any +*/ +GF_Err gf_isom_set_sample_padding(GF_ISOFile *isom_file, u32 trackNumber, u32 padding_bytes); + +/*! fetches a sample from a track. The sample must be destroyed using \ref gf_isom_sample_del +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample +\return the ISO sample or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +*/ +GF_ISOSample *gf_isom_get_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex); + +/*! fetches a sample from a track without allocating a new sample. +This function is the same as \ref gf_isom_get_sample except that it fills in the static_sample passed as argument, potentially reallocating buffers + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample +\param static_sample a caller-allocated ISO sample to use as the returned sample +\param data_offset set to data offset in file / current bitstream - may be NULL +\return the ISO sample or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +\note If the function returns NULL, the static_sample and its associated data are NOT destroyed +*/ +GF_ISOSample *gf_isom_get_sample_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, GF_ISOSample *static_sample, u64 *data_offset); + +/*! gets sample information. This is the same as \ref gf_isom_get_sample but doesn't fetch media data + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) +\note When both sampleDescriptionIndex and data_offset are NULL, only DTS, CTS_Offset and RAP indications are retrieved (faster) +\return the ISO sample without data or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +*/ +GF_ISOSample *gf_isom_get_sample_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, u64 *data_offset); + +/*! gets sample information with a user-allocated sample. This is the same as \ref gf_isom_get_sample_info but uses a static allocated sample as input +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) +\note When both sampleDescriptionIndex and data_offset are NULL, only DTS, CTS_Offset and RAP indications are retrieved (faster) +\param static_sample a caller-allocated ISO sample to use as the returned sample +\return the ISO sample without data or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +\note If the function returns NULL, the static_sample and its associated data if any are NOT destroyed +*/ +GF_ISOSample *gf_isom_get_sample_info_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, u64 *data_offset, GF_ISOSample *static_sample); + +/*! get sample decoding time +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return decoding time in media timescale +*/ +u64 gf_isom_get_sample_dts(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets sample duration +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return sample duration in media timescale*/ +u32 gf_isom_get_sample_duration(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets sample size +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return sample size in bytes*/ +u32 gf_isom_get_sample_size(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets maximum sample size in track +\param isom_file the target ISO file +\param trackNumber the target track +\return max size of any sample in track*/ +u32 gf_isom_get_max_sample_size(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets average sample size in a track +\param isom_file the target ISO file +\param trackNumber the target track +\return average size of sample in track +*/ +u32 gf_isom_get_avg_sample_size(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets maximum sample duration in track +\param isom_file the target ISO file +\param trackNumber the target track +\return max sample delta in media timescale +*/ +u32 gf_isom_get_max_sample_delta(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets max sample CTS offset (CTS-DTS) in track +\param isom_file the target ISO file +\param trackNumber the target track +\return max sample cts offset in media timescale*/ +u32 gf_isom_get_max_sample_cts_offset(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets sample sync flag. This does not check other sample groups ('rap ' or 'sap ') +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return GF_TRUE if sample is sync, GF_FALSE otherwise*/ +Bool gf_isom_get_sample_sync(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets sample dependency flags - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param is_leading set to 1 if sample is a leading picture +\param dependsOn set to the depends_on flag +\param dependedOn set to the depended_on flag +\param redundant set to the redundant flag +\return error if any +*/ +GF_Err gf_isom_get_sample_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *is_leading, u32 *dependsOn, u32 *dependedOn, u32 *redundant); + +/*! gets a sample given a desired decoding time and set the sampleDescriptionIndex of this sample + +\warning The sample may not be sync even though the sync was requested (depends on the media and the editList) +the SampleNum is optional. If non-NULL, will contain the sampleNumber + +\param isom_file the target ISO file +\param trackNumber the target track +\param desiredTime the desired time in media timescale +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param SearchMode the search direction mode +\param sample set to the fetched sample if any. If NULL, sample is not fetched (optional, can be NULL) +\param sample_number set to the fetched sample number if any, set to 0 otherwise (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) + +\return GF_EOS if the desired time exceeds the media duration or error if any +*/ +GF_Err gf_isom_get_sample_for_media_time(GF_ISOFile *isom_file, u32 trackNumber, u64 desiredTime, u32 *sampleDescriptionIndex, GF_ISOSearchMode SearchMode, GF_ISOSample **sample, u32 *sample_number, u64 *data_offset); + +/*! gets sample number for a given decode time +\param isom_file the target ISO file +\param trackNumber the target track +\param dts the desired decode time in media timescale +\return the sample number or 0 if not found +*/ +u32 gf_isom_get_sample_from_dts(GF_ISOFile *isom_file, u32 trackNumber, u64 dts); + + +/*! enumerates the type and references IDs of a track +\param isom_file the target ISO file +\param trackNumber the target track +\param idx 0-based index of reference to query +\param referenceType set to the four character code of the reference entry +\param referenceCount set to the number of track ID references for the reference entry +\return list of track IDs, NULL if no references - do NOT modify !*/ +const GF_ISOTrackID *gf_isom_enum_track_references(GF_ISOFile *isom_file, u32 trackNumber, u32 idx, u32 *referenceType, u32 *referenceCount); + +/*! get the number of track references of a track for a given ReferenceType +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\return -1 if error or the number of references*/ +s32 gf_isom_get_reference_count(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType); + +/*! get the referenced track number for a track and a given ReferenceType and Index +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\param referenceIndex the 1-based index of the reference to query (see \ref gf_isom_get_reference_count) +\param refTrack set to the track number of the referenced track +\return error if any +*/ +GF_Err gf_isom_get_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, u32 referenceIndex, u32 *refTrack); + +/*! get the referenced track ID for a track and a given ReferenceType and Index +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\param referenceIndex the 1-based index of the reference to query (see \ref gf_isom_get_reference_count) +\param refTrackID set to the track ID of the referenced track +\return error if any +*/ +GF_Err gf_isom_get_reference_ID(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, u32 referenceIndex, GF_ISOTrackID *refTrackID); + +/*! checks if a track has a reference of given type to another track +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\param refTrackID set to the track number of the referenced track +\return the reference index if the given track has a reference of type referenceType to refTreckID, 0 otherwise*/ +u32 gf_isom_has_track_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, GF_ISOTrackID refTrackID); + +/*! fetches a sample for a given movie time, handling possible track edit lists. + +if no sample is playing, an empty sample is returned with no data and a DTS set to MovieTime when searching in sync modes +if no sample is playing, the closest sample in the edit time-line is returned when searching in regular modes + +\warning The sample may not be sync even though the sync was requested (depends on the media and the editList) + +\note This function will handle re-timestamping the sample according to the mapping of the media time-line +on the track time-line. The sample TSs (DTS / CTS offset) are expressed in MEDIA TIME SCALE +(to match the media stream TS resolution as indicated in media header / SLConfig) + + +\param isom_file the target ISO file +\param trackNumber the target track +\param movieTime the desired movie time in media timescale +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param SearchMode the search direction mode +\param sample set to the fetched sample if any. If NULL, sample is not fetched (optional, can be NULL) +\param sample_number set to the fetched sample number if any, set to 0 otherwise (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_sample_for_movie_time(GF_ISOFile *isom_file, u32 trackNumber, u64 movieTime, u32 *sampleDescriptionIndex, GF_ISOSearchMode SearchMode, GF_ISOSample **sample, u32 *sample_number, u64 *data_offset); + +/*! gets edit list type +\param isom_file the target ISO file +\param trackNumber the target track +\param mediaOffset set to the media offset of the edit for time-shifting edits +\return GF_TRUE if complex edit list, GF_FALSE if no edit list or time-shifting only edit list, in which case mediaOffset is set to the CTS of the first sample to present at presentation time 0 +A negative value implies that the samples with CTS between 0 and mediaOffset should not be presented (skip) +A positive value value implies that there is nothing to present between 0 and CTS (hold) +*/ +Bool gf_isom_get_edit_list_type(GF_ISOFile *isom_file, u32 trackNumber, s64 *mediaOffset); + +/*! gets the number of edits in an edit list +\param isom_file the target ISO file +\param trackNumber the target track +\return number of edits +*/ +u32 gf_isom_get_edits_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the desired edit information +\param isom_file the target ISO file +\param trackNumber the target track +\param EditIndex index of the edit to query (1-based index) +\param EditTime set to the edit time in movie timescale +\param SegmentDuration set to the edit duration in movie timescale +\param MediaTime set to the edit media start time in media timescale +\param EditMode set to the mode of the edit +\return error if any +*/ +GF_Err gf_isom_get_edit(GF_ISOFile *isom_file, u32 trackNumber, u32 EditIndex, u64 *EditTime, u64 *SegmentDuration, u64 *MediaTime, GF_ISOEditType *EditMode); + +/*! gets the number of languages for the copyright +\param isom_file the target ISO file +\return number of languages, 0 if no copyright*/ +u32 gf_isom_get_copyright_count(GF_ISOFile *isom_file); + +/*! gets a copyright and its language code +\param isom_file the target ISO file +\param Index the 1-based index of the copyright notice to query +\param threeCharCodes set to the copyright language code +\param notice set to the copyright notice +\return error if any +*/ +GF_Err gf_isom_get_copyright(GF_ISOFile *isom_file, u32 Index, const char **threeCharCodes, const char **notice); + +/*! gets the number of chapter for movie or track (chapters can be assigned to tracks or to movies) +\param isom_file the target ISO file +\param trackNumber the target track to queryy. If 0, looks for chapter at the movie level +\return number of chapters +*/ +u32 gf_isom_get_chapter_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the given chapter time and name for a movie or track +\param isom_file the target ISO file +\param trackNumber the target track to queryy. If 0, looks for chapter at the movie level +\param Index the index of the ckhapter to queryy +\param chapter_time set to chapter start time in milliseconds (optional, may be NULL) +\param name set to chapter name (optional, may be NULL) +\return error if any +*/ +GF_Err gf_isom_get_chapter(GF_ISOFile *isom_file, u32 trackNumber, u32 Index, u64 *chapter_time, const char **name); + +/*! 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 + in scalable context) +*/ +u8 gf_isom_has_sync_points(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the number of sync points in a media +\param isom_file the target ISO file +\param trackNumber the target track +\return number of sync points*/ +u32 gf_isom_get_sync_point_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a media track hhas composition time offset +\param isom_file the target ISO file +\param trackNumber the target track +\return 1 if the track uses unsigned compositionTime offsets (B-frames or similar), 2 if the track uses signed compositionTime offsets (B-frames or similar), 0 if the track does not use compositionTime offsets (CTS == DTS) +*/ +u32 gf_isom_has_time_offset(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets cts to dts shift value if defined. +This shift is defined only in cases of negative CTS offset (ctts version 1) and not always present in files! +Adding shift to CTS guarantees that the shifted CTS is always greater than the DTS for any sample +\param isom_file the target ISO file +\param trackNumber the target track +\return the shift from composition time to decode time for that track if indicated, or 0 if not found +*/ +s64 gf_isom_get_cts_to_dts_shift(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a track has sync shadow samples (RAP samples replacing non RAP ones) +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if the track has sync shadow samples*/ +Bool gf_isom_has_sync_shadows(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a track has sample dependencoes indicated +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if the track has sample dep indications*/ +Bool gf_isom_has_sample_dependency(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets a rough estimation of file size. This only works for completely self-contained files and without fragmentation +for the current time +\param isom_file the target ISO file +\return estimated file size in bytes*/ +u64 gf_isom_estimate_size(GF_ISOFile *isom_file); + +/*! gets next alternate group ID available +\param isom_file the target ISO file +\return next available ID for alternate groups +*/ +u32 gf_isom_get_next_alternate_group_id(GF_ISOFile *isom_file); + + +/*! gets file name of an opened ISO file +\param isom_file the target ISO file +\return the file name*/ +const char *gf_isom_get_filename(GF_ISOFile *isom_file); + + +/*! gets file brand information +The brand is used to +- differenciate MP4, MJPEG2000 and QT while indicating compatibilities +- identify tools that shall be supported for correct parsing of the file + +The function will set brand, minorVersion and AlternateBrandsCount to 0 if no brand indication is found in the file + +\param isom_file the target ISO file +\param brand set to the four character code of the brand +\param minorVersion set to an informative integer for the minor version of the major brand (optional, can be NULL) +\param AlternateBrandsCount set to the number of compatible brands (optional, can be NULL). +\return error if any*/ +GF_Err gf_isom_get_brand_info(GF_ISOFile *isom_file, u32 *brand, u32 *minorVersion, u32 *AlternateBrandsCount); + +/*! gets an alternate brand indication +\note the Major brand should always be indicated in the alternate brands +\param isom_file the target ISO file +\param BrandIndex 1-based index of alternate brand to query (cf \ref gf_isom_get_brand_info) +\param brand set to the four character code of the brand +\return error if any*/ +GF_Err gf_isom_get_alternate_brand(GF_ISOFile *isom_file, u32 BrandIndex, u32 *brand); + +/*! gets the internal list of brands +\param isom_file the target ISO file +\return the internal list of brands. DO NOT MODIFY the content +*/ +const u32 *gf_isom_get_brands(GF_ISOFile *isom_file); + +/*! gets the number of padding bits at the end of a given sample if any +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param NbBits set to the number of padded bits at the end of the sample +\return error if any*/ +GF_Err gf_isom_get_sample_padding_bits(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u8 *NbBits); + +/*! checks if a track samples use padding bits or not +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if samples have padding bits information, GF_FALSE otherwise*/ +Bool gf_isom_has_padding_bits(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets information of a visual track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param Width set to the width of the sample description in pixels +\param Height set to the height of the sample description in pixels +\return error if any*/ +GF_Err gf_isom_get_visual_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *Width, u32 *Height); + +/*! gets bit depth of a sample description of a visual track (for uncompressed media usually) +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param bitDepth the bit depth of each pixel (eg 24 for RGB, 32 for RGBA) +\return error if any +*/ +GF_Err gf_isom_get_visual_bit_depth(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 *bitDepth); + +/*! gets information of an audio track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param SampleRate set to the audio sample rate of the sample description +\param Channels set to the audio channel count of the sample description +\param bitsPerSample set to the audio bits per sample for raw audio of the sample description +\return error if any*/ +GF_Err gf_isom_get_audio_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *SampleRate, u32 *Channels, u32 *bitsPerSample); + +/*! Audio channel layout description, ISOBMFF style*/ +typedef struct +{ + /*! stream structure flags, 1: has channel layout, 2: has objects*/ + u8 stream_structure; + + /*! defined CICP channel layout*/ + u8 definedLayout; + + /*! number of channels*/ + u32 channels_count; + struct { + /*! speaker position*/ + u8 position; + /*! speaker elevation if position==126*/ + s8 elevation; + /*! speaker azimuth if position==126*/ + s16 azimuth; + } layouts[64]; + /*! bit-map of omitted channels using bit positions defined in CICP - only valid if definedLayout is not 0*/ + u64 omittedChannelsMap; + /*! number of objects in the stream*/ + u8 object_count; +} GF_AudioChannelLayout; + +/*! get channel layout info for an audio track, ISOBMFF style + \param isom_file the target ISO file + \param trackNumber the target track + \param sampleDescriptionIndex the target sample description index (1-based) + \param layout set to the channel/object layout info for this track + \return GF_NOT_FOUND if not set in file, or other error if any*/ +GF_Err gf_isom_get_audio_layout(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AudioChannelLayout *layout); + + +/*! gets visual track layout information +\param isom_file the target ISO file +\param trackNumber the target track +\param width set to the width of the track in pixels +\param height set to the height of the track in pixels +\param translation_x set to the horizontal translation of the track in pixels +\param translation_y set to the vertical translation of the track in pixels +\param layer set to the z-order of the track +\return error if any*/ +GF_Err gf_isom_get_track_layout_info(GF_ISOFile *isom_file, u32 trackNumber, u32 *width, u32 *height, s32 *translation_x, s32 *translation_y, s16 *layer); + +/*! gets matrix of a visual track +\param isom_file the target ISO file +\param trackNumber the target track +\param matrix set to the track matrix - all coord values are expressed as 16.16 fixed point floats +\return error if any*/ +GF_Err gf_isom_get_track_matrix(GF_ISOFile *isom_file, u32 trackNumber, u32 matrix[9]); + +/*! gets sample (pixel) aspect ratio information of a visual track for a given sample description +The aspect ratio is hSpacing divided by vSpacing +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param hSpacing horizontal spacing +\param vSpacing vertical spacing +\return error if any*/ +GF_Err gf_isom_get_pixel_aspect_ratio(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *hSpacing, u32 *vSpacing); + +/*! gets color information of a visual track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param colour_type set to the four character code of the colour type mode used (nclx, nclc, prof or ricc currently defined) +\param colour_primaries set to the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param transfer_characteristics set to the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param matrix_coefficients set to the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param full_range_flag set to the colour primaries for nclc as defined in ISO/IEC 23001-8 +\return error if any*/ +GF_Err gf_isom_get_color_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *colour_type, u16 *colour_primaries, u16 *transfer_characteristics, u16 *matrix_coefficients, Bool *full_range_flag); + + +/*! gets clean aperture (crop window, see ISO/IEC 14496-12) for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param cleanApertureWidthN set to nominator of clean aperture horizontal size, may be NULL +\param cleanApertureWidthD set to denominator of clean aperture horizontal size, may be NULL +\param cleanApertureHeightN set to nominator of clean aperture vertical size, may be NULL +\param cleanApertureHeightD set to denominator of clean aperture vertical size, may be NULL +\param horizOffN set to nominator of horizontal offset of clean aperture center minus (width-1)/2 (eg 0 sets center to center of video), may be NULL +\param horizOffD set to denominator of horizontal offset of clean aperture center minus (width-1)/2 (eg 0 sets center to center of video), may be NULL +\param vertOffN set to nominator of vertical offset of clean aperture center minus (height-1)/2 (eg 0 sets center to center of video), may be NULL +\param vertOffD set to denominator of vertical offset of clean aperture center minus (height-1)/2 (eg 0 sets center to center of video), may be NULL +\return error if any +*/ +GF_Err gf_isom_get_clean_aperture(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *cleanApertureWidthN, u32 *cleanApertureWidthD, u32 *cleanApertureHeightN, u32 *cleanApertureHeightD, s32 *horizOffN, u32 *horizOffD, s32 *vertOffN, u32 *vertOffD); + +/*! content light level info*/ +typedef struct { + /*! max content ligth level*/ + u16 max_content_light_level; + /*! max picture average ligth level*/ + u16 max_pic_average_light_level; +} GF_ContentLightLevelInfo; + +/*! mastering display colour volume info*/ +typedef struct { + /*! display primaries*/ + struct { + u16 x; + u16 y; + } display_primaries[3]; + /*! X white point*/ + u16 white_point_x; + /*! Y white point*/ + u16 white_point_y; + u32 max_display_mastering_luminance; + /*! min display mastering luminance*/ + u32 min_display_mastering_luminance; +} GF_MasteringDisplayColourVolumeInfo; + +/*! gets master display colour info if any +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\return the mdcv info, or NULL if none or not a valid video track +*/ +const GF_MasteringDisplayColourVolumeInfo *gf_isom_get_mastering_display_colour_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets content light level info if any +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\return the clli info, or NULL if none or not a valid video track +*/ +const GF_ContentLightLevelInfo *gf_isom_get_content_light_level_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + + +/*! gets the media language code of a track +\param isom_file the target ISO file +\param trackNumber the target track +\param lang set to a newly allocated string containg 3 chars (if old files) or longer form (BCP-47) - shall be freed by caller +\return error if any*/ +GF_Err gf_isom_get_media_language(GF_ISOFile *isom_file, u32 trackNumber, char **lang); + +/*! gets the number of kind (media role) for a given track +\param isom_file the target ISO file +\param trackNumber the target track +\return number of kind defined +*/ +u32 gf_isom_get_track_kind_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets a given kind (media role) information for a given track +\param isom_file the target ISO file +\param trackNumber the target track +\param index the 1-based index of the kind to retrieve +\param scheme set to the scheme of the kind information - shall be freed by caller +\param value set to the value of the kind information - shall be freed by caller +\return error if any*/ +GF_Err gf_isom_get_track_kind(GF_ISOFile *isom_file, u32 trackNumber, u32 index, char **scheme, char **value); + +/*! gets the magic number associated with a track. The magic number is usually set by a file muxer, and is not serialized to file +\param isom_file the target ISO file +\param trackNumber the target track +\return the magic number (0 by default) +*/ +u64 gf_isom_get_track_magic(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets track group ID of a given track group type for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param track_group_type the target track group type +\return the track group ID, 0 if not found +*/ +u32 gf_isom_get_track_group(GF_ISOFile *isom_file, u32 trackNumber, u32 track_group_type); + +/*! checks if file is a single AV file with max one audio, one video, one text and basic od/bifs +\param isom_file the target ISO file +\return GF_TRUE if file is single AV, GF_FALSE otherwise +*/ +Bool gf_isom_is_single_av(GF_ISOFile *isom_file); + +/*! guesses which specification this file refers to. +\param isom_file the target ISO file +\return possible values are: + GF_ISOM_BRAND_ISOM: unrecognized std + GF_ISOM_BRAND_3GP5: 3GP file (max 1 audio, 1 video) without text track + GF_ISOM_BRAND_3GP6: 3GP file (max 1 audio, 1 video) with text track + GF_ISOM_BRAND_3GG6: 3GP file multitrack file + GF_ISOM_BRAND_3G2A: 3GP2 file + GF_ISOM_BRAND_AVC1: AVC file + FCC("ISMA"): ISMA file (may overlap with 3GP) + GF_ISOM_BRAND_MP42: any generic MP4 file (eg with BIFS/OD/MPEG-4 systems stuff) + + for files without movie, returns the file meta handler type +*/ +u32 gf_isom_guess_specification(GF_ISOFile *isom_file); + + +/*! gets the nalu_length_field size used for this sample description if NALU-based (AVC/HEVC/...) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return number of bytes used to code the NALU size, or 0 if not NALU-based*/ +u32 gf_isom_get_nalu_length_field(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets max/average rate information as indicated in ESDS or BTRT boxes. If not found all values are set to 0 +if sampleDescriptionIndex is 0, gather for all sample descriptions + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param average_bitrate set to the average bitrate in bits per second of the media +\param max_bitrate set to the maximum bitrate in bits per second of the media +\param decode_buffer_size set to the decoder buffer size in bytes of the media +\return error if any*/ +GF_Err gf_isom_get_bitrate(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *average_bitrate, u32 *max_bitrate, u32 *decode_buffer_size); + + +/*! gets the track template of a track. This serializes track box without serializing sample tables nor sample description info +\param isom_file the destination ISO file +\param trackNumber the destination track +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_track_template(GF_ISOFile *isom_file, u32 trackNumber, u8 **output, u32 *output_size); + +/*! gets the trex template of a track. This serializes trex box +\param isom_file the destination ISO file +\param trackNumber the destination track +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_trex_template(GF_ISOFile *isom_file, u32 trackNumber, u8 **output, u32 *output_size); + +/*! 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 +\param bytes_removed number of bytes removed +\return error if any +*/ +GF_Err gf_isom_set_removed_bytes(GF_ISOFile *isom_file, u64 bytes_removed); + +/*! gets the current file offset of the current incomplete top level box not parsed + This shall be checked to avoid discarding bytes at or after the current top box header +\param isom_file the target ISO file +\param current_top_box_offset set to the offset from file first byte +\return error if any +*/ +GF_Err gf_isom_get_current_top_box_offset(GF_ISOFile *isom_file, u64 *current_top_box_offset); + +/*! purges the given number of samples, starting from the first sample, from a track of a fragmented file. + This avoids having sample tables growing in size when reading a fragmented file in pure streaming mode (no seek). + You should always keep one sample in the track +\param isom_file the target ISO file +\param trackNumber the desired track to purge +\param nb_samples the number of samples to remove +\return error if any +*/ +GF_Err gf_isom_purge_samples(GF_ISOFile *isom_file, u32 trackNumber, u32 nb_samples); + +#ifndef GPAC_DISABLE_ISOM_DUMP + +/*! dumps file structures into XML trace file +\param isom_file the target ISO file +\param trace the file object to dump to +\param skip_init does not dump init segment structure +\param skip_samples does not dump sample tables +\return error if any +*/ +GF_Err gf_isom_dump(GF_ISOFile *isom_file, FILE *trace, Bool skip_init, Bool skip_samples); + +#endif /*GPAC_DISABLE_ISOM_DUMP*/ + + +/*! gets number of chunks in track +\param isom_file the target ISO file +\param trackNumber the desired track to purge +\return number of chunks in track +*/ +u32 gf_isom_get_chunk_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets info for a given chunk in track +\param isom_file the target ISO file +\param trackNumber the desired track to purge +\param chunkNumber the 1-based index of the desired chunk +\param chunk_offset set to the chunk offset in bytes from start of file +\param first_sample_num set to the sample number of the first sample in the chunk +\param sample_per_chunk set to number of samples per chunk +\param sample_desc_idx set to sample desc index of samples of this chunk +\param cache_1 updated by function at each call. May be NULL (slower). Must be set to 0 if not querying consecutive chunks +\param cache_2 updated by function at each call. May be NULL (slower). Must be set to 0 if not querying consecutive chunks +\return error if any +*/ +GF_Err gf_isom_get_chunk_info(GF_ISOFile *isom_file, u32 trackNumber, u32 chunkNumber, u64 *chunk_offset, u32 *first_sample_num, u32 *sample_per_chunk, u32 *sample_desc_idx, u32 *cache_1, u32 *cache_2); + + +/*! gets the file offset of the first usable byte of the first mdat box in the file +\param isom_file the target ISO file +\return byte offset +*/ +u64 gf_isom_get_first_mdat_start(GF_ISOFile *isom_file); + +/*! gets the size of all skip, free and wide boxes present in the file and bytes skipped during parsing (assumes a single file was opened) +\param isom_file the target ISO file +\return size +*/ +u64 gf_isom_get_unused_box_bytes(GF_ISOFile *isom_file); + +/*! @} */ + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! +\addtogroup isowrite_grp ISOBMFF Writing +\ingroup iso_grp + +ISOBMF file writing +@{ +*/ + +/*! Movie Storage modes*/ +typedef enum +{ + /*! FLAT: the MediaData is stored at the beginning of the file*/ + GF_ISOM_STORE_FLAT = 1, + /*! STREAMABLE: the MetaData (File Info) is stored at the beginning of the file + for fast access during download*/ + GF_ISOM_STORE_STREAMABLE, + /*! INTERLEAVED: Same as STREAMABLE, plus the media data is mixed by chunk of fixed duration*/ + GF_ISOM_STORE_INTERLEAVED, + /*! INTERLEAVED +DRIFT: Same as INTERLEAVED, and adds time drift control to avoid creating too long chunks*/ + GF_ISOM_STORE_DRIFT_INTERLEAVED, + /*! tightly interleaves samples based on their DTS, therefore allowing better placement of samples in the file. + This is used for both http interleaving and Hinting optimizations*/ + GF_ISOM_STORE_TIGHT, + /*! FASTSTART: same as FLAT but moves moov before mdat at the end*/ + GF_ISOM_STORE_FASTSTART, +} GF_ISOStorageMode; + +/*! writes the file without deleting (see \ref gf_isom_delete) +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_write(GF_ISOFile *isom_file); + +/*! freezes order of the current box tree in the file. +By default the library always reorder boxes in the recommended order in the various specifications implemented. +New created tracks or meta items will not have a frozen order of boxes, but the function can be called several time +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_freeze_order(GF_ISOFile *isom_file); + +/*! keeps UTC edit times when storing +\param isom_file the target ISO file +\param keep_utc if GF_TRUE, do not edit times +*/ +void gf_isom_keep_utc_times(GF_ISOFile *isom_file, Bool keep_utc); + +/*! Checks if UTC keeping is enabled +\param isom_file the target ISO file +\return GF_TRUE if UTC keeping is enabled +*/ +Bool gf_isom_has_keep_utc_times(GF_ISOFile *isom_file); + +/*! sets the timescale of the movie. This rescales times expressed in movie timescale in edit lists and mvex boxes +\param isom_file the target ISO file +\param timeScale the target timescale +\return error if any +*/ +GF_Err gf_isom_set_timescale(GF_ISOFile *isom_file, u32 timeScale); + +/*! loads a set of top-level boxes in moov udta and child boxes. UDTA will be replaced if already present +\param isom_file the target ISO file +\param moov_boxes a serialized array of boxes to add +\param moov_boxes_size the size of the serialized array of boxes +\param udta_only only replace/inject udta box and entries +\return error if any +*/ +GF_Err gf_isom_load_extra_boxes(GF_ISOFile *isom_file, u8 *moov_boxes, u32 moov_boxes_size, Bool udta_only); + +/*! creates a new track +\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 MediaType the handler type (four character code) of the media +\param TimeScale the time scale of the media +\return the track number or 0 if error*/ +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 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); + +/*! removes a track - internal cross dependencies will be updated. +\warning Any OD streams with references to this track through ODUpdate, ESDUpdate, ESDRemove commands +will be rewritten +\param isom_file the target ISO file +\param trackNumber the target track to remove file +\return error if any +*/ +GF_Err gf_isom_remove_track(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets the enable flag of a track +\param isom_file the target ISO file +\param trackNumber the target track +\param enableTrack if GF_TRUE, track is enabled, otherwise disabled +\return error if any +*/ +GF_Err gf_isom_set_track_enabled(GF_ISOFile *isom_file, u32 trackNumber, Bool enableTrack); + +/*! Track header flags operation type*/ +typedef enum +{ + /*! set flags, erasing previous value*/ + GF_ISOM_TKFLAGS_SET = 1, + /*! add flags*/ + GF_ISOM_TKFLAGS_REM, + /*! remove flags*/ + GF_ISOM_TKFLAGS_ADD, +} GF_ISOMTrackFlagOp; + + +/*! Track header flags*/ +enum +{ + /*! track is enabled */ + GF_ISOM_TK_ENABLED = 1, + /*! track is in regular presentation*/ + GF_ISOM_TK_IN_MOVIE = 1<<1, + /*! track is in preview*/ + GF_ISOM_TK_IN_PREVIEW = 1<<2, + /*! track size is an aspect ratio indicator only*/ + GF_ISOM_TK_SIZE_IS_AR = 1<<3 +}; + +/*! toggles track flags on or off +\param isom_file the target ISO file +\param trackNumber the target track +\param flags flags to modify +\param op flag operation mode +\return error if any +*/ +GF_Err gf_isom_set_track_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 flags, GF_ISOMTrackFlagOp op); + +/*! sets creationTime and modificationTime of the movie to the specified dates (no validty check) +\param isom_file the target ISO file +\param create_time the new creation time +\param modif_time the new modification time +\return error if any +*/ +GF_Err gf_isom_set_creation_time(GF_ISOFile *isom_file, u64 create_time, u64 modif_time); + +/*! sets creationTime and modificationTime of the track to the specified dates +\param isom_file the target ISO file +\param trackNumber the target track +\param create_time the new creation time +\param modif_time the new modification time +\return error if any +*/ +GF_Err gf_isom_set_track_creation_time(GF_ISOFile *isom_file, u32 trackNumber, u64 create_time, u64 modif_time); + +/*! sets creationTime and modificationTime of the track media header to the specified dates +\param isom_file the target ISO file +\param trackNumber the target track +\param create_time the new creation time +\param modif_time the new modification time +\return error if any +*/ +GF_Err gf_isom_set_media_creation_time(GF_ISOFile *isom_file, u32 trackNumber, u64 create_time, u64 modif_time); + +/*! changes the ID of a track - all track references present in the file are updated +\param isom_file the target ISO file +\param trackNumber the target track +\param trackID the new track ID +\return error if trackID is already in used in the file*/ +GF_Err gf_isom_set_track_id(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID trackID); + +/*! forces to rewrite all dependencies when track ID changes. Used to check if track references are broken during import of a single track +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_rewrite_track_dependencies(GF_ISOFile *isom_file, u32 trackNumber); + +/*! adds a sample to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index associated with the sample +\param sample the target sample to add +\return error if any +*/ +GF_Err gf_isom_add_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_ISOSample *sample); + +/*! copies all sample dependency, subSample and sample group information from the given sampleNumber in source file to the last added sample in dest file +\param dst the destination ISO file +\param dst_track the destination track +\param src the source ISO file +\param src_track the source track +\param sampleNumber the source sample number +\return error if any +*/ +GF_Err gf_isom_copy_sample_info(GF_ISOFile *dst, u32 dst_track, GF_ISOFile *src, u32 src_track, u32 sampleNumber); + +/*! adds a sync shadow sample to a track. +- There must be a regular sample with the same DTS. +- Sync Shadow samples MUST be RAP and can only use the same sample description as the sample they shadow +- Currently, adding sync shadow must be done in order (no sample insertion) + +\param isom_file the target ISO file +\param trackNumber the target track +\param sample the target shadow sample to add +\return error if any +*/ +GF_Err gf_isom_add_sample_shadow(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOSample *sample); + +/*! adds data to current sample in the track. This will update the data size. +CANNOT be used with OD media type +There shall not be any other +\param isom_file the target ISO file +\param trackNumber the target track +\param data the data to append to the sample +\param data_size the size of the data to append +\return error if any +*/ +GF_Err gf_isom_append_sample_data(GF_ISOFile *isom_file, u32 trackNumber, u8 *data, u32 data_size); + +/*! adds sample references to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index associated with the sample +\param sample the target sample to add +\param dataOffset is the offset in bytes of the data in the referenced file. +\return error if any +*/ +GF_Err gf_isom_add_sample_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_ISOSample *sample, u64 dataOffset); + +/*! sets the duration of the last media sample. If not set, the duration of the last sample is the +duration of the previous one if any, or media TimeScale (default value). This does not modify the edit list if any, +you must modify this using \ref gf_isom_set_edit +\param isom_file the target ISO file +\param trackNumber the target track +\param duration duration of last sample in media timescale +\return error if any +*/ +GF_Err gf_isom_set_last_sample_duration(GF_ISOFile *isom_file, u32 trackNumber, u32 duration); + +/*! sets the duration of the last media sample. If not set, the duration of the last sample is the +duration of the previous one if any, or media TimeScale (default value). This does not modify the edit list if any, +you must modify this using \ref gf_isom_set_edit. +If both dur_num and dur_den are both zero, forces last sample duration to be the same as previous sample +\param isom_file the target ISO file +\param trackNumber the target track +\param dur_num duration num value +\param dur_den duration num value +\return error if any +*/ +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 +\param isom_file the target ISO file +\param trackNumber the target track +\param next_dts target decode time of next sample +\return error if any +*/ +GF_Err gf_isom_patch_last_sample_duration(GF_ISOFile *isom_file, u32 trackNumber, u64 next_dts); + +/*! adds a track reference to another track +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference +\param ReferencedTrackID the ID of the track referred to +\return error if any +*/ +GF_Err gf_isom_set_track_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, GF_ISOTrackID ReferencedTrackID); + +/*! removes all track references +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_remove_track_references(GF_ISOFile *isom_file, u32 trackNumber); + +/*! removes any track reference poiting to a non-existing track +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_purge_track_reference(GF_ISOFile *isom_file, u32 trackNumber); + +/*! removes all track references of a given type +\param isom_file the target ISO file +\param trackNumber the target track +\param ref_type the reference type to remove +\return error if any +*/ +GF_Err gf_isom_remove_track_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 ref_type); + +/*! sets track handler name. +\param isom_file the target ISO file +\param trackNumber the target track +\param nameUTF8 the handler name; either NULL (reset), a UTF-8 formatted string or a UTF8 file resource in the form "file://path/to/file_utf8" +\return error if any +*/ +GF_Err gf_isom_set_handler_name(GF_ISOFile *isom_file, u32 trackNumber, const char *nameUTF8); + +/*! updates the sample size table - this is needed when using \ref gf_isom_append_sample_data in case the resulting samples +are of same sizes (typically in 3GP speech tracks) +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_refresh_size_info(GF_ISOFile *isom_file, u32 trackNumber); + +/*! updates the duration of the movie +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_update_duration(GF_ISOFile *isom_file); + + +/*! updates a given sample of the media. This function updates both media data of sample and sample properties (DTS, CTS, SAP type) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the number of the sample to update +\param sample the new sample +\param data_only if set to GF_TRUE, only the sample data is updated, not other info +\return error if any +*/ +GF_Err gf_isom_update_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, Bool data_only); + +/*! updates a sample reference in the media. Note that the sample MUST exists, and that sample->data MUST be NULL and sample->dataLength must be NON NULL. +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the number of the sample to update +\param sample the new sample +\param data_offset new offset of sample in referenced file +\return error if any +*/ +GF_Err gf_isom_update_sample_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, u64 data_offset); + +/*! removes a given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the number of the sample to update +\return error if any +*/ +GF_Err gf_isom_remove_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + + +/*! changes media time scale + +\param isom_file the target ISO file +\param trackNumber the target track +\param new_timescale the new timescale to set +\param new_tsinc if not 0, changes sample duration and composition offsets to new_tsinc/new_timescale. If non-constant sample dur is used, uses the samllest sample dur in the track. Otherwise, only changes the timescale +\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 +\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); + + + +/*! adds sample auxiliary data + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the sample number. Must be equal or larger to last auxiliary +\param aux_type auxiliary sample data type, shall not be 0 +\param aux_info auxiliary sample data specific info type, may be 0 +\param data data to add +\param size size of data to add +\return error if any +*/ +GF_Err gf_isom_add_sample_aux_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 aux_type, u32 aux_info, u8 *data, u32 size); + +/*! sets the save file name of the (edited) movie. +If the movie is edited, the default fileName is the open name suffixed with an internally defined extension "%p_isotmp")" +\note you cannot save an edited file under the same name (overwrite not allowed) +If the movie is created (WRITE mode), the default filename is $OPEN_NAME + +\param isom_file the target ISO file +\param filename the new final filename +\return error if any +*/ +GF_Err gf_isom_set_final_name(GF_ISOFile *isom_file, char *filename); + +/*! sets the storage mode of a file (FLAT, STREAMABLE, INTERLEAVED) +\param isom_file the target ISO file +\param storage_mode the target storage mode +\return error if any +*/ +GF_Err gf_isom_set_storage_mode(GF_ISOFile *isom_file, GF_ISOStorageMode storage_mode); + +/*! sets the interleaving time of media data (INTERLEAVED mode only) +\param isom_file the target ISO file +\param InterleaveTime the target interleaving time in movie timescale +\return error if any +*/ +GF_Err gf_isom_set_interleave_time(GF_ISOFile *isom_file, u32 InterleaveTime); + +/*! forces usage of 64 bit chunk offsets +\param isom_file the target ISO file +\param set_on if GF_TRUE, 64 bit chunk offsets are always used; otherwise, they are used only for large files +\return error if any +*/ +GF_Err gf_isom_force_64bit_chunk_offset(GF_ISOFile *isom_file, Bool set_on); + +/*! compression mode of top-level boxes*/ +typedef enum +{ + /*! no compression is used*/ + GF_ISOM_COMP_NONE=0, + /*! only moov box is compressed*/ + GF_ISOM_COMP_MOOV, + /*! only moof boxes are compressed*/ + GF_ISOM_COMP_MOOF, + /*! only moof and sidx boxes are compressed*/ + GF_ISOM_COMP_MOOF_SIDX, + /*! only moof, sidx and ssix boxes are compressed*/ + GF_ISOM_COMP_MOOF_SSIX, + /*! all (moov, moof, sidx and ssix) boxes are compressed*/ + GF_ISOM_COMP_ALL, +} GF_ISOCompressMode; + +enum +{ + /*! forces compressed box even if compress size is larger than uncompressed size*/ + GF_ISOM_COMP_FORCE_ALL = 0x01, + /*! wraps ftyp in otyp*/ + GF_ISOM_COMP_WRAP_FTYPE = 0x02, +}; + + +/*! sets compression mode of file +\param isom_file the target ISO file +\param compress_mode the desired compress mode +\param compress_flags compress mode flags +\return error if any +*/ +GF_Err gf_isom_enable_compression(GF_ISOFile *isom_file, GF_ISOCompressMode compress_mode, u32 compress_flags); + +/*! sets the copyright in one language +\param isom_file the target ISO file +\param threeCharCode the ISO three character language code for copyright +\param notice the copyright notice to add +\return error if any +*/ +GF_Err gf_isom_set_copyright(GF_ISOFile *isom_file, const char *threeCharCode, char *notice); + +/*! adds a kind type to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param schemeURI the scheme URI of the added kind +\param value the value of the added kind +\return error if any +*/ +GF_Err gf_isom_add_track_kind(GF_ISOFile *isom_file, u32 trackNumber, const char *schemeURI, const char *value); + +/*! removes a kind type to the track, all if NULL params +\param isom_file the target ISO file +\param trackNumber the target track +\param schemeURI the scheme URI of the removed kind +\param value the value of the removed kind +\return error if any +*/ +GF_Err gf_isom_remove_track_kind(GF_ISOFile *isom_file, u32 trackNumber, const char *schemeURI, const char *value); + +/*! changes the handler type of the media +\warning This may completely breaks the parsing of the media track +\param isom_file the target ISO file +\param trackNumber the target track +\param new_type the new handler four character type +\return error if any +*/ +GF_Err gf_isom_set_media_type(GF_ISOFile *isom_file, u32 trackNumber, u32 new_type); + +/*! changes the type of the sampleDescriptionBox +\warning This may completely breaks the parsing of the media track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param new_type the new four character code type of the smaple description +\return error if any +*/ +GF_Err gf_isom_set_media_subtype(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 new_type); + +/*! sets a track in an alternate group +\param isom_file the target ISO file +\param trackNumber the target track +\param groupId the alternate group ID +\return error if any +*/ +GF_Err gf_isom_set_alternate_group_id(GF_ISOFile *isom_file, u32 trackNumber, u32 groupId); + +/*! adds chapter info: + +\param isom_file the target ISO file +\param trackNumber the target track. If 0, the chapter info is added to the movie, otherwise to the track +\param timestamp the chapter start time in milliseconds. Chapters are added in order to the file. If a chapter with same timestamp + is found, its name is updated but no entry is created. +\param name the chapter name. If NULL, defaults to 'Chapter N' +\return error if any +*/ +GF_Err gf_isom_add_chapter(GF_ISOFile *isom_file, u32 trackNumber, u64 timestamp, char *name); + +/*! deletes copyright +\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 copyrights +\return error if any +*/ +GF_Err gf_isom_remove_chapter(GF_ISOFile *isom_file, u32 trackNumber, u32 index); + +/*! updates or inserts a new edit in the track time line. Edits are used to modify +the media normal timing. EditTime and EditDuration are expressed in movie timescale +\note If a segment with EditTime already exists, it is erase +\note If there is a segment before this new one, its duration is adjust to match EditTime of the new segment +\warning The first segment always have an EditTime of 0. You should insert an empty or dwelled segment first + +\param isom_file the target ISO file +\param trackNumber the target track +\param EditTime the start of the edit in movie timescale +\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 +*/ +GF_Err gf_isom_set_edit(GF_ISOFile *isom_file, u32 trackNumber, u64 EditTime, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); + + + +/*! updates or inserts a new edit in the track time line. Edits are used to modify +the media normal timing. EditTime and EditDuration are expressed in movie timescale +\note If a segment with EditTime already exists, it is erase +\note If there is a segment before this new one, its duration is adjust to match EditTime of the new segment +\warning The first segment always have an EditTime of 0. You should insert an empty or dwelled segment first + +\param isom_file the target ISO file +\param trackNumber the target track +\param EditTime the start of the edit in movie timescale +\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 MediaRate a 16.16 rate (0x10000 means normal playback) +\return error if any +*/ +GF_Err gf_isom_set_edit_with_rate(GF_ISOFile *isom_file, u32 trackNumber, u64 EditTime, u64 EditDuration, u64 MediaTime, u32 MediaRate); + + +/*! same as \ref gf_isom_set_edit except only modifies duration type and mediaType +\param isom_file the target ISO file +\param trackNumber the target track +\param edit_index the 1-based index of the edit to update +\param EditDuration duration of the edit in movie timescale +\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_Err gf_isom_modify_edit(GF_ISOFile *isom_file, u32 trackNumber, u32 edit_index, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); + +/*! same as \ref gf_isom_modify_edit except only appends new segment +\param isom_file the target ISO file +\param trackNumber the target track +\param EditDuration duration of the edit in movie timescale +\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_Err gf_isom_append_edit(GF_ISOFile *isom_file, u32 trackNumber, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); + +/*! removes all edits in the track +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_remove_edits(GF_ISOFile *isom_file, u32 trackNumber); + +/*! removes the given edit. If this is not the last segment, the next segment duration is updated to maintain a continous timeline +\param isom_file the target ISO file +\param trackNumber the target track +\param edit_index the 1-based index of the edit to update +\return error if any +*/ +GF_Err gf_isom_remove_edit(GF_ISOFile *isom_file, u32 trackNumber, u32 edit_index); + +/*! updates edit list after track edition. All edit entries with a duration or media starttime larger than the media duration are clamped to media duration +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_update_edit_list_duration(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! remove track, moov or file-level UUID box of matching type +\param isom_file the target ISO file +\param trackNumber the target track for the UUID box; if 0, removes from movie; if 0xFFFFFFFF, removes from file +\param UUID the UUID of the box to remove +\return error if any +*/ +GF_Err gf_isom_remove_uuid(GF_ISOFile *isom_file, u32 trackNumber, bin128 UUID); + +/*! adds track, moov or file-level UUID box +\param isom_file the target ISO file +\param trackNumber the target track for the UUID box; if 0, removes from movie; if 0xFFFFFFFF, removes from file +\param UUID the UUID of the box to remove +\param data the data to add, may be NULL +\param size the size of the data to add, shall be 0 when data is NULL +\return error if any +*/ +GF_Err gf_isom_add_uuid(GF_ISOFile *isom_file, u32 trackNumber, bin128 UUID, const u8 *data, u32 size); + +/*! uses a compact track version for sample size. This is not usually recommended +except for speech codecs where the track has a lot of small samples +compaction is done automatically while writing based on the track's sample sizes +\param isom_file the target ISO file +\param trackNumber the target track for the udta box; if 0, add the udta to the movie; +\param CompactionOn if set to GF_TRUE, compact size tables are used; otherwise regular size tables are used +\return error if any +*/ +GF_Err gf_isom_use_compact_size(GF_ISOFile *isom_file, u32 trackNumber, Bool CompactionOn); + +/*! disabled brand rewrite for file, usually done for temporary import in an existing file +\param isom_file the target ISO file +\param do_disable if true, brand rewrite is disabled, otherwise enabled +\return error if any +*/ +GF_Err gf_isom_disable_brand_rewrite(GF_ISOFile *isom_file, Bool do_disable); + +/*! sets the brand of the movie +\note this automatically adds the major brand to the set of alternate brands if not present +\param isom_file the target ISO file +\param MajorBrand four character code of the major brand to set +\param MinorVersion version of the brand +\return error if any +*/ +GF_Err gf_isom_set_brand_info(GF_ISOFile *isom_file, u32 MajorBrand, u32 MinorVersion); + +/*! adds or removes an alternate brand for the movie. +\note When removing an alternate brand equal to the major brand, the major brand is updated with the first alternate brand remaining, or 'isom' otherwise +\param isom_file the target ISO file +\param Brand four character code of the brand to add or remove +\param AddIt if set to GF_TRUE, the brand is added, otherwise it is removed +\return error if any +*/ +GF_Err gf_isom_modify_alternate_brand(GF_ISOFile *isom_file, u32 Brand, Bool AddIt); + +/*! removes all alternate brands except major brand +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_reset_alt_brands(GF_ISOFile *isom_file); + +/*! removes all alternate brands except major brand +\param isom_file the target ISO file +\param leave_empty if GF_TRUE, does not create a default alternate brand matching the major brand +\return error if any +*/ +GF_Err gf_isom_reset_alt_brands_ex(GF_ISOFile *isom_file, Bool leave_empty); + +/*! set sample dependency flags - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleNumber the target sample number +\param isLeading indicates that the sample is a leading picture +\param dependsOn indicates the sample dependency towards other samples +\param dependedOn indicates the sample dependency from other samples +\param redundant indicates that the sample contains redundant coding +\return error if any +*/ +GF_Err gf_isom_set_sample_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant); + +/*! sets size information of a sample description of a visual track +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param Width the width in pixels +\param Height the height in pixels +\return error if any +*/ +GF_Err gf_isom_set_visual_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 Width, u32 Height); + +/*! sets bit depth of a sample description of a visual track (for uncompressed media usually) +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param bitDepth the bit depth of each pixel (eg 24 for RGB, 32 for RGBA) +\return error if any +*/ +GF_Err gf_isom_set_visual_bit_depth(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 bitDepth); + +/*! sets a visual track layout info +\param isom_file the target ISO file +\param trackNumber the target track number +\param width the track width in pixels +\param height the track height in pixels +\param translation_x the horizontal translation (from the left) of the track in the movie canvas, expressed as 16.16 fixed point float +\param translation_y the vertical translation (from the top) of the track in the movie canvas, expressed as 16.16 fixed point float +\param layer z order of the track on the canvas +\return error if any +*/ +GF_Err gf_isom_set_track_layout_info(GF_ISOFile *isom_file, u32 trackNumber, u32 width, u32 height, s32 translation_x, s32 translation_y, s16 layer); + +/*! 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 +\return error if any + */ +GF_Err gf_isom_set_track_matrix(GF_ISOFile *isom_file, u32 trackNumber, s32 matrix[9]); + +/*! sets the pixel aspect ratio for a sample description +\note the aspect ratio is expressed as hSpacing divided by vSpacing; 2:1 means pixel is twice as wide as it is high +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param hSpacing horizontal spacing of the aspect ratio; a value of 0 removes PAR; negative value means 1 +\param vSpacing vertical spacing of the aspect ratio; a value of 0 removes PAR; negative value means 1 +\param force_par if set, forces PAR to 1:1 when hSpacing=vSpacing; otherwise removes PAR when hSpacing=vSpacing +\return error if any +*/ +GF_Err gf_isom_set_pixel_aspect_ratio(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, s32 hSpacing, s32 vSpacing, Bool force_par); + +/*! sets clean aperture (crop window, see ISO/IEC 14496-12) for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param cleanApertureWidthN nominator of clean aperture horizontal size +\param cleanApertureWidthD denominator of clean aperture horizontal size +\param cleanApertureHeightN nominator of clean aperture vertical size +\param cleanApertureHeightD denominator of clean aperture vertical size +\param horizOffN nominator of horizontal offset of clean aperture center minus (width-1)/2 (eg 0 sets center to center of video) +\param horizOffD denominator of horizontal offset of clean aperture center minus (width-1)/2 (eg 0 sets center to center of video) +\param vertOffN nominator of vertical offset of clean aperture center minus (height-1)/2 (eg 0 sets center to center of video) +\param vertOffD denominator of vertical offset of clean aperture center minus (height-1)/2 (eg 0 sets center to center of video) +\return error if any +*/ +GF_Err gf_isom_set_clean_aperture(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 cleanApertureWidthN, u32 cleanApertureWidthD, u32 cleanApertureHeightN, u32 cleanApertureHeightD, s32 horizOffN, u32 horizOffD, s32 vertOffN, u32 vertOffD); + +/*! updates track aperture information for QT/ProRes +\param isom_file the target ISO file +\param trackNumber the target track number +\param remove if GF_TRUE, remove track aperture information, otherwise updates it +\return error if any +*/ +GF_Err gf_isom_update_aperture_info(GF_ISOFile *isom_file, u32 trackNumber, Bool remove); + + +/*! sets high dynamic range information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param mdcv the mastering display colour volume to set, if NULL removes the info +\param clli the content light level to set, if NULL removes the info +\return error if any +*/ +GF_Err gf_isom_set_high_dynamic_range_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_MasteringDisplayColourVolumeInfo *mdcv, GF_ContentLightLevelInfo *clli); + +/*! force Dolby Vision profile: mainly used when the bitstream doesn't contain all the necessary information +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param dvcc the Dolby Vision configuration +\return error if any +*/ +GF_Err gf_isom_set_dolby_vision_profile(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_DOVIDecoderConfigurationRecord *dvcc); + + +/*! sets image sequence coding constraints (mostly used for HEIF image files) +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param remove if set to GF_TRUE, removes coding constraints +\param all_ref_pics_intra indicates if all reference pictures are intra frames +\param intra_pred_used indicates if intra prediction is used +\param max_ref_per_pic indicates the max number of reference images per picture +\return error if any + */ +GF_Err gf_isom_set_image_sequence_coding_constraints(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool remove, Bool all_ref_pics_intra, Bool intra_pred_used, u32 max_ref_per_pic); + +/*! sets image sequence alpha flag (mostly used for HEIF image files). The alpha flag indicates the image sequence is an alpha plane +or has an alpha channel +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param remove if set to GF_TRUE, removes coding constraints +\return error if any +*/ +GF_Err gf_isom_set_image_sequence_alpha(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool remove); + +/*! sets colour information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param colour_type the four character code of the colour type to set (nclc, nclx, prof, ricc); if 0, removes all color info +\param colour_primaries the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param transfer_characteristics the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param matrix_coefficients the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param full_range_flag the colour primaries for nclc as defined in ISO/IEC 23001-8 +\param icc_data the icc data pto set for prof and ricc types +\param icc_size the size of the icc data +\return error if any +*/ +GF_Err gf_isom_set_visual_color_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 colour_type, u16 colour_primaries, u16 transfer_characteristics, u16 matrix_coefficients, Bool full_range_flag, u8 *icc_data, u32 icc_size); + + +/*! Audio Sample Description signaling mode*/ +typedef enum { + /*! 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 and forces channel count to 2*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2, + /*! 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 +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param sampleRate the audio sample rate +\param nbChannels the number of audio channels +\param bitsPerSample the number of bits per sample, mostly used for raw audio +\param asemode type of audio entry signaling desired +\return error if any +*/ +GF_Err gf_isom_set_audio_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 sampleRate, u32 nbChannels, u8 bitsPerSample, GF_AudioSampleEntryImportMode asemode); + + +/*! sets audio channel and object layout information for a sample description, ISOBMFF style +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param layout the layout information +\return error if any +*/ +GF_Err gf_isom_set_audio_layout(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AudioChannelLayout *layout); + +/*! sets CTS unpack mode (used for B-frames & like): in unpack mode, each sample uses one entry in CTTS tables + +\param isom_file the target ISO file +\param trackNumber the target track number +\param unpack if GF_TRUE, sets unpack on, creating a ctts table if none found; if GF_FALSE, sets unpack off and repacks all table info +\return error if any +*/ +GF_Err gf_isom_set_cts_packing(GF_ISOFile *isom_file, u32 trackNumber, Bool unpack); + +/*! shifts all CTS with the given offset. This MUST be called in unpack mode only +\param isom_file the target ISO file +\param trackNumber the target track number +\param offset_shift CTS offset shift in media timescale +\return error if any +*/ +GF_Err gf_isom_shift_cts_offset(GF_ISOFile *isom_file, u32 trackNumber, s32 offset_shift); + +/*! enables negative composition offset in track +\note this will compute the composition to decode time information +\param isom_file the target ISO file +\param trackNumber the target track +\param use_negative_offsets if GF_TRUE, negative offsets are used, otherwise they are disabled +\return error if any +*/ +GF_Err gf_isom_set_composition_offset_mode(GF_ISOFile *isom_file, u32 trackNumber, Bool use_negative_offsets); + +/*! enables negative composition offset in track and shift offsets + +\param isom_file the target ISO file +\param trackNumber the target track +\param ctts_shift shif CTS offsets by the given time in media timescale if positive offsets only are used +\return error if any +*/ +GF_Err gf_isom_set_ctts_v1(GF_ISOFile *isom_file, u32 trackNumber, u32 ctts_shift); + + +/*! sets language for a track +\param isom_file the target ISO file +\param trackNumber the target track number +\param code 3-character code or BCP-47 code media language +\return error if any +*/ +GF_Err gf_isom_set_media_language(GF_ISOFile *isom_file, u32 trackNumber, char *code); + +/*! gets the ID of the last created track +\param isom_file the target ISO file +\return the last created track ID +*/ +GF_ISOTrackID gf_isom_get_last_created_track_id(GF_ISOFile *isom_file); + +/*! applies a box patch to the file. See examples in gpac test suite, media/boxpatch/ +\param isom_file the target ISO file +\param trackID the ID of the track to patch, in case one of the box patch applies to a track +\param box_patch_filename the name of the file containing the box patches +\param for_fragments indicates if the patch targets movie fragments or regular moov +\return error if any +*/ +GF_Err gf_isom_apply_box_patch(GF_ISOFile *isom_file, GF_ISOTrackID trackID, const char *box_patch_filename, Bool for_fragments); + +/*! sets track magic number +\param isom_file the target ISO file +\param trackNumber the target track +\param magic the magic number to set; magic number is not written to file +\return error if any +*/ +GF_Err gf_isom_set_track_magic(GF_ISOFile *isom_file, u32 trackNumber, u64 magic); + +/*! sets track index in moov +\param isom_file the target ISO file +\param trackNumber the target track +\param index the 1-based index to set. Tracks will be reordered after this! +\param track_num_changed callback function used to notify track changes during the call to this function +\param udta opaque user data for the callback function +\return error if any +*/ +GF_Err gf_isom_set_track_index(GF_ISOFile *isom_file, u32 trackNumber, u32 index, void (*track_num_changed)(void *udta, u32 old_track_num, u32 new_track_num), void *udta); + +/*! removes a sample description with the given index +\warning This does not remove any added samples for that stream description, nor rewrite the sample to chunk and other boxes referencing the sample description index ! +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description to remove +\return error if any +*/ +GF_Err gf_isom_remove_stream_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! 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 +\return error if any +*/ +GF_Err gf_isom_update_bitrate(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size); + + +/*! track clone flags*/ +typedef enum +{ + /*! set this flag to keep data reference entries while cloning track*/ + GF_ISOM_CLONE_TRACK_KEEP_DREF = 1, + /*! set this flag to avoid cloning track as a QT track while cloning track*/ + GF_ISOM_CLONE_TRACK_NO_QT = 1<<1, + /*! drop track ID while importing*/ + GF_ISOM_CLONE_TRACK_DROP_ID = 1<<2 +} GF_ISOTrackCloneFlags; + +/*! clones a track. This clones everything except media data and sample info (DTS, CTS, RAPs, etc...), and also clones sample descriptions +\param orig_file the source ISO file +\param orig_track the source track +\param dest_file the destination ISO file +\param flags flags to use during clone +\param dest_track set to the track number of cloned track +\return error if any +*/ +GF_Err gf_isom_clone_track(GF_ISOFile *orig_file, u32 orig_track, GF_ISOFile *dest_file, GF_ISOTrackCloneFlags flags, u32 *dest_track); + + +/*! sets the GroupID of a track (only used for optimized interleaving). By setting GroupIDs +you can specify the storage order for media data of a group of streams. This is useful +for BIFS presentation so that static resources of the scene can be downloaded before BIFS + +\param isom_file the target ISO file +\param trackNumber the target track +\param GroupID the desired group ID +\return error if any +*/ +GF_Err gf_isom_set_track_interleaving_group(GF_ISOFile *isom_file, u32 trackNumber, u32 GroupID); + +/*! sets the priority of a track within a Group (used for optimized interleaving and hinting). +This allows tracks to be stored before other within a same group, for instance the +hint track data can be stored just before the media data, reducing disk seeking + +\param isom_file the target ISO file +\param trackNumber the target track +\param InversePriority the desired priority. For a same time, within a group of tracks, the track with the lowest InversePriority will +be written first +\return error if any +*/ +GF_Err gf_isom_set_track_priority_in_group(GF_ISOFile *isom_file, u32 trackNumber, u32 InversePriority); + +/*! sets the maximum chunk size for a track +\param isom_file the target ISO file +\param trackNumber the target track +\param maxChunkSize the maximum chunk size in bytes +\return error if any +*/ +GF_Err gf_isom_hint_max_chunk_size(GF_ISOFile *isom_file, u32 trackNumber, u32 maxChunkSize); + +/*! sets the maximum chunk duration for a track +\param isom_file the target ISO file +\param trackNumber the target track +\param maxChunkDur the maximum chunk duration in media timescale +\return error if any +*/ +GF_Err gf_isom_hint_max_chunk_duration(GF_ISOFile *isom_file, u32 trackNumber, u32 maxChunkDur); + +/*! sets up interleaving for storage (shortcut for storeage mode + interleave_time) +\param isom_file the target ISO file +\param TimeInSec the desired interleaving time in seconds +\return error if any +*/ +GF_Err gf_isom_make_interleave(GF_ISOFile *isom_file, Double TimeInSec); + +/*! sets up interleaving for storage (shortcut for storeage mode + interleave_time) +\param isom_file the target ISO file +\param fTimeInSec the desired interleaving time in seconds, as a fraction +\return error if any +*/ +GF_Err gf_isom_make_interleave_ex(GF_ISOFile *isom_file, GF_Fraction *fTimeInSec); + +/*! sets progress callback when writing a file +\param isom_file the target ISO file +\param progress_cbk the progress callback function +\param progress_cbk_udta opaque data passed to the progress callback function +*/ +void gf_isom_set_progress_callback(GF_ISOFile *isom_file, void (*progress_cbk)(void *udta, u64 nb_done, u64 nb_total), void *progress_cbk_udta); + +/*! sets write callback functions for in-memory file writing +\param isom_file the target ISO file +\param on_block_out the block write callback function +\param on_block_patch the block patch callback function +\param usr_data opaque user data passed to callback functions +\param block_size desired block size in bytes +\return error if any +*/ +GF_Err gf_isom_set_write_callback(GF_ISOFile *isom_file, + GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size), + GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert), + void *usr_data, + u32 block_size); + +/*! checks if file will use in-place rewriting or not +\param isom_file the target ISO file +\return GF_TRUE if in-place rewrite is used, GF_FALSE otherwise +*/ +Bool gf_isom_is_inplace_rewrite(GF_ISOFile *isom_file); + +/*! Disables inplace rewrite. Once in-place rewrite is disabled, the file can no longer be rewrittten in place. + + In-place rewriting allows editing the file structure (ftyp, moov and meta boxes) without modifying the media data size. + + In-place rewriting is disabled for any of the following: + - 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://" + +In-place rewriting is enabled by default on files open in edit mode. + +\param isom_file the target ISO file +*/ +void gf_isom_disable_inplace_rewrite(GF_ISOFile *isom_file); + +/*! sets amount of bytes to reserve after moov for future in-place editing. This may be ignored depending on the final write mode +\param isom_file the target ISO file +\param padding amount of bytes to reserve +\return error if any +*/ +GF_Err gf_isom_set_inplace_padding(GF_ISOFile *isom_file, u32 padding); + +/*! @} */ + +#endif // GPAC_DISABLE_ISOM_WRITE + +/*! +\addtogroup isomp4sys_grp ISOBMFF MPEG-4 Systems +\ingroup iso_grp + +MPEG-4 Systems extensions +@{ +*/ + + +/*! MPEG-4 ProfileAndLevel codes*/ +typedef enum +{ + /*! Audio PL*/ + GF_ISOM_PL_AUDIO, + /*! Visual PL*/ + GF_ISOM_PL_VISUAL, + /*! Graphics PL*/ + GF_ISOM_PL_GRAPHICS, + /*! Scene PL*/ + GF_ISOM_PL_SCENE, + /*! OD PL*/ + GF_ISOM_PL_OD, + /*! MPEG-J PL*/ + GF_ISOM_PL_MPEGJ, + /*! not a profile, just set/unset inlineFlag*/ + GF_ISOM_PL_INLINE, +} GF_ISOProfileLevelType; + +/*! gets MPEG-4 subtype of a sample description entry (eg, mp4a, mp4v, enca, encv, resv, etc...) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the media type FOUR CHAR code type of an MPEG4 media, or 0 if not MPEG-4 subtype + */ +u32 gf_isom_get_mpeg4_subtype(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! fetches the root OD of a file (can be NULL, OD or IOD, you have to check its tag) +\param isom_file the target ISO file +\return the OD/IOD if any. Caller must destroy the descriptor +*/ +GF_Descriptor *gf_isom_get_root_od(GF_ISOFile *isom_file); + +/*! disable OD conversion from ISOM internal to regular OD tags +\param isom_file the target ISO file +\param disable if TRUE, ODs and ESDs will not be converted +*/ +void gf_isom_disable_odf_conversion(GF_ISOFile *isom_file, Bool disable); + +/*! checks the presence of a track in rood OD/IOD +\param isom_file the target ISO file +\param trackNumber the target track +\return 0: NO, 1: YES, 2: ERROR*/ +u8 gf_isom_is_track_in_root_od(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the GF_ESD given the sampleDescriptionIndex +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the ESD associated to the sample description index, or NULL if error or not supported. Caller must destroy the ESD*/ +GF_ESD *gf_isom_get_esd(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the decoderConfigDescriptor given the sampleDescriptionIndex +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the decoder configuration descriptor associated to the sample description index, or NULL if error or not supported. Caller must destroy the descriptor +*/ +GF_DecoderConfig *gf_isom_get_decoder_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! sets default TrackID (or ES_ID) for clock references. +\param isom_file the target ISO file +\param trackNumber the target track to set as a clock reference. If 0, default sync track ID is reseted and will be reassigned at next ESD fetch*/ +void gf_isom_set_default_sync_track(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the profile and level value for MPEG-4 streams +\param isom_file the target ISO file +\param PL_Code the target profile to query file +\return the profile and level value, 0xFF if not defined +*/ +u8 gf_isom_get_pl_indication(GF_ISOFile *isom_file, GF_ISOProfileLevelType PL_Code); + +/*! finds the first ObjectDescriptor using the given track by inspecting all OD tracks +\param isom_file the target ISO file +\param trackNumber the target track +\return the OD ID if dound, 0 otherwise*/ +u32 gf_isom_find_od_id_for_track(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets a profile and level indication for the movie iod (created if needed) +\note Use for MPEG-4 Systems only +if the flag is ProfileLevel is 0 this means the movie doesn't require +the specific codec (equivalent to 0xFF value in MPEG profiles) +\param isom_file the target ISO file +\param PL_Code the profile and level code to set +\param ProfileLevel the profile and level value to set +\return error if any +*/ +GF_Err gf_isom_set_pl_indication(GF_ISOFile *isom_file, GF_ISOProfileLevelType PL_Code, u8 ProfileLevel); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets the rootOD ID of the movie if you need it. By default, movies are created without root ODs +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param OD_ID ID to assign to the root OD/IOD +\return error if any +*/ +GF_Err gf_isom_set_root_od_id(GF_ISOFile *isom_file, u32 OD_ID); + +/*! sets the rootOD URL of the movie if you need it (only needed to create an empty file pointing +to external resource) +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param url_string the URL to assign to the root OD/IOD +\return error if any +*/ +GF_Err gf_isom_set_root_od_url(GF_ISOFile *isom_file, const char *url_string); + +/*! removes the root OD +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_remove_root_od(GF_ISOFile *isom_file); + +/*! adds a system descriptor to the OD of the movie +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param theDesc the descriptor to add +\return error if any +*/ +GF_Err gf_isom_add_desc_to_root_od(GF_ISOFile *isom_file, const GF_Descriptor *theDesc); + +/*! adds a track to the root OD +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param trackNumber the track to add to the root OD +\return error if any +*/ +GF_Err gf_isom_add_track_to_root_od(GF_ISOFile *isom_file, u32 trackNumber); + +/*! removes a track to the root OD +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param trackNumber the track to remove from the root OD +\return error if any +*/ +GF_Err gf_isom_remove_track_from_root_od(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! creates a new MPEG-4 sample description in a track + +\note Used for MPEG-4 Systems, AAC and MPEG-4 Visual (part 2) + +\param isom_file the target ISO file +\param trackNumber the target track number +\param esd the ESD to use for that 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 index of the new sample description +\return error if any +*/ +GF_Err gf_isom_new_mpeg4_description(GF_ISOFile *isom_file, u32 trackNumber, const GF_ESD *esd, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! changes an MPEG-4 sample description +\note Used for MPEG-4 Systems, AAC and MPEG-4 Visual (part 2) +\warning This will replace the whole ESD +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param newESD the new ESD to use for that sample description +\return error if any +*/ +GF_Err gf_isom_change_mpeg4_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_ESD *newESD); + +/*! adds an MPEG-4 systems descriptor to the ESD of a sample description +\note Used for MPEG-4 Systems, AAC and MPEG-4 Visual (part 2) +\warning This will replace the whole ESD +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param theDesc the descriptor to add to the ESD of the sample description +\return error if any +*/ +GF_Err gf_isom_add_desc_to_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_Descriptor *theDesc); + +/*! clones IOD PLs from orig to dest if any +\param orig_file the source ISO file +\param dest_file the destination ISO file +\return error if any +*/ +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 +only full AUs are extracted, therefore the calling application must SL-packetize the streams + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param slConfig the SL configuration descriptor to set. The descriptor is copied by the API for further use. A NULL pointer will result +in using the default SLConfig (predefined = 2) remapped to predefined = 0 +\return error if any +*/ +GF_Err gf_isom_set_extraction_slc(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_SLConfig *slConfig); + +#endif //GPAC_DISABLE_ISOM_WRITE + + +/*! @} */ + +/*! +\addtogroup isostsd_grp ISOBMFF Sample Descriptions +\ingroup iso_grp + +Sample Description functions are used to query and set codec parameters of a track + +@{ +*/ + +/*! Unknown sample description*/ +typedef struct +{ + /*! codec tag is the containing box's tag, 0 if UUID is used*/ + u32 codec_tag; + /*! entry UUID if no tag is used*/ + bin128 UUID; + /*! codec version*/ + u16 version; + /*! codec revision*/ + u16 revision; + /*! vendor four character code*/ + u32 vendor_code; + + /*! temporal quality, video codecs only*/ + u32 temporal_quality; + /*! spatial quality, video codecs only*/ + u32 spatial_quality; + /*! width in pixels, video codecs only*/ + u16 width; + /*! height in pixels, video codecs only*/ + u16 height; + /*! horizontal resolution as 16.16 fixed point, video codecs only*/ + u32 h_res; + /*! vertical resolution as 16.16 fixed point, video codecs only*/ + u32 v_res; + /*! bit depth resolution in bits, video codecs only*/ + u16 depth; + /*! color table, video codecs only*/ + u16 color_table_index; + /*! compressor name, video codecs only*/ + char compressor_name[33]; + + /*! sample rate, audio codecs only*/ + u32 samplerate; + /*! number of channels, audio codecs only*/ + u16 nb_channels; + /*! bits per sample, audio codecs only*/ + u16 bits_per_sample; + /*! indicates if QTFF signaling should be used, audio codecs only*/ + Bool is_qtff; + + /*optional, sample description specific configuration*/ + u8 *extension_buf; + /*optional, sample description specific size*/ + u32 extension_buf_size; + /*optional, wraps sample description specific data into a box if given type*/ + u32 ext_box_wrap; +} GF_GenericSampleDescription; + +/*! gets an unknown sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return generic sample description information, or NULL if error +*/ +GF_GenericSampleDescription *gf_isom_get_generic_sample_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the decoder configuration of a JP2 file +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param out_dsi set to the decoder configuration - shall be freed by user +\param out_size set to the decoder configuration size +\return error if any +*/ +GF_Err gf_isom_get_jp2_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **out_dsi, u32 *out_size); + + + +/*! gets RVC (Reconvigurable Video Coding) config of a track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param rvc_predefined set to a predefined value of RVC +\param data set to the RVC config buffer if not predefined, NULL otherwise +\param size set to the RVC config buffer size +\param mime set to the associated mime type of the stream +\return error if any*/ +GF_Err gf_isom_get_rvc_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 *rvc_predefined, u8 **data, u32 *size, const char **mime); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets the RVC config for the given track sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param rvc_predefined the predefined RVC configuration code, 0 if not predefined +\param mime the associated mime type of the video +\param data the RVC configuration data; ignored if rvc_predefined is not 0 +\param size the size of the RVC configuration data; ignored if rvc_predefined is not 0 +\return error if any +*/ +GF_Err gf_isom_set_rvc_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 rvc_predefined, char *mime, u8 *data, u32 size); + + +/*! updates fields of given visual sample description - these fields are reserved in ISOBMFF, this should only be used for QT, see QTFF +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param revision revision of the sample description format +\param vendor four character code of the vendor +\param temporalQ temporal quality +\param spatialQ spatial quality +\param horiz_res horizontal resolution as 16.16 fixed point number +\param vert_res vertical resolution as 16.16 fixed point number +\param frames_per_sample number of frames per media samples +\param compressor_name human readable name for the compressor +\param color_table_index color table index, use -1 if no color table (most common case) +\return error if any +*/ +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); + +/*! 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 +\param sampleDescriptionIndex the target sample description +\param data a serialized sample description box +\param size size of the serialized sample description +\return error if any +*/ +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 +\param trackNumber the target track +\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 udesc generic sample description information to use +\param outDescriptionIndex set to index of the new sample description +\return error if any +*/ +GF_Err gf_isom_new_generic_sample_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, GF_GenericSampleDescription *udesc, u32 *outDescriptionIndex); + +/*! clones a sample description without inspecting media types +\param isom_file the destination ISO file +\param trackNumber the destination track +\param orig_file the source ISO file +\param orig_track the source track +\param orig_desc_index the source sample description to clone +\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 index of the new sample description +\return error if any +*/ +GF_Err gf_isom_clone_sample_description(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOFile *orig_file, u32 orig_track, u32 orig_desc_index, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! gets the sample description template of a track. This serializes sample description box +\param isom_file the destination ISO file +\param trackNumber the destination track +\param sampleDescriptionIndex the target sample description +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_stsd_template(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **output, u32 *output_size); + +#endif // GPAC_DISABLE_ISOM_WRITE + +/*! checks if sample descriptions are the same. This does include self-contained checking and reserved flags. The specific media cfg (DSI & co) is not analysed, only a memory comparaison is done +\param f1 the first ISO file +\param tk1 the first track +\param sdesc_index1 the first sample description +\param f2 the second ISO file +\param tk2 the second track +\param sdesc_index2 the second sample description +\return GF_TRUE if sample descriptions match, GF_FALSE otherwise +*/ +Bool gf_isom_is_same_sample_description(GF_ISOFile *f1, u32 tk1, u32 sdesc_index1, GF_ISOFile *f2, u32 tk2, u32 sdesc_index2); + + +/*! Generic 3GP/3GP2 config record*/ +typedef struct +{ + /*GF_4CC record type, one fo the above GF_ISOM_SUBTYPE_3GP_ * subtypes*/ + u32 type; + /*4CC vendor name*/ + u32 vendor; + /*codec version*/ + u8 decoder_version; + /*number of sound frames per IsoMedia sample, >0 and <=15. The very last sample may contain less frames. */ + u8 frames_per_sample; + /*H263 ONLY - Level*/ + u8 H263_level; + /*H263 Profile*/ + u8 H263_profile; + /*AMR(WB) ONLY - num of mode for the codec*/ + u16 AMR_mode_set; + /*AMR(WB) ONLY - changes in codec mode per sample*/ + u8 AMR_mode_change_period; +} GF_3GPConfig; + + +/*! gets a 3GPP sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the 3GP config for this sample description, NULL if not a 3GPP track +*/ +GF_3GPConfig *gf_isom_3gp_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a 3GPP sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param config the 3GP 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_3gp_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_3GPConfig *config, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! updates the 3GPP config - subtypes shall NOT differ +\param isom_file the target ISO file +\param trackNumber the target track +\param config the 3GP config for this sample description +\param sampleDescriptionIndex the target sample description index +\return error if any +*/ +GF_Err gf_isom_3gp_config_update(GF_ISOFile *isom_file, u32 trackNumber, GF_3GPConfig *config, u32 sampleDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +/*! gets AVC 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 AVC config - user is responsible for deleting it +*/ +GF_AVCConfig *gf_isom_avc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets SVC 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 SVC config - user is responsible for deleting it +*/ +GF_AVCConfig *gf_isom_svc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets MVC 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 SVC config - user is responsible for deleting it +*/ +GF_AVCConfig *gf_isom_mvc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! AVC familiy type*/ +typedef enum +{ + /*! not an AVC codec*/ + GF_ISOM_AVCTYPE_NONE=0, + /*! AVC only*/ + GF_ISOM_AVCTYPE_AVC_ONLY, + /*! AVC+SVC in same track*/ + GF_ISOM_AVCTYPE_AVC_SVC, + /*! SVC only*/ + GF_ISOM_AVCTYPE_SVC_ONLY, + /*! AVC+MVC in same track*/ + GF_ISOM_AVCTYPE_AVC_MVC, + /*! SVC only*/ + GF_ISOM_AVCTYPE_MVC_ONLY, +} GF_ISOMAVCType; + +/*! gets the AVC family type for a sample description +\param isom_file the target ISO file +\param trackNumber the target hint track +\param sampleDescriptionIndex the target sample description index +\return the type of AVC media +*/ +GF_ISOMAVCType gf_isom_get_avc_svc_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! HEVC family type*/ +typedef enum +{ + /*! not an HEVC codec*/ + GF_ISOM_HEVCTYPE_NONE=0, + /*! HEVC only*/ + GF_ISOM_HEVCTYPE_HEVC_ONLY, + /*! HEVC+LHVC in same track*/ + GF_ISOM_HEVCTYPE_HEVC_LHVC, + /*! LHVC only*/ + GF_ISOM_HEVCTYPE_LHVC_ONLY, +} GF_ISOMHEVCType; + +/*! gets the HEVC family type 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 type of HEVC media +*/ +GF_ISOMHEVCType gf_isom_get_hevc_lhvc_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets HEVC 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 HEVC config - user is responsible for deleting it +*/ +GF_HEVCConfig *gf_isom_hevc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets LHVC 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 LHVC config - user is responsible for deleting it +*/ +GF_HEVCConfig *gf_isom_lhvc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! VVC family type*/ +typedef enum +{ + /*! not an VVC codec*/ + GF_ISOM_VVCTYPE_NONE=0, + /*! VVC only*/ + GF_ISOM_VVCTYPE_ONLY, + /*! VVC subpicture track*/ + GF_ISOM_VVCTYPE_SUBPIC, + /*! VVC non-VCL only*/ + GF_ISOM_VVCTYPE_NVCL, +} GF_ISOMVVCType; + +/*! gets the VVC family type 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 type of VVC media +*/ +GF_ISOMVVCType gf_isom_get_vvc_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets VVC 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 VVC config - user is responsible for deleting it +*/ +GF_VVCConfig *gf_isom_vvc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets AV1 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 AV1 config - user is responsible for deleting it +*/ +GF_AV1Config *gf_isom_av1_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets VP8/9 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 VP8/9 config - user is responsible for deleting it +*/ +GF_VPConfig *gf_isom_vp_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 +\param sampleDescriptionIndex the target sample description index +\return the DOVI config - user is responsible for deleting it +*/ +GF_DOVIDecoderConfigurationRecord* gf_isom_dovi_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 +*/ +Bool gf_isom_needs_layer_reconstruction(GF_ISOFile *isom_file); + +/*! NALU extract modes and flags*/ +typedef enum +{ + /*! all extractors are rewritten*/ + GF_ISOM_NALU_EXTRACT_DEFAULT = 0, + /*! all extractors are skipped but NALU data from this track is kept*/ + GF_ISOM_NALU_EXTRACT_LAYER_ONLY, + /*! all extractors are kept (untouched sample) - used for dumping modes*/ + GF_ISOM_NALU_EXTRACT_INSPECT, + /*! above mode is applied and PPS/SPS/... are appended in the front of every IDR*/ + GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG = 1<<16, + /*! above mode is applied and all start codes are rewritten (xPS inband as well)*/ + GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG = 2<<17, + /*! above mode is applied and VDRD NAL unit is inserted before SVC slice*/ + GF_ISOM_NALU_EXTRACT_VDRD_FLAG = 1<<18, + /*! all extractors are skipped and only tile track data is kept*/ + GF_ISOM_NALU_EXTRACT_TILE_ONLY = 1<<19 +} GF_ISONaluExtractMode; + +/*! sets the NALU extraction mode for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param nalu_extract_mode the NALU extraction mode to set +\return error if any +*/ +GF_Err gf_isom_set_nalu_extract_mode(GF_ISOFile *isom_file, u32 trackNumber, GF_ISONaluExtractMode nalu_extract_mode); +/*! gets the NALU extraction mode for this track +\param isom_file the target ISO file +\param trackNumber the target track +\return the NALU extraction mode used +*/ +GF_ISONaluExtractMode gf_isom_get_nalu_extract_mode(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the composition offset shift if any for track using negative ctts +\param isom_file the target ISO file +\param trackNumber the target track +\return the composition offset shift or 0 +*/ +s32 gf_isom_get_composition_offset_shift(GF_ISOFile *isom_file, u32 trackNumber); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a new AVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AVC 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_avc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! updates an AVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the AVC config for this sample description +\return error if any +*/ +GF_Err gf_isom_avc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AVCConfig *cfg); + +/*! creates a new SVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the SVC 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_svc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! updates an SVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the AVC config for this sample description +\param is_additional if set, the SVCConfig will be added to the AVC sample description, otherwise the sample description will be SVC-only +\return error if any +*/ +GF_Err gf_isom_svc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AVCConfig *cfg, Bool is_additional); +/*! deletes an SVC sample description +\warning Associated samples if any are NOT deleted +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to delete +\return error if any +*/ +GF_Err gf_isom_svc_config_del(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! creates a new MVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the SVC 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_mvc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! updates an MVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the AVC config for this sample description +\param is_additional if set, the MVCConfig will be added to the AVC sample description, otherwise the sample description will be MVC-only +\return error if any +*/ +GF_Err gf_isom_mvc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AVCConfig *cfg, Bool is_additional); + +/*! deletes an MVC sample description +\warning Associated samples if any are NOT deleted +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to delete +\return error if any +*/ +GF_Err gf_isom_mvc_config_del(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! sets avc3 entry type (inband SPS/PPS) instead of avc1 (SPS/PPS in avcC box) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param keep_xps if set to GF_TRUE, keeps parameter set in the configuration record otherwise removes them +\return error if any +*/ +GF_Err gf_isom_avc_set_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool keep_xps); + +/*! sets hev1 entry type (inband SPS/PPS) instead of hvc1 (SPS/PPS in hvcC box) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param keep_xps if set to GF_TRUE, keeps parameter set in the configuration record otherwise removes them +\return error if any +*/ +GF_Err gf_isom_hevc_set_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool keep_xps); + +/*! sets lhe1 entry type instead of lhc1 but keep lhcC box intact +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return error if any +*/ +GF_Err gf_isom_lhvc_force_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! sets hvt1 entry type (tile track) or hev2/hvc2 type if is_base_track is set. It is the use responsability to set the tbas track reference to the base hevc track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param cfg may be set to the tile track configuration to indicate sub-profile of the tile, or NULL +\param is_base_track if set to GF_TRUE, indicates this is a tile base track, otherwise this is a tile track +\return error if any +*/ +GF_Err gf_isom_hevc_set_tile_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_HEVCConfig *cfg, Bool is_base_track); + +/*! creates a new HEVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the HEVC 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_hevc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_HEVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! updates an HEVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the HEVC config for this sample description +\return error if any +*/ +GF_Err gf_isom_hevc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_HEVCConfig *cfg); + +/*! Updates L-HHVC config*/ +typedef enum { + //! changes track type to LHV1/LHE1: no base nor extractors in track, just enhancement layers + GF_ISOM_LEHVC_ONLY = 0, + //! changes track type to HVC2/HEV2: base and extractors/enh. in track + GF_ISOM_LEHVC_WITH_BASE, + //! changes track type to HVC1/HEV1 with additional cfg: base and enh. in track no extractors + GF_ISOM_LEHVC_WITH_BASE_BACKWARD, + //! changes track type to HVC2/HEV2 for tile base tracks + GF_ISOM_HEVC_TILE_BASE, +} GF_ISOMLHEVCTrackType; + +/*! updates an HEVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the LHVC config for this sample description +\param track_type indicates the LHVC track type to set +\return error if any +*/ +GF_Err gf_isom_lhvc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_HEVCConfig *cfg, GF_ISOMLHEVCTrackType track_type); + +/*! sets nalu size length +\warning Any previously added samples must be rewritten by the caller +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param nalu_size_length the new NALU size length in bytes +\return error if any +*/ +GF_Err gf_isom_set_nalu_length_field(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 nalu_size_length); + + +/*! creates a new VVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the VVC 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_vvc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_VVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! sets vvi1 entry type (inband SPS/PPS) instead of vvc1 (SPS/PPS in hvcC box) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param keep_xps if set to GF_TRUE, keeps parameter set in the configuration record otherwise removes them +\return error if any +*/ +GF_Err gf_isom_vvc_set_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool keep_xps); + +/*! updates vvcC configuration +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param cfg new config to set +\return error if any +*/ +GF_Err gf_isom_vvc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_VVCConfig *cfg); + +/*! creates new VPx config +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the VPx 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 +\param vpx_type four character code of entry ('vp08', 'vp09' or 'vp10') +\return error if any +*/ +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 +\param cfg the AV1 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_av1_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AV1Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +/*! Sample entry description for 3GPP DIMS*/ +typedef struct +{ + /*! profile*/ + u8 profile; + /*! level*/ + u8 level; + /*! number of components in path*/ + u8 pathComponents; + /*! full request*/ + Bool fullRequestHost; + /*! stream type*/ + Bool streamType; + /*! has redundant sample (carousel)*/ + u8 containsRedundant; + /*! text encoding string*/ + const char *textEncoding; + /*! content encoding string*/ + const char *contentEncoding; + /*! script string*/ + const char *content_script_types; + /*! mime type string*/ + const char *mime_type; + /*! xml schema location string*/ + const char *xml_schema_loc; +} GF_DIMSDescription; + +/*! gets a DIMS sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param desc set to the DIMS description +\return error if any +*/ +GF_Err gf_isom_get_dims_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_DIMSDescription *desc); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a DIMS sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param desc the DIMS 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_new_dims_description(GF_ISOFile *isom_file, u32 trackNumber, GF_DIMSDescription *desc, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets an AC3 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return AC-3 config +*/ +GF_AC3Config *gf_isom_ac3_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates an AC3 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AC3 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_ac3_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AC3Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets TrueHD sample description info +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param format_info set to the format info - may be NULL +\param peak_data_rate set to the peak data rate info - may be NULL +\return error if any +*/ +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 +\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) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param format_info TrueHD format info +\param peak_data_rate TrueHD peak data rate +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_truehd_config_new(GF_ISOFile *isom_file, u32 trackNumber, char *URLname, char *URNname, u32 format_info, u32 peak_data_rate, u32 *outDescriptionIndex); +#endif + +/*! gets a FLAC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param dsi set to the flac decoder config - shall be freeed by caller +\param dsi_size set to the size of the flac decoder config +\return error if any +*/ +GF_Err gf_isom_flac_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **dsi, u32 *dsi_size); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a FLAC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param metadata the flac decoder config buffer +\param metadata_size the size of flac decoder config +\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_flac_config_new(GF_ISOFile *isom_file, u32 trackNumber, u8 *metadata, u32 metadata_size, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets a OPUS sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param dsi set to the OPUS decoder config - shall be freeed by caller +\param dsi_size set to the size of the OPUS decoder config +\return error if any +*/ +GF_Err gf_isom_opus_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **dsi, u32 *dsi_size); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! creates a motion jpeg 2000 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) +\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 jpeg2000 decoder config buffer +\param dsi_len the size of jpeg2000 decoder config +\return error if any +*/ +GF_Err gf_isom_new_mj2k_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u8 *dsi, u32 dsi_len); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! creates a time code metadata sample description +\note frames_per_counter_tick<0 disables counter flag but signals frames_per_tick +\param isom_file the target ISO file +\param trackNumber the target track +\param fps_num the frame rate numerator +\param fps_den the frame rate denumerator (frame rate numerator will be track media timescale) +\param frames_per_counter_tick if not 0, enables counter mode (sample data is an counter) and use this value as number of frames per counter tick. Otherwise, disables counter mode (sample data write h,m,s,frames) +\param is_drop indicates that the time code in samples is a drop timecode +\param is_counter indicates that the counter flag should be set +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_tmcd_config_new(GF_ISOFile *isom_file, u32 trackNumber, u32 fps_num, u32 fps_den, s32 frames_per_counter_tick, Bool is_drop, Bool is_counter, u32 *outDescriptionIndex); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets information of a time code metadata sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param tmcd_flags set to the timecode description flags +\param tmcd_fps_num set to fps numerator of timecode description +\param tmcd_fps_den set to fps denominator of timecode description +\param tmcd_fpt set to the ticks per second for counter mode (tmcd_flags & 0x1) +\return error if any +*/ +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); + +/*! gets information of a raw PCM sample description, ISOBMFF style +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param flags set to the pcm config flags (0: big endian, 1: little endian) +\param pcm_size set to PCM sample size (per channel, 16, 24, 32, 64 +\return error if any +*/ +GF_Err gf_isom_get_pcm_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *flags, u32 *pcm_size); + + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! creates a MPHA 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) +\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): byte[0]=1 (config version) ,byte[1]=ProfileLevel, byte[2]=channel layout, byte[3],byte[4]: the size of what follows the rest being a mpegh3daConfig +\param dsi_size the size of the MPEGH audio config +\return error if any +*/ +GF_Err gf_isom_new_mpha_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u8 *dsi, u32 dsi_size); +#endif + +/*! gets compatible profile list for mpegh entry +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param nb_compatible_profiles set to the number of compatible profiles returned +\return array of compatible profiles, NULL if none found +*/ +const u8 *gf_isom_get_mpegh_compatible_profiles(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *nb_compatible_profiles); + + + +/*! structure holding youtube 360 video info +- cf https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#stereoscopic-3d-video-box-st3d + */ +typedef struct +{ + /*! stereo type holding youtube 360 video info*/ + u32 stereo_type; + /*! 0: unknown (not present), 1: cube map, 2: EQR, 3: mesh*/ + u32 projection_type; + /*! metadata about 3D software creator*/ + const char *meta_data; + /*! indicate default pause is present*/ + Bool pose_present; + /*! default pause yaw as 16.16 fixed point*/ + u32 yaw; + /*! default pause pitch as 16.16 fixed point*/ + u32 pitch; + /*! default pause roll as 16.16 fixed point*/ + u32 roll; + + /*! cube map layout*/ + u32 layout; + /*! cube map padding*/ + u32 padding; + + /*! EQR top crop pos in frame, in pixels*/ + u32 top; + /*! EQR bottom crop pos in frame, in pixels*/ + u32 bottom; + /*! EQR left crop pos in frame, in pixels*/ + u32 left; + /*! EQR right crop pos in frame, in pixels*/ + u32 right; + +} GF_ISOM_Y3D_Info; + + +/*! gets youtube 3D/360 info +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param info filled with 3D info +\return error if any, GF_NOT_FOUND if no 3D/360 or setero info +*/ +GF_Err gf_isom_get_y3d_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_ISOM_Y3D_Info *info); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets youtube 3D/360 info +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param info 3D info to set +\return error if any +*/ +GF_Err gf_isom_set_y3d_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_ISOM_Y3D_Info *info); +#endif + +/*! @} */ + + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! +\addtogroup isofragred_grp Fragmented ISOBMFF Read +\ingroup iso_grp + +This describes function specific to fragmented ISOBMF files + +@{ +*/ + +/*! checks if a movie file is fragmented +\param isom_file the target ISO file +\return GF_FALSE if movie isn't fragmented, GF_TRUE otherwise +*/ +Bool gf_isom_is_fragmented(GF_ISOFile *isom_file); +/*! checks if a movie file is fragmented +\param isom_file the target ISO file +\param TrackID the target track +\return GF_FALSE if track isn't fragmented, GF_TRUE otherwise*/ +Bool gf_isom_is_track_fragmented(GF_ISOFile *isom_file, GF_ISOTrackID TrackID); + +/*! checks if a file has a top styp box +\param isom_file the target ISO file +\param brand set to the major brand of the styp box +\param version set to version of the styp box +\return GF_TRUE of the file has a styp box, GF_FALSE otherwise +*/ +Bool gf_isom_has_segment(GF_ISOFile *isom_file, u32 *brand, u32 *version); +/*! gets number of movie fragments in the file +\param isom_file the target ISO file +\returns number of movie fragments in the file, 0 if none +*/ +u32 gf_isom_segment_get_fragment_count(GF_ISOFile *isom_file); +/*! gets number of track fragments in the indicated movie fragment +\param isom_file the target ISO file +\param moof_index the target movie fragment (1-based index) +\return number of track fragments, 0 if none +*/ +u32 gf_isom_segment_get_track_fragment_count(GF_ISOFile *isom_file, u32 moof_index); +/*! get the track fragment decode time of a track fragment +\param isom_file the target ISO file +\param moof_index the target movie fragment (1-based index) +\param traf_index the target track fragment (1-based index) +\param decode_time set to the track fragment decode time if present, 0 otherwise +\return the track ID of the track fragment +*/ +u32 gf_isom_segment_get_track_fragment_decode_time(GF_ISOFile *isom_file, u32 moof_index, u32 traf_index, u64 *decode_time); + +/*! get the movie fragment size, i.e. the size of moof, mdat and related boxes before moof/mdat + +\param isom_file the target ISO file +\param moof_index the target movie fragment (1-based index) +\param moof_size set to moof box size, may be NULL +\return the movie fragemnt size +*/ +u64 gf_isom_segment_get_fragment_size(GF_ISOFile *isom_file, u32 moof_index, u32 *moof_size); + +/*! enables single moof mode. In single moof mode, file is parsed only one moof/mdat at a time + in order to proceed to next moof, \ref gf_isom_reset_data_offset must be called to parse the next moof +\param isom_file the target ISO file +\param mode if GF_TRUE, enables single moof mode; otherwise disables it +*/ +void gf_isom_set_single_moof_mode(GF_ISOFile *isom_file, Bool mode); + +/*! gets closest file offset for the given time, when the file uses an segment index (sidx) +\param isom_file the target ISO file +\param start_time the start time in seconds +\param offset set to the file offset of the segment containing the desired time +\return error if any +*/ +GF_Err gf_isom_get_file_offset_for_time(GF_ISOFile *isom_file, Double start_time, u64 *offset); + +/*! gets sidx duration, when the file uses an segment index (sidx) +\param isom_file the target ISO file +\param sidx_dur set to the total duration documented in the segment index +\param sidx_timescale set timescale used to represent the duration in the segment index +\return error if any +*/ +GF_Err gf_isom_get_sidx_duration(GF_ISOFile *isom_file, u64 *sidx_dur, u32 *sidx_timescale); + + +/*! refreshes a fragmented file +A file being downloaded may be a fragmented file. In this case only partial info +is available once the file is successfully open (gf_isom_open_progressive), and since there is +no information wrt number fragments (which could actually be generated on the fly +at the sender side), you must call this function on regular basis in order to +load newly downloaded fragments. Note this may result in Track/Movie duration changes +and SampleCount change too ... + +This function should also be called when using memory read (gmem://) to refresh the underlying bitstream after appendin data to your blob. +In the case where the file is not fragmented, no further box parsing will be done. + +\param isom_file the target ISO file +\param MissingBytes set to the number of missing bytes to parse the last incomplete top-level box found +\param new_location if set, the previous bitstream is changed to this new location, otherwise it is refreshed (disk flush) +\return error if any +*/ +GF_Err gf_isom_refresh_fragmented(GF_ISOFile *isom_file, u64 *MissingBytes, const char *new_location); + +/*! gets the current track fragment decode time of the track (the one of the last fragment parsed). +\param isom_file the target ISO file +\param trackNumber the target track +\return the track fragment decode time in media timescale +*/ +u64 gf_isom_get_current_tfdt(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the estimated DTS of the first sample of the next segment for SmoothStreaming files (no tfdt, no tfxd) +\param isom_file the target ISO file +\param trackNumber the target track +\return the next track fragment decode time in media timescale +*/ +u64 gf_isom_get_smooth_next_tfdt(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if the movie is a smooth streaming recomputed initial movie +\param isom_file the target ISO file +\return GF_TRUE if the file init segment (moov) was generated from external meta-data (smooth streaming) +*/ +Bool gf_isom_is_smooth_streaming_moov(GF_ISOFile *isom_file); + + +/*! gets default values of samples in a track to use for track fragments default. Each variable is optional and +if set will contain the default value for this track samples + +\param isom_file the target ISO file +\param trackNumber the target track +\param defaultDuration set to the default duration of samples, 0 if not computable +\param defaultSize set to the default size of samples, 0 if not computable +\param defaultDescriptionIndex set to the default sample description index of samples, 0 if not computable +\param defaultRandomAccess set to the default sync flag of samples, 0 if not computable +\param defaultPadding set to the default padding bits of samples, 0 if not computable +\param defaultDegradationPriority set to the default degradation priority of samples, 0 if not computable +\return error if any*/ +GF_Err gf_isom_get_fragment_defaults(GF_ISOFile *isom_file, u32 trackNumber, + u32 *defaultDuration, u32 *defaultSize, u32 *defaultDescriptionIndex, + u32 *defaultRandomAccess, u8 *defaultPadding, u16 *defaultDegradationPriority); + + + +/*! gets last UTC/timestamp values indicated for the reference track in the file if any (pfrt box) +\param isom_file the target ISO file +\param refTrackID set to the ID of the reference track used by the pfrt box +\param ntp set to the NTP timestamp found +\param timestamp set to the corresponding media timestamp in refTrackID timescale +\param reset_info if GF_TRUE, discards current NTP mapping info; this will trigger parsing of the next prft box found. If not set, subsequent pfrt boxes will not be parsed until the function is called with reset_info=GF_TRUE +\return GF_FALSE if no info found, GF_TRUE if OK +*/ +Bool gf_isom_get_last_producer_time_box(GF_ISOFile *isom_file, GF_ISOTrackID *refTrackID, u64 *ntp, u64 *timestamp, Bool reset_info); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! enables storage of traf templates (serialized sidx/moof/traf without trun/senc) at segment boundaries +This is mostly used to recreate identical segment information when refragmenting a file +\param isom_file the target ISO file +*/ +void gf_isom_enable_traf_map_templates(GF_ISOFile *isom_file); + +/*! Segment boundary information*/ +typedef struct +{ + /*! fragment start offset*/ + u64 frag_start; + /*! mdat end offset*/ + u64 mdat_end; + /*segment start offset plus one: + 0 if regular fragment, 1 if dash segment, offset indicates start of segment (styp or sidx) + if sidx, it is written in the moof_template + */ + u64 seg_start_plus_one; + + /*! serialized array of styp (if present) sidx (if present) and moof with only the current traf*/ + const u8 *moof_template; + /*! size of serialized buffer*/ + u32 moof_template_size; + /*! sidx start, 0 if absent*/ + u64 sidx_start; + /*! sidx end, 0 if absent*/ + u64 sidx_end; +} GF_ISOFragmentBoundaryInfo; + +/*! checks if a sample is a fragment start +Only use this function if \ref gf_isom_enable_traf_map_templates has been called +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNum the target sample number +\param frag_info filled with information on fragment boundaries (optional - can be NULL) +\return GF_TRUE if this sample was the first sample of a traf in the fragmented source file, GF_FALSE otherwise*/ +Bool gf_isom_sample_is_fragment_start(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNum, GF_ISOFragmentBoundaryInfo *frag_info); +#endif //GPAC_DISABLE_ISOM_WRITE + +/*! resets sample information for all tracks setup. This allows keeping the memory footprint low when playing DASH/CMAF segments +\note seeking in the file is then no longer possible +\param isom_file the target ISO file +\param reset_sample_count if GF_TRUE, sets sample count of all tracks back to 0 +\return error if any +*/ +GF_Err gf_isom_reset_tables(GF_ISOFile *isom_file, Bool reset_sample_count); + +/*! sets the offset for parsing from the input buffer to 0 (used to reclaim input buffer) +\param isom_file the target ISO file +\param top_box_start set to the byte offset in the source buffer of the first top level box +\return error if any +*/ +GF_Err gf_isom_reset_data_offset(GF_ISOFile *isom_file, u64 *top_box_start); + + +/*! releases current movie segment. This closes the associated file IO object. +\note seeking in the file is no longer possible when tables are rested +\warning The sample count is not reseted after the release of tables. use \ref gf_isom_reset_tables for this + +\param isom_file the target ISO file +\param reset_tables if set, sample information for all tracks setup as segment are destroyed, along with all PSSH boxes. This allows keeping the memory footprint low when playing segments. +\return error if any +*/ +GF_Err gf_isom_release_segment(GF_ISOFile *isom_file, Bool reset_tables); + +/*! Flags for gf_isom_open_segment*/ +typedef enum +{ + /*! do not check for movie fragment sequence number*/ + GF_ISOM_SEGMENT_NO_ORDER_FLAG = 1, + /*! the segment contains a scalable layer of the last opened segment*/ + GF_ISOM_SEGMENT_SCALABLE_FLAG = 1<<1, +} GF_ISOSegOpenMode; + +/*! opens a new segment file. Access to samples in previous segments is no longer possible +if end_range>start_range, restricts the URL to the given byterange when parsing + +\param isom_file the target ISO file +\param fileName the file name of the new segment to open +\param start_range the start offset in bytes in the file of the segment data +\param end_range the end offset in bytes in the file of the segment data +\param flags flags to use when opening the segment +\return error if any +*/ +GF_Err gf_isom_open_segment(GF_ISOFile *isom_file, const char *fileName, u64 start_range, u64 end_range, GF_ISOSegOpenMode flags); + +/*! returns the track ID of the track containing the highest enhancement layer for the given base track +\param isom_file the target ISO file +\param for_base_track the number of the base track +\return the track ID of the highest enahnacement track +*/ +GF_ISOTrackID gf_isom_get_highest_track_in_scalable_segment(GF_ISOFile *isom_file, u32 for_base_track); + +/*! resets internal info (track fragement decode time, number of samples, next moof number)used with fragments and segment. +\note This should be called when seeking (with keep_sample_count=0) or when loading a media segments with the same timing as the previously loaded segment +\param isom_file the target ISO file +\param keep_sample_count if GF_TRUE, does not reset the sample count on tracks +*/ +void gf_isom_reset_fragment_info(GF_ISOFile *isom_file, Bool keep_sample_count); + +/*! resets sample count to 0 and next moof number to 0. When doing scalable media, should be called before opening the segment containing +the base layer in order to make sure the sample count base number is always the same (ie 1) on all tracks +\param isom_file the target ISO file +*/ +void gf_isom_reset_sample_count(GF_ISOFile *isom_file); +/*! resets moof sequence number to 0 +\param isom_file the target ISO file +*/ +void gf_isom_reset_seq_num(GF_ISOFile *isom_file); + +/*! gets the duration of movie+fragments +\param isom_file the target ISO file +\return the duration in movie timescale, 0 if unknown or if error*/ +u64 gf_isom_get_fragmented_duration(GF_ISOFile *isom_file); + +/*! gets the number of fragments or segments when the file is opened in \ref GF_ISOM_OPEN_READ_DUMP mode +\param isom_file the target ISO file +\param segments_only if set to GF_TRUE, counts segments (sidx), otherwise counts fragments +\return the number of segments or fragments +*/ +u32 gf_isom_get_fragments_count(GF_ISOFile *isom_file, Bool segments_only); + +/*! gets total sample number and duration when the file is opened in \ref GF_ISOM_OPEN_READ_DUMP mode +\param isom_file the target ISO file +\param trackID the ID of the target track +\param nb_samples set to the number of samples in the track +\param duration set to the total duration in media timescale +\return error if any +*/ +GF_Err gf_isom_get_fragmented_samples_info(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 *nb_samples, u64 *duration); + +/*! gets the number of the next moof to be produced +\param isom_file the target ISO file +\return number of the next moof +*/ +u32 gf_isom_get_next_moof_number(GF_ISOFile *isom_file); + +/*! @} */ +#endif //GPAC_DISABLE_ISOM_FRAGMENTS + + +/*! +\addtogroup isoudta_grp ISOBMFF UserData Manipulation +\ingroup iso_grp + + User Data Manipulation + +You can add specific typed data to either a track or the movie: the UserData + The type must be formatted as a FourCC if you have a registered 4CC type + but the usual is to set a UUID (128 bit ID for box type) which never conflict + with existing structures in the format + To manipulate a UUID user data set the UserDataType to 0 and specify a valid UUID. +Otherwise the UUID parameter is ignored + Several items with the same ID or UUID can be added (this allows you to store any + kind/number of private information under a unique ID / UUID) + +@{ +*/ + +/*! gets number of udta (user data) entries of a movie or track +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\return the number of entries in UDTA*/ +u32 gf_isom_get_udta_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks type of a given udta entry +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\param udta_idx 1-based index of the user data to query +\param UserDataType set to the four character code of the user data entry (optional, can be NULL) +\param UUID set to the UUID of the user data entry (optional, can be NULL) +\return error if any*/ +GF_Err gf_isom_get_udta_type(GF_ISOFile *isom_file, u32 trackNumber, u32 udta_idx, u32 *UserDataType, bin128 *UUID); + +/*! gets the number of UserDataItems with the same ID / UUID in the desired track or movie +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\param UserDataType the four character code of the user data entry to query +\param UUID the UUID of the user data entry +\return number of UDTA entries with the given type*/ +u32 gf_isom_get_user_data_count(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID); + +/*! gets the UserData for the specified item from the track or the movie +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\param UserDataType the four character code of the user data entry to query +\param UUID the UUID of the user data entry +\param UserDataIndex 1-based index of the user data of the given type to fetch. If 0, all boxes with type==UserDataType will be serialized (including box header and size) in the output buffer +\param userData set to a newly allocated buffer containing the serialized data - shall be freed by caller, you must pass (userData != NULL && *userData=NULL) +\param userDataSize set to the size of the allocated buffer +\return error if any*/ +GF_Err gf_isom_get_user_data(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex, u8 **userData, u32 *userDataSize); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! adds a user data item in the desired track or in the movie +\param isom_file the target ISO file +\param trackNumber the target track for the user data; if 0, adds user data to the movie +\param UserDataType the user data four character code type +\param UUID the user data UUID +\param data the data to add, may be NULL +\param size the size of the data to add, shall be 0 when data is NULL +\return error if any +*/ +GF_Err gf_isom_add_user_data(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u8 *data, u32 size); + +/*! removes all user data items from a track or movie +\param isom_file the target ISO file +\param trackNumber the target track for the user data; if 0, adds user data to the movie +\param UserDataType the user data four character code type +\param UUID the user data UUID +\return error if any +*/ +GF_Err gf_isom_remove_user_data(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID); + +/*! removes a user data item from a track or movie +use the UDAT read functions to get the item index +\param isom_file the target ISO file +\param trackNumber the target track for the user data; if 0, adds user data to the movie +\param UserDataType the user data four character code type +\param UUID the user data UUID +\param UserDataIndex the 1-based index of the user data item to remove - see \ref gf_isom_get_user_data_count +\return error if any +*/ +GF_Err gf_isom_remove_user_data_item(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex); + +/*! adds a user data item in a track or movie using a serialzed buffer of ISOBMFF boxes +\param isom_file the target ISO file +\param trackNumber the target track for the udta box; if 0, add the udta to the movie; +\param data the serialized udta box to add, shall not be NULL +\param size the size of the data to add +\return error if any +*/ +GF_Err gf_isom_add_user_data_boxes(GF_ISOFile *isom_file, u32 trackNumber, u8 *data, u32 size); + +/*! gets serialized user data box of a movie +\param isom_file the destination ISO file +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_raw_user_data(GF_ISOFile *isom_file, u8 **output, u32 *output_size); + +#endif //GPAC_DISABLE_ISOM_WRITE + +/*! @} */ + + +#if !defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_WRITE) + +/*! +\addtogroup isofragwrite_grp Fragmented ISOBMFF Writing +\ingroup iso_grp + + Movie Fragments Writing API + Movie Fragments is a feature of ISO media files for fragmentation + of a presentation meta-data and interleaving with its media data. + This enables faster http fast start for big movies, and also reduces the risk + of data loss in case of a recording crash, because meta data and media data + can be written to disk at regular times + This API provides simple function calls to setup such a movie and write it + The process implies: + 1- creating a movie in the usual way (track, stream descriptions, (IOD setup + copyright, ...) + 2- possibly add some samples in the regular fashion + 3- setup track fragments for all track that will be written in a fragmented way + (note that you can create/write a track that has no fragmentation at all) + 4- finalize the movie for fragmentation (this will flush all meta-data and + any media-data added to disk, ensuring all vital information for the presentation + is stored on file and not lost in case of crash/poweroff) + + then 5-6 as often as desired + 5- start a new movie fragment + 6- add samples to each setup track + + + IMPORTANT NOTES: + * Movie Fragments can only be used in GF_ISOM_OPEN_WRITE mode (capturing) + and no editing functionalities can be used + * the fragmented movie API uses TrackID and not TrackNumber + +@{ +*/ + +/*! sets up a track for fragmentation by specifying some default values for storage efficiency +\note If all the defaults are 0, traf flags will always be used to signal them. +\param isom_file the target ISO file +\param TrackID ID of the target track +\param DefaultSampleDescriptionIndex the default description used by samples in this track +\param DefaultSampleDuration default duration of samples in this track +\param DefaultSampleSize default size of samples in this track (0 if unknown) +\param DefaultSampleIsSync default key-flag (RAP) of samples in this track +\param DefaultSamplePadding default padding bits for samples in this track +\param DefaultDegradationPriority default degradation priority for samples in this track +\param force_traf_flags if GF_TRUE, will ignore these default in each traf but will still write them in moov +\return error if any +*/ +GF_Err gf_isom_setup_track_fragment(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, + u32 DefaultSampleDescriptionIndex, + u32 DefaultSampleDuration, + u32 DefaultSampleSize, + u8 DefaultSampleIsSync, + u8 DefaultSamplePadding, + u16 DefaultDegradationPriority, + Bool force_traf_flags); + +/*! changes the default parameters of an existing trak fragment +\warning This should not be used if samples have already been added + +\param isom_file the target ISO file +\param TrackID ID of the target track +\param DefaultSampleDescriptionIndex the default description used by samples in this track +\param DefaultSampleDuration default duration of samples in this track +\param DefaultSampleSize default size of samples in this track (0 if unknown) +\param DefaultSampleIsSync default key-flag (RAP) of samples in this track +\param DefaultSamplePadding default padding bits for samples in this track +\param DefaultDegradationPriority default degradation priority for samples in this track +\param force_traf_flags if GF_TRUE, will ignore these default in each traf but will still write them in moov +\return error if any +*/ +GF_Err gf_isom_change_track_fragment_defaults(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, + u32 DefaultSampleDescriptionIndex, + u32 DefaultSampleDuration, + u32 DefaultSampleSize, + u8 DefaultSampleIsSync, + u8 DefaultSamplePadding, + u16 DefaultDegradationPriority, + u8 force_traf_flags); + +/*! flushes data to disk and prepare movie fragmentation +\param isom_file the target ISO file +\param media_segment_type 0 if no segments, 1 if regular segment, 2 if single segment +\param mvex_after_tracks forces writing mvex box after track boxes +\return error if any +*/ +GF_Err gf_isom_finalize_for_fragment(GF_ISOFile *isom_file, u32 media_segment_type, Bool mvex_after_tracks); + +/*! sets the duration of the movie in case of movie fragments +\param isom_file the target ISO file +\param duration the complete duration (movie and all fragments) in movie timescale +\param remove_mehd force removal of mehd box, only setting mvhd.duration to 0 +\return error if any +*/ +GF_Err gf_isom_set_movie_duration(GF_ISOFile *isom_file, u64 duration, Bool remove_mehd); + +/*! fragment creatio option*/ +typedef enum +{ + /*! moof is stored before mdat - will require temporary storage of data in memory*/ + GF_ISOM_FRAG_MOOF_FIRST = 1, +#ifdef GF_ENABLE_CTRN + /*! use compact fragment syntax*/ + GF_ISOM_FRAG_USE_COMPACT = 1<<1, +#endif +} GF_ISOStartFragmentFlags; +/*! starts a new movie fragment +\param isom_file the target ISO file +\param moof_first if GF_TRUE, the moof will be written before the mdat +\return error if any +*/ +GF_Err gf_isom_start_fragment(GF_ISOFile *isom_file, GF_ISOStartFragmentFlags moof_first); + +/*! starts a new segment in the file +\param isom_file the target ISO file +\param SegName if not NULL, the output will be written in the SegName file. If NULL, segment will be created in same file as movie. The special name "_gpac_isobmff_redirect" is used to indicate that segment shall be written to a memory buffer passed to callback function set through \ref gf_isom_set_write_callback +\param memory_mode if set, all samples writing is done in memory rather than on disk. Ignored in callback mode +\return error if any +*/ +GF_Err gf_isom_start_segment(GF_ISOFile *isom_file, const char *SegName, Bool memory_mode); + +/*! sets the baseMediaDecodeTime of the first sample of the given track +\param isom_file the target ISO file +\param TrackID ID of the target track +\param decode_time the decode time in media timescale +\return error if any +*/ +GF_Err gf_isom_set_traf_base_media_decode_time(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u64 decode_time); + +/*! enables mfra (movie fragment random access computing) when writing movie fragments +\note this should only be used when generating segments in a single file +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_enable_mfra(GF_ISOFile *isom_file); + +/*! sets Microsoft Smooth Streaming traf 'tfxd' box info, written at the end of each traf +\param isom_file the target ISO file +\param reference_track_ID ID of the reference track giving the media timescale +\param decode_traf_time decode time of the first sample in the segment in media timescale (hardcoded to 10MHz in Smooth) +\param traf_duration duration of all samples in the traf in media timescale (hardcoded to 10MHz in Smooth) +\return error if any +*/ +GF_Err gf_isom_set_traf_mss_timeext(GF_ISOFile *isom_file, GF_ISOTrackID reference_track_ID, u64 decode_traf_time, u64 traf_duration); + +/*! closes current segment, producing a segment index box if desired +\param isom_file the target ISO file +\param subsegs_per_sidx number of subsegments per sidx box; a negative value disables sidx, 0 forces a single sidx for the segment (or subsegment) +\param referenceTrackID the ID of the track used as a reference for the segment index box +\param ref_track_decode_time the decode time fo the first sample in the reference track for this segment +\param timestamp_shift the constant difference between media time and presentation time (derived from edit list) +\param ref_track_next_cts the CTS of the first sample in the reference track in the next segment +\param daisy_chain_sidx if GF_TRUE, indicates chained sidx shall be used. Otherwise, an array of indexes is used +\param use_ssix if GF_TRUE, produces an ssix box using I-frames as first level and all other frames as second level +\param last_segment indicates if this is the last segment of the session +\param close_segment_handle if set to GF_TRUE, the associated file if any will be closed +\param segment_marker_4cc a four character code used to insert an empty box at the end of the saegment with the given type. If 0, no such box is inserted +\param index_start_range set to the start offset in bytes of the segment in the media file +\param index_end_range set to the end offset in bytes of the segment in the media file +\param out_seg_size set to the segment size in bytes (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_close_segment(GF_ISOFile *isom_file, s32 subsegs_per_sidx, GF_ISOTrackID referenceTrackID, u64 ref_track_decode_time, s32 timestamp_shift, u64 ref_track_next_cts, Bool daisy_chain_sidx, Bool use_ssix, Bool last_segment, Bool close_segment_handle, u32 segment_marker_4cc, u64 *index_start_range, u64 *index_end_range, u64 *out_seg_size); + +/*! writes any pending fragment to file for low-latency output. +\warning This shall only be used if no SIDX is used: subsegs_per_sidx<0 or flushing all fragments before calling \ref gf_isom_close_segment + +\param isom_file the target ISO file +\param last_segment indicates if this is the last segment of the session +\return error if any +*/ +GF_Err gf_isom_flush_fragments(GF_ISOFile *isom_file, Bool last_segment); + +/*! sets fragment prft box info, written just before the moof +\param isom_file the target ISO file +\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 +\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); + +/*! writes an empty sidx in the current movie. + +The SIDX will be forced to have nb_segs entries, and nb_segs shall match the number of calls to +\ref gf_isom_close_segment that will follow. +This avoids wasting time and disk space moving data around. Once \ref gf_isom_close_segment has then been called nb_segs times, +the pre-allocated SIDX is destroyed and successive calls to \ref gf_isom_close_segment will create their own sidx, unless gf_isom_allocate_sidx is called again. + +\param isom_file the target ISO file +\param subsegs_per_sidx reserved to 0, currently ignored +\param daisy_chain_sidx reserved to 0, currently ignored +\param nb_segs number of entries in the segment index +\param frags_per_segment reserved, currently ignored +\param start_range set to the start offset in bytes of the segment index box +\param end_range set to the end offset in bytes of the segment index box +\param use_ssix if GF_TRUE, produces an ssix box using I-frames as first level and all other frames as second level +\return error if any +*/ +GF_Err gf_isom_allocate_sidx(GF_ISOFile *isom_file, s32 subsegs_per_sidx, Bool daisy_chain_sidx, u32 nb_segs, u32 *frags_per_segment, u32 *start_range, u32 *end_range, Bool use_ssix); + +/*! 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 boxes serialized array of trex boxes +\param boxes_size size of the serialized array +\param force_traf_flags if GF_TRUE, will ignore these default in each traf but will still write them in moov +\return error if any +*/ +GF_Err gf_isom_setup_track_fragment_template(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u8 *boxes, u32 boxes_size, u8 force_traf_flags); + +#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 +sample values can be inherited + +\param isom_file the target ISO file +\param TrackID ID of the target track +\param BaseTrackID ID of the track from which sample values are inherited in track fragments +\return error if any +*/ +GF_Err gf_isom_enable_traf_inherit(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, GF_ISOTrackID BaseTrackID); +#endif + +/*! Track fragment options*/ +typedef enum +{ + /*! indicates that the track fragment has no samples but still has a duration + (silence-detection in audio codecs, ...). + param: indicates duration*/ + GF_ISOM_TRAF_EMPTY, + /*! I-Frame detection: this can reduce file size by detecting I-frames and + optimizing sample flags (padding, priority, ..) + param: on/off (0/1)*/ + GF_ISOM_TRAF_RANDOM_ACCESS, + /*! activate data cache on track fragment. This is useful when writing interleaved + media from a live source (typically audio-video), and greatly reduces file size + param: Number of samples (> 1) to cache before disk flushing. You shouldn't try + to cache too many samples since this will load your memory. base that on FPS/SR*/ + GF_ISOM_TRAF_DATA_CACHE, + /*! forces moof base offsets when traf based offsets would be chosen + param: on/off (0/1)*/ + GF_ISOM_TFHD_FORCE_MOOF_BASE_OFFSET, + /*! use sdtp box in traf rather than storing sample deps in trun entry. param values are: + 0: disables sdtp + 1: enables sdtp and disables sample dependency flags in trun + 2: enables sdtp and also use sample dependency flags in trun + */ + GF_ISOM_TRAF_USE_SAMPLE_DEPS_BOX, + /*! forces new trun at next sample add + param: ignored*/ + GF_ISOM_TRUN_FORCE, + /*! sets interleave group ID of the next sample add. Samples with lower interleave ID will be stored first, creating new trun whenever a new group is detected + This will enable data cache + param: interleave ID*/ + GF_ISOM_TRUN_SET_INTERLEAVE_ID, + /*! store truns before sample encryption and sample groups info + param: 1 to store before and follow CMAF (recommended?) order, 0, to store after*/ + GF_ISOM_TRAF_TRUNS_FIRST, + /*! forces trun v1 + param: on/off (0/1)*/ + GF_ISOM_TRAF_TRUN_V1, + /*force usage of 64 bits in tfdt and in per-segment sidx*/ + GF_ISOM_TRAF_USE_LARGE_TFDT +} GF_ISOTrackFragmentOption; + +/*! sets a track fragment option. Options can be set at the beginning of each new fragment only, and for the +lifetime of the fragment +\param isom_file the target ISO file +\param TrackID ID of the target track +\param Code the option type to set +\param param the option value +\return error if any +*/ +GF_Err gf_isom_set_fragment_option(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, GF_ISOTrackFragmentOption Code, u32 param); + +/*! adds a sample to a fragmented track + +\param isom_file the target ISO file +\param TrackID destination track +\param sample sample to add +\param sampleDescriptionIndex sample description for this sample. If 0, the default one +is used +\param Duration sample duration; the sample duration MUST be provided at least for the last sample (for intermediate samples, it is recomputed internally by the lib) +\param PaddingBits padding bits for the sample, or 0 +\param DegradationPriority for the sample, or 0 +\param redundantCoding indicates this is samples acts as a sync shadow point +\return error if any +*/ +GF_Err gf_isom_fragment_add_sample(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, const GF_ISOSample *sample, + u32 sampleDescriptionIndex, + u32 Duration, u8 PaddingBits, u16 DegradationPriority, Bool redundantCoding); + +/*! appends data into last sample of track for video fragments/other media +\warning This shall not be used with OD tracks +\param isom_file the target ISO file +\param TrackID destination track +\param data the data to append +\param data_size the size of the data to append +\param PaddingBits padding bits for the sample, or 0 +\return error if any +*/ +GF_Err gf_isom_fragment_append_data(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u8 *data, u32 data_size, u8 PaddingBits); + + +/*! sets side information for common encryption for the last added sample +\param isom_file the target ISO file +\param trackID the ID of the target track +\param sai_b buffer containing the SAI information of the sample +\param sai_b_size size of the SAI buffer. If sai_b is NULL or sai_b_size is 0, add a clear SAI data +\param use_subsample indicates if the media uses CENC subsamples +\param use_saio_32bit indicates if 32-bit saio shall be used +\param use_multikey indicates if multikey is in use (required to tag saiz/saio boxes) +\return error if any +*/ +GF_Err gf_isom_fragment_set_cenc_sai(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u8 *sai_b, u32 sai_b_size, Bool use_subsample, Bool use_saio_32bit, Bool use_multikey); +/*! clones PSSH data between two files +\param dst_file the target ISO file +\param src_file the source ISO file +\param in_moof if GF_TRUE, indicates the pssh should be cloned in current moof box +\return error if any +*/ +GF_Err gf_isom_clone_pssh(GF_ISOFile *dst_file, GF_ISOFile *src_file, Bool in_moof); + + +/*! sets roll 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 the sample number of the last sample +\param roll_type indicate the sample roll type +\param roll_distance set to the roll distance for a roll sample +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_roll_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number, GF_ISOSampleRollType roll_type, s16 roll_distance); + +/*! sets rap 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_rap_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_rap, u32 num_leading_samples); + +/*! 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 +\param is_leading indicates that the sample is a leading picture +\param dependsOn indicates the sample dependency towards other samples +\param dependedOn indicates the sample dependency from other samples +\param redundant indicates that the sample contains redundant coding +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_flags(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 is_leading, u32 dependsOn, u32 dependedOn, u32 redundant); + + + +/*! adds sample auxiliary data + +\param isom_file the target ISO file +\param trackID the ID of the target track +\param sample_number_in_frag the sample number in the current fragment. Must be equal or larger to last auxiliary added +\param aux_type auxiliary sample data type, shall not be 0 +\param aux_info auxiliary sample data specific info type, may be 0 +\param data data to add +\param size size of data to add +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_aux_info(GF_ISOFile *isom_file, u32 trackID, u32 sample_number_in_frag, u32 aux_type, u32 aux_info, u8 *data, u32 size); + + +/*! sets the number of the next moof to be produced +\param isom_file the target ISO file +\param value the number of the next moof +*/ +void gf_isom_set_next_moof_number(GF_ISOFile *isom_file, u32 value); + + +/*! @} */ +#endif// !defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_WRITE) + + +/*! +\addtogroup isortp_grp ISOBMFF RTP Hinting +\ingroup iso_grp + +@{ +*/ + +/*! supported hint formats - ONLY RTP now*/ +typedef enum +{ + /*! RTP hint type*/ + GF_ISOM_HINT_RTP = GF_4CC('r', 't', 'p', ' '), +} GF_ISOHintFormat; + +#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_ISOM_HINTING) + +/*! sets up a hint track based on the hint format +\warning This function MUST be called after creating a new hint track and before any other calls on this track +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintType the desired hint type +\return error if any +*/ +GF_Err gf_isom_setup_hint_track(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOHintFormat HintType); + +/*! creates a HintDescription for the HintTrack +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintTrackVersion version of hint track +\param LastCompatibleVersion last compatible version of hint track +\param Rely flag indicating whether a reliable transport protocol is desired/required +for data transport + 0: not desired (UDP/IP). NB: most RTP streaming servers only support UDP/IP for data + 1: preferable (TCP/IP if possible or UDP/IP) + 2: required (TCP/IP only) +\param HintDescriptionIndex is set to the newly created hint sample description index +\return error if any +*/ +GF_Err gf_isom_new_hint_description(GF_ISOFile *isom_file, u32 trackNumber, s32 HintTrackVersion, s32 LastCompatibleVersion, u8 Rely, u32 *HintDescriptionIndex); + +/*! starts a new sample for the hint track. A sample is just a collection of packets +the transmissionTime is indicated in the media timeScale of the hint track +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param TransmissionTime the target transmission time in hint media timescale +\return error if any +*/ +GF_Err gf_isom_begin_hint_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TransmissionTime); + +/*! ends an hint sample once all your packets for this sample are done +\param isom_file the target ISO file +\param trackNumber the target hint track +\param IsRandomAccessPoint set to GF_TRUE if you want to indicate that this is a random access point in the stream +\return error if any +*/ +GF_Err gf_isom_end_hint_sample(GF_ISOFile *isom_file, u32 trackNumber, u8 IsRandomAccessPoint); + + +/*! + PacketHandling functions + Data can be added at the end or at the beginning of the current packet + by setting AtBegin to 1 the data will be added at the beginning + This allows constructing the packet payload before any meta-data +*/ + +/*! adds a blank chunk of data in the sample that is skipped while streaming +\param isom_file the target ISO file +\param trackNumber the target hint track +\param AtBegin indicates if the blank chunk should be at the end or at the beginning of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_blank_data(GF_ISOFile *isom_file, u32 trackNumber, u8 AtBegin); + +/*! adds a chunk of data in the packet that is directly copied while streaming +\note DataLength MUST BE <= 14 bytes, and you should only use this function +to add small blocks of data (encrypted parts, specific headers, ...) +\param isom_file the target ISO file +\param trackNumber the target hint track +\param data buffer to add to the RTP packet +\param dataLength size of buffer to add to the RTP packet +\param AtBegin indicates if the blank chunk should be at the end or at the beginning of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_direct_data(GF_ISOFile *isom_file, u32 trackNumber, u8 *data, u32 dataLength, u8 AtBegin); + +/*! adds a reference to some sample data in the packet +\note if you want to reference a previous HintSample in the hintTrack, you will have to parse the sample yourself ... + +\param isom_file the target ISO file +\param trackNumber the target hint track +\param SourceTrackID the ID of the track where the referenced sample is +\param SampleNumber the sample number containing the data to be added +\param DataLength the length of bytes to copy in the packet +\param offsetInSample the offset in bytes in the sample at which to begin copying data +\param extra_data only used when the sample is actually the sample that will contain this packet +(useful to store en encrypted version of a packet only available while streaming) + In this case, set SourceTrackID to the HintTrack ID and SampleNumber to 0 + In this case, the DataOffset MUST BE NULL and length will indicate the extra_data size +\param AtBegin indicates if the blank chunk should be at the end or at the beginning of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_sample_data(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID SourceTrackID, u32 SampleNumber, u16 DataLength, u32 offsetInSample, u8 *extra_data, u8 AtBegin); + + +/*! adds a reference to some stream description data in the packet (headers, ...) + +\param isom_file the target ISO file +\param trackNumber the target hint track +\param SourceTrackID the ID of the track where the referenced sample is +\param sampleDescriptionIndex the index of the stream description in the desired track +\param DataLength the length of bytes to copy in the packet +\param offsetInDescription the offset in bytes in the description at which to begin copying data. Since it is far from being obvious / interoperable what this offset is, we recommend not using this function and injecting the data instead using \ref gf_isom_hint_direct_data. +\param AtBegin indicates if the blank chunk should be at the end or at the beginning of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_sample_description_data(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID SourceTrackID, u32 sampleDescriptionIndex, u16 DataLength, u32 offsetInDescription, u8 AtBegin); + + +/*! creates a new RTP packet in the HintSample. If a previous packet was created, +it is stored in the hint sample and a new packet is created. + +\param isom_file the target ISO file +\param trackNumber the target hint track +\param relativeTime RTP time offset of this packet in the HintSample if any - in hint track +time scale. Used for data smoothing by servers. +\param PackingBit the 'P' bit of the RTP packet header +\param eXtensionBit the'X' bit of the RTP packet header +\param MarkerBit the 'M' bit of the RTP packet header +\param PayloadType the payload type, on 7 bits, format 0x0XXXXXXX +\param disposable_packet indicates if this packet can be skipped by a server +\param IsRepeatedPacket indicates if this is a duplicate packet of a previous one and can be skipped by a server +\param SequenceNumber the RTP base sequence number of the packet. Because of support for repeated packets, you have to set the sequence number yourself. +\return error if any +*/ +GF_Err gf_isom_rtp_packet_begin(GF_ISOFile *isom_file, u32 trackNumber, s32 relativeTime, u8 PackingBit, u8 eXtensionBit, u8 MarkerBit, u8 PayloadType, u8 disposable_packet, u8 IsRepeatedPacket, u16 SequenceNumber); + +/*! sets the flags of the RTP packet +\param isom_file the target ISO file +\param trackNumber the target hint track +\param PackingBit the 'P' bit of the RTP packet header +\param eXtensionBit the'X' bit of the RTP packet header +\param MarkerBit the 'M' bit of the RTP packet header +\param disposable_packet indicates if this packet can be skipped by a server +\param IsRepeatedPacket indicates if this is a duplicate packet of a previous one and can be skipped by a server +\return error if any*/ +GF_Err gf_isom_rtp_packet_set_flags(GF_ISOFile *isom_file, u32 trackNumber, u8 PackingBit, u8 eXtensionBit, u8 MarkerBit, u8 disposable_packet, u8 IsRepeatedPacket); + +/*! sets the time offset of this packet. This enables packets to be placed in the hint track +in decoding order, but have their presentation time-stamp in the transmitted +packet in a different order. Typically used for MPEG video with B-frames +\param isom_file the target ISO file +\param trackNumber the target hint track +\param timeOffset time offset in RTP media timescale +\return error if any +*/ +GF_Err gf_isom_rtp_packet_set_offset(GF_ISOFile *isom_file, u32 trackNumber, s32 timeOffset); + + +/*! sets the RTP TimeScale that the server use to send packets +some RTP payloads may need a specific timeScale that is not the timeScale in the file format +the default timeScale choosen by the API is the MediaTimeScale of the hint track +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param TimeScale the RTP timescale to use +\return error if any +*/ +GF_Err gf_isom_rtp_set_timescale(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TimeScale); + +/*! sets the RTP TimeOffset that the server will add to the packets +if not set, the server adds a random offset +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param TimeOffset the time offset in RTP timescale +\return error if any +*/ +GF_Err gf_isom_rtp_set_time_offset(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TimeOffset); + +/*! sets the RTP SequenceNumber Offset that the server will add to the packets +if not set, the server adds a random offset +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param SequenceNumberOffset the sequence number offset +\return error if any +*/ +GF_Err gf_isom_rtp_set_time_sequence_offset(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 SequenceNumberOffset); + +/*! adds an SDP line to the SDP container at the track level (media-specific SDP info) +\note the CRLF end of line for SDP is automatically inserted +\param isom_file the target ISO file +\param trackNumber the target hint track +\param text the SDP text to add the target hint track +\return error if any +*/ +GF_Err gf_isom_sdp_add_track_line(GF_ISOFile *isom_file, u32 trackNumber, const char *text); +/*! removes all SDP info at the track level +\param isom_file the target ISO file +\param trackNumber the target hint track +\return error if any +*/ +GF_Err gf_isom_sdp_clean_track(GF_ISOFile *isom_file, u32 trackNumber); + +/*! adds an SDP line to the SDP container at the movie level (presentation SDP info) +\note The CRLF end of line for SDP is automatically inserted +\param isom_file the target ISO file +\param text the SDP text to add the target hint track +\return error if any +*/ +GF_Err gf_isom_sdp_add_line(GF_ISOFile *isom_file, const char *text); +/*! removes all SDP info at the movie level +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_sdp_clean(GF_ISOFile *isom_file); + +#endif// !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_ISOM_HINTING) + + +#ifndef GPAC_DISABLE_ISOM_HINTING + +#ifndef GPAC_DISABLE_ISOM_DUMP +/*! dumps RTP hint samples structure into XML trace file +\param isom_file the target ISO file +\param trackNumber the target track +\param SampleNum the target sample number +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_hint_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 SampleNum, FILE * trace); +#endif + +/*! gets SDP info at the movie level +\param isom_file the target ISO file +\param sdp set to the sdp text, including a null-terminating character - do not modify +\param length set to the sdp length, not including the null-terminating character +\return error if any +*/ +GF_Err gf_isom_sdp_get(GF_ISOFile *isom_file, const char **sdp, u32 *length); +/*! gets SDP info at the track level +\param isom_file the target ISO file +\param trackNumber the target track +\param sdp set to the sdp text, including a null-terminating character - do not modify +\param length set to the sdp length, not including the null-terminating character +\return error if any +*/ +GF_Err gf_isom_sdp_track_get(GF_ISOFile *isom_file, u32 trackNumber, const char **sdp, u32 *length); +/*! gets number of payload type defines for an RTP hint track +\param isom_file the target ISO file +\param trackNumber the target track +\return the number of payload types defined +*/ +u32 gf_isom_get_payt_count(GF_ISOFile *isom_file, u32 trackNumber); +/*! gets payload type information for an RTP hint track +\param isom_file the target ISO file +\param trackNumber the target track +\param index the payload type 1_based index +\param payID set to the ID of the payload type +\return the sdp fmtp attribute describing the payload +*/ +const char *gf_isom_get_payt_info(GF_ISOFile *isom_file, u32 trackNumber, u32 index, u32 *payID); + + +#endif /*GPAC_DISABLE_ISOM_HINTING*/ + + +/*! @} */ + +/*! +\addtogroup isotxt_grp Subtitles and Timed Text +\ingroup iso_grp + +@{ +*/ + + +/*! sets streaming text reading mode (MPEG-4 text vs 3GPP) +\param isom_file the target ISO file +\param do_convert is set, all text samples will be retrieved as TTUs and ESD will be emulated for text tracks +\return error if any +*/ +GF_Err gf_isom_text_set_streaming_mode(GF_ISOFile *isom_file, Bool do_convert); + + +#ifndef GPAC_DISABLE_ISOM_DUMP +/*! text track export type*/ +typedef enum { + /*! dump as TTXT XML*/ + GF_TEXTDUMPTYPE_TTXT = 0, + /*! dump as TTXT XML with box */ + GF_TEXTDUMPTYPE_TTXT_BOXES, + /*! dump as SRT*/ + GF_TEXTDUMPTYPE_SRT, + /*! dump as SVG*/ + GF_TEXTDUMPTYPE_SVG, + /*! dump as TTXT chapters (omits empty text samples)*/ + GF_TEXTDUMPTYPE_TTXT_CHAP, + /*! dump as OGG chapters*/ + GF_TEXTDUMPTYPE_OGG_CHAP, + /*! dump as Zoom chapters*/ + GF_TEXTDUMPTYPE_ZOOM_CHAP +} GF_TextDumpType; +/*! dumps a text track to a file +\param isom_file the target ISO file +\param trackNumber the target track +\param dump the file object to write to (binary open mode) +\param dump_type the dump type mode +\return error if any +*/ +GF_Err gf_isom_text_dump(GF_ISOFile *isom_file, u32 trackNumber, FILE *dump, GF_TextDumpType dump_type); +#endif + +/*! gets encoded TX3G box (text sample description for 3GPP text streams) as needed by RTP or other standards: +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param sidx_offset if 0, the sidx will NOT be written before the encoded TX3G. If not 0, the sidx will be written before the encoded TX3G, with the given offset. Offset sshould be at least 128 for most common usage of TX3G (RTP, MPEG-4 timed text, etc) +\param tx3g set to a newly allocated buffer containing the encoded tx3g - to be freed by caller +\param tx3g_size set to the size of the encoded config +\return error if any +*/ +GF_Err gf_isom_text_get_encoded_tx3g(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 sidx_offset, u8 **tx3g, u32 *tx3g_size); + +/*! text sample formatting*/ +typedef struct _3gpp_text_sample GF_TextSample; +/*! creates text sample handle +\return a newly allocated text sample +*/ +GF_TextSample *gf_isom_new_text_sample(); +/*! destroys text sample handle +\param tx_samp the target text sample +*/ +void gf_isom_delete_text_sample(GF_TextSample *tx_samp); + +/*! generic subtitle sample formatting*/ +typedef struct _generic_subtitle_sample GF_GenericSubtitleSample; +/*! creates generic subtitle sample handle +\return a newly allocated generic subtitle sample +*/ +GF_GenericSubtitleSample *gf_isom_new_generic_subtitle_sample(); +/*! destroys generic subtitle sample handle +\param generic_subtitle_samp the target generic subtitle sample +*/ +void gf_isom_delete_generic_subtitle_sample(GF_GenericSubtitleSample *generic_subtitle_samp); + +#ifndef GPAC_DISABLE_VTT +/*! creates new WebVTT config +\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) +\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 config the WebVTT configuration string +\return error if any +*/ +GF_Err gf_isom_new_webvtt_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, const char *config); +#endif + +/*! gets WebVTT 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 WebVTT configuration string +*/ +const char *gf_isom_get_webvtt_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets simple streaming text config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param mime set to the mime type (optional, can be NULL) +\param encoding set to the text encoding type (optional, can be NULL) +\param config set to the WebVTT configuration string (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_stxt_get_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **mime, const char **encoding, const char **config); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates new simple streaming text config +\param isom_file the target ISO file +\param trackNumber the target track +\param type the four character code of the simple text sample description (sbtt, stxt, mett) +\param mime the mime type +\param encoding the text encoding, if any +\param config the configuration string, if any +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_stxt_description(GF_ISOFile *isom_file, u32 trackNumber, u32 type, const char *mime, const char *encoding, const char *config, u32 *outDescriptionIndex); + +#endif // GPAC_DISABLE_ISOM_WRITE + +/*! gets XML streaming text config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param xmlnamespace set to the XML namespace (optional, can be NULL) +\param xml_schema_loc set to the XML schema location (optional, can be NULL) +\param mimes set to the associated mime(s) types (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_xml_subtitle_get_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, + const char **xmlnamespace, const char **xml_schema_loc, const char **mimes); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a new XML streaming text config +\param isom_file the target ISO file +\param trackNumber the target track +\param xmlnamespace the XML namespace +\param xml_schema_loc the XML schema location (optional, can be NULL) +\param auxiliary_mimes the associated mime(s) types (optional, can be NULL) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_xml_subtitle_description(GF_ISOFile *isom_file, u32 trackNumber, + const char *xmlnamespace, const char *xml_schema_loc, const char *auxiliary_mimes, + u32 *outDescriptionIndex); +#endif // GPAC_DISABLE_ISOM_WRITE + + +/*! gets MIME parameters (type/subtype + codecs and profiles) associated with a sample descritpion +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return MIME param if present, NULL otherwise +*/ +const char *gf_isom_subtitle_get_mime(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! gets MIME parameters associated with a sample descritpion +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param full_mime MIME param (type/subtype + codecs and profiles) to set, if NULL removes MIME parameter info +\return error if any +*/ +GF_Err gf_isom_subtitle_set_mime(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char *full_mime); +#endif + + +/*! gets XML metadata for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param xmlnamespace set to the XML namespace (optional, can be NULL) +\param schema_loc set to the XML schema location (optional, can be NULL) +\param content_encoding set to the content encoding string (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_xml_metadata_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **xmlnamespace, const char **schema_loc, const char **content_encoding); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a new timed metadata sample description for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param xmlnamespace the XML namespace +\param schema_loc the XML schema location (optional, can be NULL) +\param content_encoding the content encoding string (optional, can be NULL) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_xml_metadata_description(GF_ISOFile *isom_file, u32 trackNumber, const char *xmlnamespace, const char *schema_loc, const char *content_encoding, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + + +/*! text flags operation type*/ +typedef enum +{ + GF_ISOM_TEXT_FLAGS_OVERWRITE = 0, + GF_ISOM_TEXT_FLAGS_TOGGLE, + GF_ISOM_TEXT_FLAGS_UNTOGGLE, +} GF_TextFlagsMode; +/*! sets text display flags according to given mode. +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index. If 0, sets the flags for all text descriptions +\param flags the flag to set +\param op_type the flag toggle mode +\return error if any +*/ +GF_Err gf_isom_text_set_display_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 flags, GF_TextFlagsMode op_type); + +/*! gets text description of a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param out_desc set to a newly allocated text sample descriptor - shall be freeed by user +\return error if any +*/ +GF_Err gf_isom_get_text_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_TextSampleDescriptor **out_desc); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! creates a new TextSampleDescription in the file. +\param isom_file the target ISO file +\param trackNumber the target track +\param desc the text 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_new_text_description(GF_ISOFile *isom_file, u32 trackNumber, GF_TextSampleDescriptor *desc, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! resets text sample content +\param tx_samp the target text sample +\return error if any +*/ +GF_Err gf_isom_text_reset(GF_TextSample * tx_samp); +/*! resets text sample styles but keep text +\param tx_samp the target text sample +\return error if any +*/ +GF_Err gf_isom_text_reset_styles(GF_TextSample *tx_samp); + +/*! appends text to sample - text_len is the number of bytes to be written from text_data. This allows +handling UTF8 and UTF16 strings in a transparent manner +\param tx_samp the target text sample +\param text_data the text data to add +\param text_len the size of the data to add +\return error if any +*/ +GF_Err gf_isom_text_add_text(GF_TextSample *tx_samp, char *text_data, u32 text_len); +/*! appends style modifyer to sample +\param tx_samp the target text sample +\param rec the style record to add +\return error if any +*/ +GF_Err gf_isom_text_add_style(GF_TextSample *tx_samp, GF_StyleRecord *rec); +/*! appends highlight modifier for the sample +\param tx_samp the target text sample +\param start_char first char highlighted, +\param end_char first char not highlighted +\return error if any +*/ +GF_Err gf_isom_text_add_highlight(GF_TextSample *tx_samp, u16 start_char, u16 end_char); + +/*! sets highlight color for the whole sample +\param tx_samp the target text sample +\param argb color value +\return error if any +*/ +GF_Err gf_isom_text_set_highlight_color(GF_TextSample *tx_samp, u32 argb); +/*! appends a new karaoke sequence in the sample +\param tx_samp the target text sample +\param start_time karaoke start time expressed in text stream timescale, but relative to the sample media time +\return error if any +*/ +GF_Err gf_isom_text_add_karaoke(GF_TextSample *tx_samp, u32 start_time); +/*! appends a new segment in the current karaoke sequence - you must build sequences in order to be compliant +\param tx_samp the target text sample +\param end_time segment end time expressed in text stream timescale, but relative to the sample media time +\param start_char first char highlighted, +\param end_char first char not highlighted +\return error if any +*/ +GF_Err gf_isom_text_set_karaoke_segment(GF_TextSample *tx_samp, u32 end_time, u16 start_char, u16 end_char); +/*! sets scroll delay for the whole sample (scrolling is enabled through GF_TextSampleDescriptor.DisplayFlags) +\param tx_samp the target text sample +\param scroll_delay delay for scrolling expressed in text stream timescale +\return error if any +*/ +GF_Err gf_isom_text_set_scroll_delay(GF_TextSample *tx_samp, u32 scroll_delay); +/*! appends hyperlinking for the sample +\param tx_samp the target text sample +\param URL UTF-8 url +\param altString UTF-8 hint (tooltip, ...) for end user +\param start_char first char hyperlinked, +\param end_char first char not hyperlinked +\return error if any +*/ +GF_Err gf_isom_text_add_hyperlink(GF_TextSample *tx_samp, char *URL, char *altString, u16 start_char, u16 end_char); +/*! sets current text box (display pos&size within the text track window) for the sample +\param tx_samp the target text sample +\param top top coordinate of box +\param left left coordinate of box +\param bottom bottom coordinate of box +\param right right coordinate of box +\return error if any +*/ +GF_Err gf_isom_text_set_box(GF_TextSample *tx_samp, s16 top, s16 left, s16 bottom, s16 right); +/*! appends blinking for the sample +\param tx_samp the target text sample +\param start_char first char blinking, +\param end_char first char not blinking +\return error if any +*/ +GF_Err gf_isom_text_add_blink(GF_TextSample *tx_samp, u16 start_char, u16 end_char); +/*! sets wrap flag for the sample +\param tx_samp the target text sample +\param wrap_flags text wrap flags - currently only 0 (no wrap) and 1 ("soft wrap") are allowed in 3GP +\return error if any +*/ +GF_Err gf_isom_text_set_wrap(GF_TextSample *tx_samp, u8 wrap_flags); + +/*! formats sample as a regular GF_ISOSample payload in a bitstream object. +\param tx_samp the target text sample +\param bs thetarget bitstream +\return error if any +*/ +GF_Err gf_isom_text_sample_write_bs(const GF_TextSample *tx_samp, GF_BitStream *bs); + + +/*! formats sample as a regular GF_ISOSample. +The resulting sample will always be marked as random access +\param tx_samp the target text sample +\return the corresponding serialized ISO sample +*/ +GF_ISOSample *gf_isom_text_to_sample(const GF_TextSample *tx_samp); + +/*! gets the serialized size of the text sample +\param tx_samp the target text sample +\return the serialized size +*/ +u32 gf_isom_text_sample_size(GF_TextSample *tx_samp); + +/*! creates a new XML subtitle sample +\return a new XML subtitle sample +*/ +GF_GenericSubtitleSample *gf_isom_new_xml_subtitle_sample(); +/*! deletes an XML subtitle sample +\param subt_samp the target XML subtitle sample +*/ +void gf_isom_delete_xml_subtitle_sample(GF_GenericSubtitleSample *subt_samp); +/*! resets content of an XML subtitle sample +\param subt_samp the target XML subtitle sample +\return error if any +*/ +GF_Err gf_isom_xml_subtitle_reset(GF_GenericSubtitleSample *subt_samp); +/*! the corresponding serialized ISO sample +\param subt_samp the target XML subtitle sample +\return the corresponding serialized ISO sample +*/ +GF_ISOSample *gf_isom_xml_subtitle_to_sample(GF_GenericSubtitleSample *subt_samp); +/*! appends text to an XML subtitle sample +\param subt_samp the target XML subtitle sample +\param text_data the UTF-8 or UTF-16 data to add +\param text_len the size of the text to add in bytes +\return error if any +*/ +GF_Err gf_isom_xml_subtitle_sample_add_text(GF_GenericSubtitleSample *subt_samp, char *text_data, u32 text_len); + + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! @} */ + + +/*! +\addtogroup isocrypt_grp Content Protection +\ingroup iso_grp + +@{ +*/ + +/*! DRM related code points*/ +enum +{ + /*! Storage location of CENC sample auxiliary in PSEC UUID box*/ + GF_ISOM_BOX_UUID_PSEC = GF_4CC( 'P', 'S', 'E', 'C' ), + /*! Storage location of CENC sample auxiliary in senc box*/ + GF_ISOM_BOX_TYPE_SENC = GF_4CC( 's', 'e', 'n', 'c'), + /*! PSSH box type */ + GF_ISOM_BOX_TYPE_PSSH = GF_4CC( 'p', 's', 's', 'h'), + /*! ISMA Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_ISMACRYP_SCHEME = GF_4CC( 'i', 'A', 'E', 'C' ), + /*! OMA DRM Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_OMADRM_SCHEME = GF_4CC('o','d','k','m'), + /*! CENC AES-CTR Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CENC_SCHEME = GF_4CC('c','e','n','c'), + /*! CENC AES-CBC Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CBC_SCHEME = GF_4CC('c','b','c','1'), + /*! Adobe Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_ADOBE_SCHEME = GF_4CC('a','d','k','m'), + /*! CENC AES-CTR Pattern Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CENS_SCHEME = GF_4CC('c','e','n','s'), + /*! CENC AES-CBC Pattern Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CBCS_SCHEME = GF_4CC('c','b','c','s'), + /*! PIFF Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_PIFF_SCHEME = GF_4CC('p','i','f','f'), + /*! CENC sensitive encryption */ + GF_ISOM_SVE1_SCHEME = GF_4CC('s','v','e','1'), +}; + +/*! checks if a track is encrypted or protected +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if track is protected, GF_FALSE otherwise*/ +Bool gf_isom_is_track_encrypted(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! flags for GF_ISMASample*/ +typedef enum +{ + /*! signals the stream the sample belongs to uses selective encryption*/ + GF_ISOM_ISMA_USE_SEL_ENC = 1, + /*! signals the sample is encrypted*/ + GF_ISOM_ISMA_IS_ENCRYPTED = 2, +} GF_ISOISMACrypFlags; + +/*! ISMA sample*/ +typedef struct +{ + /*! IV in ISMACryp is Byte Stream Offset*/ + u64 IV; + /*! IV size in bytes, repeated from sampleDesc for convenience*/ + u8 IV_length; + /*! key indicator*/ + u8 *key_indicator; + /*! key indicator size, repeated from sampleDesc for convenience*/ + u8 KI_length; + /*! payload size*/ + u32 dataLength; + /*! payload*/ + u8 *data; + /*! flags*/ + u32 flags; +} GF_ISMASample; +/*! creates a new empty ISMA sample +\return a new empty ISMA sample +*/ +GF_ISMASample *gf_isom_ismacryp_new_sample(); + +/*! delete an ISMA sample. +\note the buffer content will be destroyed by default. If you wish to keep the buffer, set dataLength to 0 in the sample before deleting it +\param samp the target ISMA sample +*/ +void gf_isom_ismacryp_delete_sample(GF_ISMASample *samp); + +/*! decodes ISMACryp sample based on all info in ISMACryp sample description +\param data sample data +\param dataLength sample data size in bytes +\param use_selective_encryption set to GF_TRUE if sample uses selective encryption +\param KI_length set to the size in bytes of the key indicator - 0 means no key roll +\param IV_length set to the size in bytes of the initialization vector +\return a newly allocated ISMA sample with the parsed data +*/ +GF_ISMASample *gf_isom_ismacryp_sample_from_data(u8 *data, u32 dataLength, Bool use_selective_encryption, u8 KI_length, u8 IV_length); + +/*! decodes ISMACryp sample based on sample and its descrition index +\param isom_file the target ISO file +\param trackNumber the target track +\param samp the sample to decode +\param sampleDescriptionIndex the sample description index of the sample to decode +\return the ISMA sample or NULL if not an ISMA sample or error +*/ +GF_ISMASample *gf_isom_get_ismacryp_sample(GF_ISOFile *isom_file, u32 trackNumber, const GF_ISOSample *samp, u32 sampleDescriptionIndex); + +/*! checks if sample description is protected or not +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index. If 0, checks all sample descriptions for protected ones +\return scheme protection 4CC or 0 if not protected*/ +u32 gf_isom_is_media_encrypted(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! checks if sample description is protected with ISMACryp +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if ISMA protection is used*/ +Bool gf_isom_is_ismacryp_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! checks if sample description is protected with OMA DRM +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if OMA DRM protection is used*/ +Bool gf_isom_is_omadrm_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets OMA DRM configuration - all output parameters are optional and may be NULL +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat four character code of the unprotected sample description +\param outSchemeType set to four character code of the protection scheme type +\param outSchemeVersion set to scheme protection version +\param outContentID set to associated ID of content +\param outRightsIssuerURL set to the rights issuer (license server) URL +\param outTextualHeaders set to OMA textual headers +\param outTextualHeadersLen set to the size in bytes of OMA textual headers +\param outPlaintextLength set to the size in bytes of clear data in file +\param outEncryptionType set to the OMA encryption type used +\param outSelectiveEncryption set to GF_TRUE if sample description uses selective encryption +\param outIVLength set to the size of the initialization vector +\param outKeyIndicationLength set to the size of the key indicator +\return error if any +*/ +GF_Err gf_isom_get_omadrm_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, + u32 *outSchemeType, u32 *outSchemeVersion, + const char **outContentID, const char **outRightsIssuerURL, const char **outTextualHeaders, u32 *outTextualHeadersLen, u64 *outPlaintextLength, u32 *outEncryptionType, Bool *outSelectiveEncryption, u32 *outIVLength, u32 *outKeyIndicationLength); + +/*! retrieves ISMACryp info for the given track & SDI - all output parameters are optional - URIs SHALL NOT BE MODIFIED BY USER + +\note outSelectiveEncryption, outIVLength and outKeyIndicationLength are usually not needed to decode an ISMA sample when using \ref gf_isom_get_ismacryp_sample + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat set to orginal unprotected media format +\param outSchemeType set to 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param outSchemeVersion set to version of protection scheme (1 in ISMACryp 1.0) +\param outSchemeURI set to URI location of scheme +\param outKMS_URI set to URI location of key management system - only valid with ISMACryp 1.0 +\param outSelectiveEncryption set to whether sample-based encryption is used in media - only valid with ISMACryp 1.0 +\param outIVLength set to length of Initial Vector - only valid with ISMACryp 1.0 +\param outKeyIndicationLength set to length of key indicator - only valid with ISMACryp 1.0 +\return error if any +*/ +GF_Err gf_isom_get_ismacryp_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, const char **outSchemeURI, const char **outKMS_URI, Bool *outSelectiveEncryption, u32 *outIVLength, u32 *outKeyIndicationLength); + +/*! gets original format four character code type of a protected media sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index. If 0, checks all sample descriptions for a protected one +\param outOriginalFormat set to orginal unprotected media format +\return error if any +*/ +GF_Err gf_isom_get_original_format_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! creates ISMACryp protection info for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param scheme_uri URI location of scheme +\param kms_URI URI location of key management system - only valid with ISMACryp 1.0 +\param selective_encryption whether sample-based encryption is used in media - only valid with ISMACryp 1.0 +\param KI_length length of key indicator - only valid with ISMACryp 1.0 +\param IV_length length of Initial Vector - only valid with ISMACryp 1.0 +\return error if any +*/ +GF_Err gf_isom_set_ismacryp_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, + u32 scheme_version, char *scheme_uri, char *kms_URI, + Bool selective_encryption, u32 KI_length, u32 IV_length); + +/*! changes scheme URI and/or KMS URI for crypted files. Other params cannot be changed once the media is crypted +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_uri new scheme URI, or NULL to keep previous +\param kms_uri new KMS URI, or NULL to keep previous +\return error if any +*/ +GF_Err gf_isom_change_ismacryp_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, char *scheme_uri, char *kms_uri); + + +/*! creates OMA DRM protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param contentID associated ID of content +\param kms_URI the rights issuer (license server) URL +\param encryption_type the OMA encryption type used +\param plainTextLength the size in bytes of clear data in file +\param textual_headers OMA textual headers +\param textual_headers_len the size in bytes of OMA textual headers +\param selective_encryption GF_TRUE if sample description uses selective encryption +\param KI_length the size of the key indicator +\param IV_length the size of the initialization vector +\return error if any +*/ +GF_Err gf_isom_set_oma_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, + 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); + +/*! creates a generic protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param scheme_uri URI location of scheme +\param kms_URI the rights issuer (license server) URL +\return error if any +*/ +GF_Err gf_isom_set_generic_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, u32 scheme_version, char *scheme_uri, char *kms_URI); + +/*! allocates storage for CENC side data in a senc box +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_cenc_allocate_storage(GF_ISOFile *isom_file, u32 trackNumber); + +/*! allocates storage for CENC side data in a PIFF senc UUID box +\param isom_file the target ISO file +\param trackNumber the target track +\param AlgorithmID algorith ID, usually 0 +\param IV_size the size of the init vector +\param KID the default Key ID +\return error if any +*/ +GF_Err gf_isom_piff_allocate_storage(GF_ISOFile *isom_file, u32 trackNumber, u32 AlgorithmID, u8 IV_size, bin128 KID); + + +/*! adds cenc SAI for the last sample added to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param container_type the code of the container (currently 'senc' for CENC or 'PSEC' for smooth) +\param buf the SAI buffer +\param len the size of the SAI buffer. If buf is NULL or len is 0, this adds an unencrypted entry (not written to file) +\param use_subsamples if GF_TRUE, the media format uses CENC subsamples +\param use_saio_32bit forces usage of 32-bit saio boxes +\param is_multi_key indicates if multi key is in use (required to tag saio and saiz boxes) +\return error if any +*/ +GF_Err gf_isom_track_cenc_add_sample_info(GF_ISOFile *isom_file, u32 trackNumber, u32 container_type, u8 *buf, u32 len, Bool use_subsamples, Bool use_saio_32bit, Bool is_multi_key); + + + +/*! creates CENC protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param default_IsEncrypted default isEncrypted flag +\param default_crypt_byte_block default crypt block size for pattern encryption +\param default_skip_byte_block default skip block size for pattern encryption +\param key_info key descriptor formatted as a multi-key info (cf GF_PROP_PID_CENC_KEY) +\param key_info_size key descriptor size +\return error if any +*/ +GF_Err gf_isom_set_cenc_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, + u32 scheme_version, u32 default_IsEncrypted, u8 default_crypt_byte_block, u8 default_skip_byte_block, + u8 *key_info, u32 key_info_size); + + +/*! creates CENC protection for a multi-key sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param default_IsEncrypted default isEncrypted flag +\param default_crypt_byte_block default crypt block size for pattern encryption +\param default_skip_byte_block default skip block size for pattern encryption +\param key_info key info (cf CENC and GF_PROP_PID_CENC_KEY) +\param key_info_size key info size +\return error if any +*/ +GF_Err gf_isom_set_cenc_protection_mkey(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, + u32 scheme_version, u32 default_IsEncrypted, u8 default_crypt_byte_block, u8 default_skip_byte_block, + u8 *key_info, u32 key_info_size); + + +/*! adds PSSH info for a file, can be called several time per system ID +\param isom_file the target ISO file +\param systemID the ID of the protection system +\param version the version of the protection system +\param KID_count the number of key IDs +\param KID the list of key IDs +\param data opaque data for the protection system +\param len size of the opaque data +\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); + +/*! 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); +/*! 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); + +#endif //GPAC_DISABLE_ISOM_WRITE + +/*! checks if sample description is protected with Adobe systems +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if ADOBE protection is used +*/ +Bool gf_isom_is_adobe_protection_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets adobe protection information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat set to orginal unprotected media format +\param outSchemeType set to 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param outSchemeVersion set to version of protection scheme (1 in ISMACryp 1.0) +\param outMetadata set to adobe metadata string +\return GF_TRUE if ADOBE protection is used +*/ +GF_Err gf_isom_get_adobe_protection_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, const char **outMetadata); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates an adobe protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param is_selective_enc indicates if selective encryption is used +\param metadata metadata information +\param len size of metadata information in bytes +\return error if any +*/ +GF_Err gf_isom_set_adobe_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, u32 scheme_version, Bool is_selective_enc, char *metadata, u32 len); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! checks of sample description is protected with CENC +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index. If 0, checks all sample descriptions for protected ones +\return GF_TRUE if sample protection is CENC +*/ +Bool gf_isom_is_cenc_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets CENC information of a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat set to orginal unprotected media format +\param outSchemeType set to 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param outSchemeVersion set to version of protection scheme (1 in ISMACryp 1.0) +\return error if any +*/ +GF_Err gf_isom_get_cenc_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion); + + +/*! gets CENC auxiliary info of a sample as a buffer +\note the serialized buffer format is exactly a CencSampleAuxiliaryDataFormat + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample +\param sampleDescIndex the sample description index +\param container_type is type of box which contains the sample auxiliary information. Now we have two type: GF_ISOM_BOX_UUID_PSEC and GF_ISOM_BOX_TYPE_SENC +\param out_buffer set to a newly allocated buffer, or reallocated buffer if not NULL +\param outSize set to the size of the serialized buffer. If an existing buffer was passed, the passed value shall be the allocated buffer size (the returned value is still the buffer size) +\return error if any +*/ +GF_Err gf_isom_cenc_get_sample_aux_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 sampleDescIndex, u32 *container_type, u8 **out_buffer, u32 *outSize); + +/*! gets CENC default info for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param container_type set to the container type of SAI data +\param default_IsEncrypted set to default isEncrypted flag +\param crypt_byte_block set to default crypt block size for pattern encryption +\param skip_byte_block set to default skip block size for pattern encryption +\param key_info set to multikey descriptor (cf CENC and GF_PROP_PID_CENC_KEY) +\param key_info_size set to multikey descriptor size +\return error if any +*/ +GF_Err gf_isom_cenc_get_default_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *container_type, Bool *default_IsEncrypted, u8 *crypt_byte_block, u8 *skip_byte_block, const u8 **key_info, u32 *key_info_size); + +/*! gets the number of PSSH defined +\param isom_file the target ISO file +\return number of PSSH defined +*/ +u32 gf_isom_get_pssh_count(GF_ISOFile *isom_file); + +/*! gets PSS info +\param isom_file the target ISO file +\param pssh_index 1-based index of PSSH to query, see \ref gf_isom_get_pssh_count +\param SystemID set to the protection system ID +\param version set to the protection system version +\param KID_count set to the number of key IDs defined +\param KIDs array of defined key IDs +\param private_data set to a buffer containing system ID private data +\param private_data_size set to the size of the system ID private data +\return error if any +*/ +GF_Err gf_isom_get_pssh_info(GF_ISOFile *isom_file, u32 pssh_index, bin128 SystemID, u32 *version, u32 *KID_count, const bin128 **KIDs, const u8 **private_data, u32 *private_data_size); + +#ifndef GPAC_DISABLE_ISOM_DUMP +/*! dumps ismacrypt protection of sample descriptions to xml trace +\param isom_file the target ISO file +\param trackNumber the target track +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_ismacryp_protection(GF_ISOFile *isom_file, u32 trackNumber, FILE * trace); +/*! dumps ismacrypt sample to xml trace +\param isom_file the target ISO file +\param trackNumber the target track +\param SampleNum the target sample number +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_ismacryp_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 SampleNum, FILE *trace); +#endif + +/*! gets CENC configuration for a given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param IsEncrypted set to GF_TRUE if the sample is encrypted, GF_FALSE otherwise (optional can be NULL) +\param crypt_byte_block set to crypt block count for pattern encryption (optional can be NULL) +\param skip_byte_block set to skip block count for pattern encryption (optional can be NULL) +\param key_info set to key descriptor (cf GF_PROP_PID_CENC_KEY) +\param key_info_size set to key descriptor size +\return error if any +*/ +GF_Err gf_isom_get_sample_cenc_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool *IsEncrypted, u8 *crypt_byte_block, u8 *skip_byte_block, const u8 **key_info, u32 *key_info_size); + +/*! @} */ + +/*! +\addtogroup isometa_grp Meta and Image File Format +\ingroup iso_grp + +@{ +*/ + + +/*! gets meta type +\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 +\return 0 if no meta found, or four char code of meta (eg, "mp21", "smil", ...)*/ +u32 gf_isom_get_meta_type(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! checks if the meta has an XML container (note that XML data can also be included as items). +\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 +\return 0 (no XML or error), 1 (XML text), 2 (BinaryXML, eg BiM) */ +u32 gf_isom_has_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! extracts XML (if any) from given meta +\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 outName output file path and location for writing +\param is_binary indicates if XML is Bim or regular XML +\return error if any +*/ +GF_Err gf_isom_extract_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, char *outName, Bool *is_binary); + +/*! checks the number of items in a meta +\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 +\return number of items*/ +u32 gf_isom_get_meta_item_count(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! gets item info for the given item +\note When an item is fully contained in file, both item_url and item_urn are set to NULL + +\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 item_num 1-based index of item to query +\param itemID set to item ID in file (optional, can be NULL) +\param type set to item 4CC type +\param protection_scheme set to 0 if not protected, or scheme type used if item is protected. If protected but scheme type not present, set to 'unkw' +\param protection_scheme_version set to 0 if not protected, or scheme version used if item is protected +\param is_self_reference set to item is the file itself +\param item_name set to the item name (optional, can be NULL) +\param item_mime_type set to the item mime type (optional, can be NULL) +\param item_encoding set to the item content encoding type (optional, can be NULL) +\param item_url set to the URL of external resource containing this item data if any. +\param item_urn set to the URN of external resource containing this item data if any. +\return error if any +*/ +GF_Err gf_isom_get_meta_item_info(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num, + u32 *itemID, u32 *type, u32 *protection_scheme, u32 *protection_scheme_version, Bool *is_self_reference, + const char **item_name, const char **item_mime_type, const char **item_encoding, + const char **item_url, const char **item_urn); + +/*! gets item flags for the givesn item + +\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 item_num 1-based index of item to query +\return item flags +*/ +GF_Err gf_isom_get_meta_item_flags(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num); + +/*! gets item index from 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 item_ID ID of the item to search +\return item index if found, 0 otherwise +*/ +u32 gf_isom_get_meta_item_by_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_ID); + +/*! extracts an item from given meta +\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 item_num 1-based index of item to query +\param dump_file_name if NULL, uses item name for dumping, otherwise dumps in given file object (binary write mode) +\return error if any +*/ +GF_Err gf_isom_extract_meta_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num, const char *dump_file_name); + +/*! extracts item from given meta in memory +\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 item_id the ID of the item to dump +\param out_data set to allocated buffer containing the item, shall be freeed by user +\param out_size set to the size of the allocated buffer +\param out_alloc_size set to the allocated size of the buffer (this allows passing an existing buffer without always reallocating it) +\param mime_type set to the mime type of the item +\param use_annex_b for image items based on NALU formats (AVC, HEVC) indicates to extract the data as Annex B format (with start codes) +\return error if any +*/ +GF_Err gf_isom_extract_meta_item_mem(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_id, u8 **out_data, u32 *out_size, u32 *out_alloc_size, const char **mime_type, Bool use_annex_b); + + +/*! fetch CENC info for an item +\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 item_id the ID of the item to dump +\param is_protected set to GF_TRUE if item is protected +\param skip_byte_block set to skip_byte_block or 0 if no pattern +\param crypt_byte_block set to crypt_byte_block or 0 if no pattern +\param key_info set to key info +\param key_info_size set to key info size +\param aux_info_type_parameter set to the CENC auxiliary type param of SAI data +\param sai_out_data set to allocated buffer containing the item, shall be freeed by user - may be NULL to only retrieve the info +\param sai_out_size set to the size of the allocated buffer - may be NULL if sai_out_data is NULL +\param sai_out_alloc_size set to the allocated size of the buffer (this allows passing an existing buffer without always reallocating it) - may be NULL if sai_out_data is NULL +\return error if any +*/ +GF_Err gf_isom_extract_meta_item_get_cenc_info(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_id, Bool *is_protected, + u8 *skip_byte_block, u8 *crypt_byte_block, const u8 **key_info, u32 *key_info_size, u32 *aux_info_type_parameter, + u8 **sai_out_data, u32 *sai_out_size, u32 *sai_out_alloc_size); + +/*! gets primary 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 +\return primary item ID, 0 if none found (primary can also be stored through meta XML)*/ +u32 gf_isom_get_meta_primary_item_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! gets number of references of a given type from a given 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 from_id item ID to check +\param type reference type to check +\return number of referenced items*/ +u32 gf_isom_meta_get_item_ref_count(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 from_id, u32 type); + +/*! gets ID of reference of a given type and index from a given 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 from_id item ID to check +\param type reference type to check +\param ref_idx 1-based index of reference to check +\return ID if the referred item*/ +u32 gf_isom_meta_get_item_ref_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 from_id, u32 type, u32 ref_idx); + +/*! item tile mode*/ +typedef enum { + /*! not a tile item*/ + TILE_ITEM_NONE = 0, + /*! a tile item without base*/ + TILE_ITEM_ALL_NO_BASE, + /*! a tile item with base*/ + TILE_ITEM_ALL_BASE, + /*! a tile item grid*/ + TILE_ITEM_ALL_GRID, + /*! a tile item single*/ + TILE_ITEM_SINGLE +} GF_TileItemMode; + +/*! Image overlay offset properties*/ +typedef struct { + u32 horizontal; + u32 vertical; +} GF_ImageItemOverlayOffset; + +/*! Image protection item properties*/ +typedef struct +{ + u32 scheme_type; + u32 scheme_version; + u8 crypt_byte_block; + u8 skip_byte_block; + const u8 *key_info; + u32 key_info_size; + const u8 *sai_data; + u32 sai_data_size; +} GF_ImageItemProtection; + +/*! Image item properties*/ +typedef struct +{ + /*! width in pixels*/ + u32 width; + /*! height in pixless*/ + u32 height; + /*! pixel aspect ratio numerator*/ + u32 hSpacing; + /*! pixel aspect ratio denominator*/ + u32 vSpacing; + /*! horizontal offset in pixels*/ + u32 hOffset; + /*! vertical offset in pixels*/ + u32 vOffset; + /*! angle in radians*/ + u32 angle; + /*! mirroring axis: 0 = not set, 1 = vertical, 2 = horizontal*/ + u32 mirror; + /*! hidden flag*/ + Bool hidden; + /*! clean aperture */ + u32 clap_wnum, clap_wden, clap_hnum, clap_hden, clap_hoden, clap_voden; + s32 clap_honum, clap_vonum; + /*! pointer to configuration box*/ + void *config; + /*! tile item mode*/ + GF_TileItemMode tile_mode; + /*! tile number */ + u32 single_tile_number; + /*! time for importing*/ + Double time; + /*! end time for importing*/ + Double end_time; + /*! step time between imports*/ + Double step_time; + /*! sample num for importing*/ + u32 sample_num; + /*! file containg iCC data for importing*/ + char iccPath[GF_MAX_PATH]; + /*! is alpha*/ + Bool alpha; + /*! is depth*/ + Bool depth; + /*! number of channels*/ + u8 num_channels; + /*! bits per channels in bits*/ + u32 bits_per_channel[3]; + /*! number of columns in grid*/ + u32 num_grid_columns; + /*! number of rows in grid*/ + u32 num_grid_rows; + /*! number of overlayed images*/ + u32 overlay_count; + /*! overlay offsets*/ + GF_ImageItemOverlayOffset *overlay_offsets; + /*! canvas overlay color*/ + u32 overlay_canvas_fill_value_r; + u32 overlay_canvas_fill_value_g; + u32 overlay_canvas_fill_value_b; + u32 overlay_canvas_fill_value_a; + /*! protection info, NULL if item is not protected*/ + GF_ImageItemProtection *cenc_info; + /*! If set, reference image from sample sample_num (same file data used for sample and item)*/ + Bool use_reference; + /*ID of item to use as source*/ + u32 item_ref_id; + /*if set, copy all properties of source item*/ + Bool copy_props; + /*only set when importing non-ref from ISOBMF*/ + GF_ISOFile *src_file; + Bool auto_grid; + Double auto_grid_ratio; + /*AV1 layer sizes except last layer - set during import*/ + u32 av1_layer_size[3]; + /*AV1 operation point index*/ + u8 av1_op_index; + + const char *aux_urn; + const u8 *aux_data; + u32 aux_data_len; +} GF_ImageItemProperties; + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! sets meta type (four char int, eg "mp21", ...), creating a meta box if not found +\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 metaType the type of meta to create. If 0, removes the meta box +\return error if any +*/ +GF_Err gf_isom_set_meta_type(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 metaType); + +/*! removes meta XML info if any +\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 +\return error if any +*/ +GF_Err gf_isom_remove_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! sets meta XML data from file or memory - erase any previously (Binary)XML info +\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 XMLFileName the XML file to import as XML item, or NULL if data is specified +\param data buffer containing XML data, or NULL if file is specified +\param data_size size of buffer in bytes, ignored if file is specified +\param IsBinaryXML indicates if the content of the XML file is binary XML (BIM) or not +\return error if any +*/ +GF_Err gf_isom_set_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, char *XMLFileName, unsigned char *data, u32 data_size, Bool IsBinaryXML); + +/*! gets next available item ID in a meta +\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 item_id set to the next available item ID +\return error if any +*/ +GF_Err gf_isom_meta_get_next_item_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 *item_id); + +/*! adds an item to a meta box from file +\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 item_id ID of the item, can be 0 +\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_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); + +/*! item extend description*/ +typedef struct +{ + /*! offset of extent in file*/ + u64 extent_offset; + /*! size of extent*/ + u64 extent_length; + /*! index of extent*/ + u64 extent_index; +#ifndef GPAC_DISABLE_ISOM_WRITE + /*! for storage only, original offset in source file*/ + u64 original_extent_offset; +#endif +} GF_ItemExtentEntry; + + +/*! adds an item to a meta box from memory +\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 item_name name of the item +\param item_id ID of the item, can be NULL, can be 0 in input, set to item ID after call +\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 image_props image properties information for image items +\param data buffer containing the item data +\param data_len size of item data buffer in bytes +\param item_extent_refs list of item extend description, or NULL +\return error if any +*/ +GF_Err gf_isom_add_meta_item_memory(GF_ISOFile *isom_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); + +/*! adds an item to a meta box as a reference to a sample +\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 item_name name of the item +\param item_id ID of the item, can be 0 +\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 image_props image properties information for image items +\param tk_id source track ID +\param sample_num number of sample to reference +\return error if any +*/ +GF_Err gf_isom_add_meta_item_sample_ref(GF_ISOFile *isom_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, GF_ISOTrackID tk_id, u32 sample_num); + +/*! creates an image grid item +\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 meta_track_number is 0 +\param meta_track_number if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param image_props image properties information for image items +\return error if any +*/ +GF_Err gf_isom_iff_create_image_grid_item(GF_ISOFile *isom_file, Bool root_meta, u32 meta_track_number, const char *item_name, u32 item_id, GF_ImageItemProperties *image_props); + +/*! creates an image overlay item +\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 meta_track_number is 0 +\param meta_track_number if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param image_props image properties information for image items +\return error if any +*/ +GF_Err gf_isom_iff_create_image_overlay_item(GF_ISOFile *isom_file, Bool root_meta, u32 meta_track_number, const char *item_name, u32 item_id, GF_ImageItemProperties *image_props); + +/*! creates an image identity item +\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 meta_track_number is 0 +\param meta_track_number if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param image_props image properties information for image items +\return error if any +*/ +GF_Err gf_isom_iff_create_image_identity_item(GF_ISOFile *isom_file, Bool root_meta, u32 meta_track_number, const char *item_name, u32 item_id, GF_ImageItemProperties *image_props); + +/*! creates image item(s) from samples of a media track +\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 meta_track_number is 0 +\param meta_track_number if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param media_track track number to import samples from +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param image_props image properties information for image items +\param item_extent_refs list of item extend description, or NULL +\return error if any +*/ +GF_Err gf_isom_iff_create_image_item_from_track(GF_ISOFile *isom_file, Bool root_meta, u32 meta_track_number, u32 media_track, const char *item_name, u32 item_id, GF_ImageItemProperties *image_props, GF_List *item_extent_refs); + +/*! removes item from meta +\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 item_num 1-based index of the item to remove +\param keep_refs do not modify item reference, typically used when replacing an item +\param keep_props keep property association for properties with 4CC listed in keep_props (coma-seprated list) +\return error if any +*/ +GF_Err gf_isom_remove_meta_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num, Bool keep_refs, const char *keep_props); + +/*! sets the given item as the primary one +\warning This SHALL NOT be used if the meta has a valid XML data +\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 item_num 1-based index of the item to remove +\return error if any +*/ +GF_Err gf_isom_set_meta_primary_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num); + +/*! adds an item reference to another item +\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 from_id ID of item the reference is from +\param to_id ID of item the reference is to +\param type four character code of reference +\param ref_index set to the 1-based index of the reference +\return error if any +*/ +GF_Err gf_isom_meta_add_item_ref(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 from_id, u32 to_id, u32 type, u64 *ref_index); + +/*! adds the item to the given group +\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 item_id ID of item to add +\param group_id ID of group, 0 if needs to be determined from the file +\param group_type four character code of group +\return error if any +*/ +GF_Err gf_isom_meta_add_item_group(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_id, u32 group_id, u32 group_type); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets image item properties +\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 item_id ID of the item +\param out_image_props set to the image properties information of the item +\return error if any +*/ +GF_Err gf_isom_get_meta_image_props(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_id, GF_ImageItemProperties *out_image_props); + +/*! @} */ + +/*! +\addtogroup isotags_grp iTunes tagging +\ingroup iso_grp + +@{ +*/ + +/*! iTunes info tags */ +typedef enum +{ + /*probe is only used to check if iTunes info are present*/ + GF_ISOM_ITUNE_PROBE = 0, + /*all is only used to remove all tags*/ + GF_ISOM_ITUNE_RESET = 1, + GF_ISOM_ITUNE_NAME = GF_4CC( 0xA9, 'n', 'a', 'm' ), + GF_ISOM_ITUNE_ARTIST = GF_4CC( 0xA9, 'A', 'R', 'T' ), + GF_ISOM_ITUNE_ALBUM_ARTIST = GF_4CC( 'a', 'A', 'R', 'T' ), + GF_ISOM_ITUNE_ALBUM = GF_4CC( 0xA9, 'a', 'l', 'b' ), + GF_ISOM_ITUNE_GROUP = GF_4CC( 0xA9, 'g', 'r', 'p' ), + GF_ISOM_ITUNE_WRITER = GF_4CC( 0xA9, 'w', 'r', 't' ), + GF_ISOM_ITUNE_COMMENT = GF_4CC( 0xA9, 'c', 'm', 't' ), + GF_ISOM_ITUNE_GENRE_USER = GF_4CC( 0xA9, 'g', 'n', 'r'), + GF_ISOM_ITUNE_GENRE = GF_4CC( 'g', 'n', 'r', 'e' ), + GF_ISOM_ITUNE_CREATED = GF_4CC( 0xA9, 'd', 'a', 'y' ), + GF_ISOM_ITUNE_TRACKNUMBER = GF_4CC( 't', 'r', 'k', 'n' ), + GF_ISOM_ITUNE_DISK = GF_4CC( 'd', 'i', 's', 'k' ), + GF_ISOM_ITUNE_TEMPO = GF_4CC( 't', 'm', 'p', 'o' ), + GF_ISOM_ITUNE_COMPILATION = GF_4CC( 'c', 'p', 'i', 'l' ), + GF_ISOM_ITUNE_TV_SHOW = GF_4CC( 't', 'v', 's', 'h'), + GF_ISOM_ITUNE_TV_EPISODE = GF_4CC( 't', 'v', 'e', 'n'), + GF_ISOM_ITUNE_TV_SEASON = GF_4CC( 't', 'v', 's', 'n'), + GF_ISOM_ITUNE_TV_EPISODE_NUM = GF_4CC( 't', 'v', 'e', 's'), + GF_ISOM_ITUNE_TV_NETWORK = GF_4CC( 't', 'v', 'n', 'n'), + GF_ISOM_ITUNE_DESCRIPTION = GF_4CC( 'd', 'e', 's', 'c' ), + GF_ISOM_ITUNE_LONG_DESCRIPTION = GF_4CC( 'l', 'd', 'e', 's'), + GF_ISOM_ITUNE_LYRICS = GF_4CC( 0xA9, 'l', 'y', 'r' ), + GF_ISOM_ITUNE_SORT_NAME = GF_4CC( 's', 'o', 'n', 'm' ), + GF_ISOM_ITUNE_SORT_ARTIST = GF_4CC( 's', 'o', 'a', 'r' ), + GF_ISOM_ITUNE_SORT_ALB_ARTIST = GF_4CC( 's', 'o', 'a', 'a' ), + GF_ISOM_ITUNE_SORT_ALBUM = GF_4CC( 's', 'o', 'a', 'l' ), + GF_ISOM_ITUNE_SORT_COMPOSER = GF_4CC( 's', 'o', 'c', 'o' ), + GF_ISOM_ITUNE_SORT_SHOW = GF_4CC( 's', 'o', 's', 'n' ), + GF_ISOM_ITUNE_COVER_ART = GF_4CC( 'c', 'o', 'v', 'r' ), + GF_ISOM_ITUNE_COPYRIGHT = GF_4CC( 'c', 'p', 'r', 't' ), + GF_ISOM_ITUNE_TOOL = GF_4CC( 0xA9, 't', 'o', 'o' ), + GF_ISOM_ITUNE_ENCODER = GF_4CC( 0xA9, 'e', 'n', 'c' ), + GF_ISOM_ITUNE_PURCHASE_DATE = GF_4CC( 'p', 'u', 'r', 'd' ), + GF_ISOM_ITUNE_PODCAST = GF_4CC( 'p', 'c', 's', 't' ), + GF_ISOM_ITUNE_PODCAST_URL = GF_4CC( 'p', 'u', 'r', 'l' ), + GF_ISOM_ITUNE_KEYWORDS = GF_4CC( 'k', 'y', 'y', 'w'), + GF_ISOM_ITUNE_CATEGORY = GF_4CC( 'c', 'a', 't', 'g'), + GF_ISOM_ITUNE_HD_VIDEO = GF_4CC( 'h', 'd', 'v', 'd'), + GF_ISOM_ITUNE_MEDIA_TYPE = GF_4CC( 's', 't', 'i', 'k'), + GF_ISOM_ITUNE_RATING = GF_4CC( 'r', 't', 'n', 'g'), + GF_ISOM_ITUNE_GAPLESS = GF_4CC( 'p', 'g', 'a', 'p' ), + GF_ISOM_ITUNE_COMPOSER = GF_4CC( 0xA9, 'c', 'o', 'm' ), + GF_ISOM_ITUNE_TRACK = GF_4CC( 0xA9, 't', 'r', 'k' ), + GF_ISOM_ITUNE_CONDUCTOR = GF_4CC( 0xA9, 'c', 'o', 'n' ), + + GF_ISOM_ITUNE_ART_DIRECTOR = GF_4CC( 0xA9, 'a', 'r', 'd' ), + GF_ISOM_ITUNE_ARRANGER = GF_4CC( 0xA9, 'a', 'r', 'g' ), + GF_ISOM_ITUNE_LYRICIST = GF_4CC( 0xA9, 'a', 'u', 't' ), + GF_ISOM_ITUNE_COPY_ACK = GF_4CC( 0xA9, 'c', 'a', 'k' ), + GF_ISOM_ITUNE_SONG_DESC = GF_4CC( 0xA9, 'd', 'e', 's' ), + GF_ISOM_ITUNE_DIRECTOR = GF_4CC( 0xA9, 'd', 'i', 'r' ), + GF_ISOM_ITUNE_EQ_PRESET = GF_4CC( 0xA9, 'e', 'q', 'u' ), + GF_ISOM_ITUNE_LINER_NOTES = GF_4CC( 0xA9, 'l', 'n', 't' ), + GF_ISOM_ITUNE_REC_COMPANY = GF_4CC( 0xA9, 'm', 'a', 'k' ), + GF_ISOM_ITUNE_ORIG_ARTIST = GF_4CC( 0xA9, 'o', 'p', 'e' ), + GF_ISOM_ITUNE_PHONO_RIGHTS = GF_4CC( 0xA9, 'p', 'h', 'g' ), + GF_ISOM_ITUNE_PRODUCER = GF_4CC( 0xA9, 'p', 'r', 'd' ), + GF_ISOM_ITUNE_PERFORMER = GF_4CC( 0xA9, 'p', 'r', 'f' ), + GF_ISOM_ITUNE_PUBLISHER = GF_4CC( 0xA9, 'p', 'u', 'b' ), + GF_ISOM_ITUNE_SOUND_ENG = GF_4CC( 0xA9, 's', 'n', 'e' ), + GF_ISOM_ITUNE_SOLOIST = GF_4CC( 0xA9, 's', 'o', 'l' ), + GF_ISOM_ITUNE_CREDITS = GF_4CC( 0xA9, 's', 'r', 'c' ), + GF_ISOM_ITUNE_THANKS = GF_4CC( 0xA9, 't', 'h', 'x' ), + GF_ISOM_ITUNE_ONLINE = GF_4CC( 0xA9, 'u', 'r', 'l' ), + GF_ISOM_ITUNE_EXEC_PRODUCER = GF_4CC( 0xA9, 'x', 'p', 'd' ), + + + GF_ISOM_ITUNE_ITUNES_DATA = GF_4CC( '-', '-', '-', '-' ), + + /* not mapped: +Purchase Account apID UTF-8 string iTunesAccount (read only) +Account Type akID 8-bit integer Identifies the iTunes Store account type iTunesAccountType (read only) + cnID 32-bit integer iTunes Catalog ID, used for combing SD and HD encodes in iTunes cnID +Country Code sfID 32-bit integer Identifies in which iTunes Store a file was bought iTunesCountry (read only) + atID 32-bit integer Use? atID + plID 64-bit integer Use? + geID 32-bit integer Use? geID + ©st3 UTF-8 string Use? + */ + +} GF_ISOiTunesTag; + +/*! gets the given itunes tag info. +\warning 'genre' may be coded by ID, the libisomedia doesn't translate the ID. In such a case, the result data is set to NULL and the data_len to the genre ID + +\param isom_file the target ISO file +\param tag the tag to query +\param data set to the tag data pointer - do not modify +\param data_len set to the size of the tag data +\return error if any (GF_URL_ERROR if no tag is present in the file) +*/ +GF_Err gf_isom_apple_get_tag(GF_ISOFile *isom_file, GF_ISOiTunesTag tag, const u8 **data, u32 *data_len); + +/*! 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 +\return error if any (GF_URL_ERROR if no more tags) +*/ +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 WMA 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 name +\param data set to the tag data pointer - do not modify +\param data_len set to the size of the tag data +\param version set to the WMA tag version +\param data_type set to the WMA data type +\return error if any (GF_URL_ERROR if no more tags) +*/ +GF_Err gf_isom_wma_enum_tag(GF_ISOFile *isom_file, u32 idx, char **out_tag, const u8 **data, u32 *data_len, u32 *version, u32 *data_type); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets the given tag info. + +\param isom_file the target ISO file +\param 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 +\return error if any +*/ +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 WMA tag info (only string tags are supported). + +\param isom_file the target ISO file +\param name name of the tag to set +\param value string value to set +\return error if any +*/ +GF_Err gf_isom_wma_set_tag(GF_ISOFile *isom_file, char *name, char *value); + + +/*! sets compatibility tag on AVC tracks (needed by iPod to play files... hurray for standards) +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_set_ipod_compatible(GF_ISOFile *isom_file, u32 trackNumber); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! @} */ + +/*! +\addtogroup isogrp_grp Track Groups +\ingroup iso_grp + +@{ +*/ + +/*! gets the number of switching groups declared in this track if any +\param isom_file the target ISO file +\param trackNumber the target track +\param alternateGroupID alternate group id of track if speciifed, 0 otherwise +\param nb_groups set to number of switching groups defined for this track +\return error if any +*/ +GF_Err gf_isom_get_track_switch_group_count(GF_ISOFile *isom_file, u32 trackNumber, u32 *alternateGroupID, u32 *nb_groups); + +/*! get the list of criteria (expressed as 4CC IDs, cf 3GPP TS 26.244) +\param isom_file the target ISO file +\param trackNumber the track number +\param group_index the 1-based index of the group to inspect +\param switchGroupID set to the ID of the switch group if any, 0 otherwise (alternate-only group) +\param criteriaListSize set to the number of criteria items in returned list +\return list of criteria (four character codes, cf 3GPP TS 26.244) for the switch group +*/ +const u32 *gf_isom_get_track_switch_parameter(GF_ISOFile *isom_file, u32 trackNumber, u32 group_index, u32 *switchGroupID, u32 *criteriaListSize); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets a new (switch) group for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param trackRefGroup number of a track belonging to the same alternate group. If 0, a new alternate group will be created for this track +\param is_switch_group if set, indicates that a switch group identifier shall be assigned to the created group. Otherwise, the criteria list is associated with the entire alternate group +\param switchGroupID set to the ID of the switch group. On input, specifies the desired switchGroupID to use; if value is 0, next available switchGroupID in file is used. On output, is set to the switchGroupID used. +\param criteriaList list of four character codes used as criteria - cf 3GPP TS 26.244 +\param criteriaListCount number of criterias in list +\return error if any +*/ +GF_Err gf_isom_set_track_switch_parameter(GF_ISOFile *isom_file, u32 trackNumber, u32 trackRefGroup, Bool is_switch_group, u32 *switchGroupID, u32 *criteriaList, u32 criteriaListCount); + +/*! resets track switch group information +\param isom_file the target ISO file +\param trackNumber the target track +\param reset_all_group if GF_TRUE, resets the entire alternate group this track belongs to; otherwise, resets switch group for the track only +\return error if any +*/ +GF_Err gf_isom_reset_track_switch_parameter(GF_ISOFile *isom_file, u32 trackNumber, Bool reset_all_group); + +/*! resets all track switch group information in the entire movie +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_reset_switch_parameters(GF_ISOFile *isom_file); + +/*! sets track in group of a given type and ID +\param isom_file the target ISO file +\param trackNumber the target track +\param track_group_id ID of the track group +\param group_type four character code of the track group +\param do_add if GF_FALSE, track is removed from that group, otherwise it is added +\return error if any +*/ +GF_Err gf_isom_set_track_group(GF_ISOFile *isom_file, u32 trackNumber, u32 track_group_id, u32 group_type, Bool do_add); + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! @} */ + +/*! +\addtogroup isosubs_grp Subsamples +\ingroup iso_grp + +@{ +*/ + +/*! gets serialized subsample info for the sample +The buffer is formatted as N times [(u32)flags(u32)sub_size(u32)codec_param(u8)priority(u8) discardable] +If several subsample info are present, they are gathered by flags + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param osize set to output buffer size +\return the serialized buffer, or NULL oif no associated subsample*/ +u8 *gf_isom_sample_get_subsamples_buffer(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *osize); + +/*! checks if a sample has subsample information +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number. Set to 0 to check for presence of subsample info (will return 1 or 0 in this case) +\param flags the subsample flags to query (may be 0) +\return the number of subsamples in the given sample for the given flags*/ +u32 gf_isom_sample_has_subsamples(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 flags); + +/*! gets subsample information on a sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param flags the subsample flags to query (may be 0) +\param subSampleNumber the 1-based index of the subsample (see \ref gf_isom_sample_has_subsamples) +\param size set to the subsample size +\param priority set to the subsample priority +\param reserved set to the subsample reserved value (may be used by derived specifications) +\param discardable set to GF_TRUE if subsample is discardable +\return error if any*/ +GF_Err gf_isom_sample_get_subsample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 flags, u32 subSampleNumber, u32 *size, u8 *priority, u32 *reserved, Bool *discardable); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! adds subsample information to a given sample. Subsample information shall be added in increasing order of sampleNumbers, insertion of information is not supported + +\note it is possible to add subsample information for samples not yet added to the file +\note specifying 0 as subSampleSize will remove the last subsample information if any +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param flags the subsample flags to query (may be 0) +\param subSampleSize size of the subsample. If 0, this will remove the last subsample information if any +\param priority the subsample priority +\param reserved the subsample reserved value (may be used by derived specifications) +\param discardable indicates if the subsample is discardable +\return error if any*/ +GF_Err gf_isom_add_subsample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); + + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! adds subsample information for the latest sample added to the current track fragment +\param isom_file the target ISO file +\param TrackID the ID of the target track +\param flags the subsample flags to query (may be 0) +\param subSampleSize size of the subsample. If 0, this will remove the last subsample information if any +\param priority the subsample priority +\param reserved the subsample reserved value (may be used by derived specifications) +\param discardable indicates if the subsample is discardable +\return error if any*/ +GF_Err gf_isom_fragment_add_subsample(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); +#endif // GPAC_DISABLE_ISOM_FRAGMENTS + +#endif //GPAC_DISABLE_ISOM_WRITE + +/*! @} */ + +/*! +\addtogroup isosgdp_grp Sample Groups +\ingroup iso_grp + +@{ +*/ + +/*! defined sample groups in GPAC*/ +enum { + GF_ISOM_SAMPLE_GROUP_ROLL = GF_4CC( 'r', 'o', 'l', 'l'), + GF_ISOM_SAMPLE_GROUP_PROL = GF_4CC( 'p', 'r', 'o', 'l'), + GF_ISOM_SAMPLE_GROUP_RAP = GF_4CC( 'r', 'a', 'p', ' ' ), + GF_ISOM_SAMPLE_GROUP_SEIG = GF_4CC( 's', 'e', 'i', 'g' ), + GF_ISOM_SAMPLE_GROUP_OINF = GF_4CC( 'o', 'i', 'n', 'f'), + GF_ISOM_SAMPLE_GROUP_LINF = GF_4CC( 'l', 'i', 'n', 'f'), + GF_ISOM_SAMPLE_GROUP_TRIF = GF_4CC( 't', 'r', 'i', 'f' ), + GF_ISOM_SAMPLE_GROUP_NALM = GF_4CC( 'n', 'a', 'l', 'm'), + GF_ISOM_SAMPLE_GROUP_TELE = GF_4CC( 't', 'e', 'l', 'e'), + GF_ISOM_SAMPLE_GROUP_SAP = GF_4CC( 's', 'a', 'p', ' '), + GF_ISOM_SAMPLE_GROUP_ALST = GF_4CC( 'a', 'l', 's', 't'), + GF_ISOM_SAMPLE_GROUP_RASH = GF_4CC( 'r', 'a', 's', 'h'), + GF_ISOM_SAMPLE_GROUP_AVLL = GF_4CC( 'a', 'v', 'l', 'l'), //p15 + GF_ISOM_SAMPLE_GROUP_AVSS = GF_4CC( 'a', 'v', 's', 's'), //p15 + GF_ISOM_SAMPLE_GROUP_DTRT = GF_4CC( 'd', 't', 'r', 't'), //p15 + GF_ISOM_SAMPLE_GROUP_MVIF = GF_4CC( 'm', 'v', 'i', 'f'), //p15 + GF_ISOM_SAMPLE_GROUP_SCIF = GF_4CC( 's', 'c', 'i', 'f'), //p15 + GF_ISOM_SAMPLE_GROUP_SCNM = GF_4CC( 's', 'c', 'n', 'm'), //p15 + GF_ISOM_SAMPLE_GROUP_STSA = GF_4CC( 's', 't', 's', 'a'), //p15 + GF_ISOM_SAMPLE_GROUP_TSAS = GF_4CC( 't', 's', 'a', 's'), //p15 + GF_ISOM_SAMPLE_GROUP_SYNC = GF_4CC( 's', 'y', 'n', 'c'), //p15 + GF_ISOM_SAMPLE_GROUP_TSCL = GF_4CC( 't', 's', 'c', 'l'), //p15 + 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_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 +}; + +/*! gets 'rap ' and 'roll' group info for the given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param is_rap set to GF_TRUE if sample is a rap (open gop), GF_FALSE otherwise +\param roll_type set to GF_ISOM_SAMPLE_ROLL if sample has roll information, GF_ISOM_SAMPLE_PREROLL if sample has preroll information, GF_ISOM_SAMPLE_ROLL_NONE otherwise +\param roll_distance if sample has roll information, set to roll distance +\return error if any*/ +GF_Err gf_isom_get_sample_rap_roll_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool *is_rap, GF_ISOSampleRollType *roll_type, s32 *roll_distance); + +/*! returns opaque data of sample group +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_group_description_index index of sample group description entry to query +\param grouping_type four character code of grouping type of sample group description to query +\param default_index set to the default index for this sample group description if any, 0 otherwise (no defaults) +\param data set to the internal sample group description data buffer +\param size set to size of the sample group description data buffer +\return GF_TRUE if found, GF_FALSE otherwise*/ +Bool gf_isom_get_sample_group_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_group_description_index, u32 grouping_type, u32 *default_index, const u8 **data, u32 *size); + +/*! gets sample group description index for a given sample and grouping type. +\param isom_file the target ISO file +\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 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 +*/ +GF_Err gf_isom_get_sample_to_group_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_number, u32 grouping_type, u32 grouping_type_parameter, u32 *sampleGroupDescIndex); + +/*! checks if a track as a CENC seig sample group used for key rolling +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if found, GF_FALSE otherwise*/ +Bool gf_isom_has_cenc_sample_group(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets HEVC tiling info +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_group_description_index index of sample group description entry to query +\param default_sample_group_index set to the default index for this sample group description if any, 0 otherwise (no defaults) +\param id set to the tile group ID +\param independent set to independent flag of the tile group (0: not constrained, 1: constrained in layer, 2: all intra slices) +\param full_frame set to GF_TRUE if the tile corresponds to the entire picture +\param x set to the horizontal position in pixels +\param y set to the vertical position in pixels +\param w set to the width in pixels +\param h set to the height in pixels +\return GF_TRUE if found, GF_FALSE otherwise*/ +Bool gf_isom_get_tile_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_group_description_index, u32 *default_sample_group_index, u32 *id, u32 *independent, Bool *full_frame, u32 *x, u32 *y, u32 *w, u32 *h); + + +/*! enumerates custom sample groups (not natively supported by this library) for a given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_number the target sample +\param sgrp_idx the current index. Must be et to 0 on first call, incremented by this call on each success, must not be NULL +\param sgrp_type set to the grouping type, or set to 0 if no more sample group descriptions, must not be NULL +\param sgrp_parameter set to the grouping_type_parameter or 0 if not defined +\param sgrp_data set to the sample group description data +\param sgrp_size set to the sample group description size +\return error if any +*/ +GF_Err gf_isom_enum_sample_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_number, u32 *sgrp_idx, u32 *sgrp_type, u32 *sgrp_parameter, const u8 **sgrp_data, u32 *sgrp_size); + +/*! enumerates custom sample auxiliary data (not natively supported by this library) for a given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_number the target sample +\param sai_idx the current index. Must be et to 0 on first call, incremented by this call on each success, must not be NULL +\param sai_type set to the grouping type, or set to 0 if no more sample group descriptions, must not be NULL +\param sai_parameter set to the grouping_type_parameter or 0 if not defined +\param sai_data set (allocated) to the sample group description data, must not be NULL and must be freed by caller +\param sgrp_size set to the sample group description size, must not be NULL +\return error if any +*/ +GF_Err gf_isom_enum_sample_aux_data(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_number, u32 *sai_idx, u32 *sai_type, u32 *sai_parameter, u8 **sai_data, u32 *sai_size); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +/*! sets rap flag for sample_number - this is used by non-IDR RAPs in AVC (also in USAC) were SYNC flag (stss table) cannot be used +\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_rap_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool is_rap, u32 num_leading_samples); + +/*! 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) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number. If 0, assumes last added sample. If 0xFFFFFFFF, marks all samples as belonging to the roll group +\param roll_type indicates the sample roll recovery type +\param roll_distance indicates the roll distance before a correct decoding is produced +\return error if any +*/ +GF_Err gf_isom_set_sample_roll_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, GF_ISOSampleRollType roll_type, s16 roll_distance); + +/*! sets encryption group for a sample number +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param isEncrypted isEncrypted flag +\param crypt_byte_block crypt block size for pattern encryption, can be 0 +\param skip_byte_block skip block size for pattern encryption, can be 0 +\param key_info multikey descriptor (cf CENC and GF_PROP_PID_CENC_KEY) +\param key_info_size multikey descriptor size +\return error if any +*/ +GF_Err gf_isom_set_sample_cenc_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u8 isEncrypted, u8 crypt_byte_block, u8 skip_byte_block, u8 *key_info, u32 key_info_size); + + +/*! sets a sample using the default CENC parameters in a CENC saig sample group SEIG, creating a sample group description if needed (when seig is already defined) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\return error if any +*/ +GF_Err gf_isom_set_sample_cenc_default_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! adds the given blob as a sample group description entry of the given grouping type. +\param isom_file the target ISO file +\param trackNumber the target track +\param grouping_type the four character code of the grouping type +\param data the payload of the sample group description +\param data_size the size of the payload +\param is_default if GF_TRUE, thie created entry will be marked as the default entry for the sample group description +\param sampleGroupDescriptionIndex is set to the sample group description index (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_add_sample_group_info(GF_ISOFile *isom_file, u32 trackNumber, u32 grouping_type, void *data, u32 data_size, Bool is_default, u32 *sampleGroupDescriptionIndex); + +/*! removes a sample group description of the give grouping type, if found +\param isom_file the target ISO file +\param trackNumber the target track +\param grouping_type the four character code of the grouping type +\return error if any +*/ +GF_Err gf_isom_remove_sample_group(GF_ISOFile *isom_file, u32 trackNumber, u32 grouping_type); + +/*! adds the given blob as a sample group description entry of the given grouping type for the given sample. +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number.Use 0 for setting sample group info to last sample in a track fragment +\param grouping_type the four character code of the grouping type +\param grouping_type_parameter associated grouping type parameter (usually 0) +\param data the payload of the sample group description +\param data_size the size of the payload +\return error if any +*/ +GF_Err gf_isom_set_sample_group_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 grouping_type, u32 grouping_type_parameter, void *data, u32 data_size); + + +/*! adds a sample to the given sample group +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param grouping_type the four character code of the grouping type +\param sampleGroupDescriptionIndex the 1-based index of the sample group description entry +\param grouping_type_parameter the grouping type paramter (see ISO/IEC 14496-12) +\return error if any +*/ +GF_Err gf_isom_add_sample_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 grouping_type, u32 sampleGroupDescriptionIndex, u32 grouping_type_parameter); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! sets sample group descriptions storage in trafs and not in initial movie (Smooth compatibility) +\param isom_file the target ISO file +\return error if any*/ +GF_Err gf_isom_set_sample_group_in_traf(GF_ISOFile *isom_file); +#endif + +#endif // GPAC_DISABLE_ISOM_WRITE + + +/*! @} */ + +#endif /*GPAC_DISABLE_ISOM*/ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_ISOMEDIA_H_*/ + + diff --git a/include/gpac/laser.h b/include/gpac/laser.h new file mode 100644 index 0000000..521b645 --- /dev/null +++ b/include/gpac/laser.h @@ -0,0 +1,169 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2019 + * All rights reserved + * + * This file is part of GPAC / BIFS codec 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_LASER_H_ +#define _GF_LASER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/laser.h> +\brief MPEG-4 LASeR encoding and decoding. +*/ + +/*! +\addtogroup laser_grp MPEG-4 LASER +\ingroup mpeg4sys_grp +\brief MPEG-4 LASeR encoding and decoding + +This section documents the LASeR encoding and decoding functions of the GPAC framework. For scene graph documentation, check scenegraph.h + +@{ + */ + +#include <gpac/nodes_svg.h> + +#ifndef GPAC_DISABLE_LASER + +/*for LASeRConfig*/ +#include <gpac/mpeg4_odf.h> + +/*! a LASeR codec*/ +typedef struct __tag_laser_codec GF_LASeRCodec; + + +/*! creates a new LASeR decoder +\param scenegraph the scenegraph on which the decoder operates +\return a newly allocated LASeR decoder*/ +GF_LASeRCodec *gf_laser_decoder_new(GF_SceneGraph *scenegraph); +/*! destroys a LASeR decoder +\param codec the target LASeR decoder +*/ +void gf_laser_decoder_del(GF_LASeRCodec *codec); + +/*! sets the scene time. Scene time is the real clock of the bifs stream in secs +\param codec the target LASeR decoder +\param GetSceneTime the scene clock query callback function +\param st_cbk opaque data for the callback function +*/ +void gf_laser_decoder_set_clock(GF_LASeRCodec *codec, Double (*GetSceneTime)(void *st_cbk), void *st_cbk ); + +/*! sets up a stream +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\param DecoderSpecificInfo the decoder configuration data of the LASeR stream +\param DecoderSpecificInfoLength the size in bytes of the decoder configuration data +\return error if any +*/ +GF_Err gf_laser_decoder_configure_stream(GF_LASeRCodec *codec, u16 ESID, u8 *DecoderSpecificInfo, u32 DecoderSpecificInfoLength); +/*! removes a stream +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\return error if any +*/ +GF_Err gf_laser_decoder_remove_stream(GF_LASeRCodec *codec, u16 ESID); + +/*! decodes a LASeR AU and applies it to the graph (non-memory mode only) +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\param data the access unit payload +\param data_length the access unit size in bytes +\return error if any +*/ +GF_Err gf_laser_decode_au(GF_LASeRCodec *codec, u16 ESID, const u8 *data, u32 data_length); + +/*! decodes a LASeR AU in memory - fills the command list with the content of the AU - cf scenegraph_vrml.h for commands usage +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\param data the access unit payload +\param data_length the access unit size in bytes +\param com_list a list to be filled with decoded commands +\return error if any +*/ +GF_Err gf_laser_decode_command_list(GF_LASeRCodec *codec, u16 ESID, u8 *data, u32 data_length, GF_List *com_list); + +/*! checks if a LASeR decoder has associated conditionnals + \param codec the target LASeR decoder +\return GF_TRUE if conditionnals are attached to this decoder +*/ +Bool gf_laser_decode_has_conditionnals(GF_LASeRCodec *codec); + +/*! creates a new LASeR encoder +\param scenegraph the scenegraph on which the encoder operates +\return a newly allocated LASeR encoder*/ +GF_LASeRCodec *gf_laser_encoder_new(GF_SceneGraph *scenegraph); +/*! destroys a LASeR encoder +\param codec the target LASeR encoder +*/ +void gf_laser_encoder_del(GF_LASeRCodec *codec); +/*! sets up a destination stream +\param codec the target LASeR encoder +\param ESID the ID of the stream +\param cfg the LASeR configuration descriptor +\return error if any +*/ +GF_Err gf_laser_encoder_new_stream(GF_LASeRCodec *codec, u16 ESID, GF_LASERConfig *cfg); + +/*! encodes a list of commands for the given stream in the output buffer - data is dynamically allocated for output +\param codec the target LASeR encoder +\param ESID the ID of the stream +\param command_list a list of commands to encode +\param reset_encoding_context if GF_TRUE, resets all color and font tables of LASeR streams (used for clean RAPs) +\param out_data set to an allocated buffer containing the encoded AU - shall be destroyed by caller +\param out_data_length set to the size of the encoded AU +\return error if any +*/ +GF_Err gf_laser_encode_au(GF_LASeRCodec *codec, u16 ESID, GF_List *command_list, Bool reset_encoding_context, u8 **out_data, u32 *out_data_length); +/*! gets a stream encoded config description +\param codec the target LASeR encoder +\param ESID the ID of the stream +\param out_data set to an allocated buffer containing the encoded configuration - shall be destroyed by caller +\param out_data_length set to the size of the encoded configuration +\return error if any +*/ +GF_Err gf_laser_encoder_get_config(GF_LASeRCodec *codec, u16 ESID, u8 **out_data, u32 *out_data_length); + +/*! encodes current graph as a scene replace +\param codec the target LASeR encoder +\param out_data set to an allocated buffer containing the encoded scene replace AU - shall be destroyed by caller +\param out_data_length set to the size of the encoded AU +\return error if any +*/ +GF_Err gf_laser_encoder_get_rap(GF_LASeRCodec *codec, u8 **out_data, u32 *out_data_length); + + +#endif /*GPAC_DISABLE_LASER*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/include/gpac/list.h b/include/gpac/list.h new file mode 100644 index 0000000..db22d9a --- /dev/null +++ b/include/gpac/list.h @@ -0,0 +1,235 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_LIST_H_ +#define _GF_LIST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/list.h> +\brief Generic list of objects. + */ + +/*! +\addtogroup list_grp +\brief List object + +This section documents the list object of the GPAC framework. + +@{ + */ + +#include <gpac/tools.h> + +/*! list object*/ +typedef struct _tag_array GF_List; + +/*! +\brief list constructor + +Constructs a new list object +\return new list object + */ +GF_List *gf_list_new(); + +/*! +\brief list destructor + +Destructs a list object +\param ptr list object to destruct +\note It is the caller responsability to destroy the content of the list if needed + */ +void gf_list_del(GF_List *ptr); + +/*! +\brief get count + +Returns number of items in the list +\param ptr target list object +\return number of items in the list + */ +u32 gf_list_count(const GF_List *ptr); + +/*! +\brief add item + +Adds an item at the end of the list +\param ptr target list object +\param item item to add +\return error if any + */ +GF_Err gf_list_add(GF_List *ptr, void* item); + +/*! +\brief inserts item + +Insert an item in the list +\param ptr target list object +\param item item to add +\param position insertion position. It is expressed between 0 and gf_list_count-1, and any bigger value is equivalent to gf_list_add +\return error if any + */ +GF_Err gf_list_insert(GF_List *ptr, void *item, u32 position); + +/*! +\brief removes item + +Removes an item from the list given its position +\param ptr target list object +\param position position of the item to remove. It is expressed between 0 and gf_list_count-1. +\return error if any +\note It is the caller responsability to destroy the content of the list if needed + */ +GF_Err gf_list_rem(GF_List *ptr, u32 position); + +/*! +\brief gets item + +Gets an item from the list given its position +\param ptr target list object +\param position position of the item to get. It is expressed between 0 and gf_list_count-1. +\return the item or NULL if not found + */ +void *gf_list_get(GF_List *ptr, u32 position); + +/*! +\brief finds item + +Finds an item in the list +\param ptr target list object. +\param item the item to find. +\return 0-based item position in the list, or -1 if the item could not be found. + */ +s32 gf_list_find(GF_List *ptr, void *item); + +/*! +\brief deletes item + +Deletes an item from the list +\param ptr target list object. +\param item the item to find. +\return 0-based item position in the list before removal, or -1 if the item could not be found. + */ +s32 gf_list_del_item(GF_List *ptr, void *item); + +/*! +\brief resets list + +Resets the content of the list +\param ptr target list object. +\note It is the caller responsability to destroy the content of the list if needed + */ +void gf_list_reset(GF_List *ptr); + +/*! +\brief gets last item + +Gets last item o fthe list +\param ptr target list object +\return the last item + */ +void *gf_list_last(GF_List *ptr); + +/*! +\brief removes last item + +Removes the last item of the list +\param ptr target list object +\return error if any +\note It is the caller responsability to destroy the content of the list if needed + */ +GF_Err gf_list_rem_last(GF_List *ptr); + + +/*! +\brief list enumerator + +Retrieves given list item and increment current position +\param ptr target list object +\param pos target item position. The position is automatically incremented regardless of the return value +\note A typical enumeration will start with a value of 0 until NULL is returned. +\return the current item for the given index +*/ +void *gf_list_enum(GF_List *ptr, u32 *pos); + +/*! +\brief list swap + +Swaps content of two lists +\param l1 first list object +\param l2 second list object +\return error if any + */ +GF_Err gf_list_swap(GF_List *l1, GF_List *l2); + +/*! +\brief list transfer + +Transfer content between lists +\param dst destination list object +\param src source list object +\return error if any + */ +GF_Err gf_list_transfer(GF_List *dst, GF_List *src); + +/*! +\brief clone list + +Returns a new list as an exact copy of the given list +\param ptr the list to clone +\return the cloned list + */ +GF_List* gf_list_clone(GF_List *ptr); + +/*! +\brief Pop the first element in the list + +Removes the first element in the list container, effectively reducing its size by one and returns the popped element. +\param ptr the list to pop +\return the popped element + */ +void* gf_list_pop_front(GF_List *ptr); + +/*! +\brief Pop the last element in the list + +Removes the last element in the list container, effectively reducing the container size by one and return the popped element. +\param ptr the list to pop +\return the popped element + */ +void* gf_list_pop_back(GF_List *ptr); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_LIST_H_*/ + diff --git a/include/gpac/main.h b/include/gpac/main.h new file mode 100644 index 0000000..e85afe8 --- /dev/null +++ b/include/gpac/main.h @@ -0,0 +1,268 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC + * + * 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_MAIN_H_ +#define _GF_MAIN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/main.h> +\brief main() macro for win32. + */ + +/*! +\addtogroup sysmain_grp +@{ + +Thiis section decribes functions useful when developing an application using libgpac such as: +- quick UTF8 conversion of arguments for main() on windows +- setting, checking and printing libgpac arguments as given from command line +*/ + +#include <gpac/setup.h> +#include <gpac/utf.h> + + + + +#if defined(WIN32) && !defined(NO_WMAIN) +/*! macro for main() with wide to char conversion on windows platforms*/ +#define GF_MAIN_FUNC(__fun) \ +int wmain( int argc, wchar_t** wargv )\ +{\ + int i;\ + int res;\ + u32 len;\ + u32 res_len;\ + char **argv;\ + argv = (char **)malloc(argc*sizeof(wchar_t *));\ + for (i = 0; i < argc; i++) {\ + wchar_t *src_str = wargv[i];\ + len = UTF8_MAX_BYTES_PER_CHAR*gf_utf8_wcslen(wargv[i]);\ + argv[i] = (char *)malloc(len + 1);\ + res_len = gf_utf8_wcstombs(argv[i], len, (const unsigned short **) &src_str);\ + if (res_len != GF_UTF8_FAIL)\ + argv[i][res_len] = 0;\ + if (res_len > len) {\ + fprintf(stderr, "Length allocated for conversion of wide char to UTF-8 not sufficient\n");\ + return -1;\ + }\ + }\ + res = __fun(argc, argv);\ + for (i = 0; i < argc; i++) {\ + free(argv[i]);\ + }\ + free(argv);\ + return res;\ +} + +#else + +/*! macro for main() with wide to char conversion on windows platforms*/ +#define GF_MAIN_FUNC(__fun) \ +int main(int argc, char **argv) {\ + return __fun( argc, argv ); \ +} + +#endif //win32 + +/*! macro defining fields of a libgpac arg (not a filter arg)*/ +#define GF_GPAC_ARG_BASE \ + /*! name of arg*/ \ + const char *name; \ + /*! alternate name of arg*/ \ + const char *altname; \ + /*! description of arg*/ \ + const char *description; \ + /*! default value of arg*/ \ + const char *val; \ + /*! possible value of arg*/ \ + const char *values; \ + /*! argument type for UI construction - note that argument values are not parsed and shall be set as strings*/ \ + u16 type; \ + /*! argument flags*/ \ + u16 flags; \ + + +/*! structure holding a libgpac arg (not a filter arg)*/ +typedef struct +{ + /*! base structure, shall always be placed first if you extend args in your application*/ + GF_GPAC_ARG_BASE +} GF_GPACArg; + +//these 3 values match argument hints of filters +/*! argument is of advanced type*/ +#define GF_ARG_HINT_ADVANCED (1<<1) +/*! argument is of expert type*/ +#define GF_ARG_HINT_EXPERT (1<<2) +/*! argument should not be presented in UIs*/ +#define GF_ARG_HINT_HIDE (1<<3) +/*! argument is highly experimental*/ +#define GF_ARG_HINT_EXPERIMENTAL (1<<4) + +/*! argument applies to the libgpac core subsystem*/ +#define GF_ARG_SUBSYS_CORE (1<<5) +/*! argument applies to the log subsystem*/ +#define GF_ARG_SUBSYS_LOG (1<<6) +/*! argument applies to the filter subsystem*/ +#define GF_ARG_SUBSYS_FILTERS (1<<7) +/*! argument applies to the HTTP subsystem*/ +#define GF_ARG_SUBSYS_HTTP (1<<8) +/*! argument applies to the video subsystem*/ +#define GF_ARG_SUBSYS_VIDEO (1<<9) +/*! argument applies to the audio subsystem*/ +#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*/ +#define GF_ARG_SUBSYS_RMT (1<<12) +/*! argument belongs to hack tools, usually never used*/ +#define GF_ARG_SUBSYS_HACKS (1<<13) + +/*! argument is a boolean*/ +#define GF_ARG_BOOL 0 +/*! argument is a 32 bit integer*/ +#define GF_ARG_INT 1 +/*! argument is a double*/ +#define GF_ARG_DOUBLE 2 +/*! argument is a string*/ +#define GF_ARG_STRING 3 +/*! argument is a camma-separated list of strings*/ +#define GF_ARG_STRINGS 4 +/*! argument is a custom arg, default value contains the syntax of the argument*/ +#define GF_ARG_4CC 5 +/*! argument is a custom arg, default value contains the syntax of the argument*/ +#define GF_ARG_4CCS 6 +/*! argument is a custom arg, default value contains the syntax of the argument*/ +#define GF_ARG_CUSTOM 7 + +/*! macros for defining a GF_GPACArg argument*/ +#define GF_DEF_ARG(_a, _b, _c, _d, _e, _f, _g) {_a, _b, _c, _d, _e, _f, _g} + +/*! gets the options defined for libgpac +\return array of options*/ +const GF_GPACArg *gf_sys_get_options(); + +/*! check if the given option is a libgpac argument +\param arg_name name of the argument +\return 0 if not a libgpac core option, 1 if option not consuming an argument, 2 if option consuming an argument*/ +u32 gf_sys_is_gpac_arg(const char *arg_name); + +/*! parses config string and update config accordingly +\param opt_string section/key/val formatted as Section:Key (discard key), Section:Key=null (discard key), Section:Key=Val (set key) or Section=null (discard section) +\return GF_TRUE if update is OK, GF_FALSE otherwise*/ +Bool gf_sys_set_cfg_option(const char *opt_string); + +/*! argument dump hint options */ +typedef enum +{ + /*! only dumps simple arguments*/ + GF_ARGMODE_BASE=0, + /*! only dumps advanced arguments*/ + GF_ARGMODE_ADVANCED, + /*! only dumps expert arguments*/ + GF_ARGMODE_EXPERT, + /*! dumps all arguments*/ + GF_ARGMODE_ALL +} GF_SysArgMode; + +/*! flags for help formatting*/ +typedef enum +{ + /*! first word in format string should be highlighted */ + GF_PRINTARG_HIGHLIGHT_FIRST = 1, + /*! prints <br/> instead of new line*/ + GF_PRINTARG_NL_TO_BR = 1<<1, + /*! first word in format string is an option descripttor*/ + GF_PRINTARG_OPT_DESC = 1<<2, + /*! the format string is an application string, not a gpac core one*/ + GF_PRINTARG_IS_APP = 1<<3, + /*! insert an extra '-' at the beginning*/ + GF_PRINTARG_ADD_DASH = 1<<4, + /*! do not insert '-' before arg name*/ + GF_PRINTARG_NO_DASH = 1<<5, + /*! insert '-: before arg name*/ + GF_PRINTARG_COLON = 1<<6, + /*! the generation is for markdown*/ + GF_PRINTARG_MD = 1<<16, + /*! the generation is for man pages*/ + GF_PRINTARG_MAN = 1<<17, + /*! XML < and > should be escaped (for markdown generation only) */ + GF_PRINTARG_ESCAPE_XML = 1<<18, + /*! '|' should be escaped (for markdown generation only) */ + GF_PRINTARG_ESCAPE_PIPE = 1<<19, +} GF_SysPrintArgFlags; + + +/*! prints a argument +\param helpout destination file - if NULL, uses stderr +\param flags dump flags +\param arg argument to print +\param arg_subsystem name of subsystem of argument (core, gpac, filter name) for localization) +*/ +void gf_sys_print_arg(FILE *helpout, GF_SysPrintArgFlags flags, const GF_GPACArg *arg, const char *arg_subsystem); + +/*! prints libgpac help for builton core options to stderr +\param helpout destination file - if NULL, uses stderr +\param flags dump flags +\param mode filtering mode based on argument type +\param subsystem_flags filtering mode based on argument subsytem flags +*/ +void gf_sys_print_core_help(FILE *helpout, GF_SysPrintArgFlags flags, GF_SysArgMode mode, u32 subsystem_flags); + +/*! gets localized version of string identified by module name and identifier. +\param sec_name name of the module to query, such as "gpac", "core", or filter name +\param str_name name of string to query, such as acore/app option or a filter argument +\param def_val default value to return if no locaization exists +\return localized version of the string +*/ +const char *gf_sys_localized(const char *sec_name, const char *str_name, const char *def_val); + +/*! formats help to output +\param output output file to dump to +\param flags help formatting flags +\param fmt arguments of the format +*/ +void gf_sys_format_help(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...); + +/*! very basic word match, check the number of source characters in order in dest +\param orig word to test +\param dst word to compare to +\return GF_TRUE if words are similar, GF_FALSE otherwise +*/ +Bool gf_sys_word_match(const char *orig, const char *dst); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif //_GF_MAIN_H_ + diff --git a/include/gpac/maths.h b/include/gpac/maths.h new file mode 100644 index 0000000..8160d6a --- /dev/null +++ b/include/gpac/maths.h @@ -0,0 +1,1175 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_MATH_H_ +#define _GF_MATH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/maths.h> +\brief Mathematics and Trigonometric. + */ + +#include <gpac/setup.h> + +#include <math.h> + + +/*! +\addtogroup math_grp +\brief Mathematics and Trigonometric + +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 + 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 + +@{ + */ + + +/***************************************************************************************** + FIXED-POINT SUPPORT - HARDCODED FOR 16.16 representation + the software rasterizer also use a 16.16 representation even in non-fixed version +******************************************************************************************/ + +#ifdef GPAC_FIXED_POINT + +/*! +Fixed 16.16 number +\hideinitializer + \note This documentation has been generated for a fixed-point version of the GPAC framework. + */ +typedef s32 Fixed; +#define FIX_ONE 0x10000L +#define INT2FIX(v) ((Fixed)( ((s32) (v) ) << 16)) +#define FLT2FIX(v) ((Fixed) ((v) * FIX_ONE)) +#define FIX2INT(v) ((s32)(((v)+((FIX_ONE>>1)))>>16)) +#define FIX2FLT(v) ((Float)( ((Float)(v)) / ((Float) FIX_ONE))) +#define FIX_EPSILON 2 +#define FIX_MAX 0x7FFFFFFF +#define FIX_MIN -FIX_MAX +#define GF_PI2 102944 +#define GF_PI 205887 +#define GF_2PI 411774 + +/*!\return 1/a, expressed as fixed number*/ +Fixed gf_invfix(Fixed a); +/*!\return a*b, expressed as fixed number*/ +Fixed gf_mulfix(Fixed a, Fixed b); +/*!\return a*b/c, expressed as fixed number*/ +Fixed gf_muldiv(Fixed a, Fixed b, Fixed c); +/*!\return a/b, expressed as fixed number*/ +Fixed gf_divfix(Fixed a, Fixed b); +/*!\return sqrt(a), expressed as fixed number*/ +Fixed gf_sqrt(Fixed x); +/*!\return ceil(a), expressed as fixed number*/ +Fixed gf_ceil(Fixed a); +/*!\return floor(a), expressed as fixed number*/ +Fixed gf_floor(Fixed a); +/*!\return cos(a), expressed as fixed number*/ +Fixed gf_cos(Fixed angle); +/*!\return sin(a), expressed as fixed number*/ +Fixed gf_sin(Fixed angle); +/*!\return tan(a), expressed as fixed number*/ +Fixed gf_tan(Fixed angle); +/*!\return acos(a), expressed as fixed number*/ +Fixed gf_acos(Fixed angle); +/*!\return asin(a), expressed as fixed number*/ +Fixed gf_asin(Fixed angle); +/*!\return atan(y, x), expressed as fixed number*/ +Fixed gf_atan2(Fixed y, Fixed x); + +#else + + +/*!Fixed is 32bit float number + \note This documentation has been generated for a float version of the GPAC framework. +*/ +typedef Float Fixed; +#define FIX_ONE 1.0f +#define INT2FIX(v) ((Float) (v)) +#define FLT2FIX(v) ((Float) (v)) +#define FIX2INT(v) ((s32)(v)) +#define FIX2FLT(v) ((Float) (v)) +#define FIX_EPSILON GF_EPSILON_FLOAT +#define FIX_MAX GF_MAX_FLOAT +#define FIX_MIN -GF_MAX_FLOAT +#define GF_PI2 1.5707963267949f +#define GF_PI 3.1415926535898f +#define GF_2PI 6.2831853071796f + +/*!\hideinitializer 1/_a, expressed as fixed number*/ +#define gf_invfix(_a) (FIX_ONE/(_a)) +/*!\hideinitializer _a*_b, expressed as fixed number*/ +#define gf_mulfix(_a, _b) ((_a)*(_b)) +/*!\hideinitializer _a*_b/_c, expressed as fixed number*/ +#define gf_muldiv(_a, _b, _c) (((_c != 0)) ? (_a)*(_b)/(_c) : GF_MAX_FLOAT) +/*!\hideinitializer _a/_b, expressed as fixed number*/ +#define gf_divfix(_a, _b) (((_b != 0)) ? (_a)/(_b) : GF_MAX_FLOAT) +/*!\hideinitializer sqrt(_a), expressed as fixed number*/ +#define gf_sqrt(_a) ((Float) sqrt(_a)) +/*!\hideinitializer ceil(_a), expressed as fixed number*/ +#define gf_ceil(_a) ((Float) ceil(_a)) +/*!\hideinitializer floor(_a), expressed as fixed number*/ +#define gf_floor(_a) ((Float) floor(_a)) +/*!\hideinitializer cos(_a), expressed as fixed number*/ +#define gf_cos(_a) ((Float) cos(_a)) +/*!\hideinitializer sin(_a), expressed as fixed number*/ +#define gf_sin(_a) ((Float) sin(_a)) +/*!\hideinitializer tan(_a), expressed as fixed number*/ +#define gf_tan(_a) ((Float) tan(_a)) +/*!\hideinitializer atan2(_y,_x), expressed as fixed number*/ +#define gf_atan2(_y, _x) ((Float) atan2(_y, _x)) +/*!\hideinitializer acos(_a), expressed as fixed number*/ +#define gf_acos(_a) ((Float) acos(_a)) +/*!\hideinitializer asin(_a), expressed as fixed number*/ +#define gf_asin(_a) ((Float) asin(_a)) + +#endif + +/*!\def FIX_ONE + \hideinitializer + Fixed unit value +*/ +/*!\def INT2FIX(v) + \hideinitializer + Conversion from integer to fixed +*/ +/*!\def FLT2FIX(v) + \hideinitializer + Conversion from float to fixed +*/ +/*!\def FIX2INT(v) + \hideinitializer + Conversion from fixed to integer +*/ +/*!\def FIX2FLT(v) + \hideinitializer + Conversion from fixed to float +*/ +/*!\def FIX_EPSILON + \hideinitializer + Epsilon Fixed (positive value closest to 0) +*/ +/*!\def FIX_MAX + \hideinitializer + Maximum Fixed (maximum representable fixed value) +*/ +/*!\def FIX_MIN + \hideinitializer + Minimum Fixed (minimum representable fixed value) +*/ +/*!\def GF_PI2 + \hideinitializer + PI/2 expressed as Fixed +*/ +/*!\def GF_PI + \hideinitializer + PI expressed as Fixed +*/ +/*!\def GF_2PI + \hideinitializer + 2*PI expressed as Fixed +*/ + +/*! compute the difference between two angles, with a result in [-PI, PI] +\param a first angle +\param b first angle +\return angle difference +*/ +Fixed gf_angle_diff(Fixed a, Fixed b); + +/*! +\brief Field bit-size + +Gets the number of bits needed to represent the value. +\param MaxVal Maximum value to be represented. +\return number of bits required to represent the value. + */ +u32 gf_get_bit_size(u32 MaxVal); + +/*! +\brief Get power of 2 + +Gets the closest power of 2 greater or equal to the value. +\param val value to be used. +\return requested power of 2. + */ +u32 gf_get_next_pow2(u32 val); + +/*! +\addtogroup math2d_grp Math 2d +\ingroup math_grp +\brief 2D Mathematics + +This section documents mathematic tools for 2D geometry and color matrices operations + +@{ + */ + +/*!\brief 2D point + * + *The 2D point object is used in all the GPAC framework for both point and vector representation. +*/ +typedef struct __vec2f +{ + Fixed x; + Fixed y; +} GF_Point2D; +/*! +\brief get 2D vector length + +Gets the length of a 2D vector +\param vec the target vector +\return length of the vector + */ +Fixed gf_v2d_len(GF_Point2D *vec); +/*! +\brief get distance between 2 points + +Gets the distance between the 2 points +\param a first point +\param b second point +\return distance + */ +Fixed gf_v2d_distance(GF_Point2D *a, GF_Point2D *b); +/*! +\brief 2D vector from polar coordinates + +Constructs a 2D vector from its polar coordinates +\param length the length of the vector +\param angle the angle of the vector in radians +\return the 2D vector + */ +GF_Point2D gf_v2d_from_polar(Fixed length, Fixed angle); + +/*! +\brief rectangle 2D + +The 2D rectangle used in the GPAC project. + */ +typedef struct +{ + /*!the left coordinate of the rectangle*/ + Fixed x; + /*!the top coordinate of the rectangle, regardless of the canvas orientation. In other words, y is always the + greatest coordinate value, even if the rectangle is presented bottom-up. This insures proper rectangles testing*/ + Fixed y; + /*!the width of the rectangle. Width must be greater than or equal to 0*/ + Fixed width; + /*!the height of the rectangle. Height must be greater than or equal to 0*/ + Fixed height; +} GF_Rect; + +/*! + \brief rectangle union + +Gets the union of two rectangles. +\param rc1 first rectangle of the union. Upon return, this rectangle will contain the result of the union +\param rc2 second rectangle of the union +*/ +void gf_rect_union(GF_Rect *rc1, GF_Rect *rc2); +/*! + \brief centers a rectangle + +Builds a rectangle centered on the origin +\param w width of the rectangle +\param h height of the rectangle +\return centered rectangle object +*/ +GF_Rect gf_rect_center(Fixed w, Fixed h); +/*! + \brief rectangle overlap test + +Tests if two rectangles overlap. +\param rc1 first rectangle to test +\param rc2 second rectangle to test +\return 1 if rectangles overlap, 0 otherwise +*/ +Bool gf_rect_overlaps(GF_Rect rc1, GF_Rect rc2); +/*! +\brief rectangle identity test + +Tests if two rectangles are identical. +\param rc1 first rectangle to test +\param rc2 second rectangle to test +\return 1 if rectangles are identical, 0 otherwise +*/ +Bool gf_rect_equal(GF_Rect *rc1, GF_Rect *rc2); + +/*! +\brief rectangle intersection + +Intersects two rectangle. +\param rc1 rectangle to use, updated to intersection result +\param rc2 second rectangle to use +*/ +void gf_rect_intersect(GF_Rect *rc1, GF_Rect *rc2); + +/*! +\brief pixel-aligned rectangle + +Pixel-aligned rectangle used in the GPAC framework. This is usually needed for 2D drawing algorithms. + */ +typedef struct +{ + /*!the left coordinate of the rectangle*/ + s32 x; + /*!the top coordinate of the rectangle, regardless of the canvas orientation. In other words, y is always the + greatest coordinate value, even if the rectangle is presented bottom-up. This insures proper rectangles operations*/ + s32 y; + /*!the width of the rectangle. Width must be greater than or equal to 0*/ + s32 width; + /*!the height of the rectangle. Height must be greater than or equal to 0*/ + s32 height; +} GF_IRect; +/*! +\brief gets the pixelized version of a rectangle + +Gets the smallest pixel-aligned rectangle completely containing a rectangle +\param r the rectangle to transform +\return the pixel-aligned transformed rectangle +*/ +GF_IRect gf_rect_pixelize(GF_Rect *r); + + +/*! +\brief 2D matrix + +The 2D affine matrix object usied in GPAC. The transformation of P(x,y) in P'(X, Y) is: + \code + X = m[0]*x + m[1]*y + m[2]; + Y = m[3]*x + m[4]*y + m[5]; + \endcode +*/ +typedef struct +{ + Fixed m[6]; +} GF_Matrix2D; + +/*!\brief matrix initialization +\hideinitializer + +Inits the matrix to the identity matrix +*/ +#define gf_mx2d_init(_obj) { memset((_obj).m, 0, sizeof(Fixed)*6); (_obj).m[0] = (_obj).m[4] = FIX_ONE; } +/*!\brief matrix copy +\hideinitializer + +Copies the matrix _from to the matrix _obj +*/ +#define gf_mx2d_copy(_obj, from) memcpy((_obj).m, (from).m, sizeof(Fixed)*6) +/*!\brief matrix identity testing +\hideinitializer + +This macro evaluates to 1 if the matrix _obj is the identity matrix, 0 otherwise +*/ +#define gf_mx2d_is_identity(_obj) ((!(_obj).m[1] && !(_obj).m[2] && !(_obj).m[3] && !(_obj).m[5] && ((_obj).m[0]==FIX_ONE) && ((_obj).m[4]==FIX_ONE)) ? 1 : 0) + +/*!\brief 2D matrix multiplication + +Multiplies two 2D matrices from*_this +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param from transformation matrix to add +*/ +void gf_mx2d_add_matrix(GF_Matrix2D *_this, GF_Matrix2D *from); + +/*!\brief 2D matrix pre-multiplication + +Multiplies two 2D matrices _this*from +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param from transformation matrix to add +*/ +void gf_mx2d_pre_multiply(GF_Matrix2D *_this, GF_Matrix2D *from); + +/*!\brief matrix translating + +Translates a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param cx horizontal translation +\param cy vertical translation +*/ +void gf_mx2d_add_translation(GF_Matrix2D *_this, Fixed cx, Fixed cy); +/*!\brief matrix rotating + +Rotates a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param cx horizontal rotation center coordinate +\param cy vertical rotation center coordinate +\param angle rotation angle in radians +*/ +void gf_mx2d_add_rotation(GF_Matrix2D *_this, Fixed cx, Fixed cy, Fixed angle); +/*!\brief matrix scaling + +Scales a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param scale_x horizontal scaling factor +\param scale_y vertical scaling factor +*/ +void gf_mx2d_add_scale(GF_Matrix2D *_this, Fixed scale_x, Fixed scale_y); +/*!\brief matrix uncentered scaling + +Scales a 2D matrix with a non-centered scale +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param scale_x horizontal scaling factor +\param scale_y vertical scaling factor +\param cx horizontal scaling center coordinate +\param cy vertical scaling center coordinate +\param angle scale orienttion angle in radians +*/ +void gf_mx2d_add_scale_at(GF_Matrix2D *_this, Fixed scale_x, Fixed scale_y, Fixed cx, Fixed cy, Fixed angle); +/*!\brief matrix skewing + +Skews a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param skew_x horizontal skew factor +\param skew_y vertical skew factor +*/ +void gf_mx2d_add_skew(GF_Matrix2D *_this, Fixed skew_x, Fixed skew_y); +/*!\brief matrix horizontal skewing + +Skews a 2D matrix horizontally by a given angle +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param angle horizontal skew angle in radians +*/ +void gf_mx2d_add_skew_x(GF_Matrix2D *_this, Fixed angle); +/*!\brief matrix vertical skewing + +Skews a 2D matrix vertically by a given angle +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param angle vertical skew angle in radians +*/ +void gf_mx2d_add_skew_y(GF_Matrix2D *_this, Fixed angle); +/*!\brief matrix inversing + +Inverses a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +*/ +void gf_mx2d_inverse(GF_Matrix2D *_this); +/*!\brief matrix coordinate transformation + +Applies a 2D matrix transformation to coordinates +\param _this transformation matrix +\param x pointer to horizontal coordinate. Once the function is called, x contains the transformed horizontal coordinate +\param y pointer to vertical coordinate. Once the function is called, y contains the transformed vertical coordinate +*/ +void gf_mx2d_apply_coords(GF_Matrix2D *_this, Fixed *x, Fixed *y); +/*!\brief matrix point transformation + +Applies a 2D matrix transformation to a 2D point +\param _this transformation matrix +\param pt pointer to 2D point. Once the function is called, pt contains the transformed point +*/ +void gf_mx2d_apply_point(GF_Matrix2D *_this, GF_Point2D *pt); +/*!\brief matrix rectangle transformation + +Applies a 2D matrix transformation to a rectangle, giving the enclosing rectangle of the transformed one +\param _this transformation matrix +\param rc pointer to rectangle. Once the function is called, rc contains the transformed rectangle +*/ +void gf_mx2d_apply_rect(GF_Matrix2D *_this, GF_Rect *rc); + +/*!\brief matrix decomposition + +Decomposes a 2D matrix M as M=Scale x Rotation x Translation if possible +\param _this transformation matrix +\param scale resulting scale part +\param rotate resulting rotation part +\param translate resulting translation part +\return 0 if matrix cannot be decomposed, 1 otherwise +*/ +Bool gf_mx2d_decompose(GF_Matrix2D *_this, GF_Point2D *scale, Fixed *rotate, GF_Point2D *translate); + +/*! @} */ + + +/*! +\addtogroup math3d_grp Math 3d +\ingroup math_grp +\brief 3D Mathematics + +This section documents mathematic tools for 3D geometry operations + +@{ + */ + +/*!\brief 3D point or vector + +The 3D point object is used in all the GPAC framework for both point and vector representation. +*/ +typedef struct __vec3f +{ + Fixed x; + Fixed y; + Fixed z; +} GF_Vec; + +/*base vector operations are MACROs for faster access*/ +/*!\hideinitializer macro evaluating to 1 if vectors are equal, 0 otherwise*/ +#define gf_vec_equal(v1, v2) (((v1).x == (v2).x) && ((v1).y == (v2).y) && ((v1).z == (v2).z)) +/*!\hideinitializer macro reversing a vector v = v*/ +#define gf_vec_rev(v) { (v).x = -(v).x; (v).y = -(v).y; (v).z = -(v).z; } +/*!\hideinitializer macro performing the minus operation res = v1 - v2*/ +#define gf_vec_diff(res, v1, v2) { (res).x = (v1).x - (v2).x; (res).y = (v1).y - (v2).y; (res).z = (v1).z - (v2).z; } +/*!\hideinitializer macro performing the add operation res = v1 + v2*/ +#define gf_vec_add(res, v1, v2) { (res).x = (v1).x + (v2).x; (res).y = (v1).y + (v2).y; (res).z = (v1).z + (v2).z; } + +/*! +\brief get 3D vector length + +Gets the length of a 3D vector +\param v the target vector +\return length of the vector + */ +Fixed gf_vec_len(GF_Vec v); + +/*! +\brief get 3D vector length + +Gets the length of a 3D vector +\param v the target vector +\return length of the vector + */ +Fixed gf_vec_len_p(GF_Vec *v); + +/*! +\brief get 3D vector square length + +Gets the square length of a 3D vector +\param v the target vector +\return square length of the vector + */ +Fixed gf_vec_lensq(GF_Vec v); + +/*! +\brief get 3D vector square length + +Gets the square length of a 3D vector +\param v the target vector +\return square length of the vector + */ +Fixed gf_vec_lensq_p(GF_Vec *v); +/*! +\brief get 3D vector dot product + +Gets the dot product of two vectors +\param v1 first vector +\param v2 second vector +\return dot product of the vectors + */ +Fixed gf_vec_dot(GF_Vec v1, GF_Vec v2); +/*! +\brief get 3D vector dot product + +Gets the dot product of two vectors +\param v1 first vector +\param v2 second vector +\return dot product of the vectors + */ +Fixed gf_vec_dot_p(GF_Vec *v1, GF_Vec *v2); +/*! +\brief vector normalization + +Normalize the vector, eg make its length equal to \ref FIX_ONE +\param v vector to normalize + */ +void gf_vec_norm(GF_Vec *v); +/*! +\brief vector scaling + +Scales a vector by a given amount +\param v vector to scale +\param f scale factor +\return scaled vector + */ +GF_Vec gf_vec_scale(GF_Vec v, Fixed f); +/*! +\brief vector scaling + +Scales a vector by a given amount +\param v vector to scale +\param f scale factor +\return scaled vector + */ +GF_Vec gf_vec_scale_p(GF_Vec *v, Fixed f); +/*! +\brief vector cross product + +Gets the cross product of two vectors +\param v1 first vector +\param v2 second vector +\return cross-product vector + */ +GF_Vec gf_vec_cross(GF_Vec v1, GF_Vec v2); +/*! +\brief vector cross product + +Gets the cross product of two vectors +\param v1 first vector +\param v2 second vector +\return cross-product vector + */ +GF_Vec gf_vec_cross_p(GF_Vec *v1, GF_Vec *v2); + +/*!\brief 4D vector + +The 4D vector object is used in all the GPAC framework for 4 dimension vectors, VRML Rotations and quaternions representation. +*/ +typedef struct __vec4f +{ + Fixed x; + Fixed y; + Fixed z; + Fixed q; +} GF_Vec4; + + +/*!\brief 3D matrix + +The 3D matrix object used in GPAC. The matrix is oriented like OpenGL matrices (column-major ordering), with + the translation part at the end of the coefficients list. + \note Unless specified otherwise, the matrix object is always expected to represent an affine transformation. + */ +typedef struct __matrix +{ + Fixed m[16]; +} GF_Matrix; + + +/*!\hideinitializer gets the len of a quaternion*/ +#define gf_quat_len(v) gf_sqrt(gf_mulfix((v).q,(v).q) + gf_mulfix((v).x,(v).x) + gf_mulfix((v).y,(v).y) + gf_mulfix((v).z,(v).z)) +/*!\hideinitializer normalizes a quaternion*/ +#define gf_quat_norm(v) { \ + Fixed __mag = gf_quat_len(v); \ + (v).x = gf_divfix((v).x, __mag); (v).y = gf_divfix((v).y, __mag); (v).z = gf_divfix((v).z, __mag); (v).q = gf_divfix((v).q, __mag); \ + } \ + +/*!\brief quaternion to rotation + +Transforms a quaternion to a Rotation, expressed as a 4 dimension vector with x,y,z for axis and q for rotation angle +\param quat the quaternion to transform +\return the rotation value + */ +GF_Vec4 gf_quat_to_rotation(GF_Vec4 *quat); +/*!\brief quaternion from rotation + +Transforms a Rotation to a quaternion +\param rot the rotation to transform +\return the quaternion value + */ +GF_Vec4 gf_quat_from_rotation(GF_Vec4 rot); +/*! Inverses a quaternion +\param quat the quaternion to inverse +\return the inverted quaternion +*/ +GF_Vec4 gf_quat_get_inv(GF_Vec4 *quat); +/*!\brief quaternion multiplication + +Multiplies two quaternions +\param q1 the first quaternion +\param q2 the second quaternion +\return the resulting quaternion + */ +GF_Vec4 gf_quat_multiply(GF_Vec4 *q1, GF_Vec4 *q2); +/*!\brief quaternion vector rotating + +Rotates a vector with a quaternion +\param quat the quaternion modelizing the rotation +\param vec the vector to rotate +\return the resulting vector + */ +GF_Vec gf_quat_rotate(GF_Vec4 *quat, GF_Vec *vec); +/*!\brief quaternion from axis and cos + +Constructs a quaternion from an axis and a cosinus value (shortcut to \ref gf_quat_from_rotation) +\param axis the rotation axis +\param cos_a the rotation cosinus value +\return the resulting quaternion + */ +GF_Vec4 gf_quat_from_axis_cos(GF_Vec axis, Fixed cos_a); +/*!\brief quaternion interpolation + +Interpolates two quaternions using spherical linear interpolation +\param q1 the first quaternion +\param q2 the second quaternion +\param frac the fraction of the interpolation, between 0 and \ref FIX_ONE +\return the interpolated quaternion + */ +GF_Vec4 gf_quat_slerp(GF_Vec4 q1, GF_Vec4 q2, Fixed frac); + +/*!\brief 3D Bounding Box + +The 3D Bounding Box is a 3D Axis-Aligned Bounding Box used to in various tools of the GPAC framework for bounds + estimation of a 3D object. It features an axis-aligned box and a sphere bounding volume for fast intersection tests. + */ +typedef struct +{ + /*!minimum x, y, and z of the object*/ + GF_Vec min_edge; + /*!maximum x, y, and z of the object*/ + GF_Vec max_edge; + + /*!center of the bounding box.\note this is computed from min_edge and max_edge*/ + GF_Vec center; + /*!radius of the bounding sphere for this box.\note this is computed from min_edge and max_edge*/ + Fixed radius; + /*!the bbox center and radius are valid*/ + Bool is_set; +} GF_BBox; +/*! updates information of the bounding box based on the edge information +\param b the target bounding box +*/ +void gf_bbox_refresh(GF_BBox *b); +/*!builds a bounding box from a 2D rectangle +\param box the bounding box to build +\param rc the source rectangle +*/ +void gf_bbox_from_rect(GF_BBox *box, GF_Rect *rc); +/*!builds a rectangle from a 3D bounding box. +\note The z dimension is lost and no projection is performed +\param rc the destination rectangle +\param box the source bounding box +*/ +void gf_rect_from_bbox(GF_Rect *rc, GF_BBox *box); +/*!\brief bounding box expansion + +Checks if a point is inside a bounding box and updates the bounding box to include it if not the case +\param box the bounding box object +\param pt the 3D point to check +*/ +void gf_bbox_grow_point(GF_BBox *box, GF_Vec pt); +/*!performs the union of two bounding boxes +\param b1 the first bounding box +\param b2 the bounding box to add +*/ +void gf_bbox_union(GF_BBox *b1, GF_BBox *b2); +/*!checks if two bounding boxes are equal or not +\param b1 the first bounding box +\param b2 the second bounding box +\return GF_TRUE if equal +*/ +Bool gf_bbox_equal(GF_BBox *b1, GF_BBox *b2); +/*!checks if a point is inside a bounding box or not +\param box the bounding box +\param p the point to check +\return GF_TRUE if point is inside +*/ +Bool gf_bbox_point_inside(GF_BBox *box, GF_Vec *p); +/*!\brief get box vertices + +Returns the 8 bounding box vertices given the minimum and maximum edge. Vertices are ordered to respect + "p-vertex indexes", (vertex from a box closest to plane) and so that n-vertex (vertex from a box farthest from plane) + is 7-p_vx_idx +\param bmin minimum edge of the box +\param bmax maximum edge of the box +\param vecs list of 8 3D points used to store the vertices. +*/ +void gf_bbox_get_vertices(GF_Vec bmin, GF_Vec bmax, GF_Vec *vecs); + + +/*!\brief matrix initialization + \hideinitializer + +Inits the matrix to the identity matrix +*/ +#define gf_mx_init(_obj) { memset((_obj).m, 0, sizeof(Fixed)*16); (_obj).m[0] = (_obj).m[5] = (_obj).m[10] = (_obj).m[15] = FIX_ONE; } + +/*! macro to check if a matrix is the identity matrix*/ +#define gf_mx_is_identity(_obj) ((!(_obj).m[1] && !(_obj).m[2] && !(_obj).m[3] && !(_obj).m[4] && !(_obj).m[6] && !(_obj).m[7] && !(_obj).m[8] && !(_obj).m[9] && !(_obj).m[11] && !(_obj).m[12] && !(_obj).m[13] && !(_obj).m[14] && ((_obj).m[0]==FIX_ONE) && ((_obj).m[5]==FIX_ONE)&& ((_obj).m[10]==FIX_ONE)&& ((_obj).m[15]==FIX_ONE)) ? 1 : 0) + +/*!\brief matrix copy + \hideinitializer + + Copies the matrix _from to the matrix _obj +*/ +#define gf_mx_copy(_obj, from) memcpy(&(_obj), &(from), sizeof(GF_Matrix)); +/*!\brief matrix constructor from 2D + +Initializes a 3D matrix from a 2D matrix.\note all z-related coefficients will be set to default. +\param mx the target matrix to initialize +\param mat2D the source 2D matrix +*/ +void gf_mx_from_mx2d(GF_Matrix *mx, GF_Matrix2D *mat2D); +/*!\brief matrix equality testing + +Tests if two matrices are equal or not. +\param mx1 the first matrix +\param mx2 the first matrix +\return GF_TRUE if matrices are same, GF_FALSE otherwise +*/ +Bool gf_mx_equal(GF_Matrix *mx1, GF_Matrix *mx2); +/*!\brief matrix translation + +Translates a matrix +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param tx horizontal translation +\param ty vertical translation +\param tz depth translation +*/ +void gf_mx_add_translation(GF_Matrix *mx, Fixed tx, Fixed ty, Fixed tz); +/*!\brief matrix scaling + +Scales a matrix +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param sx horizontal translation scaling +\param sy vertical translation scaling +\param sz depth translation scaling +*/ +void gf_mx_add_scale(GF_Matrix *mx, Fixed sx, Fixed sy, Fixed sz); +/*!\brief matrix rotating + +Rotates a matrix +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param angle rotation angle in radians +\param x horizontal coordinate of rotation axis +\param y vertical coordinate of rotation axis +\param z depth coordinate of rotation axis +*/ +void gf_mx_add_rotation(GF_Matrix *mx, Fixed angle, Fixed x, Fixed y, Fixed z); +/*!\brief matrices multiplication + +Multiplies a matrix with another one mx = mx*mul +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param mul the matrix to add +*/ +void gf_mx_add_matrix(GF_Matrix *mx, GF_Matrix *mul); +/*!\brief 2D matrix multiplication + +Adds a 2D affine matrix to a matrix +\param mx the matrix +\param mat2D the matrix to premultiply + */ +void gf_mx_add_matrix_2d(GF_Matrix *mx, GF_Matrix2D *mat2D); + +/*!\brief affine matrix inversion + +Inverses an affine matrix.\warning Results are undefined if the matrix is not an affine one +\param mx the matrix to inverse + */ +void gf_mx_inverse(GF_Matrix *mx); +/*!\brief transpose 4x4 matrix + +Transposes a 4x4 matrix +\param mx the matrix to transpose + */ +void gf_mx_transpose(GF_Matrix *mx); +/*!\brief matrix point transformation + +Applies a 3D matrix transformation to a 3D point +\param mx transformation matrix +\param pt pointer to 3D point. Once the function is called, pt contains the transformed point +*/ +void gf_mx_apply_vec(GF_Matrix *mx, GF_Vec *pt); +/*!\brief matrix rectangle transformation + +Applies a 3D matrix transformation to a rectangle, giving the enclosing rectangle of the transformed one.\note all depth information are discarded. +\param _this transformation matrix +\param rc pointer to rectangle. Once the function is called, rc contains the transformed rectangle +*/ +void gf_mx_apply_rect(GF_Matrix *_this, GF_Rect *rc); +/*!\brief ortho matrix construction + +Creates an orthogonal projection matrix. This assume the NDC Z lies in [-1,1] +\param mx matrix to initialize +\param left min horizontal coordinate of viewport +\param right max horizontal coordinate of viewport +\param bottom min vertical coordinate of viewport +\param top max vertical coordinate of viewport +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport +*/ +void gf_mx_ortho(GF_Matrix *mx, Fixed left, Fixed right, Fixed bottom, Fixed top, Fixed z_near, Fixed z_far); + +/*!\brief ortho matrix with reverse Z construction + +Creates an orthogonal projection matrix with reverse Z. This assume the NDC Z lies in [0,1], not [-1,1] +\param mx matrix to initialize +\param left min horizontal coordinate of viewport +\param right max horizontal coordinate of viewport +\param bottom min vertical coordinate of viewport +\param top max vertical coordinate of viewport +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport +*/ +void gf_mx_ortho_reverse_z(GF_Matrix *mx, Fixed left, Fixed right, Fixed bottom, Fixed top, Fixed z_near, Fixed z_far); + +/*!\brief perspective matrix construction + +Creates a perspective projection matrix. This assume the NDC Z lies in [-1,1] +\param mx matrix to initialize +\param fov camera field of view angle in radian +\param aspect_ratio viewport aspect ratio +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport +*/ +void gf_mx_perspective(GF_Matrix *mx, Fixed fov, Fixed aspect_ratio, Fixed z_near, Fixed z_far); + +/*!\brief perspective matrix with reverse Z construction + +Creates a perspective projection matrix with reverse Z. This assume the NDC Z lies in [0,1] +\param mx matrix to initialize +\param fov camera field of view angle in radian +\param aspect_ratio viewport aspect ratio +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport +*/ +void gf_mx_perspective_reverse_z(GF_Matrix *mx, Fixed fov, Fixed aspect_ratio, Fixed z_near, Fixed z_far); + +/*!\brief creates look matrix + +Creates a transformation matrix looking at a given direction from a given point (camera matrix). +\param mx matrix to initialize +\param position position +\param target look direction +\param up_vector vector describing the up direction +*/ +void gf_mx_lookat(GF_Matrix *mx, GF_Vec position, GF_Vec target, GF_Vec up_vector); +/*!\brief matrix box transformation + +Applies a 3D matrix transformation to a bounding box, giving the enclosing box of the transformed one +\param mx transformation matrix +\param b pointer to bounding box. Once the function is called, contains the transformed bounding box +*/ +void gf_mx_apply_bbox(GF_Matrix *mx, GF_BBox *b); +/*!\brief matrix box sphere transformation + +Applies a 3D matrix transformation to a bounding box, computing only the enclosing sphere of the transformed one. +\param mx transformation matrix +\param box pointer to bounding box. Once the function is called, contains the transformed bounding sphere +*/ +void gf_mx_apply_bbox_sphere(GF_Matrix *mx, GF_BBox *box); +/*!\brief non-affine matrix multiplication + +Multiplies two non-affine matrices mx = mx*mul +\param mat the target matrix +\param mul the matrix we multiply with +*/ +void gf_mx_add_matrix_4x4(GF_Matrix *mat, GF_Matrix *mul); +/*!\brief non-affine matrix inversion + +Inverses a non-affine matrices +\param mx the target matrix +\return 1 if inversion was done, 0 if inversion not possible. +*/ +Bool gf_mx_inverse_4x4(GF_Matrix *mx); +/*!\brief matrix 4D vector transformation + +Applies a 3D non-affine matrix transformation to a 4 dimension vector +\param mx transformation matrix +\param vec pointer to the vector. Once the function is called, contains the transformed vector +*/ +void gf_mx_apply_vec_4x4(GF_Matrix *mx, GF_Vec4 *vec); + +/*!\brief matrix yaw pitch roll decomposition + +Extracts yaw, pitch and roll info from a matrix +\param mx the matrix to decompose +\param yaw the extracted yaw angle in radians +\param pitch the extracted pitch angle in radians +\param roll the extracted roll angle in radians +*/ +void gf_mx_get_yaw_pitch_roll(GF_Matrix *mx, Fixed *yaw, Fixed *pitch, Fixed *roll); + +/*!\brief matrix decomposition + +Decomposes a matrix into translation, scale, shear and rotate +\param mx the matrix to decompose +\param translate the decomposed translation part +\param scale the decomposed scaling part +\param rotate the decomposed rotation part, expressed as a Rotataion (axis + angle) +\param shear the decomposed shear part + */ +void gf_mx_decompose(GF_Matrix *mx, GF_Vec *translate, GF_Vec *scale, GF_Vec4 *rotate, GF_Vec *shear); +/*!\brief matrix vector rotation + +Rotates a vector with a given matrix, ignoring any translation. +\param mx transformation matrix +\param pt pointer to 3D vector. Once the function is called, pt contains the transformed vector + */ +void gf_mx_rotate_vector(GF_Matrix *mx, GF_Vec *pt); +/*!\brief matrix initialization from vectors + +Inits a matrix to rotate the local axis in the given vectors +\param mx matrix to initialize +\param x_axis target normalized X axis +\param y_axis target normalized Y axis +\param z_axis target normalized Z axis +*/ +void gf_mx_rotation_matrix_from_vectors(GF_Matrix *mx, GF_Vec x_axis, GF_Vec y_axis, GF_Vec z_axis); +/*!\brief matrix to 2D matrix + +Inits a 2D matrix by removing all depth info from a 3D matrix +\param mx2d 2D matrix to initialize +\param mx 3D matrix to use +*/ +void gf_mx2d_from_mx(GF_Matrix2D *mx2d, GF_Matrix *mx); + +/*!\brief Plane object*/ +typedef struct +{ + /*!normal vector to the plane*/ + GF_Vec normal; + /*!distance from origin of the plane*/ + Fixed d; +} GF_Plane; +/*!\brief matrix plane transformation + +Transorms a plane by a given matrix +\param mx the matrix to use +\param plane pointer to 3D plane. Once the function is called, plane contains the transformed plane + */ +void gf_mx_apply_plane(GF_Matrix *mx, GF_Plane *plane); +/*!\brief point to plane distance + +Gets the distance between a point and a plne +\param plane the plane to use +\param p pointer to ^point to check +\return the distance between the place and the point + */ +Fixed gf_plane_get_distance(GF_Plane *plane, GF_Vec *p); +/*!\brief closest point on a line + +Gets the closest point on a line from a given point in space +\param line_pt a point of the line to test +\param line_vec the normalized direction vector of the line +\param pt the point to check +\return the closest point on the line to the desired point + */ +GF_Vec gf_closest_point_to_line(GF_Vec line_pt, GF_Vec line_vec, GF_Vec pt); +/*!\brief box p-vertex index + +Gets the p-vertex index for a given plane. The p-vertex index is the index of the closest vertex of a bounding box to the plane. The vertices of a box are always + *ordered in GPAC? cf \ref gf_bbox_get_vertices +\param p the plane to check +\return the p-vertex index value, ranging from 0 to 7 +*/ +u32 gf_plane_get_p_vertex_idx(GF_Plane *p); +/*!\brief plane line intersection + +Checks for the intersection of a plane and a line +\param plane plane to test +\param linepoint a point on the line to test +\param linevec normalized direction vector of the line to test +\param outPoint optional pointer to retrieve the intersection point, NULL otherwise +\return 1 if line and plane intersect, 0 otherwise +*/ +Bool gf_plane_intersect_line(GF_Plane *plane, GF_Vec *linepoint, GF_Vec *linevec, GF_Vec *outPoint); + +/*!Classification types for box/plane position used in \ref gf_bbox_plane_relation*/ +enum +{ + /*!box is in front of the plane*/ + GF_BBOX_FRONT, + /*!box intersects the plane*/ + GF_BBOX_INTER, + /*!box is back of the plane*/ + GF_BBOX_BACK +}; +/*!\brief box-plane relation + +Gets the spatial relation between a box and a plane +\param box the box to check +\param p the plane to check +\return the relation type + */ +u32 gf_bbox_plane_relation(GF_BBox *box, GF_Plane *p); + +/*!\brief 3D Ray + +The 3D ray object is used in GPAC for all collision and mouse interaction tests +*/ +typedef struct +{ + /*!origin point of the ray*/ + GF_Vec orig; + /*!normalized direction vector of the ray*/ + GF_Vec dir; +} GF_Ray; + +/*!\brief ray constructor + +Constructs a ray object +\param start starting point of the ray +\param end end point of the ray, or any point on the ray +\return the ray object +*/ +GF_Ray gf_ray(GF_Vec start, GF_Vec end); +/*!\brief matrix ray transformation + +Transforms a ray by a given transformation matrix +\param mx the matrix to use +\param r pointer to the ray. Once the function is called, contains the transformed ray +*/ +void gf_mx_apply_ray(GF_Matrix *mx, GF_Ray *r); +/*!\brief ray box intersection test + +Checks if a ray intersects a box or not +\param ray the ray to check +\param min_edge the minimum edge of the box to check +\param max_edge the maximum edge of the box to check +\param out_point optional location of a 3D point to store the intersection, NULL otherwise. +\return retuns 1 if the ray intersects the box, 0 otherwise +*/ +Bool gf_ray_hit_box(GF_Ray *ray, GF_Vec min_edge, GF_Vec max_edge, GF_Vec *out_point); +/*!\brief ray sphere intersection test + +Checks if a ray intersects a box or not +\param ray the ray to check +\param center the center of the sphere to check. If NULL, the origin (0,0,0)is used +\param radius the radius of the sphere to check +\param out_point optional location of a 3D point to store the intersection, NULL otherwise +\return retuns 1 if the ray intersects the sphere, 0 otherwise +*/ +Bool gf_ray_hit_sphere(GF_Ray *ray, GF_Vec *center, Fixed radius, GF_Vec *out_point); +/*!\brief ray triangle intersection test + +Checks if a ray intersects a triangle or not +\param ray the ray to check +\param v0 first vertex of the triangle +\param v1 second vertex of the triangle +\param v2 third vertex of the triangle +\param dist optional location of a fixed number to store the intersection distance from ray origin if any, NULL otherwise +\return retuns 1 if the ray intersects the triangle, 0 otherwise +*/ +Bool gf_ray_hit_triangle(GF_Ray *ray, GF_Vec *v0, GF_Vec *v1, GF_Vec *v2, Fixed *dist); + +/*! @} */ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_MATH_H_*/ + diff --git a/include/gpac/media_tools.h b/include/gpac/media_tools.h new file mode 100644 index 0000000..fd918ad --- /dev/null +++ b/include/gpac/media_tools.h @@ -0,0 +1,1464 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / Authoring 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_MEDIA_H_ +#define _GF_MEDIA_H_ + +/*! +\file <gpac/media_tools.h> +\brief media tools helper for importing, exporting and analysing + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/isomedia.h> +#include <gpac/avparse.h> +#include <gpac/config_file.h> +#include <gpac/filters.h> + +/*! +\addtogroup mt_grp ISOBMF Helper tools +\ingroup media_grp +\brief media tools helper for importing, exporting and analysing. + +This section documents media tools functions . + +@{ + */ + + +/*! + *computes file hash. + If file is ISOBMFF based, computes hash according to OMA (P)DCF (without MutableDRMInformation box). + Otherwise this is equivalent to \ref gf_sha1_file + +\param file the source file to hash +\param hash the 20 bytes buffer in which sha128 is performed for this file +\return error if any. + */ +GF_Err gf_media_get_file_hash(const char *file, u8 hash[20]); + +#ifndef GPAC_DISABLE_ISOM +/*! + Creates (if needed) a GF_ESD for the given track - THIS IS RESERVED for local playback +only, since the OTI/codecid used when emulated is not standard... +\param isom_file source file +\param trackNumber track for which the esd is to be emulated +\param sampleDescriptionIndex indicates the default sample description to map. 0 is equivalent to 1, first sample description +\return rebuilt ESD. It is the caller responsibility to delete it. + */ +GF_ESD *gf_media_map_esd(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! +Creates (if needed) a GF_ESD for the given image item - THIS IS RESERVED for local playback +only, since the OTI/codecid used when emulated is not standard... +\param mp4 source file +\param item_id item for which the esd is to be emulated +\return rebuilt ESD. It is the caller responsibility to delete it. +*/ +GF_ESD *gf_media_map_item_esd(GF_ISOFile *mp4, u32 item_id); + +/*! + * Get RFC 6381 description for a given track. +\param isom_file source ISOBMF file +\param trackNumber track to check +\param szCodec a pointer to an already allocated string of size RFC6381_CODEC_NAME_SIZE_MAX bytes. +\param force_inband_xps force inband signaling of parameter sets. +\param force_sbr forces using explicit signaling for SBR. +\return error if any. + */ +GF_Err gf_media_get_rfc_6381_codec_name(GF_ISOFile *isom_file, u32 trackNumber, char *szCodec, Bool force_inband_xps, Bool force_sbr); +#endif + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! + *Changes pixel aspect ratio for visual tracks if supported. Negative values remove any PAR info +\param isom_file target ISOBMF file +\param trackNumber target track +\param ar_num aspect ratio numerator. A value of 0 removes PAR info, a value of -1 gets info from video bitstream for BOTH num and den +\param ar_den aspect ratio denominator. A value of 0 removes PAR info, a value of -1 gets info from video bitstream for BOTH num and den +\param force_par aspect ratio is always written even when 1:1, otherwise aspect ratio info is removed if 1:1 +\param rewrite_par aspect ratio is modified in bitstream. Ignored if ar_num or ar_den are not strictly positive +\return error if any + */ +GF_Err gf_media_change_par(GF_ISOFile *isom_file, u32 trackNumber, s32 ar_num, s32 ar_den, Bool force_par, Bool rewrite_par); + +/*! Changes color property of the media (bitstream rewrite) - only AVC/H264 supported for now. See CICP for value types +Negative values keep source settings for the corresponding flags. +If source stream has no VUI info, create one and set corresponding flags to specified values. +In this case, any other flags are set to preferred values (typically, flag=0 or value=undef). +\param isom_file target ISOBMF file +\param trackNumber target track +\param fullrange fullrange flag +\param video_format video format type +\param color_primaries color primaries +\param transfer transfer characteristics +\param color_matrix olor matrix +\return error if any +*/ +GF_Err gf_media_change_color(GF_ISOFile *isom_file, u32 trackNumber, s32 fullrange, s32 video_format, s32 color_primaries, s32 transfer, s32 color_matrix); + +/*! + *Removes all non rap samples (sync and other RAP sample group info) from the track. +\param isom_file target ISOBMF file +\param trackNumber target track +\param non_ref_only if GF_TRUE, removes only non-reference pictures +\return error if any + */ +GF_Err gf_media_remove_non_rap(GF_ISOFile *isom_file, u32 trackNumber, Bool non_ref_only); + +/*! + *updates bitrate info on given track. +\param isom_file target ISOBMF file +\param trackNumber target track + */ +void gf_media_update_bitrate(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! gets AV1 scalable layer byte offsets of a sample for a1lx box +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_number the target sample to query +\param op_index AV1 operating point index to retrieve sizes for +\param layer_size returned 3 layer sizes (4th is implied, see a1lx spec) +\return error if any +*/ +GF_Err gf_media_av1_layer_size_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_number, u8 op_index, u32 layer_size[3]); + +#endif + +/*! @} */ + + +/*! +\addtogroup mimp_grp ISOBMF Importers +\ingroup media_grp +\brief Media importing. + +This section documents media tools helper functions for importing, exporting and analysing. + +@{ +*/ + +/*! +Default import FPS for video when no VUI/timing information is found +\hideinitializer +*/ +#define GF_IMPORT_DEFAULT_FPS 25.0 + +#ifndef GPAC_DISABLE_MEDIA_IMPORT + +/* + All these can import a file into a dedicated track. If esd is NULL the track is blindly added + otherwise it is added with the requested ESID if non-0, otherwise the new trackID is stored in ESID + if use_data_ref is set, data is only referenced in the file + if duration is not 0, only the first duration seconds are imported + \note If an ESD is specified, its decoderSpecificInfo is also updated + +*/ + +/*! +Track importer flags +\hideinitializer +*/ +enum +{ + /*! references data rather than copy, whenever possible*/ + GF_IMPORT_USE_DATAREF = 1, + /*! for AVI video: imports at constant FPS (eg imports N-Vops due to encoder drops)*/ + GF_IMPORT_NO_FRAME_DROP = 1<<1, + /*! for CMP ASP only: forces treating the input as packed bitsream and discards all n-vops*/ + GF_IMPORT_FORCE_PACKED = 1<<2, + /*! for AAC audio: forces SBR mode with implicit signaling (backward compatible)*/ + GF_IMPORT_SBR_IMPLICIT = 1<<3, + /*! for AAC audio: forces SBR mode with explicit signaling (non-backward compatible). + Will override GF_IMPORT_SBR_IMPLICIT flag when set*/ + GF_IMPORT_SBR_EXPLICIT = 1<<4, + /*! forces MPEG-4 import - some 3GP2 streams have both native IsoMedia sample description and MPEG-4 one possible*/ + GF_IMPORT_FORCE_MPEG4 = 1<<5, + /*! special flag for text import at run time (never set on probe), indicates to leave the text box empty + so that we dynamically adapt to display size*/ + GF_IMPORT_SKIP_TXT_BOX = 1<<6, + + /*! uses compact size in .MOV/.IsoMedia files*/ + GF_IMPORT_USE_COMPACT_SIZE = 1<<8, + /*! don't add a final empty sample when importing text tracks from srt*/ + GF_IMPORT_NO_TEXT_FLUSH = 1<<9, + /*! for SVC or LHVC video: forces explicit SVC / LHVC signaling */ + GF_IMPORT_SVC_EXPLICIT = 1<<10, + /*! for SVC / LHVC video: removes all SVC / LHVC extensions*/ + GF_IMPORT_SVC_NONE = 1<<11, + /*! for AAC audio: forces PS mode with implicit signaling (backward compatible)*/ + GF_IMPORT_PS_IMPLICIT = 1<<12, + /*! for AAC audio: forces PS mode with explicit signaling (non-backward compatible). + Will override GF_IMPORT_PS_IMPLICIT flag when set*/ + GF_IMPORT_PS_EXPLICIT = 1<<13, + /*! oversampled SBR */ + GF_IMPORT_OVSBR = 1<<14, + /*! set subsample information with SVC*/ + GF_IMPORT_SET_SUBSAMPLES = 1<<15, + /*! force to mark non-IDR frames with sync data (I slices,) to be marked as sync points points + THE RESULTING FILE IS NOT COMPLIANT*/ + GF_IMPORT_FORCE_SYNC = 1<<16, + /*! keep trailing 0 bytes in AU payloads when any*/ + GF_IMPORT_KEEP_TRAILING = 1<<17, + + /*! do not compute edit list for B-frames video tracks*/ + GF_IMPORT_NO_EDIT_LIST = 1<<19, + /*! when set, only updates tracks info and return*/ + GF_IMPORT_PROBE_ONLY = 1<<20, + /*! only set when probing, signals several frames per sample possible*/ + GF_IMPORT_3GPP_AGGREGATION = 1<<21, + /*! only set when probing, signals video FPS overridable*/ + GF_IMPORT_OVERRIDE_FPS = 1<<22, + /*! only set when probing, signals duration not usable*/ + GF_IMPORT_NO_DURATION = 1<<23, + /*! when set IP packets found in MPE sections will be sent to the local network */ + GF_IMPORT_MPE_DEMUX = 1<<24, + /*! when set HEVC VPS is rewritten to remove VPS extensions*/ + GF_IMPORT_NO_VPS_EXTENSIONS = 1<<25, + /*! when set no SEI messages are imported*/ + GF_IMPORT_NO_SEI = 1<<26, + /*! keeps track references when importing a single track*/ + GF_IMPORT_KEEP_REFS = 1<<27, + /*! keeps AV1 temporal delimiter OBU in the samples*/ + GF_IMPORT_KEEP_AV1_TEMPORAL_OBU = 1<<28, + /*! imports sample dependencies information*/ + GF_IMPORT_SAMPLE_DEPS = 1<<29, + + //GF_IMPORT_FILTER_STATS = 0x80000000 //(=1<<31) +}; + +/*! max supported numbers of tracks in importer*/ +#define GF_IMPORT_MAX_TRACKS 100 +/*! +Track info for video media +\hideinitializer + */ +struct __track_video_info +{ + /*! video width in coded samples*/ + u32 width; + /*! video height in coded samples*/ + u32 height; + /*! pixel aspect ratio expressed as 32 bits, high 16 bits being the numerator and low ones being the denominator*/ + u32 par; + /*! temporal enhancement flag*/ + Bool temporal_enhancement; + /*! Video frame rate*/ + Double FPS; +}; +/*! +Track info for audio media +\hideinitializer +*/ +struct __track_audio_info +{ + /*! audio sample rate*/ + u32 sample_rate; + /*! number of channels*/ + u32 nb_channels; + /*! samples per frame*/ + u32 samples_per_frame; +}; + +/*! +* Track info for any media +*/ +struct __track_import_info +{ + /*! ID of the track (PID, TrackID, etc ...)*/ + u32 track_num; + /*! stream type (one of GF_STREAM_XXXX)*/ + u32 stream_type; + /*! codec ID ( one of GF_CODECID_XXX*)*/ + u32 codecid; + /*! GF_ISOM_MEDIA_* : vide, auxv, pict*/ + u32 media_subtype; + Bool is_chapter; + /*! video format info*/ + struct __track_video_info video_info; + /*! audio format info*/ + struct __track_audio_info audio_info; + /*! codec profile according to 6381*/ + char szCodecProfile[20]; + /*! language of the media, 0/'und ' if not known */ + u32 lang; + /*! MPEG-4 ES-ID, only used for MPEG4 on MPEG-2 TS*/ + u32 mpeg4_es_id; + /*! Program number for MPEG2 TS*/ + u16 prog_num; +}; + +/*! + * Program info for the source file/stream + */ +struct __program_import_info +{ + /*! program number, as used in MPEG-2 TS*/ + u32 number; + /*! program name*/ + char name[40]; +}; + +/*! + * Track importer object + */ +typedef struct __track_import +{ + /*! destination ISOBMFF file where the media is to be imported */ + GF_ISOFile *dest; + /*! media to import: + MP4/ISO media: trackID + AVI files: + 0: first video and first audio, + 1: video track + 2->any: audio track(s) + MPEG-PS files with N video streams: + 0: first video and first audio + 1->N: video track + N+1->any: audio track + TrackNums can be obtain with probing + */ + u32 trackID; + /*! media source - selects importer type based on extension*/ + char *in_name; + /*! import duration if any*/ + GF_Fraction duration; + /*! importer flags*/ + u32 flags; + /*! importer swf flags*/ + u32 swf_flags; + /*! importer swf flatten angle when converting curves*/ + Float swf_flatten_angle; + /*! Forced video FPS (CMP, AVI, OGG, H264) - also used by SUB import. Ignored if 0*/ + GF_Fraction video_fps; + /*! optional ESD to be updated by the importer (used for BT/XMT import)*/ + GF_ESD *esd; + /*! optional format indication for media source (used in IM1 reference software)*/ + char *streamFormat; + /*! frame per sample cumulation (3GP media only) - MAX 15, ignored when data ref is used*/ + u32 frames_per_sample; + /*! track ID of imported media in destination file*/ + u32 final_trackID; + /*! optional format indication for media source (used in IM1)*/ + char *force_ext; + /*! for MP4 import only, the source MP4 to be used*/ + GF_ISOFile *orig; + + /*! default font size for text import*/ + u32 fontSize; + /*! default font name for text import*/ + char *fontName; + /*! width of the imported text track */ + u32 text_track_width; + /*! height of the imported text track */ + u32 text_track_height; + /*! width of the imported text display area (as indicated in text sample description) */ + u32 text_width; + /*! height of the imported text display area (as indicated in text sample description) */ + u32 text_height; + /*! horizontal offset of the imported text display area (as indicated in text sample description) */ + s32 text_x; + /*! vertical offset of the imported text display area (as indicated in text sample description) */ + s32 text_y; + + /*! Initial offset of the first AU to import. Only used for still images and AFX streams*/ + Double initial_time_offset; + + /*! number of tracks after probing - may be set to 0, in which case no track + selection can be performed. It may also be inaccurate if probing doesn't + detect all available tracks (cf ogg import)*/ + u32 nb_tracks; + /*! track info after probing (GF_IMPORT_PROBE_ONLY set).*/ + struct __track_import_info tk_info[GF_IMPORT_MAX_TRACKS]; + /*! duration of the probe for MPEG_2 TS cases.*/ + u64 probe_duration; + + /*! for MPEG-TS and similar: number of program info*/ + u32 nb_progs; + /*! for MPEG-TS and similar: program info*/ + struct __program_import_info pg_info[GF_IMPORT_MAX_TRACKS]; + /*! last error encountered during import, internal to the importer*/ + GF_Err last_error; + /*! any filter options to pass to source*/ + const char *filter_src_opts; + /*! any filter options to pass to sink*/ + const char *filter_dst_opts; + /*! filter chain to insert before destination, formatted as "f1[:args]@f2[:args]" options to pass to sink*/ + const char *filter_chain; + Bool is_chain_old_syntax; + + /*! force mode for the created ISOBMFF sample entry*/ + GF_AudioSampleEntryImportMode asemode; + + /*! indicate to tag the imported media as an alpha channel stream*/ + Bool is_alpha; + /*! keep AU delimiter in file if allowed by specification*/ + Bool keep_audelim; + /*! import as NAL-based video using inband parameter sets*/ + u32 xps_inband; + /*! flag for session stats and graph dumping*/ + u32 print_stats_graph; + /*! target program ID of source MPEG-2 stream to import*/ + u32 prog_id; + + /*! target timescale to set*/ + s32 moov_timescale; + + /*! value for created track + 0: let importer decide + 0xFFFFFFFF: try to keep source ID + other value: trackk ID value + */ + u32 target_trackID; + + /*magic number for identifying source, will be set to the destination track. Only the low 32 bits are used + the high 32 bits are updated by the importer as follows: + 1<<33: if bit is set, source was an isobmff file + */ + u64 source_magic; + /*! the session in which to add the importer (for -new-fs option only). If null, the importer runs its own session right away*/ + GF_FilterSession *run_in_session; + /*! muxer arguments when running multiple importers in one session*/ + char *update_mux_args; + /*! muxer source ID argument when running multiple importers in one session*/ + char *update_mux_sid; + /*! index of source importer when running multiple importers in one session*/ + u32 track_index; + /*! target start time in source*/ + Double start_time; +} GF_MediaImporter; + +/*! + * Imports a media file +\param importer the importer object +\return error if any + */ +GF_Err gf_media_import(GF_MediaImporter *importer); + + +/*! + Adds chapter info contained in file +\param isom_file target ISOBMF file +\param chap_file target chapter file +\param import_fps specifies the chapter frame rate (optional, ignored if 0 - defaults to 25). Most formats don't use this feature +\param for_qt use QT signaling for chapter tracks +\return error if any + */ +GF_Err gf_media_import_chapters(GF_ISOFile *isom_file, char *chap_file, GF_Fraction import_fps, Bool for_qt); + +/*! + Make the file ISMA compliant: creates ISMA BIFS / OD tracks if needed, and update audio/video IDs +the file should not contain more than one audio and one video track +\param isom_file the target ISOBMF file +\param keepESIDs if true, ES IDs are not changed. +\param keepImage if true, keeps image tracks +\param no_ocr if true, doesn't write clock references in MPEG-4 system info +\return error if any +*/ +GF_Err gf_media_make_isma(GF_ISOFile *isom_file, Bool keepESIDs, Bool keepImage, Bool no_ocr); + +/*! + Make the file 3GP compliant && sets profile +\param isom_file the target ISOBMF file +\return error if any + */ +GF_Err gf_media_make_3gpp(GF_ISOFile *isom_file); + +/*! + make the file playable on a PSP +\param isom_file the target ISOBMF file +\return error if any +*/ +GF_Err gf_media_make_psp(GF_ISOFile *isom_file); + +/*! + adjust file params for QT prores +\param qt_file the target QT file +\return error if any +*/ +GF_Err gf_media_check_qt_prores(GF_ISOFile *qt_file); + +/*! @} */ + +/*! +\addtogroup mnal_grp AVC and HEVC ISOBMFF tools +\ingroup media_grp +\brief Manipulation AVC and HEVC tracks in ISOBMFF. + +This section documents functions for manipulating AVC and HEVC tracks in ISOBMFF. + +@{ +*/ + +/*! + Changes the profile (if not 0) and level (if not 0) indication of the media - only AVC/H264 supported for now +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param profile the new profile to set +\param compat profile compatibility flag for H264 +\param level the new level to set +\return error if any + */ +GF_Err gf_media_change_pl(GF_ISOFile *isom_file, u32 trackNumber, u32 profile, u32 compat, u32 level); + +/*! + Rewrite NAL-based samples (AVC/HEVC/...) samples if nalu size_length has to be changed +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param new_size_in_bits new size in bits of the NALU length field in the track, for all samples description of the track +\return error if any + */ +GF_Err gf_media_nal_rewrite_samples(GF_ISOFile *isom_file, u32 trackNumber, u32 new_size_in_bits); + +/*! + Split SVC layers +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param splitAll if set each layers will be in a single track, otherwise all non-base layers will be in the same track +\return error if any + */ +GF_Err gf_media_split_svc(GF_ISOFile *isom_file, u32 trackNumber, Bool splitAll); + +/*! + Merge SVC layers +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param mergeAll if set all layers will be merged a single track, otherwise all non-base layers will be merged in the same track +\return error if any +*/ +GF_Err gf_media_merge_svc(GF_ISOFile *isom_file, u32 trackNumber, Bool mergeAll); + +/*! LHVC extractor mode*/ +typedef enum +{ + /*! use extractors*/ + GF_LHVC_EXTRACTORS_ON, + /*! don't use extractors and keep base track inband/outofband param set signaling*/ + GF_LHVC_EXTRACTORS_OFF, + /*! don't use extractors and force inband signaling in enhancement layer*/ + GF_LHVC_EXTRACTORS_OFF_FORCE_INBAND +} GF_LHVCExtractoreMode; + +/*! Split L-HEVC layers +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param for_temporal_sublayers if set only temporal sublayers are split, otherwise layers are split +\param splitAll if set each layers will be in a single track, otherwise all non-base layers will be in the same track +\param extractor_mode extractor mode +\return error if any + */ +GF_Err gf_media_split_lhvc(GF_ISOFile *isom_file, u32 trackNumber, Bool for_temporal_sublayers, Bool splitAll, GF_LHVCExtractoreMode extractor_mode); + +/*! + Split HEVC tiles into different tracks +\param isom_file the target ISOBMF file +\param signal_only if set to 1 or 2, inserts tile description and NAL->tile mapping but does not create separate tracks. If 2, NAL->tile mapping uses RLE +\return error if any + */ +GF_Err gf_media_split_hevc_tiles(GF_ISOFile *isom_file, u32 signal_only); + + +/*! + Filter HEVC/L-HEVC NALUs by temporal IDs and layer IDs, removing all NALUs above the desired levels. +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param max_temporal_id_plus_one max temporal ID plus 1 of all NALUs to be removed. +\param max_layer_id_plus_one max layer ID plus 1 of all NALUs to be removed. +\return error if any + */ +GF_Err gf_media_filter_hevc(GF_ISOFile *isom_file, u32 trackNumber, u8 max_temporal_id_plus_one, u8 max_layer_id_plus_one); + +#endif /*GPAC_DISABLE_MEDIA_IMPORT*/ + + +/*! @} */ + + +/*! +\addtogroup dashseg_grp DASH Segmenter +\ingroup media_grp +\brief MPEG-DASH creation. + +This section documents media functions for MPEG-DASH creation. + +@{ +*/ + +/*! + * DASH segmenter information per source + */ +typedef struct +{ + /*! source file to be used*/ + char *file_name; + /*! ID of the representation, may be NULL (assigned by dasher)*/ + char *representationID; + /*! ID of the period, may be NULL (assigned by dasher)*/ + char *periodID; + /*! ID of the adaptation set, may be 0 (assigned by dasher)*/ + u32 asID; + /*! forced media duration.*/ + GF_Fraction64 media_duration; + /*! number of base URLs in the baseURL structure*/ + u32 nb_baseURL; + /*! list of baseURL to be used for this representation*/ + char **baseURL; + /*! xlink of the period if desired, NULL otherwise*/ + char *xlink; + /*! number of items in roles*/ + u32 nb_roles; + /*! role of the representation according to MPEG-DASH*/ + char **roles; + /*! number of items in rep_descs*/ + u32 nb_rep_descs; + /*! descriptors to be inserted in the representation*/ + char **rep_descs; + /*! number of items in p_descs*/ + u32 nb_p_descs; + /*! descriptors to be inserted in the period*/ + char **p_descs; + /*! number of items in nb_as_descs*/ + u32 nb_as_descs; + /*! descriptors to be inserted in the adaptation set. Representation with non matching as_descs will be in different adaptation sets*/ + char **as_descs; + /*! number of items in nb_as_c_descs*/ + u32 nb_as_c_descs; + /*! descriptors to be inserted in the adaptation set. Ignored when matching Representation to adaptation sets*/ + char **as_c_descs; + /*! forces bandwidth in bits per seconds of the source media. If 0, computed from file */ + u32 bandwidth; + /*! forced period duration (used when using empty periods or xlink periods without content)*/ + GF_Fraction period_duration; + /*! forced dash target duration for this rep*/ + GF_Fraction dash_duration; + /*! sets default start number for this representation. if not set, assigned automatically */ + u32 startNumber; //TODO: start number, template + /*! overrides template for this input*/ + char *seg_template; + /*! sets name of HLS playlist*/ + char *hls_pl; + /*! if true and only one media stream in target segment, the moov will use the media stream timescale*/ + Bool sscale; + /*! only imports this track from the source*/ + u32 track_id; + /*! non legacy options passed to dasher for source */ + char *source_opts; + /*! filter chain to instantiate between this source and the dasher*/ + char *filter_chain; + /*! period order, internal only*/ + u32 period_order; +} GF_DashSegmenterInput; + +/*! +DASH profile constants +\hideinitializer + +Matches profile enum of dasher module: auto|live|onDemand|main|full|hbbtv1.5.live|dashavc264.live|dashavc264.onDemand + */ +typedef enum +{ + /*! auto profile, internal use only*/ + GF_DASH_PROFILE_AUTO = 0, + /*! Live dash profile for: live for ISOFF, SIMPLE for M2TS */ + GF_DASH_PROFILE_LIVE, + /*! onDemand profile*/ + GF_DASH_PROFILE_ONDEMAND, + /*! main profile*/ + GF_DASH_PROFILE_MAIN, + /*! Full dash (no profile)*/ + GF_DASH_PROFILE_FULL, + + /*! industry profile HbbTV 1.5 ISOBMFF Live */ + GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE, + /*! industry profile DASH-IF ISOBMFF Live */ + GF_DASH_PROFILE_AVC264_LIVE, + /*! industry profile DASH-IF ISOBMFF onDemand */ + GF_DASH_PROFILE_AVC264_ONDEMAND, + /*! industry profile DASH-IF ISOBMFF low latency */ + GF_DASH_PROFILE_DASHIF_LL, +} GF_DashProfile; + + +/*! +DASH bitstream switching selector +\hideinitializer + */ +typedef enum +{ + /*! inband parameter sets for live profile and none for onDemand*/ + GF_DASH_BSMODE_DEFAULT, + /*! always out of band parameter sets */ + GF_DASH_BSMODE_NONE, + /*! always inband parameter sets */ + GF_DASH_BSMODE_INBAND, + /*! out of band parameter sets except PPS and APS, used for VVC */ + GF_DASH_BSMODE_INBAND_PPS, + /*! attempts to merge parameter sets in a single sample entry */ + GF_DASH_BSMODE_MERGED, + /*! parameter sets are in different sample entries */ + GF_DASH_BSMODE_MULTIPLE_ENTRIES, + /*! forces GF_DASH_BSMODE_INBAND even if only one file is used*/ + GF_DASH_BSMODE_SINGLE +} GF_DashSwitchingMode; + + +/*! +DASH media presentation type +\hideinitializer + */ +typedef enum +{ + /*! DASH Presentation is static*/ + GF_DASH_STATIC = 0, + /*! DASH Presentation is dynamic*/ + GF_DASH_DYNAMIC, + /*! DASH Presentation is dynamic and this is the last segmenting operation in the period. This can only be used when DASH segmenter context is used, will close the period*/ + 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 +{ + /*! 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; + +/*! + Create a new DASH segmenter +\param mpdName target MPD file name, cannot be changed +\param profile target DASH profile, cannot be changed +\param tmp_dir temp dir for file generation, if NULL uses libgpac default +\param timescale timescale used to specif most of the dash timings. If 0, 1000 is used +\param dasher_context_file config file used to store the context of the DASH segmenter. This allows destroying the segmenter and restarting it later on with the right DASH segquence numbers, MPD and and timing info +\return the DASH segmenter object +*/ +GF_DASHSegmenter *gf_dasher_new(const char *mpdName, GF_DashProfile profile, const char *tmp_dir, u32 timescale, const char *dasher_context_file); +/*! + Deletes a DASH segmenter +\param dasher the DASH segmenter object +*/ +void gf_dasher_del(GF_DASHSegmenter *dasher); + +/*! + Removes the DASH inputs. Re-add new ones with gf_dasher_add_input() +\param dasher the DASH segmenter object +*/ +void gf_dasher_clean_inputs(GF_DASHSegmenter *dasher); + +/*! Sets MPD info +\param dasher the DASH segmenter object +\param title MPD title +\param copyright MPD copyright +\param moreInfoURL MPD "more info" URL +\param sourceInfo MPD source info +\param lang MPD language for title +\return error code if any +*/ +GF_Err gf_dasher_set_info(GF_DASHSegmenter *dasher, const char *title, const char *copyright, const char *moreInfoURL, const char *sourceInfo, const char *lang); + +/*! + Sets MPD Location. This is useful to distrubute a dynamic MPD by mail or any non-HTTP mean +\param dasher the DASH segmenter object +\param location the URL where this MPD can be found +\return error code if any +*/ +GF_Err gf_dasher_set_location(GF_DASHSegmenter *dasher, const char *location); + +/*! + Adds a base URL to the MPD +\param dasher the DASH segmenter object +\param base_url base url to add +\return error code if any +*/ +GF_Err gf_dasher_add_base_url(GF_DASHSegmenter *dasher, const char *base_url); + +/*! + Enable URL template - - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable usage of URL template +\param default_template template for the segment name +\param default_extension extension for the segment name +\param default_init_extension extension for the initialization segment name +\return error code if any +*/ + +GF_Err gf_dasher_enable_url_template(GF_DASHSegmenter *dasher, Bool enable, const char *default_template, const char *default_extension, const char *default_init_extension); + +/*! + Enable Segment Timeline template - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable or disable +\return error code if any +*/ +GF_Err gf_dasher_enable_segment_timeline(GF_DASHSegmenter *dasher, Bool enable); + +/*! + Enables single segment - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable or disable +\return error code if any +*/ + +GF_Err gf_dasher_enable_single_segment(GF_DASHSegmenter *dasher, Bool enable); + +/*! + Enable single file (with multiple segments) - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable or disable +\return error code if any +*/ +GF_Err gf_dasher_enable_single_file(GF_DASHSegmenter *dasher, Bool enable); + +/*! + Sets bitstream switching mode - may be overridden by the current profile +\param dasher the DASH segmenter object +\param bitstream_switching mode to use for bitstream switching +\return error code if any +*/ +GF_Err gf_dasher_set_switch_mode(GF_DASHSegmenter *dasher, GF_DashSwitchingMode bitstream_switching); + +/*! + Sets segment and fragment durations. +\param dasher the DASH segmenter object +\param default_segment_duration the duration of a dash segment +\param default_fragment_duration the duration of a dash fragment - if 0, same as default_segment_duration +\param sub_duration the duration in seconds of media to DASH. If 0, the whole sources will be processed. +\return error code if any +*/ +GF_Err gf_dasher_set_durations(GF_DASHSegmenter *dasher, Double default_segment_duration, Double default_fragment_duration, Double sub_duration); + +/*! + Enables splitting at RAP boundaries +\param dasher the DASH segmenter object +\param segments_start_with_rap segments will be split at RAP boundaries +\param fragments_start_with_rap fragments will be split at RAP boundaries +\return error code if any +*/ + +GF_Err gf_dasher_enable_rap_splitting(GF_DASHSegmenter *dasher, Bool segments_start_with_rap, Bool fragments_start_with_rap); + +/*! + Enables segment marker +\param dasher the DASH segmenter object +\param segment_marker_4cc 4CC code of the segment marker box +\return error code if any +*/ +GF_Err gf_dasher_set_segment_marker(GF_DASHSegmenter *dasher, u32 segment_marker_4cc); + +/*! + Enables segment indexes +\param dasher the DASH segmenter object +\param enable_sidx enable or disable +\param subsegs_per_sidx number of subsegments per segment +\param daisy_chain_sidx enable daisy chaining of sidx +\param use_ssix enables ssix generation, level 1 for I-frames, the rest of the segment not mapped +\return error code if any +*/ +GF_Err gf_dasher_enable_sidx(GF_DASHSegmenter *dasher, Bool enable_sidx, u32 subsegs_per_sidx, Bool daisy_chain_sidx, Bool use_ssix); + +/*! + Sets mode for the dash segmenter. +\param dasher the DASH segmenter object +\param dash_mode the mode to use. Currently switching from static mode to dynamic mode is not well supported and may produce non-compliant MPDs +\param mpd_update_time time between MPD refresh, in seconds. Used for dynamic mode, may be 0 if \p mpd_live_duration is set +\param time_shift_depth the depth of the time shift buffer in seconds, -1 for infinite time shift. +\param mpd_live_duration total duration of the DASH session in dynamic mode, in seconds. May be set to 0 if \p mpd_update_time is set +\return error code if any +*/ +GF_Err gf_dasher_set_dynamic_mode(GF_DASHSegmenter *dasher, GF_DashDynamicMode dash_mode, Double mpd_update_time, s32 time_shift_depth, Double mpd_live_duration); + +/*! + Sets the minimal buffer desired. +\param dasher the DASH segmenter object +\param min_buffer min buffer time in seconds for the DASH session. Currently the minimal buffer is NOT computed from the source material and must be set to an appropriate value. +\return error code if any +*/ +GF_Err gf_dasher_set_min_buffer(GF_DASHSegmenter *dasher, Double min_buffer); + +/*! + Sets the availability start time offset. +\param dasher the DASH segmenter object +\param ast_offset ast offset in milliseconds. If >0, the DASH session availabilityStartTime will be earlier than UTC by the amount of seconds specified. If <0, the media representation will have an availabilityTimeOffset of the amount of seconds specified, instructing the client that segments may be accessed earlier. +\return error code if any +*/ +GF_Err gf_dasher_set_ast_offset(GF_DASHSegmenter *dasher, s32 ast_offset); + +/*! + Enables memory fragmenting: fragments will be written to disk only once completed +\param dasher the DASH segmenter object +\param enable Enables or disables. Default is disabled. +\return error code if any +*/ +GF_Err gf_dasher_enable_memory_fragmenting(GF_DASHSegmenter *dasher, Bool enable); + +/*! + Sets initial values for ISOBMFF sequence number and TFDT in movie fragments. +\param dasher the DASH segmenter object +\param initial_moof_sn sequence number of the first moof to be generated. Default value is 1. +\param initial_tfdt initial tfdt of the first traf to be generated, in DASH segmenter timescale units. Default value is 0. +\return error code if any +*/ +GF_Err gf_dasher_set_initial_isobmf(GF_DASHSegmenter *dasher, u32 initial_moof_sn, u64 initial_tfdt); + + +/*! DASH PSSH storage mode*/ +typedef enum +{ + //! PSSH box in moov only + GF_DASH_PSSH_MOOV = 0, + //! PSSH box in moof only + GF_DASH_PSSH_MOOF, + //! PSSH box in moov and MPD + GF_DASH_PSSH_MOOV_MPD, + //! PSSH box in moof and MPD + GF_DASH_PSSH_MOOF_MPD, + //! PSSH box in MPD only + GF_DASH_PSSH_MPD +} GF_DASHPSSHMode; + +/*! + Configure how default values for ISOBMFF are stored +\param dasher the DASH segmenter object +\param no_fragments_defaults if set, fragments default values are repeated in each traf and not set in trex. Default value is GF_FALSE +\param pssh_mode sets the storage mode of PSSH in moov/moof/mpd. +\param samplegroups_in_traf if set, all sample group definitions are stored in each traf and not set in init segment. Default value is GF_FALSE +\param single_traf_per_moof if set, each moof will contain a single traf, even if source media is multiplexed. Default value is GF_FALSE +\param tfdt_per_traf if set, each traf will contain a tfdt. Only applicable when single_traf_per_moof is GF_TRUE. Default value is GF_FALSE +\param mvex_after_traks if set, the mvex box will be written after all track boxes +\param sdtp_in_traf mode for sdtp storage in traf (smooth compatibility): 0: not allowed, 1: only stdp, no trun flags, 2: both (trun for sync, stdp for the rest) +\return error code if any +*/ +GF_Err gf_dasher_configure_isobmf_default(GF_DASHSegmenter *dasher, Bool no_fragments_defaults, GF_DASHPSSHMode pssh_mode, Bool samplegroups_in_traf, Bool single_traf_per_moof, Bool tfdt_per_traf, Bool mvex_after_traks, u32 sdtp_in_traf); + +/*! + Enables insertion of UTC reference in the beginning of segments +\param dasher the DASH segmenter object +\param insert_utc if set, UTC will be inserted. Default value is disabled. +\return error code if any +*/ + +GF_Err gf_dasher_enable_utc_ref(GF_DASHSegmenter *dasher, Bool insert_utc); + +/*! + Enables real-time generation of media segments. +\param dasher the DASH segmenter object +\param real_time if set, segments are generated in real time. Only supported for single representation (potentially multiplexed) DASH session. Default is disabled. +\return error code if any +*/ +GF_Err gf_dasher_enable_real_time(GF_DASHSegmenter *dasher, Bool real_time); + +/*! + Sets where the ContentProtection element is inserted in an adaptation set. +* \param dasher the DASH segmenter object +* \param mode ContentProtection element location mode. +* \return error code if any +*/ +GF_Err gf_dasher_set_content_protection_location_mode(GF_DASHSegmenter *dasher, GF_DASH_ContentLocationMode mode); + +/*! + Sets profile extension as used by DASH-IF and DVB. +\param dasher the DASH segmenter object +\param dash_profile_extension specifies a string of profile extensions, as used by DASH-IF and DVB. +\return error code if any +*/ +GF_Err gf_dasher_set_profile_extension(GF_DASHSegmenter *dasher, const char *dash_profile_extension); + +/*! + Enable/Disable cached inputs . +\param dasher the DASH segmenter object +\param no_cache if true, input file will be reopen each time the dasher process function is called . +\return error code if any +*/ +GF_Err gf_dasher_enable_cached_inputs(GF_DASHSegmenter *dasher, Bool no_cache); + +/*! + Enable/Disable loop inputs . +\param dasher the DASH segmenter object +\param do_loop if true, input files will be looped at the end of the file in a live simulation. Otherwise a new period will be created. +\return error code if any +*/ +GF_Err gf_dasher_enable_loop_inputs(GF_DASHSegmenter *dasher, Bool do_loop); + + +/*! +DASH selector for segment split mode +\hideinitializer + */ +typedef enum +{ + /*! segment start time is greater than or equal to theoretical segment start (segment_duration*segment_number)*/ + GF_DASH_SPLIT_OUT=0, + /*! segment start time is as close as possible to theoretical segment start (segment_duration*segment_number), but may be greater*/ + GF_DASH_SPLIT_CLOSEST, + /*! segment start time is less than or equal to theoretical segment start (segment_duration*segment_number) so that the theoretical start time is always present in the segment*/ + GF_DASH_SPLIT_IN, +} GF_DASH_SplitMode; + +/*! + Enable/Disable split on bound mode. +\param dasher the DASH segmenter object +\param split_mode the desired segmentation mode +\return error code if any +*/ +GF_Err gf_dasher_set_split_mode(GF_DASHSegmenter *dasher, GF_DASH_SplitMode split_mode); + + +/*! + Enable/Disable last segment merging (disabled by default). + * \param dasher the DASH segmenter object + * \param merge_last_seg if true, last segment is merged into previous if duration less than half target dur + * \return error code if any +*/ +GF_Err gf_dasher_set_last_segment_merge(GF_DASHSegmenter *dasher, Bool merge_last_seg); + +/*! + Sets m3u8 file name - if not set, no m3u8 output +\param dasher the DASH segmenter object +\param insert_clock if true UTC clock is inserted in variant playlists in live +\return error code if any +*/ +GF_Err gf_dasher_set_hls_clock(GF_DASHSegmenter *dasher, Bool insert_clock); + +/*! + Sets cue file for the session. +\param dasher the DASH segmenter object +\param cues_file name of the cue file. This is an XML document with root 'DASHCues' element, one or multiple 'Stream' elements with attribute ID (trackID) + and timescale (trackTimescale), and a set of 'cues' elements per Stream with attributes sampleNumber, dts or cts. +\param strict_cues if true will fail if one cue doesn't match a timestamp in the stream or if the split sample is not RAP +\return error code if any +*/ +GF_Err gf_dasher_set_cues(GF_DASHSegmenter *dasher, const char *cues_file, Bool strict_cues); + +/*! + Adds a media input to the DASHer +\param dasher the DASH segmenter object +\param input media source to add +\return error code if any +*/ +GF_Err gf_dasher_add_input(GF_DASHSegmenter *dasher, const GF_DashSegmenterInput *input); + +/*! + Process the media source and generate segments +\param dasher the DASH segmenter object +\return error code if any +*/ +GF_Err gf_dasher_process(GF_DASHSegmenter *dasher); + +/*! + Returns time to wait until end of currently generated segments +\param dasher the DASH segmenter object +\param ms_ins_session if set, retrives the number of ms since the start of the dash session +\return time to wait in milliseconds +*/ +u32 gf_dasher_next_update_time(GF_DASHSegmenter *dasher, u64 *ms_ins_session); + + +/*! + Sets dasher start date, rather than use current time. Used for debugging purposes, such as simulating long lasting sessions. +\param dasher the DASH segmenter object +\param dash_utc_start_date start date as as xs:date, eg YYYY-MM-DDTHH:MM:SSZ. If 0, current time is used +*/ +void gf_dasher_set_start_date(GF_DASHSegmenter *dasher, const char *dash_utc_start_date); + + +/*! + Sets print flags for filter session +\param dasher the DASH segmenter object +\param fs_print_flags flags for statistics (1) and graph (2) printing +\return error if any +*/ +GF_Err gf_dasher_print_session_info(GF_DASHSegmenter *dasher, u32 fs_print_flags); + +/*! + Keeps UTC creation and modification dates from sources, if any (default is no) +\param dasher the DASH segmenter object +\param keep_utc if GF_TRUE, keeps UTC times +\return error if any +*/ +GF_Err gf_dasher_keep_source_utc(GF_DASHSegmenter *dasher, Bool keep_utc); + +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! + save file as fragmented movie +\param isom_file the target file to be fragmented +\param output_file name of the output file +\param max_duration_sec max fragment duration in seconds +\param use_mfra insert track fragment movie fragments +\return error if any + */ +GF_Err gf_media_fragment_file(GF_ISOFile *isom_file, const char *output_file, Double max_duration_sec, Bool use_mfra); +#endif + +/*! @} */ + + +/*! +\addtogroup mexp_grp Media Exporter +\ingroup media_grp +\brief Media exporting and extraction. + +This section documents functions for media exporting and extraction. + +@{ + */ + +#ifndef GPAC_DISABLE_MEDIA_EXPORT + +/*! +Track dumper formats and flags +\hideinitializer + */ +enum +{ + /*! track dumper types are formatted as flags for conveniency for + authoring tools, but never used as a OR'ed set*/ + /*native format (JPG, PNG, MP3, etc) if supported*/ + GF_EXPORT_NATIVE = 1, + /*! raw samples (including hint tracks for rtp)*/ + GF_EXPORT_RAW_SAMPLES = (1<<1), + /*! NHNT format (any MPEG-4 media)*/ + GF_EXPORT_NHNT = (1<<2), + /*! full remux of source file - equivalent to `gpac -i in_name:FID=1 reframer:FID=2:SID=1 -o out_name:SID=2` */ + GF_EXPORT_REMUX = (1<<3), + /*! MP4 (all except OD)*/ + GF_EXPORT_MP4 = (1<<4), + /*! currently unused*/ + GF_EXPORT_UNUSED = (1<<4), + /*! NHML format (any media)*/ + GF_EXPORT_NHML = (1<<6), + /*! SAF format*/ + GF_EXPORT_SAF = (1<<7), + /*! WebVTT metadata format (any media)*/ + GF_EXPORT_WEBVTT_META = (1<<8), + /*! WebVTT metadata format: media data will be embedded in webvtt*/ + GF_EXPORT_WEBVTT_META_EMBEDDED = (1<<9), + + /*! following ones are real flags*/ + /*! + for MP4 extraction, indicates track should be added to dest file if any + for raw extraction, indicates data shall be appended at the end of output file if present + */ + GF_EXPORT_MERGE = (1<<10), + /*! don't infer file extension */ + GF_EXPORT_NO_FILE_EXT = (1 << 11), + /*! indicates QCP file format possible as well as native (EVRC and SMV audio only)*/ + GF_EXPORT_USE_QCP = (1<<12), + /*! indicates full NHML dump*/ + GF_EXPORT_NHML_FULL = (1<<13), + /*! exports a single svc layer*/ + GF_EXPORT_SVC_LAYER = (1<<14), + /*! Don't merge identical cues in consecutive samples */ + GF_EXPORT_WEBVTT_NOMERGE = (1<<15), + + /*! Experimental Streaming Instructions */ + GF_EXPORT_SIX = (1<<16), + + /*! only probes extraction format*/ + GF_EXPORT_PROBE_ONLY = (1<<30), + /*when set by user during export, will abort*/ + GF_EXPORT_DO_ABORT = 0x80000000 //(1<<31) +}; + +/*! + track dumper + */ +typedef struct __track_exporter +{ + /*! source ISOBMF file */ + GF_ISOFile *file; + /*! ID of track/PID/... to be dumped*/ + u32 trackID; + /*! sample number to export for GF_EXPORT_RAW_SAMPLES only*/ + u32 sample_num; + /*! output name, if no extension set the extension will be added based on track type*/ + char *out_name; + /*! dump type and flags*/ + u32 flags; + /*! non-ISOBMF source file (AVI, TS)*/ + char *in_name; + /*! optional FILE for output*/ + FILE *dump_file; + /*! filter session dump flags*/ + u32 print_stats_graph; + /*! track type: 0: none specified, 1: video, 2: audio*/ + u32 track_type; +} GF_MediaExporter; + +/*! + Dumps a given media track +\param dump the track dumper object +\return error if any + */ +GF_Err gf_media_export(GF_MediaExporter *dump); + +#ifndef GPAC_DISABLE_VTT +/*! dumps a webvtt tracl to a given file +\param dumper media dumper object +\param trackNumber the target track to dump +\param merge if GF_TRUE, merge vtt cues while dumping them +\param box_dump if GF_TRUE, dumps box structures +\return error if any +*/ +GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, u32 trackNumber, Bool merge, Bool box_dump); +#endif + +#endif /*GPAC_DISABLE_MEDIA_EXPORT*/ + +/*! @} */ + + +/*! +\addtogroup mhint_grp Media Hinting +\ingroup media_grp +\brief ISOBMFF file hinting. + + This section documents functions for ISOBMFF file hinting. + +@{ + */ + + +#ifndef GPAC_DISABLE_ISOM_HINTING +/* + RTP IsoMedia file hinting +*/ +/*! ISOBMFF RTP hinter object*/ +typedef struct __tag_isom_hinter GF_RTPHinter; + +/*! + Creates a new track hinter object +\param isom_file the target ISOBMF file +\param trackNumber the track to hint +\param Path_MTU max RTP packet size (excluding IP/UDP/IP headers) +\param max_ptime max packet duration in RTP timescale, can be set to 0 for auto compute +\param default_rtp_rate RTP rate for the track, can be set to 0 for auto compute +\param hint_flags RTP flags as defined in <gpac/ietf.h> +\param PayloadID RTP payload ID, can be set to 0 for auto compute +\param copy_media if set, media is copied inside the hint samples, otherwise only referenced from the media track +\param InterleaveGroupID sets the group ID of this track for interleaving - same semantics as in gf_isom_set_track_interleaving_group +\param InterleaveGroupPriority sets the group priority of this track for interleaving - same semantics as in gf_isom_set_track_priority_in_group +\param e output error code if any +\return the hinter object + */ +GF_RTPHinter *gf_hinter_track_new(GF_ISOFile *isom_file, u32 trackNumber, + u32 Path_MTU, u32 max_ptime, u32 default_rtp_rate, u32 hint_flags, u8 PayloadID, + Bool copy_media, u32 InterleaveGroupID, u8 InterleaveGroupPriority, GF_Err *e); + +/*! + Delete the track hinter +\param tkHinter track hinter object +*/ +void gf_hinter_track_del(GF_RTPHinter *tkHinter); + +/*! + hints all samples in the media track +\param tkHinter track hinter object +\return error if any + */ +GF_Err gf_hinter_track_process(GF_RTPHinter *tkHinter); + +/*! + Gets media bandwidth in kbps +\param tkHinter track hinter object +\return media bandwidth in kbps +*/ +u32 gf_hinter_track_get_bandwidth(GF_RTPHinter *tkHinter); + +/*! + Force file to use no random offsets for sequence number and time, if supported by server +\param tkHinter track hinter object +\return error if any +*/ +GF_Err gf_hinter_track_force_no_offsets(GF_RTPHinter *tkHinter); + +/*! + Gets track hinter flags +\param tkHinter track hinter object +\return hint flags for this object + */ +u32 gf_hinter_track_get_flags(GF_RTPHinter *tkHinter); + +/*! + Gets rtp payload name +\param tkHinter track hinter object +\param payloadName static buffer for retrieval, minimum 30 bytes +*/ +void gf_hinter_track_get_payload_name(GF_RTPHinter *tkHinter, char *payloadName); + +/*! + Finalizes hinting process for the track (setup flags, write SDP for RTP, ...) +\param tkHinter track hinter object +\param AddSystemInfo if true, systems info are duplicated in the SDP (decoder cfg, PL IDs ..) +\return error if any +*/ +GF_Err gf_hinter_track_finalize(GF_RTPHinter *tkHinter, Bool AddSystemInfo); + +/*! +SDP IOD Profile +\hideinitializer + */ +typedef enum +{ + /*! no IOD included*/ + GF_SDP_IOD_NONE = 0, + /*! base64 encoding of the regular MPEG-4 IOD*/ + GF_SDP_IOD_REGULAR, + /*! base64 encoding of IOD containing BIFS and OD tracks (one AU only) - this is used for ISMA 1.0 profiles + note that the "hinted" file will loose all systems info*/ + GF_SDP_IOD_ISMA, + /*! same as ISMA but removes all clock references from IOD*/ + GF_SDP_IOD_ISMA_STRICT, +} GF_SDP_IODProfile; + +/*! +Finalizes hinting process for the file (setup flags, write SDP for RTP, ...) +\param isom_file target ISOBMF file +\param IOD_Profile the IOD profile to use for SDP +\param bandwidth total bandwidth in kbps of all hinted tracks, 0 means no bandwidth info at session level +\return error if any +*/ +GF_Err gf_hinter_finalize(GF_ISOFile *isom_file, GF_SDP_IODProfile IOD_Profile, u32 bandwidth); + +/*! + Check if the given data fits in an ESD url +\param data data to be encoded +\param data_size size of data to be encoded +\param streamType systems stream type needed to signal data mime-type (OD, BIFS or any) +\return GF_TRUE if the encoded data fits in an ESD url + */ +Bool gf_hinter_can_embbed_data(u8 *data, u32 data_size, u32 streamType); + +#endif /*GPAC_DISABLE_ISOM_HINTING*/ + +/*! @} */ + + +/*! +\addtogroup msaf_grp LASeR SAF creation +\ingroup media_grp +\brief LASeR SAF multiplexing. + +This section documents functions for LASeR SAF multiplexing. + +@{ + */ + + +/*! SAF Multiplexer object. The multiplexer supports concurencial (multi-threaded) access*/ +typedef struct __saf_muxer GF_SAFMuxer; +/*! + Creates a new SAF Multiplexer +\return the SAF multiplexer object + */ +GF_SAFMuxer *gf_saf_mux_new(); + +/*! + SAF Multiplexer destructor +\param mux the SAF multiplexer object + */ +void gf_saf_mux_del(GF_SAFMuxer *mux); + +/*! + Adds a new stream in the SAF multiplex +\param mux the SAF multiplexer object +\param stream_id ID of the SAF stream to create +\param ts_res timestamp resolution for AUs in this stream +\param buffersize_db size of decoding buffer in bytes +\param stream_type MPEG-4 systems stream type of this stream +\param object_type MPEG-4 systems object type indication of this stream +\param mime_type MIME type for this stream, NULL if unknown +\param dsi Decoder specific info for this stream +\param dsi_len specific info size for this stream +\param remote_url URL of the SAF stream if not embedded in the multiplex +\return error if any + */ +GF_Err gf_saf_mux_stream_add(GF_SAFMuxer *mux, u32 stream_id, u32 ts_res, u32 buffersize_db, u8 stream_type, u8 object_type, char *mime_type, char *dsi, u32 dsi_len, char *remote_url); + + +/*! + adds an AU to the given Warning, AU data will be freed by the multiplexer. AUs are NOT re-sorted by CTS, in order to enable audio interleaving. +\param mux the SAF multiplexer object +\param stream_id ID of the SAF stream to remove +\param CTS composition timestamp of the AU +\param data payload of the AU +\param data_len payload size of the AU +\param is_rap set to GF_TRUE to signal a random access point +\return error if any +*/ +GF_Err gf_saf_mux_add_au(GF_SAFMuxer *mux, u32 stream_id, u32 CTS, char *data, u32 data_len, Bool is_rap); + +/*! + Gets the content of the multiplexer for the given time. +\param mux the SAF multiplexer object +\param time_ms target mux time in ms +\param force_end_of_session if set to GF_TRUE, this flushes the SAF Session - no more operations will be allowed on the muxer +\param out_data output SAF data +\param out_size output SAF data size +\return error if any + */ +GF_Err gf_saf_mux_for_time(GF_SAFMuxer *mux, u32 time_ms, Bool force_end_of_session, u8 **out_data, u32 *out_size); + +/*! + Gets timescale and TS increment from double FPS value. +\param fps the target fps +\param timescale output timescale value +\param ts_inc output timestamp increment value +*/ +void gf_media_get_video_timing(Double fps, u32 *timescale, u32 *ts_inc); + +/*! gets dolby vision level + \param width width in pixels of video + \param height height in pixels of video + \param fps_num framerate numerator + \param fps_den framerate denominator + \param codecid GPAC codec ID + \return dv level +*/ +u32 gf_dolby_vision_level(u32 width, u32 height, u64 fps_num, u64 fps_den, u32 codecid); + +#ifdef __cplusplus +} +#endif + +/*! @} */ + +#endif /*_GF_MEDIA_H_*/ + diff --git a/include/gpac/mediaobject.h b/include/gpac/mediaobject.h new file mode 100644 index 0000000..b9133f8 --- /dev/null +++ b/include/gpac/mediaobject.h @@ -0,0 +1,416 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / Stream Management 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_MEDIA_OBJECT_H_ +#define _GF_MEDIA_OBJECT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/mediaobject.h> +\brief Interface between compositor and decoding engine for media data access. + */ + +/*! +\addtogroup mobj_grp MediaObject +\ingroup playback_grp +\brief Interface between compositor and decoding engine for media data access. + +This section documents the API between the compositor of GPAC and the decoding engine (terminal) + +@{ + */ + +#include <gpac/filters.h> +#include <gpac/scenegraph_vrml.h> + + +/*! Media Object + + opaque handler for all natural media objects (audio, video, image) so that compositor and systems engine +are not too tied up. + \note The media object location relies on the node parent graph (this is to deal with namespaces in OD framework) +therefore it is the task of the media management app to setup clear links between the scene graph and its resources +(but this is not mandatory, cf URLs in VRML ) +*/ +typedef struct _mediaobj GF_MediaObject; + +/*! locates media object related to the given node - url designes the object to find - returns NULL if +URL cannot be handled - note that until the mediaObject.isInit member is true, the media object is not valid +(and could actually never be) +\param node node querying the URL +\param url the URL to open +\param lock_timelines if GF_TRUE, forces media timelines in remote URL to be locked to the node timeline +\param force_new_res if GF_TRUE, forces loading of a new resource +\return new media object or NULL of URL not valid + */ +GF_MediaObject *gf_mo_register(GF_Node *node, MFURL *url, Bool lock_timelines, Bool force_new_res); +/*! unregisters the node from the media object +\param node node querying the URL +\param mo the media object to unregister - can be destroyed during this function call +*/ +void gf_mo_unregister(GF_Node *node, GF_MediaObject *mo); + +/*! opens media object +\param mo the target media object +\param clipBegin the playback start time in seconds +\param clipEnd the playback end time in seconds +\param can_loop if GF_TRUE, indicates the playback can be looped +*/ +void gf_mo_play(GF_MediaObject *mo, Double clipBegin, Double clipEnd, Bool can_loop); + +/*! stops media object. Object is set to null if stop triggers a removal. +If not removed, video memory is not reset, last frame is kept +\param mo the target media object +*/ +void gf_mo_stop(GF_MediaObject **mo); + +/*! restarts media object - shall be used for all looping media instead of stop/play for mediaControl +to restart appropriated objects +\param mo the target media object +*/ +void gf_mo_restart(GF_MediaObject *mo); +/*! pauses a media object +\param mo the target media object +*/ +void gf_mo_pause(GF_MediaObject *mo); +/*! resumes a media object +\param mo the target media object +*/ +void gf_mo_resume(GF_MediaObject *mo); + +/*! + Note on mediaControl: mediaControl is the media management app responsability, therefore +is hidden from the rendering app. Since MediaControl overrides default settings of the node (speed and loop) +you must use the gf_mo_get_speed and gf_mo_get_loop in order to know whether the related field applies or not +*/ + +/*! sets speed of media - speed is not always applied, depending on media control settings. +\note audio pitching is the responsability of the rendering app +\param mo the target media object +\param speed the playback speed to set +*/ +void gf_mo_set_speed(GF_MediaObject *mo, Fixed speed); +/*! gets current speed of media +\param mo the target media object +\param in_speed is the speed of the media as set in the node (MovieTexture, AudioClip and AudioSource) +\return the real speed of the media as overloaded by mediaControl if any*/ +Fixed gf_mo_get_speed(GF_MediaObject *mo, Fixed in_speed); +/*! gets current looping flag of media +\param mo the target media object +\param in_loop is the looping flag of the media as set in the node (MovieTexture, AudioClip) +\return the real loop flag of the media as overloaded by mediaControl if any*/ +Bool gf_mo_get_loop(GF_MediaObject *mo, Bool in_loop); +/*! get media object duration +\param mo the target media object +\return media object duration*/ +Double gf_mo_get_duration(GF_MediaObject *mo); +/*! checks if object should be deactivated (stop) or not - this checks object status as well as +mediaControl status +\param mo the target media object +\return GF_TRUE if should be deactivated*/ +Bool gf_mo_should_deactivate(GF_MediaObject *mo); +/*! checks whether the target object is changed - you MUST use this in order to detect url changes +\param mo the target media object +\param url URL to compare to object current URL +\return GF_TRUE if URL changed +*/ +Bool gf_mo_url_changed(GF_MediaObject *mo, MFURL *url); + + +/*! gets minimum frame duration for an object +\param mo the target media object +\return min frame duration or 0 if unknown*/ +u32 gf_mo_get_min_frame_dur(GF_MediaObject *mo); +/*! map a timestamp to the object clock +\param mo the target media object +\param ts a timestamp in media object clock base +\return the timestamp in system time base +*/ +u32 gf_mo_map_timestamp_to_sys_clock(GF_MediaObject *mo, u32 ts); +/*! checks if object is buffering +\param mo the target media object +\return GF_TRUE if object is buffering*/ +Bool gf_mo_is_buffering(GF_MediaObject *mo); + +/*! frame fetch mode for media object*/ +typedef enum +{ + //never resync the content of the decoded media buffer (used fo audio) + //if clock is paused do not fetch + GF_MO_FETCH = 0, + //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) + GF_MO_FETCH_PAUSED +} GF_MOFetchMode; + +/*! fetches media data +\param mo the target media object +\param resync resync mode for fetch operation +\param upload_time_ms average time needed to push frame on GPU +\param eos set to end of stream status of the media +\param timestamp set to the frmae timestamp in object time +\param size set to the frame data size +\param ms_until_pres set to the number of milliseconds until presentation is due +\param ms_until_next set to the number of milliseconds until presentation of next frame is due +\param outFrame set to the associated frame interface object if any +\param planar_size set to the planar size of audio data, or 0 +\return frame data +*/ +u8 *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_time_ms, Bool *eos, u32 *timestamp, u32 *size, s32 *ms_until_pres, s32 *ms_until_next, GF_FilterFrameInterface **outFrame, u32 *planar_size); + +/*! releases given amount of media data - nb_bytes is used for audio +\param mo the target media object +\param nb_bytes number of audio bytes to remove (ignored for other media types) +\param drop_mode can take the following values: +-1: do not drop +0: do not force drop: the unlocked frame it will be dropped based on object time (typically video) +1: force drop : the unlocked frame will be dropped if all bytes are consumed (typically audio) +2: the frame will be stated as a discraded frame +*/ +void gf_mo_release_data(GF_MediaObject *mo, u32 nb_bytes, s32 drop_mode); +/*! gets object clock +\param mo the target media object +\param obj_time set to the object current time in milliseconds in object time base +*/ +void gf_mo_get_object_time(GF_MediaObject *mo, u32 *obj_time); +/*! checks if object is muted. Muted media shouldn't be displayed +\param mo the target media object +\return GF_TRUE if muted +*/ +Bool gf_mo_is_muted(GF_MediaObject *mo); +/*! checks if a media object is done +\param mo the target media object +\return GF_TRUE if end of stream*/ +Bool gf_mo_is_done(GF_MediaObject *mo); +/*! adjusts clock sync (only audio objects are allowed to use this) +\param mo the target media object +\param ms_drift drift in milliseconds between object clock and actual rendering time +*/ +void gf_mo_adjust_clock(GF_MediaObject *mo, s32 ms_drift); + +/*! checks if a media object is started +\param mo the target media object +\return GF_TRUE if started*/ +Bool gf_mo_is_started(GF_MediaObject *mo); + +/*! gets visual information of a media object +\param mo the target media object +\param width set to width in pixels +\param height set to height in pixels +\param stride set to stride in bytes for visual objects with data frame, 0 if unknown +\param pixel_ar set to the pixel aspect ratio as \code (PAR_NUM<<16)|PAR_DEN \endcode +\param pixelFormat set to the pixel format of the video +\param is_flipped set to GF_TRUE if the pixels are vertically flipped (happens when reading back OpenGL textures) +\return GF_TRUE if success*/ +Bool gf_mo_get_visual_info(GF_MediaObject *mo, u32 *width, u32 *height, u32 *stride, u32 *pixel_ar, u32 *pixelFormat, Bool *is_flipped); + +/*! gets number of views for 3D video object +\param mo the target media object +\param nb_views set to the number of views in the object, vertically packed +*/ +void gf_mo_get_nb_views(GF_MediaObject *mo, u32 *nb_views); + +/*! gets visual information of a media object +\param mo the target media object +\param sample_rate set to the sampling frequency of the object +\param afmt set to the decoded PCM audio format +\param num_channels set to the number of channels +\param channel_config set to the channel configuration +\param forced_layout set to GF_TRUE if the channel layout is forced (prevents recomputing the layout for mono/stereo setups) +\return GF_TRUE if success*/ +Bool gf_mo_get_audio_info(GF_MediaObject *mo, u32 *sample_rate, u32 *afmt, u32 *num_channels, u64 *channel_config, Bool *forced_layout); + +/*! gets current playback speed of a media object +\param mo the target media object +\return the playback speed*/ +Fixed gf_mo_get_current_speed(GF_MediaObject *mo); + +/*! checks if the service associated withthis object has an audio stream +\param mo the target media object +\return 0 if no audio is associated, 1 if there is an audio object associated, 2 if the service is not yet ready (not connected)*/ +u32 gf_mo_has_audio(GF_MediaObject *mo); + +/*! media object user flags*/ +typedef enum +{ + /*used by animation stream to remove TEXT from display upon delete and URL change*/ + GF_MO_DISPLAY_REMOVE = (1<<1), + /*used when resyncing a stream (dropping late frames)*/ + GF_MO_IN_RESYNC = (1<<2), +} GF_MOUserFlags; +/*! sets flags on a media object +\param mo the target media object +\param flag the flag(s) to adjust +\param set_on if GF_TRUE, set the flag(s), otherwise removes the flag(s) +*/ +void gf_mo_set_flag(GF_MediaObject *mo, GF_MOUserFlags flag, Bool set_on); + +/*! loads a new resource as indicated in the xlink:href attribute of the node. If this points to a fragment +of the current document, returns NULL. This will automatically trigger a play request on the resource +\param node node with xlink:href attribute to a media resource +\param primary_resource indicates if this is a primary resource (foreign object in SVG) +\param clipBegin the playback start time in seconds +\param clipEnd the playback end time in seconds +\return the media object for this resource +*/ +GF_MediaObject *gf_mo_load_xlink_resource(GF_Node *node, Bool primary_resource, Double clipBegin, Double clipEnd); +/*! unloads a media object associated with a given node through xlink:href +\param node node with xlink:href attribute to the media resource +\param mo the target media object +*/ +void gf_mo_unload_xlink_resource(GF_Node *node, GF_MediaObject *mo); +/*! gets the scene graph associated with a scene/document object +\param mo the target media object +\return the scene graph, or NULL if wrong type or not loaded +*/ +GF_SceneGraph *gf_mo_get_scenegraph(GF_MediaObject *mo); + +/*! VR and SRD (Spatial Relationship Description) information of media object*/ +typedef struct +{ + u32 vr_type; + s32 srd_x; + s32 srd_y; + s32 srd_w; + s32 srd_h; + + s32 srd_min_x; + s32 srd_min_y; + s32 srd_max_x; + s32 srd_max_y; + + u32 scene_width; + u32 scene_height; + + Bool has_full_coverage; + Bool is_tiled_srd; +} GF_MediaObjectVRInfo; + +/*! gets SRD and VR info for a media object. Returns FALSE if no VR and no SRD info +\param mo the target media object +\param vr_info set to the VR and SRD info of the media object +\return GF_TRUE if SRD info present +*/ +Bool gf_mo_get_srd_info(GF_MediaObject *mo, GF_MediaObjectVRInfo *vr_info); + +/*! sets quality degradation hint for a media object +\param mo the target media object +\param quality_degradation quality hint value between 0 (max quality) and 100 (worst quality) +*/ +void gf_mo_hint_quality_degradation(GF_MediaObject *mo, u32 quality_degradation); + +/*! sets visible rectangle for a media object, only used in 360 videos for now +\param mo the target media object +\param min_x minimum horizontal coordinate of visible region, in pixel in the media frame (0 being left column) +\param max_x maximum horizontal coordinate of visible region, in pixel in the media frame (width being right column) +\param min_y minimum vertical coordinate of visible region, in pixel in the media frame (0 being top row) +\param max_y maximum vertical coordinate of visible region, in pixel in the media frame (height being bottom row) +*/ +void gf_mo_hint_visible_rect(GF_MediaObject *mo, u32 min_x, u32 max_x, u32 min_y, u32 max_y); + +/*! sets gaze position in a media object, only used in 360 videos for now +\param mo the target media object +\param gaze_x minimum horizontal coordinate of visible region, in pixel in the media frame (0 being left column, width being right column) +\param gaze_y maximum horizontal coordinate of visible region, in pixel in the media frame (0 being top row, bottom row) +*/ +void gf_mo_hint_gaze(GF_MediaObject *mo, u32 gaze_x, u32 gaze_y); + +#include <gpac/scenegraph_svg.h> +/*! destroys a media object +\param mo the target media object +*/ +void gf_mo_del(GF_MediaObject *mo); + +/*! adds event target node to a media object +\param mo the target media object +\param node the event target node +\return a DOM event target interface object +*/ +GF_DOMEventTarget *gf_mo_event_target_add_node(GF_MediaObject *mo, GF_Node *node); +/*! removes event target node from a media object +\param mo the target media object +\param node the event target node +\return error if any +*/ +GF_Err gf_mo_event_target_remove_by_node(GF_MediaObject *mo, GF_Node *node); +/*! counts number of event targets associated with a media object +\param mo the target media object +\return number of event targets +*/ +u32 gf_mo_event_target_count(GF_MediaObject *mo); + +/*! removes event target node from a media object by index +\param mo the target media object +\param index 0-based index of the event target to remove +\return error if any +*/ +GF_Err gf_mo_event_target_remove_by_index(GF_MediaObject *mo, u32 index); +/*! gets event target node of a media object by index +\param mo the target media object +\param index 0-based index of the event target to get +\return a DOM event target interface object +*/ +GF_DOMEventTarget *gf_mo_event_target_get(GF_MediaObject *mo, u32 index); + +/*! resets all event targets of a media object +\param mo the target media object +*/ +void gf_mo_event_target_reset(GF_MediaObject *mo); + +/*! finds an even target interface of a media object associated to a given node +\param mo the target media object +\param node the node associated with the event target +\return the index of the event target interface object +*/ +s32 gf_mo_event_target_find_by_node(GF_MediaObject *mo, GF_Node *node); +/*! enumerates event target nodes associated with a media object +\param mo the target media object +\param i current index to query, incremented upon function return +\return an event target node +*/ +GF_Node *gf_mo_event_target_enum_node(GF_MediaObject *mo, u32 *i); +/*! gets the event target node of an event target interface +\param target the target event target interface +\return the associated node, NULL if error +*/ +GF_Node *gf_event_target_get_node(GF_DOMEventTarget *target); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_MEDIA_OBJECT_H_*/ + + diff --git a/include/gpac/module.h b/include/gpac/module.h new file mode 100644 index 0000000..38733bf --- /dev/null +++ b/include/gpac/module.h @@ -0,0 +1,291 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_MODULE_H_ +#define _GF_MODULE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/module.h> +\brief plugable dynamic module. + */ + +/*! +\ingroup mods_grp +\brief Plugable Dynamic Modules + +This section documents the plugable module functions of the GPAC framework. +A module is a dynamic/shared library providing one or several interfaces to the GPAC framework. +A module cannot provide several interfaces of the same type. Each module must export the following functions: + \code + u32 *QueryInterfaces(u32 interface_type); + \endcode + This function is used to query supported interfaces. It returns a zero-terminated array of supported interface types.\n + \code + GF_BaseInterface *LoadInterface(u32 interface_type); + \endcode + This function is used to load an interface. It returns the interface object, NULL if error.\n + \code + void ShutdownInterface(GF_BaseInterface *interface); + \endcode + This function is used to destroy an interface.\n\n + Each interface must begin with the interface macro in order to be type-casted to the base interface structure. + \code + struct { + GF_DECL_MODULE_INTERFACE + extensions; + }; + \endcode + +@{ + */ + +#include <gpac/config_file.h> + +/*! +\brief common module interface +\hideinitializer + +This is the module interface declaration macro. It must be placed first in an interface structure declaration. +*/ +#define GF_DECL_MODULE_INTERFACE \ + u32 InterfaceType; \ + const char *module_name; \ + const char *author_name; \ + void *HPLUG; \ + +/*! +\brief Base Interface + * + *This structure represent a base interface, e.g. the minimal interface declaration without functionalities. Each interface is + *type-casted to this structure and shall always be checked against its interface type. API Versioning is taken care of in the + *interface type itsel, changing at each modification of the interface API + */ +typedef struct +{ + GF_DECL_MODULE_INTERFACE +} GF_BaseInterface; + +/*! +\brief module interface registration +\hideinitializer + +This is the module interface registration macro. A module must call this macro whenever creating a new interface. + - \e _ifce: interface being registered + - \e _ifce_type: the four character code defining the interface type. + - \e _ifce_name: a printable string giving the interface name (const char *). + - \e _ifce_author: a printable string giving the author name (const char *). + \n + This is a sample GPAC codec interface declaration: + \code +GF_BaseInterface *MyDecoderInterfaceLoad() { + GF_MediaDecoder *ifce; + GF_SAFEALLOC(ifce, GF_MediaDecoder); + GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "Sample Decoder", "The Author") + //follows any initialization private to the decoder + return (GF_BaseInterface *)ifce; +} + \endcode +*/ +#define GF_REGISTER_MODULE_INTERFACE(_ifce, _ifce_type, _ifce_name, _ifce_author) \ + _ifce->InterfaceType = _ifce_type; \ + _ifce->module_name = _ifce_name ? _ifce_name : "unknown"; \ + _ifce->author_name = _ifce_author ? _ifce_author : "gpac distribution"; \ + +/*! +\brief static module declaration + \hideinitializer +*/ +#ifdef GPAC_STATIC_MODULES +#define GPAC_MODULE_EXPORT static +#else +#define GPAC_MODULE_EXPORT GF_EXPORT +#endif + +/*! +\brief Interface Registry + +This structure represent a base interface loader, when not using dynamic / shared libraries + */ +typedef struct +{ + /*! name of interface*/ + const char *name; + /*! query interface callback*/ + const u32 *(*QueryInterfaces) (); + /*! load interface callback*/ + GF_BaseInterface * (*LoadInterface) (u32 InterfaceType); + /*! shutdown interface callback*/ + void (*ShutdownInterface) (GF_BaseInterface *interface_obj); +} GF_InterfaceRegister; + +/*! +\brief module interface +\hideinitializer + +Module interface function export. Modules that can be compiled in libgpac rather than in sharde libraries shall use this macro to declare the 3 exported functions +*/ +#ifdef GPAC_STATIC_MODULES + +#define GPAC_MODULE_STATIC_DECLARATION(__name) \ + GF_InterfaceRegister *gf_register_module_##__name() { \ + GF_InterfaceRegister *reg; \ + GF_SAFEALLOC(reg, GF_InterfaceRegister); \ + if (!reg) return NULL;\ + reg->name = "gsm_" #__name; \ + reg->QueryInterfaces = QueryInterfaces; \ + reg->LoadInterface = LoadInterface; \ + reg->ShutdownInterface = ShutdownInterface; \ + return reg;\ + } \ + +#else +#define GPAC_MODULE_STATIC_DECLARATION(__name) +#endif + +/*! +\brief load a static module given its interface function + +\param register_module the register interface function +\return error if any + */ +GF_Err gf_module_load_static(GF_InterfaceRegister *(*register_module)()); + +/*! +\brief declare a module for loading + +When using GPAC as a static library, if GPAC_MODULE_CUSTOM_LOAD is +defined, this macro can be used with GF_MODULE_STATIC_DECLARE() and +gf_module_refresh() to load individual modules. +\warning function load_all_modules You will need to patch src/utils/module.c + */ +#ifdef __cplusplus +#define GF_MODULE_STATIC_DECLARE(_name) \ + extern "C" GF_InterfaceRegister *gf_register_module_##_name() +#else +#define GF_MODULE_STATIC_DECLARE(_name) \ + GF_InterfaceRegister *gf_register_module_##_name() +#endif +/*! +\brief load a static module given its name + +Use this function to load a statically compiled module. GF_MODULE_STATIC_DECLARE() should be called before and gf_modules_refresh() after loading all the needed modules. + +\param _name the module name +\see GF_MODULE_STATIC_DECLARE() gf_modules_refresh() + */ +#define GF_MODULE_LOAD_STATIC(_name) \ + gf_module_load_static(gf_register_module_##_name) + +/*! +\brief get module count + +Gets the number of modules found in the manager directory +\return the number of loaded modules + */ +u32 gf_modules_count(); + +/*! +\brief get module file name + +Gets a module shared library file name based on its index +\param index the 0-based index of the module to query +\return the name of the shared library module + */ +const char *gf_modules_get_file_name(u32 index); + +/*! +\brief get module file name + +Gets a module shared library file name based on its index +\param ifce the module instance to query +\return the name of the shared library module + */ +const char *gf_module_get_file_name(GF_BaseInterface *ifce); + +/*! +\brief loads an interface + +Loads an interface in the desired module. +\param index the 0-based index of the module to load the interface from +\param InterfaceFamily type of the interface to load +\return the interface object if found and loaded, NULL otherwise. + */ +GF_BaseInterface *gf_modules_load(u32 index, u32 InterfaceFamily); + +/*! +\brief loads an interface by module name + +Loads an interface in the desired module +\param mod_name the name of the module (shared library file) or of the interface as declared when registered. +\param InterfaceFamily type of the interface to load +\return the interface object if found and loaded, NULL otherwise. + */ +GF_BaseInterface *gf_modules_load_by_name(const char *mod_name, u32 InterfaceFamily); + +/*! +\brief interface shutdown + +Closes an interface +\param interface_obj the interface to close +\return error if any + */ +GF_Err gf_modules_close_interface(GF_BaseInterface *interface_obj); + + +/*! +\brief module load + +Loads a module based on a preferred name. +If not found, check for predefined names for the given interface type in fthe global config and loads by predefined name. +If still not found, enumerate modules. +\param ifce_type type of module interface to load +\param name name of preferred module +\return the loaded module, or NULL if not found +*/ +GF_BaseInterface *gf_module_load(u32 ifce_type, const char *name); + + +/*! +\brief filter module load + +Loads a filter module (not using GF_BaseInterface) from its index. Returns NULL if no such filter. +\param index index of the filter module to load +\param fsess opaque handle passed to the filter loader +\return the loaded filter register, or NULL if not found +*/ +void *gf_modules_load_filter(u32 index, void *fsess); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_MODULE_H_*/ diff --git a/include/gpac/modules/audio_out.h b/include/gpac/modules/audio_out.h new file mode 100644 index 0000000..0c3de44 --- /dev/null +++ b/include/gpac/modules/audio_out.h @@ -0,0 +1,135 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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. + * + */ + + +/* + + Note on video driver: this is not a graphics driver, the only thing requested from this driver + is accessing video memory and performing stretch of YUV and RGB on the backbuffer (bitmap node) + the graphics driver is a different entity that performs 2D rasterization + +*/ + +#ifndef _GF_MODULE_AUDIO_OUT_H_ +#define _GF_MODULE_AUDIO_OUT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*include event system*/ +#include <gpac/module.h> +/*include audio formats*/ +#include <gpac/constants.h> + + +/* + Audio hardware output module +*/ + +/*interface name and version for audio output*/ +#define GF_AUDIO_OUTPUT_INTERFACE GF_4CC('G','A','O', '5') + +/*interface returned on query interface*/ +typedef struct _audiooutput +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + /*setup system + Win32: os_handle is HWND + + if num_buffer is set, the audio driver should work with num_buffers with a total amount of audio data + equal to total_duration ms + if not set the driver is free to decide what to do + */ + GF_Err (*Setup) (struct _audiooutput *aout, void *os_handle, u32 num_buffers, u32 total_duration); + + /*shutdown system */ + void (*Shutdown) (struct _audiooutput *aout); + + /*query output frequency available - if the requested sampleRate is not available, the driver shall return the best + possible sampleRate able to handle NbChannels and NbBitsPerSample - if it doesn't handle the NbChannels + the internal mixer will do it + */ + GF_Err (*QueryOutputSampleRate)(struct _audiooutput *aout, u32 *io_desired_samplerate, u32 *io_NbChannels, u32 *io_AudioFormat); + + /*set output config - if audio is not running, driver must start it + *SampleRate, *NbChannels, *audioFormat: + input: desired value + output: final values + channel_layout is the channels output cfg, eg set of flags as specified in constants.h + */ + GF_Err (*Configure) (struct _audiooutput *aout, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_layout); + + /*returns total buffer size used in ms. This is needed to compute the min size of audio decoders output*/ + u32 (*GetTotalBufferTime)(struct _audiooutput *aout); + + /*returns audio delay in ms, eg time delay until written audio data is outputed by the sound card + This function is only called after ConfigureOutput*/ + u32 (*GetAudioDelay)(struct _audiooutput *aout); + + /*set output volume(between 0 and 100) */ + void (*SetVolume) (struct _audiooutput *aout, u32 Volume); + /*set balance (between 0 and 100, 0=full left, 100=full right)*/ + void (*SetPan) (struct _audiooutput *aout, u32 pan); + /*freezes soundcard flow - must not be NULL for self threaded + PlayType: 0: pause, 1: resume, 2: reset HW buffer and play. + */ + void (*Play) (struct _audiooutput *aout, u32 PlayType); + /*specifies whether the driver relies on the app to feed data or runs standalone*/ + Bool SelfThreaded; + + /*if not using private thread, this should perform sleep & fill of HW buffer + the audio render loop in this case is: while (run) {driver->WriteAudio(); if (reconf) Reconfig();} + the driver must therefore give back the hand to the renderer as often as possible - the usual way is: + gf_sleep until hw data can be written + write HW data + return + */ + void (*WriteAudio)(struct _audiooutput *aout); + + /*if using private thread the following MUST be provided*/ + void (*SetPriority)(struct _audiooutput *aout, u32 priority); + + /*your private data handler - should be allocated when creating the interface object*/ + void *opaque; + + /*these are assigned by the audio renderer once module is loaded*/ + + /*fills the buffer with audio data, returns effective bytes written - the rest is filled with 0*/ + u32 (*FillBuffer) (void *audio_renderer, u8 *buffer, u32 buffer_size); + void *audio_renderer; + +} GF_AudioOutput; + + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_MODULE_AUDIO_OUT_H_*/ + diff --git a/include/gpac/modules/codec.h b/include/gpac/modules/codec.h new file mode 100644 index 0000000..a7e435c --- /dev/null +++ b/include/gpac/modules/codec.h @@ -0,0 +1,329 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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_MODULE_CODEC_H_ +#define _GF_MODULE_CODEC_H_ + + +#include <gpac/module.h> +#include <gpac/mpeg4_odf.h> +#include <gpac/color.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/*multimedia processing levels*/ +enum +{ + /*normal, full processing*/ + GF_CODEC_LEVEL_NORMAL, + /*codec is late, should scale down processing*/ + GF_CODEC_LEVEL_LATE, + /*codec is very late, should turn off post-processing, even drop*/ + GF_CODEC_LEVEL_VERY_LATE, + /*input frames are already late before decoding*/ + GF_CODEC_LEVEL_DROP, + /*this is a special level indicating that a seek is happening (decode but no dispatch) + it is set dynamically*/ + GF_CODEC_LEVEL_SEEK +}; + + +/*codec resilience type*/ +enum +{ + GF_CODEC_NOT_RESILIENT=0, + GF_CODEC_RESILIENT_ALWAYS=1, + GF_CODEC_RESILIENT_AFTER_FIRST_RAP=2 +}; + +/*Define codec matrix*/ +typedef struct __matrix GF_CodecMatrix; + +/*the structure for capabilities*/ +typedef struct +{ + /*cap code cf below*/ + u16 CapCode; + union { + u32 valueInt; + Float valueFloat; + Bool valueBool; + } cap; +} GF_CodecCapability; + + +/* + all codecs capabilities +*/ + +enum +{ + /*size of a single composition unit */ + GF_CODEC_OUTPUT_SIZE = 0x01, + /*resilency: if packets are lost within an AU, resilience means the AU won't be discarded and the codec + will try to decode + 0: not resilient + 1: resilient + 2: resilient after first rap + */ + GF_CODEC_RESILIENT, + /*critical level of composition memory - if below, media management for the object */ + GF_CODEC_BUFFER_MIN, + /*maximum size in CU of composition memory */ + GF_CODEC_BUFFER_MAX, + /*flags that all AUs should be discarded till next RAP (needed in case RAPs are not carried by the transport + protocol */ + GF_CODEC_WAIT_RAP, + /*number of padding bytes needed - if the decoder needs padding input cannot be pulled and data is duplicated*/ + GF_CODEC_PADDING_BYTES, + /*codecs can be threaded at will - by default a single thread is used for all decoders and priority is handled + by the app, but a codec can configure itself to run in a dedicated thread*/ + GF_CODEC_WANTS_THREAD, + + /*video width and height and horizontal pitch (in YV12 we assume half Y pitch for U and V planes) */ + GF_CODEC_WIDTH, + GF_CODEC_HEIGHT, + GF_CODEC_STRIDE, + GF_CODEC_FPS, + GF_CODEC_FLIP, + /*Pixel Aspect Ratio, expressed as (par.num<<16) | par.den*/ + GF_CODEC_PAR, + /*video color mode - color modes are defined in constants.h*/ + GF_CODEC_PIXEL_FORMAT, + /*signal decoder performs frame re-ordering in temporal scalability*/ + GF_CODEC_REORDER, + /*signal decoder can safely handle CTS when outputting a picture. If not supported by the + decoder, the terminal will automatically handle CTS adjustments*/ + GF_CODEC_TRUSTED_CTS, + + /*set cap only, indicate smax bpp of display*/ + GF_CODEC_DISPLAY_BPP, + + /*Audio sample rate*/ + GF_CODEC_SAMPLERATE, + /*Audio num channels*/ + GF_CODEC_NB_CHAN, + /*Audio bps*/ + GF_CODEC_BITS_PER_SAMPLE, + /*audio frame format*/ + GF_CODEC_CHANNEL_CONFIG, + /*this is only used for audio in case transport mapping relies on sampleRate (RTP) + gets the CU duration in samplerate unit (type: int) */ + GF_CODEC_CU_DURATION, + /*queries whether data is RAW (directly dispatched to CompositionMemory) or not*/ + GF_CODEC_RAW_MEDIA, + /*queries or set support for usage of raw YUV from decoder memory - used for video codecs only, single frame pending*/ + GF_CODEC_RAW_MEMORY, + /*queries or set support for usage of decoded frame from decoder memory - used for video codecs only, multiple frames shall be supported*/ + GF_CODEC_FRAME_OUTPUT, + /*This is only called on scene decoders to signal that potential overlay scene should be + showed (cap.valueINT=1) or hidden (cap.valueINT=0). Currently only used with SetCap*/ + GF_CODEC_SHOW_SCENE, + /*This is only called on scene decoders, GetCap only. If the decoder may continue modifying the scene once the last AU is received, + it must set cap.valueINT to 1 (typically, text stream decoder will hold the scene for a given duration + after the last AU). Otherwise the decoder will be stopped and ask to remove any extra scene being displayed*/ + GF_CODEC_MEDIA_NOT_OVER, + + /*switches up (1), max (2), down (0) or min (-1) media quality for scalable coding. */ + GF_CODEC_MEDIA_SWITCH_QUALITY, + GF_CODEC_MEDIA_LAYER_DETACH, + + //notifies the codec all streams have been attached for the current time and that it may start init + //this is require for L-HEVC+AVC + GF_CODEC_CAN_INIT, + /*special cap indicating the codec should abort processing as soon as possible because it is about to be destroyed*/ + GF_CODEC_ABORT, + + /*sets current hitpoint on the video texture. hitpoint is an integer (valueInt) containing: + the X coord in upper 16 bits, ranging from 0 to 0xFFFF + the Y coord in lower 16 bits, ranging from 0 to 0xFFFF + x,y are in normalized texture coordinates (0,0 bottom left, 1,1 top right) + return GF_NOT_SUPPORTED if your codec does'nt handle this + */ + GF_CODEC_INTERACT_COORDS, + GF_CODEC_NBVIEWS, + GF_CODEC_NBLAYERS, + GF_CODEC_FORCE_ANNEXB, +}; + + +enum +{ + /*stream format is NOT supported by this codec*/ + GF_CODEC_NOT_SUPPORTED = 0, + /*stream type (eg audio, video) is supported by this codec*/ + GF_CODEC_STREAM_TYPE_SUPPORTED = 1, + GF_CODEC_PROFILE_NOT_SUPPORTED = 2, + /*stream format may be (partially) supported by this codec*/ + GF_CODEC_MAYBE_SUPPORTED = 127, + /*stream format is supported by this codec*/ + GF_CODEC_SUPPORTED = 255, +}; + +/* Generic interface used by both media decoders and scene decoders +@AttachStream: +Add a Stream to the codec. If DependsOnESID is NULL, the stream is a base layer +UpStream means that the decoder should send feedback on this channel. +WARNING: Feedback format is not standardized by MPEG +the same API is used for both encoder and decoder (decSpecInfo is ignored +for an encoder) +@DetachStream: +Remove stream +@GetCapabilities: +Get the desired capability given its code +@SetCapabilities +Set the desired capability given its code if possible +if the codec does not support the request capability, return GF_NOT_SUPPORTED +@CanHandleStream +Can module handle this codec? Return one of GF_CODEC_NOT_SUPPORTED, GF_CODEC_MAYBE_SUPPORTED or GF_CODEC_SUPPORTED +esd is provided for more advanced inspection ( eg MPEG4 audio/visual where a bunch of codecs are defined with same objectType). If esd is NULL, only +decoder type is checked (audio or video), not codec type +@GetDecoderName +returns codec name - only called once the stream is successfully attached +@privateStack +user defined. +*/ + +#define GF_CODEC_BASE_INTERFACE(IFCE_NAME) \ + GF_DECL_MODULE_INTERFACE \ + GF_Err (*AttachStream)(IFCE_NAME, GF_ESD *esd);\ + GF_Err (*DetachStream)(IFCE_NAME, u16 ES_ID);\ + GF_Err (*GetCapabilities)(IFCE_NAME, GF_CodecCapability *capability);\ + GF_Err (*SetCapabilities)(IFCE_NAME, GF_CodecCapability capability);\ + u32 (*CanHandleStream)(IFCE_NAME, u32 StreamType, GF_ESD *esd, u8 ProfileLevelIndication);\ + const char *(*GetName)(IFCE_NAME);\ + void *privateStack; \ + + +typedef struct _basedecoder +{ + GF_CODEC_BASE_INTERFACE(struct _basedecoder *) +} GF_BaseDecoder; + + +/*interface name and version for node decoder mainly used by AFX*/ +#define GF_NODE_DECODER_INTERFACE GF_4CC('G', 'N', 'D', '3') + +typedef struct _base_node *LPNODE; + +typedef struct _nodedecoder +{ + GF_CODEC_BASE_INTERFACE(struct _basedecoder *) + + /*Process the node data in inAU. + @inBuffer, inBufferLength: encoded input data (complete framing of encoded data) + @ES_ID: stream this data belongs too (scalable object) + @AU_Time: specifies the current AU time. This is usually unused, however is needed for decoder + handling the scene graph without input data (cf below). In this case the buffer passed is always NULL and the AU + time caries the time of the scene (or of the stream object attached to the scene decoder, cf below) + @mmlevel: speed indicator for the decoding - cf above for values*/ + GF_Err (*ProcessData)(struct _nodedecoder *, const char *inBuffer, u32 inBufferLength, + u16 ES_ID, u32 AU_Time, u32 mmlevel); + + /*attaches node to the decoder - currently only one node is only attached to a single decoder*/ + GF_Err (*AttachNode)(struct _nodedecoder *, LPNODE node); +} GF_NodeDecoder; + + + +/*interface name and version for scene decoder */ +#define GF_INPUT_DEVICE_INTERFACE GF_4CC('G', 'I', 'D', '1') + +typedef struct __input_device +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + Bool (*RegisterDevice)(struct __input_device *, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)); + void (*Start)(struct __input_device *); + void (*Stop)(struct __input_device *); + + void *udta; + + /*this is set upon loading and shall not be modified*/ + void *input_stream_context; + void (*DispatchFrame)(struct __input_device *, const u8 *data, u32 data_len); +} GF_InputSensorDevice; + + + +/*interface name and version for media decoder */ +#define GF_MEDIA_DECODER_INTERFACE GF_4CC('G', 'M', 'D', '3') + +typedef struct _mediadecoderframe +{ + //release media frame + void (*Release)(struct _mediadecoderframe *frame); + //get media frame plane + // @plane_idx: plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane + // @outPlane: address of target color plane + // @outStride: stride in bytes of target color plane + GF_Err (*GetPlane)(struct _mediadecoderframe *frame, u32 plane_idx, const char **outPlane, u32 *outStride); + + GF_Err (*GetGLTexture)(struct _mediadecoderframe *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix); + + //allocated space by the decoder + void *user_data; +} GF_MediaDecoderFrame; + +/*the media module interface. A media module MUST be implemented in synchronous mode as time +and resources management is done by the terminal*/ +typedef struct _mediadecoder +{ + GF_CODEC_BASE_INTERFACE(struct _basedecoder *) + + /*Process the media data in inAU. + @inBuffer, inBufferLength: encoded input data (complete framing of encoded data) + @ES_ID: stream this data belongs too (scalable object) + @outBuffer, outBufferLength: allocated data for decoding - if outBufferLength is not enough + you must set the size in outBufferLength and GF_BUFFER_TOO_SMALL + + @PaddingBits is the padding at the end of the buffer (some codecs need this info) + @mmlevel: speed indicator for the decoding - cf above for values*/ + GF_Err (*ProcessData)(struct _mediadecoder *, + char *inBuffer, u32 inBufferLength, + u16 ES_ID, u32 *CTS, + char *outBuffer, u32 *outBufferLength, + u8 PaddingBits, u32 mmlevel); + + + /*optional (may be null), retrievs internal output frame of decoder. this function is called only if the decoder returns GF_OK on a SetCapabilities GF_CODEC_RAW_MEMORY*/ + GF_Err (*GetOutputBuffer)(struct _mediadecoder *, u16 ES_ID, u8 **pY_or_RGB, u8 **pU, u8 **pV); + + /*optional (may be null), retrievs internal output frame object of decoder. this function is called only if the decoder returns GF_OK on a SetCapabilities GF_CODEC_FRAME_OUTPUT*/ + GF_Err (*GetOutputFrame)(struct _mediadecoder *, u16 ES_ID, GF_MediaDecoderFrame **frame, Bool *needs_resize); +} GF_MediaDecoder; + + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_MODULE_CODEC_H_*/ + diff --git a/include/gpac/modules/compositor_ext.h b/include/gpac/modules/compositor_ext.h new file mode 100644 index 0000000..381f4ba --- /dev/null +++ b/include/gpac/modules/compositor_ext.h @@ -0,0 +1,111 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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_MODULE_COMPOSITOR_EXT_H_ +#define _GF_MODULE_COMPOSITOR_EXT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/compositor.h> + +/*interface name and version for Terminal Extensions services*/ +#define GF_COMPOSITOR_EXT_INTERFACE GF_4CC('C','O','X', '1') + +typedef struct _gf_compositor_ext GF_CompositorExt; + + +typedef struct { + void *scenegraph; + void *ctx; + Bool unload; +} GF_CompositorExtJS; + +enum +{ + /*start terminal extension. If 0 is returned, the module will be unloaded + associated param: GF_Compositor *compositor + @return: 1 if OK, 0 otherwise (in which case the extensions will be removed without calling stop) + */ + GF_COMPOSITOR_EXT_START = 1, + /*stop terminal extension + associated param: NULL + @return: ignored + */ + GF_COMPOSITOR_EXT_STOP, + + /*process extension - only called GF_COMPOSITOR_EXTENSION_NOT_THREADED capability is set + associated param: NULL + @return: ignored + */ + GF_COMPOSITOR_EXT_PROCESS, + + /*load/unload js bindings of this extension + associated param: GF_CompositorExtJS *jsext + @return: ignored + */ + GF_COMPOSITOR_EXT_JSBIND, +}; + +enum +{ + /*signal the extension is to be called on regular basis (once per simulation tick). This MUST be set during + the GF_gf_compositor_ext_START command and cannot be changed at run-time*/ + GF_COMPOSITOR_EXTENSION_NOT_THREADED = 1<<1, + + GF_COMPOSITOR_EXTENSION_JS = 1<<2, +}; + + +struct _gf_compositor_ext +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + /*caps of the module*/ + u32 caps; + + /*terminal extension proc + termext: pointer to the module + action: action type of this call + param: associated param of the call + */ + Bool (*process)(GF_CompositorExt *comp_ext, u32 action, void *param); + + /*module private*/ + void *udta; +}; + + + +#ifdef __cplusplus +} +#endif + + +#endif /*#define _GF_MODULE_COMPOSITOR_EXT_H_*/ + diff --git a/include/gpac/modules/font.h b/include/gpac/modules/font.h new file mode 100644 index 0000000..a42287a --- /dev/null +++ b/include/gpac/modules/font.h @@ -0,0 +1,127 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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_MODULE_FONT_H_ +#define _GF_MODULE_FONT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/path2d.h> +#include <gpac/module.h> +#include <gpac/user.h> + + +typedef struct _gf_glyph +{ + /*glyphs are stored as linked lists*/ + struct _gf_glyph *next; + /*glyph ID as used in *_get_glyphs - this may not match the UTF name*/ + u32 ID; + /*UTF-name of the glyph if any*/ + u32 utf_name; + GF_Path *path; + /*width of the glyph - !! this can be more than the horizontal advance !! */ + u32 width; + /*glyph horizontal advance in font EM size*/ + s32 horiz_advance; + /*height of the glyph - !! this can be more than the vertical advance !! */ + u32 height; + /*glyph vertical advance in font EM size*/ + s32 vert_advance; +} GF_Glyph; + +enum +{ + /*font styles*/ + GF_FONT_ITALIC = 1, + GF_FONT_OBLIQUE = 1<<1, + /*font variant (smallcaps)*/ + GF_FONT_SMALLCAPS = 1<<2, + /*font decoration*/ + GF_FONT_UNDERLINED = 1<<3, + GF_FONT_STRIKEOUT = 1<<4, + + /*all font weight modification are placed AFTER 1<<9*/ + GF_FONT_WEIGHT_100 = 1<<10, + GF_FONT_WEIGHT_LIGHTER = 1<<11, + GF_FONT_WEIGHT_200 = 1<<12, + GF_FONT_WEIGHT_300 = 1<<13, + GF_FONT_WEIGHT_400 = 1<<14, + GF_FONT_WEIGHT_NORMAL = 1<<15, + GF_FONT_WEIGHT_500 = 1<<16, + GF_FONT_WEIGHT_600 = 1<<17, + GF_FONT_WEIGHT_700 = 1<<18, + GF_FONT_WEIGHT_BOLD = 1<<19, + GF_FONT_WEIGHT_800 = 1<<20, + GF_FONT_WEIGHT_900 = 1<<21, + GF_FONT_WEIGHT_BOLDER = 1<<22 +}; +/*mask for font styles used for CSS2 selection*/ +#define GF_FONT_STYLE_MASK 0x00000007 +/*mask for all font weight*/ +#define GF_FONT_WEIGHT_MASK 0xFFFFFC00 + +/*interface name and version for font engine*/ +#define GF_FONT_READER_INTERFACE GF_4CC('G','F','T', '4') + + +typedef struct _font_reader +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + /*inits font engine.*/ + GF_Err (*init_font_engine)(struct _font_reader *dr); + /*shutdown font engine*/ + GF_Err (*shutdown_font_engine)(struct _font_reader *dr); + + /*set active font . @styles indicates font styles (PLAIN, BOLD, ITALIC, + BOLDITALIC and UNDERLINED, STRIKEOUT)*/ + GF_Err (*set_font)(struct _font_reader *dr, const char *fontName, u32 styles); + /*gets font info*/ + GF_Err (*get_font_info)(struct _font_reader *dr, char **font_name, u32 *em_size, s32 *ascent, s32 *descent, s32 *underline, s32 *line_spacing, s32 *max_advance_h, s32 *max_advance_v); + + /*translate string to glyph sequence*/ + GF_Err (*get_glyphs)(struct _font_reader *dr, const char *utf_string, u32 *glyph_id_buffer, u32 *io_glyph_id_buffer_size, const char *xml_lang, Bool *rev_layout); + + /*loads glyph by name - returns NULL if glyph cannot be found*/ + GF_Glyph *(*load_glyph)(struct _font_reader *dr, u32 glyph_name); + + /*module private*/ + void *udta; +} GF_FontReader; + + + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_MODULE_FONT_H_*/ + diff --git a/include/gpac/modules/hardcoded_proto.h b/include/gpac/modules/hardcoded_proto.h new file mode 100644 index 0000000..0ea689e --- /dev/null +++ b/include/gpac/modules/hardcoded_proto.h @@ -0,0 +1,70 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2012 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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_MODULE_PROTO_MOD_H_ +#define _GF_MODULE_PROTO_MOD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/module.h> +#include <gpac/scenegraph.h> +#include <gpac/compositor.h> + +/*interface name and version for Built-in proto User Extensions*/ +#define GF_HARDCODED_PROTO_INTERFACE GF_4CC('G','H','P', '4') + +typedef struct _hc_proto_mod +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + /*Initialize hardcoded proto node. + itfs: ProtoModuleInterface + compositor: GPAC compositor + node: node to be loaded - this node is always a PROTO instance + proto_uri: the proto URI + */ + Bool (*init)(struct _hc_proto_mod* itfs, GF_Compositor* compositor, GF_Node* node, const char *proto_uri); + + /*check if the module can load a proto + uri: proto uri to check for support + */ + Bool (*can_load_proto)(const char* uri); + + /*module private*/ + void *udta; +} GF_HardcodedProto; + + +#ifdef __cplusplus +} +#endif + + +#endif /*#define _GF_MODULE_PROTO_MOD_H_*/ + diff --git a/include/gpac/modules/ipmp.h b/include/gpac/modules/ipmp.h new file mode 100644 index 0000000..966b8b9 --- /dev/null +++ b/include/gpac/modules/ipmp.h @@ -0,0 +1,148 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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_MODULE_IPMP_H_ +#define _GF_MODULE_IPMP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/module.h> + +/* + NOTE ON IPMP TOOLS + + The current implementation is very basic and does not follow MPEG-4 IPMPX architecture + This is just a place holder for ISMA/OMA-like schemes + Currently all operations are synchronous... +*/ + +enum +{ + /*push some configuration data to the IPMP tool*/ + GF_IPMP_TOOL_SETUP, + /*request access to the object (eg, PLAY)*/ + GF_IPMP_TOOL_GRANT_ACCESS, + /*release access to the object (eg, STOP)*/ + GF_IPMP_TOOL_RELEASE_ACCESS, + /*push some configuration data to the IPMP tool*/ + GF_IPMP_TOOL_PROCESS_DATA, +}; + +typedef struct +{ + u32 scheme_version; + u32 scheme_type; + const char *scheme_uri; + const char *kms_uri; +} GF_ISMACrypConfig; + +typedef struct +{ + u32 scheme_version; + u32 scheme_type; + u32 PSSH_count; + GF_NetComDRMConfigPSSH *PSSHs; +} GF_CENCConfig; + +typedef struct +{ + u32 scheme_version; + u32 scheme_type; + const char *scheme_uri; + const char *kms_uri; + /*SHA-1 hash*/ + u8 hash[20]; + + const char *contentID; + u32 oma_drm_crypt_type; + Bool oma_drm_use_pad, oma_drm_use_hdr; + const char *oma_drm_textual_headers; + u32 oma_drm_textual_headers_len; +} GF_OMADRM2Config; + +/*IPMP events*/ +typedef struct +{ + /*event type*/ + u32 event_type; + + /*gpac's channel triggering this event, NULL if unknown/unspecified*/ + struct _es_channel *channel; + + /*identifier of the config data (GF_IPMP_TOOL_SETUP)*/ + u32 config_data_code; + /*config data (GF_IPMP_TOOL_SETUP). Type depends on the config_data_code*/ + void *config_data; + + Bool restart_requested; + + /*data manipulation (GF_IPMP_TOOL_PROCESS_DATA) - data is always processed in-place in a + synchronous way*/ + char *data; + u32 data_size; + u32 out_data_size; + /*indicates if payload passed is encrypted or not - this is used by ISMA, OMA and 3GP*/ + Bool is_encrypted; + /*ISMA payload resync indicator*/ + u64 isma_BSO; + /*CENC sample auxiliary information*/ + char *sai; + u8 IV_size; + u32 saiz; + //for CENC pattern encryption mode + u8 crypt_byte_block, skip_byte_block; + u8 constant_IV_size; + bin128 constant_IV; +} GF_IPMPEvent; + +/*interface name and version for IPMP tools*/ +#define GF_IPMP_TOOL_INTERFACE GF_4CC('G','I','P', '1') + +typedef struct _ipmp_tool GF_IPMPTool; + +struct _ipmp_tool +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + /*process an ipmp event*/ + GF_Err (*process)(GF_IPMPTool *dr, GF_IPMPEvent *evt); + /*tool private*/ + void *udta; + +}; + + +#ifdef __cplusplus +} +#endif + + +#endif /*#define _GF_MODULE_IPMP_H_ +*/ + diff --git a/include/gpac/modules/video_out.h b/include/gpac/modules/video_out.h new file mode 100644 index 0000000..ed4f620 --- /dev/null +++ b/include/gpac/modules/video_out.h @@ -0,0 +1,237 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / modules interfaces + * + * 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. + * + */ + + +/* + + Note on video driver: this is not a graphics driver, the only thing requested from this driver + is accessing video memory and performing stretch of YUV and RGB on the backbuffer (bitmap node) + the graphics driver is a different entity that performs 2D rasterization + +*/ + +#ifndef _GF_MODULE_VIDEO_OUT_H_ +#define _GF_MODULE_VIDEO_OUT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*include module system*/ +#include <gpac/module.h> +/*include event system*/ +#include <gpac/events.h> +/*include framebuffer definition*/ +#include <gpac/color.h> + +/* + Video hardware output module +*/ + +enum +{ + /*HW supports RGB->backbuffer blitting*/ + GF_VIDEO_HW_HAS_RGB = (1<<1), + /*HW supports RGBA->backbuffer blitting*/ + GF_VIDEO_HW_HAS_RGBA = (1<<2), + /*HW supports YUV->backbuffer blitting*/ + GF_VIDEO_HW_HAS_YUV = (1<<3), + /*HW supports YUV overlays*/ + GF_VIDEO_HW_HAS_YUV_OVERLAY = (1<<4), + /*HW supports stretching for RGB and YUV buffers*/ + GF_VIDEO_HW_HAS_STRETCH = (1<<5), + /*HW supports OpenGL rendering. Whether this is OpenGL or OpenGL-ES depends on compilation settings + and cannot be changed at runtime*/ + GF_VIDEO_HW_OPENGL = (1<<6), + /*HW supports OpenGL offscreen rendering. Whether this is OpenGL or OpenGL-ES depends on compilation settings + and cannot be changed at runtime*/ + GF_VIDEO_HW_OPENGL_OFFSCREEN = (1<<7), + /*HW supports OpenGL offscreen rendering with alpha. Whether this is OpenGL or OpenGL-ES depends on compilation settings + and cannot be changed at runtime*/ + GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA = (1<<8), + + /*HW supports RGB+Depth or YUV+Depth blitting*/ + GF_VIDEO_HW_HAS_DEPTH = (1<<9), + + /*HW supports line blitting*/ + GF_VIDEO_HW_HAS_LINE_BLIT = (1<<15), + /*HW only supports direct rendering mode*/ + GF_VIDEO_HW_DIRECT_ONLY = (1<<17), +}; + +typedef struct +{ + GF_IRect *list; + u32 count; +} GF_DirtyRectangles; + +typedef struct _gf_sc_texture_handler GF_TextureH; + +/*interface name and version for video output*/ +#define GF_VIDEO_OUTPUT_INTERFACE GF_4CC('G','V','O','5') + +/* + video output interface + + the video output may run in 2 modes: 2D and 3D. + + ** the 2D video output works by accessing a backbuffer surface on the video mem board - + the app accesses to the surface through the GF_VideoSurface handler. + The module may support HW blitting of RGB or YUV data to backbuffer. + + ** the 3D video output only handles window management and OpenGL contexts setup. + The context shall be setup in Resize and SetFullScreen calls which are always happening in the main + rendering thread. This will take care of OpenGL context issues with multithreading + + By default all modules are required to be setup in 2D. If 3D is needed, a GF_EVENT_VIDEO_SETUP will + be sent with the desired configuration. + + Except Setup and Shutdown functions, all interface functions are called through the main compositor thread + or its user to avoid multithreading issues. Care must still be taken when handling events +*/ +typedef struct _video_out +{ + /* interface declaration*/ + GF_DECL_MODULE_INTERFACE + + /*setup system - if os_handle is NULL the driver shall create the output display (common case) + the other case is currently only used by child windows on win32 and winCE + @init_flags: a list of initialization flags as specified in user.h*/ + GF_Err (*Setup)(struct _video_out *vout, void *os_handle, void *os_display, u32 init_flags); + /*shutdown system */ + void (*Shutdown) (struct _video_out *vout); + + /*flush video: the video shall be presented to screen + the destination area to update is in client display coordinates (0,0) being top-left, (w,h) bottom-right + \note dest is always NULL in 3D mode (buffer flip only)*/ + GF_Err (*Flush) (struct _video_out *vout, GF_Window *dest); + + GF_Err (*SetFullScreen) (struct _video_out *vout, Bool fs_on, u32 *new_disp_width, u32 *new_disp_height); + + /*window events sent to output: + 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_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). + * This can be a request for an offscreen rendering surface. If supported, this surface SHALL + be readable through glReadPixels. If not supported, just return an error. + Note that GPAC never uses more than one GL context (offscreen or main video) + * Depending on windowing systems and implementations, it could be possible to resize a window + without destroying the GL context. If the GL context is destroyed, the module should send an event + of the same type to the player. + + This function is also called with a NULL event at the beginning of each rendering cycle, in order to allow event + handling for modules uncapable of safe multithreading (eg X11) + */ + GF_Err (*ProcessEvent)(struct _video_out *vout, GF_Event *event); + + /*pass events to user (assigned before setup) - return 1 if the event has been processed by GPAC + (eiher scene or navigation), 0 otherwise*/ + void *evt_cbk_hdl; + Bool (*on_event)(void *hdl, GF_Event *event); + + /* + All the following are 2D specific and are NEVER called in 3D mode + */ + /*locks backbuffer video memory + do_lock: specifies whether backbuffer shall be locked or released + */ + GF_Err (*LockBackBuffer)(struct _video_out *vout, GF_VideoSurface *video_info, Bool do_lock); + + /*lock video mem through OS context (only HDC for Win32 at the moment) + do_lock: specifies whether OS context shall be locked or released*/ + void *(*LockOSContext)(struct _video_out *vout, Bool do_lock); + + /*blit surface src to backbuffer - if a window is not specified, the full surface is used + the blitter MUST support stretching and RGB24 sources. Support for YUV is indicated in the hw caps + of the driver. If none is supported, just set this function to NULL and let gpac performs software blitting. + Whenever this function fails, the blit will be performed in software mode + if is_overlay is set, this is an overlay on the video memory (Flush would have been called before) + overlay_type 1: this is regular overlay without color keying + overlay_type 2: this is overlay with color keying + */ + GF_Err (*Blit)(struct _video_out *vout, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type); + + /*optional + blits the texture as a bitmap with the specified transform cliped with the given cliper, with alpha and + color keying (NULL if no keying) + */ + Bool (*BlitTexture)(struct _video_out *vout, GF_TextureH *texture, GF_Matrix2D *transform, GF_IRect *clip, u8 alpha, GF_ColorKey *col_key, Fixed depth_offset, Fixed depth_gain); + /*optional + releases any HW resource used by the texture object due to a call to BlitTexture. This is called when + the object is about to be destroyed or is no longer visible on screen + */ + void (*ReleaseTexture)(struct _video_out *vout, GF_TextureH *texture); + + /*optional + flushes only the listed rectangles + */ + void (*FlushRectangles)(struct _video_out *vout, GF_DirtyRectangles *rectangles); + + /*ignored if GF_VIDEO_HW_HAS_LINE_BLIT is not set*/ + void (*DrawHLine)(struct _video_out *vout, u32 x, u32 y, u32 length, GF_Color color); + void (*DrawHLineAlpha)(struct _video_out *vout, u32 x, u32 y, u32 length, GF_Color color, u8 alpha); + void (*DrawRectangle)(struct _video_out *vout, u32 x, u32 y, u32 width, u32 height, GF_Color color); + + + + /*set of above HW flags - some of the caps may change depeinding on the current video setup*/ + u32 hw_caps; + /*main pixel format of video board (informative only)*/ + u32 pixel_format; + /*yuv pixel format if HW YUV blitting is supported (informative only) */ + u32 yuv_pixel_format; + /*maximum resolution of the screen*/ + u32 max_screen_width, max_screen_height; + /* dpi of the screen*/ + u32 dpi_x, dpi_y; + /* max bits per color channel*/ + u32 max_screen_bpp; + + /*overlay color key used by the hardware bliter - if not set, only top-level overlay can be used*/ + u32 overlay_color_key; + + /*for auto-stereoscopic output*/ + /*maximum pixel disparity*/ + u32 disparity; + /*nominal display viewing distance in cm*/ + Fixed dispdist; + + /*driver private*/ + void *opaque; +} GF_VideoOutput; + + + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_MODULE_VIDEO_OUT_H_*/ + diff --git a/include/gpac/mpd.h b/include/gpac/mpd.h new file mode 100644 index 0000000..75db2f5 --- /dev/null +++ b/include/gpac/mpd.h @@ -0,0 +1,1238 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre - Cyril Concolato + * Copyright (c) Telecom ParisTech 2010-2022 + * All rights reserved + * + * This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module + * + * 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 _MPD_H_ +#define _MPD_H_ + +#include <gpac/constants.h> +#include <gpac/xml.h> +#include <gpac/media_tools.h> + +#ifndef GPAC_DISABLE_CORE_TOOLS + +/*! +\file <gpac/mpd.h> +\brief Utility tools for dash, smooth and HLS manifest loading. +*/ + +/*! +\addtogroup mpd_grp MPD/M3U8/Smooth Manifest Parsing +\ingroup media_grp +\brief Utility tools DASH manifests + +This section documents the DASH, Smooth and HLS manifest parsing functions of the GPAC framework. +@{ +*/ + +/*! DASH template resolution mode*/ +typedef enum +{ + /*! resolve template for segment*/ + GF_DASH_TEMPLATE_SEGMENT = 0, + /*! resolve template for initialization segment*/ + GF_DASH_TEMPLATE_INITIALIZATION, + /*! resolve template for segment template*/ + GF_DASH_TEMPLATE_TEMPLATE, + /*! resolve template for segment template with %d padding*/ + GF_DASH_TEMPLATE_TEMPLATE_WITH_PATH, + /*! resolve template for initialization segment template*/ + GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE, + /*! resolve template for segment index*/ + GF_DASH_TEMPLATE_REPINDEX, + /*! resolve template for segment index template*/ + GF_DASH_TEMPLATE_REPINDEX_TEMPLATE, + /*! resolve template for segment index template with %d padding*/ + GF_DASH_TEMPLATE_REPINDEX_TEMPLATE_WITH_PATH, + /*! same as GF_DASH_TEMPLATE_INITIALIZATION but skip default "init" concatenation */ + GF_DASH_TEMPLATE_INITIALIZATION_SKIPINIT, + /*! same as GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE but skip default "init" concatenation*/ + GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE_SKIPINIT, +} GF_DashTemplateSegmentType; + +/*! 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 +\param segment_name target buffer where the segment name is formatted - size must be GF_MAX_PATH +\param rep_id ID of the target representation +\param base_url base URL, may be NULL +\param seg_rad_name base name of the output segements (eg, myfile_ZZZ), shall not be NULL, may be empty ("") +\param seg_ext segment extensions +\param start_time start time of the segment in MPD timescale +\param bandwidth bandwidth used for the representation +\param segment_number number of the target segment +\param use_segment_timeline indicates if segmentTimeline is used for segment addressing in the MPD +\return error if any +*/ +GF_Err gf_media_mpd_format_segment_name(GF_DashTemplateSegmentType seg_type, Bool is_bs_switching, char *segment_name, const char *rep_id, const char *base_url, const char *seg_rad_name, const char *seg_ext, u64 start_time, u32 bandwidth, u32 segment_number, Bool use_segment_timeline); + +/*! metrics, not yet supported*/ +typedef struct +{ + u32 dummy; +} GF_MPD_Metrics; + +/*! Content Component description*/ +typedef struct +{ + /*! content component ID*/ + u32 id; + /*! content component mime type*/ + char *type; + /*! content component language*/ + char *lang; +} GF_MPD_ContentComponent; + + +/*! macro for extensible MPD element +Some elments are typically overloaded in XML, we keep the attributes / children nodes here. The attributes list is NULL if no extensions were found, otherwise it is a list of GF_XMLAttribute. +The children list is NULL if no extensions were found, otherwise it is a list of GF_XMLNode +*/ +#define MPD_EXTENSIBLE \ + GF_List *x_attributes; \ + GF_List *x_children; \ + +/*! basic extensible MPD descriptor*/ +typedef struct +{ + MPD_EXTENSIBLE + /*! mandatory schemeid URL*/ + char *scheme_id_uri; + /*! associated value, may be NULL*/ + char *value; + /*! associated ID, may be NULL*/ + char *id; +} GF_MPD_Descriptor; + +/*! subset, not yet supported*/ +typedef struct +{ + u32 dummy; +} GF_MPD_Subset; + +/*! SegmentTimeline entry*/ +typedef struct +{ + /*! startTime in representation's MPD timescale*/ + u64 start_time; + /*! duration in representation's MPD timescale - mandatory*/ + u32 duration; /*MANDATORY*/ + /*! may be 0xFFFFFFFF (-1) (\warning this needs further testing)*/ + u32 repeat_count; +} GF_MPD_SegmentTimelineEntry; + +/*! Segment Timeline*/ +typedef struct +{ + /*! list of entries*/ + GF_List *entries; +} GF_MPD_SegmentTimeline; + +/*! Byte range info*/ +typedef struct +{ + /*! start range (offset of first byte in associated resource)*/ + u64 start_range; + /*! start range (offset of last byte in associated resource)*/ + u64 end_range; +} GF_MPD_ByteRange; + + +/*! base URL*/ +typedef struct +{ + /*! URL*/ + char *URL; + /*! service location if any*/ + char *service_location; + /*! byte range if any*/ + GF_MPD_ByteRange *byte_range; + + /*!GPAC internal: redirection for that URL */ + char *redirection; +} GF_MPD_BaseURL; + +/*! MPD URL*/ +typedef struct +{ + /*! URL of source*/ + char *sourceURL; + /*! byte range if any*/ + GF_MPD_ByteRange *byte_range; + + /*! GPAC internal - indicates the URL has already been solved*/ + Bool is_resolved; +} GF_MPD_URL; + +/*! MPD fraction*/ +typedef struct +{ + /*! numerator*/ + u32 num; + /*! denominator*/ + u32 den; +} GF_MPD_Fractional; + +/*! GPAC internal, ISOBMFF info for base64 and other embedding, see N. Bouzakaria thesis*/ +typedef struct +{ + /*! ID of track/pid*/ + u32 trackID; + /*! base64 STSD entry*/ + char *stsd; + /*! media offset*/ + s64 mediaOffset; +} GF_MPD_ISOBMFInfo; + +/*! macro for MPD segment base*/ +#define GF_MPD_SEGMENT_BASE \ + u32 timescale; \ + u64 presentation_time_offset; \ + u32 time_shift_buffer_depth; /* expressed in milliseconds */ \ + GF_MPD_ByteRange *index_range; \ + Bool index_range_exact; \ + Double availability_time_offset; \ + GF_MPD_URL *initialization_segment; \ + GF_MPD_URL *representation_index; \ + + + +/*! MPD segment base*/ +typedef struct +{ + /*! inherits segment base*/ + GF_MPD_SEGMENT_BASE +} GF_MPD_SegmentBase; + +/*! macro for multiple segment base +WARNING: duration is expressed in GF_MPD_SEGMENT_BASE timescale unit + startnumber=(u32)-1 if unused, 1 bydefault. +*/ +#define GF_MPD_MULTIPLE_SEGMENT_BASE \ + GF_MPD_SEGMENT_BASE \ + u64 duration; \ + u32 start_number; \ + GF_MPD_SegmentTimeline *segment_timeline; \ + GF_MPD_URL *bitstream_switching_url; \ + +/*! Multiple segment base*/ +typedef struct +{ + /*! inherits multiple segment base*/ + GF_MPD_MULTIPLE_SEGMENT_BASE +} GF_MPD_MultipleSegmentBase; + +/*! segment URL*/ +typedef struct +{ + /*! media URL if any*/ + char *media; + /*! media range if any*/ + GF_MPD_ByteRange *media_range; + /*! index url if any*/ + char *index; + /*! index range if any*/ + GF_MPD_ByteRange *index_range; + /*! duration of segment*/ + u64 duration; + /*! key URL of segment, HLS only*/ + char *key_url; + /*! key IV of segment, HLS only*/ + bin128 key_iv; + /*! sequence number of segment, HLS only*/ + u32 hls_seq_num; + /*! informative UTC start time of segment, HLS only*/ + u64 hls_utc_time; + /*! 0: full segment, 1: LL-HLS part, 2: independent LL-HLS part */ + u8 hls_ll_chunk_type; + /*! merge flag for byte-range subsegs 0: cannot merge, 1: can merge */ + u8 can_merge; + /*! merge flag for byte-range subsegs 0: cannot merge, 1: can merge */ + u8 is_first_part; +} GF_MPD_SegmentURL; + +/*! SegmentList*/ +typedef struct +{ + /*! inherits multiple segment base*/ + GF_MPD_MULTIPLE_SEGMENT_BASE + /*! list of segments - can be NULL if no segment*/ + GF_List *segment_URLs; + /*! xlink URL for external list*/ + char *xlink_href; + /*! xlink evaluation on load if set, otherwise on use*/ + Bool xlink_actuate_on_load; + + /*! GPAC internal, number of consecutive xlink while solving*/ + u32 consecutive_xlink_count; + /*! GPAC internal, we store the segment template here*/ + char *dasher_segment_name; + /*! GPAC internal, we store the previous xlink before resolution*/ + char *previous_xlink_href; +} GF_MPD_SegmentList; + +/*! SegmentTemplate*/ +typedef struct +{ + /*! inherits multiple segment base*/ + GF_MPD_MULTIPLE_SEGMENT_BASE + /*! media segment template*/ + char *media; + /*! index segment template*/ + char *index; + /*! init segment template*/ + char *initialization; + /*! bitstream switching segment template*/ + char *bitstream_switching; + + /*! internal, for HLS generation*/ + const char *hls_init_name; +} GF_MPD_SegmentTemplate; + +/*! MPD scan types*/ +typedef enum +{ + /*! unknown*/ + GF_MPD_SCANTYPE_UNKNOWN, + /*! progressive*/ + GF_MPD_SCANTYPE_PROGRESSIVE, + /*! interlaced*/ + GF_MPD_SCANTYPE_INTERLACED +} GF_MPD_ScanType; + +/*! Macro for common attributes and elements (representation, AdaptationSet, Preselection, ...) + +not yet implemented; + GF_List *inband_event_stream; \ + GF_List *switching; \ + GF_List *random_access; \ + GF_List *group_labels; \ + GF_List *labels; \ + GF_List *content_popularity; \ + +MANDATORY: + mime_type + codecs +*/ +#define GF_MPD_COMMON_ATTRIBUTES_ELEMENTS \ + GF_List *x_attributes; \ + GF_List *x_children; \ + char *profiles; \ + u32 width; \ + u32 height; \ + GF_MPD_Fractional *sar; \ + GF_MPD_Fractional *framerate; \ + u32 samplerate; \ + char *mime_type; \ + char *segmentProfiles; \ + char *codecs; \ + u32 maximum_sap_period; \ + u32 starts_with_sap; \ + Double max_playout_rate; \ + Bool coding_dependency; \ + GF_MPD_ScanType scan_type; \ + u32 selection_priority; \ + char *tag; \ + GF_List *frame_packing; \ + GF_List *audio_channels; \ + GF_List *content_protection; \ + GF_List *essential_properties; \ + GF_List *supplemental_properties; \ + GF_List *producer_reference_time; \ + GF_List *isobmf_tracks; \ + +/*! common attributes*/ +typedef struct { + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS +} GF_MPD_CommonAttributes; + +/*! type of reference clock */ +typedef enum +{ + GF_MPD_PRODUCER_REF_ENCODER = 0, + GF_MPD_PRODUCER_REF_CAPTURED, + GF_MPD_PRODUCER_REF_APPLICATION, +} GF_MPD_ProducerRefType; + +/*! producer reference time*/ +typedef struct { + /*! ID of producer */ + u32 ID; + /*! is timing inband (prft in segment) */ + Bool inband; + /*! clock type*/ + GF_MPD_ProducerRefType type; + /*! scheme for application ref type*/ + char *scheme; + /*! wallclock time as UTC timestamp*/ + char *wallclock; + /*! presentation time in timescale of the Representation*/ + u64 presentation_time; + /*! UTC timing desc if any */ + GF_MPD_Descriptor *utc_timing; +} GF_MPD_ProducerReferenceTime; + + +/*! SubRepresentation*/ +typedef struct { + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS + /*! level of subrepresentation*/ + u32 level; + /*! dependency level of subrepresentation*/ + char *dependecy_level; + /*! bandwidth of subrepresentation, MANDATORY if level set*/ + u32 bandwidth; + /*! content comonents string*/ + char *content_components; +} GF_MPD_SubRepresentation; + +/*! State for representation playback, GPAC internal*/ +typedef struct +{ + /*! disabled*/ + Bool disabled; + /*! name of cahed init segemnt URL (usually local cache or gmem:// url)*/ + char *cached_init_segment_url; + /*! if set indicates the associated gmem memory is owned by this representation*/ + Bool owned_gmem; + /*! start range of the init segment*/ + u64 init_start_range; + /*! end range of the init segment*/ + u64 init_end_range; + /*! number of switching probes*/ + u32 probe_switch_count; + /*! init segment blob*/ + GF_Blob init_segment; + /*! associated key URL if any, for HLS*/ + char *key_url; + /*! associated key IV if any, for HLS*/ + bin128 key_IV; + /*! previous maximum speed that this representation can be played, or 0 if it has never been played*/ + Double prev_max_available_speed; + /*! after switch we may have some buffered segments of the previous representation; so codec stats at this moment is unreliable. we should wait after the codec reset*/ + Bool waiting_codec_reset; + /*! BOLA Utility*/ + Double bola_v; + + /*! index of the next enhancement representation plus 1, 0 is reserved in case of the highest representation*/ + u32 enhancement_rep_index_plus_one; + /*! set to true if the representation comes from a broadcast link (ATSC3, eMBMS)*/ + Bool broadcast_flag; + + /*! if set indicates the associated representations use vvc rpr switching*/ + Bool vvc_rpr_switch; + + /*! start of segment name in full url*/ + const char *init_seg_name_start; + /*! opaque data*/ + void *udta; + /*! SHA1 digest for xlinks / m3u8*/ + u8 xlink_digest[GF_SHA1_DIGEST_SIZE]; + /*! set to TRUE if not modified in the update of an xlink*/ + Bool not_modified; +} GF_DASH_RepresentationPlayback; + +/*! segment context used by the dasher, GPAC internal*/ +typedef struct +{ + /*! ID of active period*/ + char *period_id; + /*! start of active period*/ + GF_Fraction64 period_start; + /*! duration of active period*/ + GF_Fraction64 period_duration; + /*! if GF_TRUE, representation is over*/ + Bool done; + /*! niumber of last packet processed (to resume dashing)*/ + u64 last_pck_idx; + /*! number of last produced segment*/ + u32 seg_number; + /*! source URL*/ + char *src_url; + /*! name of init segment*/ + char *init_seg; + /*! segment template (half-resolved, no more %s in it)*/ + char *template_seg; + /*! index template (half-resolved, no more %s in it)*/ + char *template_idx; + /*! ID of output PID*/ + u32 pid_id; + /*! ID of source PID*/ + u32 source_pid; + /*! ID of source dependent PID*/ + u32 dep_pid_id; + /*! indicates if this representation drives the AS segmentation*/ + Bool owns_set; + /*! indicates if uses multi PID (eg, multiple sample descriptions in init segment)*/ + Bool multi_pids; + /*! target segment duration for this stream*/ + GF_Fraction dash_dur; + /*! estimated next segment start time in MPD timescale*/ + u64 next_seg_start; + /*! first CTS of stream in stream timescale*/ + u64 first_cts; + /*! first DCTS of stream in stream timescale*/ + u64 first_dts; + /*! number of past repetitions of the stream */ + u32 nb_repeat; + /*! timestamp offset (in stream timescale) due to repetitions*/ + u64 ts_offset; + /*! mpd timescale of the stream*/ + u32 mpd_timescale; + /*! estimated next DTS of the stream in media timescale*/ + u64 est_next_dts; + /*! cumulated sub duration of the stream (to handle partial file dashing)*/ + Double cumulated_subdur; + /*! cumulated duration of the stream (to handle loops)*/ + Double cumulated_dur; + /*! space-separated list of PID IDs of streams muxed with this stream in a multiplex representation*/ + char *mux_pids; + /*! number of segments purged from the timeline and from disk*/ + u32 segs_purged; + /*! cumulated duration of segments purged*/ + Double dur_purged; + /*! next moof sequence number*/ + u32 moof_sn; + /*! next moof sequence number increment*/ + u32 moof_sn_inc; + /*! ID of last dynamic period in manifest*/ + u32 last_dyn_period_id; + /*! one subdur was forced on this rep due to looping*/ + Bool subdur_forced; +} GF_DASH_SegmenterContext; + +/*! fragment context info for LL-HLS*/ +typedef struct +{ + /*! frag offset in bytes*/ + u64 offset; + /*! frag size in bytes*/ + u64 size; + /*! frag duration in representation timescale*/ + u32 duration; + /*! fragment contains an IDR*/ + Bool independent; +} GF_DASH_FragmentContext; + +/*! Segment context - GPAC internal, used to produce HLS manifests and segment lists/timeline*/ +typedef struct +{ + /*! time in mpd timescale*/ + u64 time; + /*! duration in mpd timescale*/ + u64 dur; + /*! name as printed in segment lists / m3u8*/ + char *filename; + /*! full path of file*/ + char *filepath; + /*! file size in bytes*/ + u32 file_size; + /*! file offset in bytes*/ + u64 file_offset; + /*! index size in bytes*/ + u32 index_size; + /*! index offset in bytes*/ + u64 index_offset; + /*! segment number */ + u32 seg_num; + /*! number of fragment infos */ + u32 nb_frags; + /*! number of fragment infos */ + GF_DASH_FragmentContext *frags; + /*! HLS LL signaling - 0: disabled, 1: byte range, 2: files */ + u32 llhls_mode; + /*! HLS LL segment done */ + Bool llhls_done; + /*! HLS set to TRUE if encrypted */ + Bool encrypted; + /*! HLS key params (URI and co)*/ + char *hls_key_uri; + /*! HLS IV*/ + bin128 hls_iv; +} GF_DASH_SegmentContext; + +/*! Representation*/ +typedef struct { + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS + + /*! ID of representation, mandatory*/ + char *id; + /*! bandwidth in bits per secon, mandatory*/ + u32 bandwidth; + /*! quality ranking*/ + u32 quality_ranking; + /*! dependency IDs of dependent representations*/ + char *dependency_id; + /*! stream structure ID, not used by GPAC*/ + char *media_stream_structure_id; + /*! list of baseURLs if any*/ + GF_List *base_URLs; + + /*! segment base of representation, or NULL if list or template is used*/ + GF_MPD_SegmentBase *segment_base; + /*! segment list of representation, or NULL if base or template is used*/ + GF_MPD_SegmentList *segment_list; + /*! segment template of representation, or NULL if base or list is used*/ + GF_MPD_SegmentTemplate *segment_template; + /*! number of subrepresentation*/ + GF_List *sub_representations; + + /*! all the below members are GPAC internal*/ + + /*! GPAC playback implementation*/ + GF_DASH_RepresentationPlayback playback; + /*! internal, HLS: min sequence number of segments in playlist*/ + u32 m3u8_media_seq_min; + /*! internal, HLS: max sequence number of segments in playlist*/ + u32 m3u8_media_seq_max; + /*! internal, HLS: indicate this is a low latency rep*/ + u32 m3u8_low_latency; + /*! internal, HLS: sequence number of last indeendent segment or PART in playlist*/ + u32 m3u8_media_seq_indep_last; + + /*! GPAC dasher context*/ + GF_DASH_SegmenterContext *dasher_ctx; + /*! list of segment states*/ + GF_List *state_seg_list; + /*! segment timescale (for HLS)*/ + u32 timescale; + /*! stream type (for HLS)*/ + u32 streamtype; + /*! segment manifest timescale (for HLS)*/ + u32 timescale_mpd; + /*! dash duration*/ + GF_Fraction dash_dur; + /*! init segment name for HLS single file*/ + const char *hls_single_file_name; + /*! number of audio channels - HLS only*/ + u32 nb_chan; + /*! video FPS - HLS only*/ + Double fps; + /*! groupID (for HLS)*/ + const char *groupID; + + /*! user assigned m3u8 name for this representation*/ + const 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*/ + u8 crypto_type; + u8 def_kms_used; + + u32 nb_hls_master_tags; + const char **hls_master_tags; + + u32 nb_hls_variant_tags; + const char **hls_variant_tags; + + /*! target part (cmaf chunk) duration for HLS LL*/ + Double hls_ll_part_dur; +} GF_MPD_Representation; + +/*! AdaptationSet*/ +typedef struct +{ + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS + /*! ID of this set, -1 if not set*/ + s32 id; + /*! group ID for this set, default value is -1: not set in MPD*/ + s32 group; + /*! language*/ + char *lang; + /*! mime type*/ + char *content_type; + /*! picture aspect ratio*/ + GF_MPD_Fractional *par; + /*! min bandwidth in bps*/ + u32 min_bandwidth; + /*! max bandwidth in bps*/ + u32 max_bandwidth; + /*! min width in pixels*/ + u32 min_width; + /*! max width in pixels*/ + u32 max_width; + /*! min height in pixels*/ + u32 min_height; + /*! max height in pixels*/ + u32 max_height; + /*! min framerate*/ + GF_MPD_Fractional min_framerate; + /*! max framerate*/ + GF_MPD_Fractional max_framerate; + /*! 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)*/ + Bool bitstream_switching; + /*! set if subsegment boundaries are time-aligned across qualities*/ + Bool subsegment_alignment; + /*! set if subsegment all start with given SAP type, 0 otherwise*/ + u32 subsegment_starts_with_sap; + /*! accessibility descriptor list if any*/ + GF_List *accessibility; + /*! role descriptor list if any*/ + GF_List *role; + /*! rating descriptor list if any*/ + GF_List *rating; + /*! viewpoint descriptor list if any*/ + GF_List *viewpoint; + /*! content component descriptor list if any*/ + GF_List *content_component; + + /*! base URL (alternate location) list if any*/ + GF_List *base_URLs; + /*! segment base of representation, or NULL if list or template is used*/ + GF_MPD_SegmentBase *segment_base; + /*! segment list of representation, or NULL if base or template is used*/ + GF_MPD_SegmentList *segment_list; + /*! segment template of representation, or NULL if base or list is used*/ + GF_MPD_SegmentTemplate *segment_template; + /*! list of representations*/ + GF_List *representations; + /*! xlink URL for the adaptation set*/ + char *xlink_href; + /*! xlink evaluation on load if set, otherwise on use*/ + Bool xlink_actuate_on_load; + + /*! user private, eg used by dasher*/ + void *udta; + + /*! mpegh compatible profile hack*/ + u32 nb_alt_mha_profiles, *alt_mha_profiles; + Bool alt_mha_profiles_only; + + /*! max number of valid chunks in smooth manifest*/ + u32 smooth_max_chunks; + + /*! INTRA-ONLY trick mode*/ + Bool intra_only; + /*! adaptation set uses HLS LL*/ + Bool use_hls_ll; + /*target fragment duration*/ + Double hls_ll_target_frag_dur; +} GF_MPD_AdaptationSet; + +/*! MPD offering type*/ +typedef enum { + /*! content is statically available*/ + GF_MPD_TYPE_STATIC, + /*! content is dynamically available*/ + GF_MPD_TYPE_DYNAMIC, + /*! content is the last if a dynamical offering, converts MPD to static (GPAC internal)*/ + GF_MPD_TYPE_DYNAMIC_LAST, +} GF_MPD_Type; + +/*! Period*/ +typedef struct +{ + /*! inherits from extensible*/ + MPD_EXTENSIBLE + + /*! ID of period*/ + char *ID; + /*! start time in milliseconds, relative to the start of the MPD */ + u64 start; + /*! duration in milliseconds*/ + u64 duration; + /*! set to GF_TRUE if adaptation sets in the period don't need reinit when switching quality*/ + Bool bitstream_switching; + + /*! base URL (alternate location) list if any*/ + GF_List *base_URLs; + /*! segment base of representation, or NULL if list or template is used*/ + GF_MPD_SegmentBase *segment_base; + /*! segment list of representation, or NULL if base or template is used*/ + GF_MPD_SegmentList *segment_list; + /*! segment template of representation, or NULL if base or list is used*/ + GF_MPD_SegmentTemplate *segment_template; + /*! list of adaptation sets*/ + GF_List *adaptation_sets; + /*! list of subsets (not yet implemented)*/ + GF_List *subsets; + /*! xlink URL for the period*/ + char *xlink_href; + /*! xlink evaluation on load if set, otherwise on use*/ + Bool xlink_actuate_on_load; + + /*! original xlink URL before resolution - GPAC internal. Used to + - identify already resolved xlinks in MPD updates + - resolve URLs in remote period if no baseURL is explictly listed + */ + char *origin_base_url; + /*! broken/ignored xlink, used to identify ignored xlinks in MPD updates - GPAC internal*/ + char *broken_xlink; + /*! type of the period - GPAC internal*/ + GF_MPD_Type type; + + /*! period is preroll - test only, GPAC internal*/ + Bool is_preroll; +} GF_MPD_Period; + +/*! Program info*/ +typedef struct +{ + /*! inherits from extensible*/ + MPD_EXTENSIBLE + + /*! languae*/ + char *lang; + /*! title*/ + char *title; + /*! source*/ + char *source; + /*! copyright*/ + char *copyright; + /*! URL to get more info*/ + char *more_info_url; +} GF_MPD_ProgramInfo; + +/*! MPD*/ +typedef struct { + /*! inherits from extensible*/ + MPD_EXTENSIBLE + /*! ID of the MPD*/ + char *ID; + /*! profile, mandatory*/ + char *profiles; + /*! offering type*/ + GF_MPD_Type type; + /*! UTC of availability start anchor, expressed in milliseconds, MANDATORY if type=dynamic*/ + u64 availabilityStartTime; + /*! UTC of availability end anchor, expressed in milliseconds*/ + u64 availabilityEndTime; + /*! UTC of last publishing of the manifest*/ + u64 publishTime; + /*! presentation duration in milliseconds, MANDATORY if type=static*/ + u64 media_presentation_duration; + /*! refresh rate of MPD for dynamic offering, in milliseconds */ + u32 minimum_update_period; + /*! minimum buffer time in milliseconds, MANDATORY*/ + u32 min_buffer_time; + + /*! time shift depth in milliseconds */ + u32 time_shift_buffer_depth; + /*! presentation delay in milliseconds */ + u32 suggested_presentation_delay; + /*! maximum segment duration in milliseconds */ + u32 max_segment_duration; + /*! maximum subsegment duration in milliseconds */ + u32 max_subsegment_duration; + + /*! list of GF_MPD_ProgramInfo */ + GF_List *program_infos; + /*! list of GF_MPD_BaseURL */ + GF_List *base_URLs; + /*! list of strings */ + GF_List *locations; + /*! list of Metrics */ + GF_List *metrics; + /*! list of GF_MPD_Period */ + GF_List *periods; + + /*! set during parsing, to set during authoring, won't be freed by GPAC*/ + const char *xml_namespace; + + /*! UTC timing desc if any */ + GF_List *utc_timings; + /*! Essential properties */ + GF_List *essential_properties; + /*! Supplemental properties */ + GF_List *supplemental_properties; + + /* internal variables for dasher*/ + Bool inject_service_desc; + + /*! dasher init NTP clock in ms - GPAC internal*/ + u64 gpac_init_ntp_ms; + /*! dasher next generation time NTP clock in ms - GPAC internal*/ + u64 gpac_next_ntp_ms; + /*! dasher current MPD time in milliseconds - GPAC internal*/ + u64 gpac_mpd_time; + + /*! indicates the GPAC state info should be written*/ + Bool write_context; + /*! indicates this is the last static serialization of a previously dynamic MPD*/ + Bool was_dynamic; + /*! indicates the HLS variant files shall be created, otherwise temp files are used*/ + Bool create_m3u8_files; + /*! indicates to insert clock reference in variant playlists*/ + Bool m3u8_time; + /*! indicates LL-HLS forced generation. 0: regular write, 1: write as byterange, 2: write as independent files*/ + u32 force_llhls_mode; + /*! HLS extensions to append in the master playlist*/ + u32 nb_hls_ext_master; + const char **hls_ext_master; + /*! if true inject EXT-X-PRELOAD-HINT*/ + Bool llhls_preload; + /*! if true inject EXT-X-RENDITION-REPORT*/ + Bool llhls_rendition_reports; +} GF_MPD; + +/*! parses an MPD Element (and subtree) from DOM +\param root root of DOM parsing result +\param mpd MPD structure to fill +\param base_url base URL of the DOM document +\return error if any +*/ +GF_Err gf_mpd_init_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *base_url); +/*! parses an MPD Period element (and subtree) from DOM +\param root root of DOM parsing result +\param mpd MPD structure to fill +\param base_url base URL of the DOM document +\return error if any +*/ +GF_Err gf_mpd_complete_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *base_url); +/*! MPD constructor +\return a new MPD*/ +GF_MPD *gf_mpd_new(); +/*! MPD destructor +\param mpd the target MPD*/ +void gf_mpd_del(GF_MPD *mpd); +/*! frees a GF_MPD_SegmentURL structure (type-casted to void *) +\param ptr the target GF_MPD_SegmentURL +*/ +void gf_mpd_segment_url_free(void *ptr); +/*! frees a GF_MPD_SegmentBase structure (type-casted to void *) +\param ptr the target GF_MPD_SegmentBase +*/ +void gf_mpd_segment_base_free(void *ptr); +/*! parses a new GF_MPD_SegmentURL from its DOM description +\param container the container list where to insert the segment URL +\param root the DOM description of the segment URL +*/ +void gf_mpd_parse_segment_url(GF_List *container, GF_XMLNode *root); + +/*! parses a xsDateTime +\param attr the date time value +\return value as UTC timestamp +*/ +u64 gf_mpd_parse_date(const char * const attr); + +/*! frees a GF_MPD_URL structure (type-casted to void *) +\param _item the target GF_MPD_URL +*/ +void gf_mpd_url_free(void *_item); + +/*! MPD Period constructor +\return a new MPD Period*/ +GF_MPD_Period *gf_mpd_period_new(); +/*! MPD Period destructor +\param _item the MPD Period to free*/ +void gf_mpd_period_free(void *_item); +/*! writes an MPD to a file stream +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param out the target file object +\param compact if set, removes all new line and indentation in the output +\return error if any +*/ +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +/*! writes an MPD to a local file +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param file_name the target file name +\return error if any +*/ +GF_Err gf_mpd_write_file(GF_MPD const * const mpd, const char *file_name); + +/*! writes an MPD to a m3u8 playlist +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param out the target file object +\param m3u8_name the base m3u8 name to use (needed when generating variant playlist file names) +\param period the MPD period for that m3u8 +\return error if any +*/ +GF_Err gf_mpd_write_m3u8_master_playlist(GF_MPD const * const mpd, FILE *out, const char* m3u8_name, GF_MPD_Period *period); + +/*! parses an MPD Period and appends it to the MPD period list +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param root the DOM element describing the period +\return error if any +*/ +GF_Err gf_mpd_parse_period(GF_MPD *mpd, GF_XMLNode *root); + +/*! creates a new MPD descriptor +\param id the descriptor ID, may be NULL +\param uri the descriptor schemeid URI, mandatory +\param value the descriptor value, may be NULL +\return the new descriptor or NULL if error +*/ +GF_MPD_Descriptor *gf_mpd_descriptor_new(const char *id, const char *uri, const char *value); + +/*! creates a new MPD AdaptationSet +\return the new GF_MPD_AdaptationSet or NULL if error +*/ +GF_MPD_AdaptationSet *gf_mpd_adaptation_set_new(); + +/*! file download abstraction object*/ +typedef struct _gf_file_get GF_FileDownload; +/*! file download abstraction object*/ +struct _gf_file_get +{ + /*! callback function for session creation, fetches the given URL*/ + GF_Err (*new_session)(GF_FileDownload *getter, char *url); + /*! callback function to destroy the session*/ + void (*del_session)(GF_FileDownload *getter); + /*! callback function to get the local file name*/ + const char *(*get_cache_name)(GF_FileDownload *getter); + /*! user private*/ + void *udta; + /*! created by user after new_session*/ + void *session; +}; + +/*! converts M3U8 to MPD - getter is optional (download will still be processed if NULL) +\param m3u8_file the path to the local m3u8 master playlist file +\param base_url the original URL of the file if any +\param mpd_file the destination MPD file, or NULL when filling an MPD structure +\param reload_count number of times the manifest was reloaded +\param mimeTypeForM3U8Segments default mime type for the segments in case not found in the m3u8 +\param do_import if GF_TRUE, will try to load the media segments to extract more info +\param use_mpd_templates if GF_TRUE, will use MPD SegmentTemplate instead of SegmentList (only if parse_sub_playlist is GF_TRUE) +\param use_segment_timeline if GF_TRUE, uses SegmentTimeline to describe the varying duration of segments +\param getter HTTP interface object +\param mpd MPD structure to fill, or NULL if converting to file +\param parse_sub_playlist if GF_TRUE, parses sub playlists, otherwise only the master playlist is parsed and xlink are added on each representation to the target m3u8 sub playlist +\param keep_files if GF_TRUE, will not delete any files downloaded in the conversion process +\return error if any +*/ +GF_Err gf_m3u8_to_mpd(const char *m3u8_file, const char *base_url, const char *mpd_file, u32 reload_count, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, Bool use_segment_timeline, GF_FileDownload *getter, GF_MPD *mpd, Bool parse_sub_playlist, Bool keep_files); + +/*! solves an m3u8 xlink on a representation, and fills the SegmentList accordingly +\param rep the target representation +\param base_url base URL of master manifest (representation xlink is likely relative to this URL) +\param getter HTTP interface object +\param is_static set to GF_TRUE if the variant subplaylist is on demand +\param duration set to the duration of the parsed subplaylist +\param signature SHA1 digest of last solved version, updated if changed +\return error if any, GF_EOS if no changes +*/ +GF_Err gf_m3u8_solve_representation_xlink(GF_MPD_Representation *rep, const char *base_url, GF_FileDownload *getter, Bool *is_static, u64 *duration, u8 signature[GF_SHA1_DIGEST_SIZE]); + +/*! creates a segment list from a remote segment list DOM root +\param mpd the target MPD to write +\param root the DOM element describing the segment list +\return a new segment list, or NULL if error +*/ +GF_MPD_SegmentList *gf_mpd_solve_segment_list_xlink(GF_MPD *mpd, GF_XMLNode *root); + +/*! inits an MPD from a smooth manifest root node +\param root the root node of a smooth manifest +\param mpd the MPD to fill +\param default_base_url the default URL of the smooth manifest +\return error if any +*/ +GF_Err gf_mpd_init_smooth_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url); + +/*! deletes a segment list +\param segment_list the segment list to delete*/ +void gf_mpd_delete_segment_list(GF_MPD_SegmentList *segment_list); + +/*! deletes a list content and optionally destructs the list +\param list the target list +\param __destructor the destructor function to use to destroy list items +\param reset_only if GF_TRUE, does not destroy the target list +*/ +void gf_mpd_del_list(GF_List *list, void (*__destructor)(void *), Bool reset_only); +/*! deletes a GF_MPD_Descriptor object +\param item the descriptor to delete +*/ +void gf_mpd_descriptor_free(void *item); + +/*! splits all adaptation sets of a source MPD, creating one adaptation set per quality of each orgingal adaptation sets +\param mpd the target MPD +\return error if any +*/ +GF_Err gf_mpd_split_adaptation_sets(GF_MPD *mpd); + +/*! converts a smooth manifest (local file) to an MPD +\param smooth_file local path to the smooth manifest +\param mpd MPD structure to fill +\param default_base_url the default URL of the smooth manifest +\return error if any +*/ +GF_Err gf_mpd_smooth_to_mpd(char * smooth_file, GF_MPD *mpd, const char *default_base_url); + +/*! get the number of base URLs for the given representation. This cumuluates all base URLs at MPD, period, AdaptationSet and Representation levels +\param mpd the target MPD +\param period the target period +\param set the target adaptation set +\param rep the target representation +\return the number of base URLs +*/ +u32 gf_mpd_get_base_url_count(GF_MPD *mpd, GF_MPD_Period *period, GF_MPD_AdaptationSet *set, GF_MPD_Representation *rep); + +/*! MPD URL resolutio mode*/ +typedef enum +{ + /*resolves template for a media segment*/ + GF_MPD_RESOLVE_URL_MEDIA, + /*resolves template for an init segment*/ + GF_MPD_RESOLVE_URL_INIT, + /*resolves template for an index segment*/ + GF_MPD_RESOLVE_URL_INDEX, + /*! same as GF_MPD_RESOLVE_URL_MEDIA but does not replace $Time$ and $Number$*/ + GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE, + /*! same as GF_MPD_RESOLVE_URL_MEDIA but does not use startNumber*/ + GF_MPD_RESOLVE_URL_MEDIA_NOSTART, + /*! same as GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE but ignores base URL*/ + GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE, +} GF_MPD_URLResolveType; + +/*! resolves a URL based for a given segment, based on the MPD url, the type of resolution +\param mpd the target MPD +\param rep the target Representation +\param set the target AdaptationSet +\param period the target Period +\param mpd_url the original URL of the MPD +\param base_url_index 0-based index of the baseURL to use +\param resolve_type the type of URL resolution desired +\param item_index the index of the target segment (startNumber based) +\param nb_segments_removed number of segments removed when purging the MPD after updates (can be 0). The start number will be offset by this value +\param out_url set to the resolved URL, to be freed by caller +\param out_range_start set to the resolved start range, 0 if no range +\param out_range_end set to the resolved end range, 0 if no range +\param segment_duration set to the resolved segment duartion, 0 if unknown +\param is_in_base_url set to GF_TRUE if the resuloved URL is a sub-part of the baseURL (optional, may be NULL) +\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) + +\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); + +/*! get duration of the presentation +\param mpd the target MPD +\return the duration in seconds +*/ +Double gf_mpd_get_duration(GF_MPD *mpd); + +/*! gets the duration of media segments +\param rep the target Representation +\param set the target AdaptationSet +\param period the target Period +\param out_duration set to the average media segment duration +\param out_timescale set to the MPD timescale used by this representation +\param out_pts_offset set to the presentation time offset if any (optional, may be NULL) +\param out_segment_timeline set to the segment timeline description if any (optional, may be NULL) +*/ +void gf_mpd_resolve_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, u64 *out_duration, u32 *out_timescale, u64 *out_pts_offset, GF_MPD_SegmentTimeline **out_segment_timeline); + +/*! gets the start_time from the segment index of a period/set/rep +\param in_segment_index the index of the target segment (startNumber based) +\param in_period the target Period +\param in_set the target AdaptationSet +\param in_rep the target Representation +\param out_segment_start_time set to the MPD start time of the segment +\param out_opt_segment_duration set to the segment duration (optional, may be NULL) +\param out_opt_scale set to the MPD timescale for this segment (optional, may be NULL) +\return error if any +*/ +GF_Err gf_mpd_get_segment_start_time_with_timescale(s32 in_segment_index, + GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, + u64 *out_segment_start_time, u64 *out_opt_segment_duration, u32 *out_opt_scale); + +/*! MPD seek mode*/ +typedef enum { + /*! will return the segment containing the requested time*/ + MPD_SEEK_PREV, + /*! will return the nearest segment start time, may be the previous or the next one*/ + MPD_SEEK_NEAREST, +} MPDSeekMode; + +/*! returns the segment index in the given period for the given time +\param seek_time the desired time in seconds +\param seek_mode the desired seek mode +\param in_period the target Period +\param in_set the target AdaptationSet +\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) +\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); + +/*! deletes a GF_MPD_BaseURL structure (type-casted to void *) +\param _item the GF_MPD_BaseURL to free +*/ +void gf_mpd_base_url_free(void *_item); +/*! creates a new GF_MPD_Representation +\return the new representation*/ +GF_MPD_Representation *gf_mpd_representation_new(); +/*! deletes a GF_MPD_Representation structure (type-casted to void *) +\param _item the GF_MPD_Representation to free +*/ +void gf_mpd_representation_free(void *_item); + +/*! creates a new GF_MPD_SegmentTimeline +\return the new segment timeline*/ +GF_MPD_SegmentTimeline *gf_mpd_segmentimeline_new(); + +/*! DASHer cues information*/ +typedef struct +{ + /*! target splice point sample number (1-based), or 0 if not set*/ + u32 sample_num; + /*! target splice point dts in cues timescale */ + u64 dts; + /*! target splice point cts in cues timescale */ + s64 cts; + /*! internal flag indicating if the cues is processed or not */ + Bool is_processed; +} GF_DASHCueInfo; + +/*! loads a cue file and allocates cues as needed +\param cues_file the XML cue file to load +\param stream_id the ID of the stream for which we load cues (typically, TrackID or GF_PROP_PID_ID) +\param cues_timescale set to the timescale used in the cues document +\param use_edit_list set to GF_TRUE if the cts values of cues have edit list applied (i.e. are ISOBMFF presentation times) +\param ts_offset set to the timestamp offset to subtract from DTS/CTS values +\param out_cues set to a newly allocated list of cues, to free by the caller +\param nb_cues set to the number of cues parsed +\return error if any +*/ +GF_Err gf_mpd_load_cues(const char *cues_file, u32 stream_id, u32 *cues_timescale, Bool *use_edit_list, s32 *ts_offset, GF_DASHCueInfo **out_cues, u32 *nb_cues); + +/*! gets first MPD descriptor from descriptor list for a given scheme_id +\param desclist list of MPD Descriptors +\param scheme_id scheme ID to look for +\return descriptor if found, NUL otherwise +*/ +GF_MPD_Descriptor *gf_mpd_get_descriptor(GF_List *desclist, char *scheme_id); + +/*! @} */ +#endif /*GPAC_DISABLE_CORE_TOOLS*/ + +#endif // _MPD_H_ diff --git a/include/gpac/mpeg4_odf.h b/include/gpac/mpeg4_odf.h new file mode 100644 index 0000000..955eee1 --- /dev/null +++ b/include/gpac/mpeg4_odf.h @@ -0,0 +1,2517 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / MPEG-4 Object Descriptor 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_MPEG4_ODF_H_ +#define _GF_MPEG4_ODF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/mpeg4_odf.h> +\brief MPEG-4 Object Descriptor Framework. + */ + +/*! +\addtogroup mpeg4sys_grp MPEG-4 Systems +\brief MPEG-4 Systems. + */ + +/*! +\addtogroup odf_grp MPEG-4 OD +\ingroup mpeg4sys_grp +\brief MPEG-4 Object Descriptor Framework + +This section documents the MPEG-4 OD, OCI and IPMPX functions of the GPAC framework. +@{ + */ + +#include <gpac/list.h> +#include <gpac/bitstream.h> +#include <gpac/sync_layer.h> + +/*! Tags for MPEG-4 systems descriptors */ +enum +{ + GF_ODF_OD_TAG = 0x01, + GF_ODF_IOD_TAG = 0x02, + GF_ODF_ESD_TAG = 0x03, + GF_ODF_DCD_TAG = 0x04, + GF_ODF_DSI_TAG = 0x05, + GF_ODF_SLC_TAG = 0x06, + GF_ODF_CI_TAG = 0x07, + GF_ODF_SCI_TAG = 0x08, + GF_ODF_IPI_PTR_TAG = 0x09, + GF_ODF_IPMP_PTR_TAG = 0x0A, + GF_ODF_IPMP_TAG = 0x0B, + GF_ODF_QOS_TAG = 0x0C, + GF_ODF_REG_TAG = 0x0D, + + /*FILE FORMAT RESERVED IDs - NEVER CREATE / USE THESE DESCRIPTORS*/ + GF_ODF_ESD_INC_TAG = 0x0E, + GF_ODF_ESD_REF_TAG = 0x0F, + GF_ODF_ISOM_IOD_TAG = 0x10, + GF_ODF_ISOM_OD_TAG = 0x11, + GF_ODF_ISOM_IPI_PTR_TAG = 0x12, + /*END FILE FORMAT RESERVED*/ + + GF_ODF_EXT_PL_TAG = 0x13, + GF_ODF_PL_IDX_TAG = 0x14, + + GF_ODF_ISO_BEGIN_TAG = 0x15, + GF_ODF_ISO_END_TAG = 0x3F, + + GF_ODF_CC_TAG = 0x40, + GF_ODF_KW_TAG = 0x41, + GF_ODF_RATING_TAG = 0x42, + GF_ODF_LANG_TAG = 0x43, + GF_ODF_SHORT_TEXT_TAG = 0x44, + GF_ODF_TEXT_TAG = 0x45, + GF_ODF_CC_NAME_TAG = 0x46, + GF_ODF_CC_DATE_TAG = 0x47, + GF_ODF_OCI_NAME_TAG = 0x48, + GF_ODF_OCI_DATE_TAG = 0x49, + GF_ODF_SMPTE_TAG = 0x4A, + + GF_ODF_SEGMENT_TAG = 0x4B, + GF_ODF_MEDIATIME_TAG = 0x4C, + + GF_ODF_IPMP_TL_TAG = 0x60, + GF_ODF_IPMP_TOOL_TAG = 0x61, + + GF_ODF_ISO_RES_BEGIN_TAG = 0x62, + GF_ODF_ISO_RES_END_TAG = 0xBF, + + GF_ODF_USER_BEGIN_TAG = 0xC0, + + /*! internal descriptor for mux input description*/ + GF_ODF_MUXINFO_TAG = GF_ODF_USER_BEGIN_TAG, + /*! internal descriptor for bifs config input description*/ + GF_ODF_BIFS_CFG_TAG = GF_ODF_USER_BEGIN_TAG + 1, + /*! internal descriptor for UI config input description*/ + GF_ODF_UI_CFG_TAG = GF_ODF_USER_BEGIN_TAG + 2, + /*! internal descriptor for TextConfig description*/ + GF_ODF_TEXT_CFG_TAG = GF_ODF_USER_BEGIN_TAG + 3, + /*! internal descriptor for Text/TX3G description*/ + GF_ODF_TX3G_TAG = GF_ODF_USER_BEGIN_TAG + 4, + /*! internal descriptor for BIFS_anim input description*/ + GF_ODF_ELEM_MASK_TAG = GF_ODF_USER_BEGIN_TAG + 5, + /*! internal descriptor for LASeR config input description*/ + GF_ODF_LASER_CFG_TAG = GF_ODF_USER_BEGIN_TAG + 6, + /*! internal descriptor for subtitle stream description*/ + GF_ODF_GEN_SUB_CFG_TAG = GF_ODF_USER_BEGIN_TAG + 7, + + GF_ODF_USER_END_TAG = 0xFE, + + GF_ODF_OCI_BEGIN_TAG = 0x40, + GF_ODF_OCI_END_TAG = (GF_ODF_ISO_RES_BEGIN_TAG - 1), + + GF_ODF_EXT_BEGIN_TAG = 0x80, + GF_ODF_EXT_END_TAG = 0xFE, + + + /*descriptor for aucilary video data*/ + GF_ODF_AUX_VIDEO_DATA = GF_ODF_EXT_BEGIN_TAG + 1, + GF_ODF_GPAC_LANG = GF_ODF_EXT_BEGIN_TAG + 2 +}; + + +/*************************************** + Descriptors +***************************************/ +/*! macro defining a base descriptor*/ +#define BASE_DESCRIPTOR \ + u8 tag; + + +/*! base descriptor used as base type in many function.*/ +typedef struct +{ + BASE_DESCRIPTOR +} GF_Descriptor; + + +/*! default descriptor. + \note The decoderSpecificInfo is used as a default desc with tag 0x05 */ +typedef struct +{ + BASE_DESCRIPTOR + u32 dataLength; + u8 *data; +} GF_DefaultDescriptor; + +/*! Object Descriptor*/ +typedef struct +{ + BASE_DESCRIPTOR + GF_List *ipmp_tools; +} GF_IPMP_ToolList; + +/*! ObjectDescriptor*/ +typedef struct +{ + BASE_DESCRIPTOR + u16 objectDescriptorID; + char *URLString; + GF_List *ESDescriptors; + GF_List *OCIDescriptors; + /*includes BOTH IPMP_DescriptorPointer (IPMP & IPMPX) and GF_IPMP_Descriptor (IPMPX only)*/ + GF_List *IPMP_Descriptors; + GF_List *extensionDescriptors; + /*MPEG-2 (or other service mux formats) service ID*/ + u32 ServiceID; + /*for ROUTE, instructs client to keep OD alive even though URL string is set*/ + Bool RedirectOnly; + /*set to true for fake remote ODs in BT/XMT (remote ODs created for OD with ESD with MuxInfo)*/ + Bool fake_remote; + +} GF_ObjectDescriptor; + +/*! GF_InitialObjectDescriptor - WARNING: even though the bitstream IOD is not +a bit extension of OD, internally it is a real overclass of OD +we usually typecast IOD to OD when flags are not needed !!!*/ +typedef struct +{ + BASE_DESCRIPTOR + u16 objectDescriptorID; + char *URLString; + GF_List *ESDescriptors; + GF_List *OCIDescriptors; + /*includes BOTH IPMP_DescriptorPointer (IPMP & IPMPX) and GF_IPMP_Descriptor (IPMPX only)*/ + GF_List *IPMP_Descriptors; + GF_List *extensionDescriptors; + /*MPEG-2 (or other service mux formats) service ID*/ + u16 ServiceID; + /*for ROUTE, instructs client to keep OD alive even though URL string is set*/ + Bool RedirectOnly; + /*set to true for fake remote ODs in BT/XMT (remote ODs created for OD with ESD with MuxInfo)*/ + Bool fake_remote; + + /*IOD extensions*/ + u8 inlineProfileFlag; + u8 OD_profileAndLevel; + u8 scene_profileAndLevel; + u8 audio_profileAndLevel; + u8 visual_profileAndLevel; + u8 graphics_profileAndLevel; + + GF_IPMP_ToolList *IPMPToolList; +} GF_InitialObjectDescriptor; + +/*! File Format Object Descriptor*/ +typedef struct +{ + BASE_DESCRIPTOR + u16 objectDescriptorID; + char *URLString; + GF_List *ES_ID_RefDescriptors; + GF_List *OCIDescriptors; + GF_List *IPMP_Descriptors; + GF_List *extensionDescriptors; + GF_List *ES_ID_IncDescriptors; + + /*MPEG-2 (or other service mux formats) service ID*/ + u32 ServiceID; + /*for ROUTE, instructs client to keep OD alive even though URL string is set*/ + Bool RedirectOnly; + /*set to true for fake remote ODs in BT/XMT (remote ODs created for OD with ESD with MuxInfo)*/ + Bool fake_remote; +} GF_IsomObjectDescriptor; + +/*! File Format Initial Object Descriptor - same remark as IOD*/ +typedef struct +{ + BASE_DESCRIPTOR + u16 objectDescriptorID; + char *URLString; + GF_List *ES_ID_RefDescriptors; + GF_List *OCIDescriptors; + GF_List *IPMP_Descriptors; + GF_List *extensionDescriptors; + GF_List *ES_ID_IncDescriptors; + + u8 inlineProfileFlag; + u8 OD_profileAndLevel; + u8 scene_profileAndLevel; + u8 audio_profileAndLevel; + u8 visual_profileAndLevel; + u8 graphics_profileAndLevel; + + GF_IPMP_ToolList *IPMPToolList; +} GF_IsomInitialObjectDescriptor; + + +/*! File Format ES Descriptor for IOD*/ +typedef struct { + BASE_DESCRIPTOR + u32 trackID; +} GF_ES_ID_Inc; + +/*! File Format ES Descriptor for OD*/ +typedef struct { + BASE_DESCRIPTOR + u16 trackRef; +} GF_ES_ID_Ref; + +/*! Decoder config Descriptor*/ +typedef struct +{ + BASE_DESCRIPTOR + /*coded on 8 bit, but we use 32 bits for internal signaling in GPAC to enable usage of 4CC*/ + u32 objectTypeIndication; + u8 streamType; + u8 upstream; + u32 bufferSizeDB; + u32 maxBitrate; + u32 avgBitrate; + GF_DefaultDescriptor *decoderSpecificInfo; + + /*placeholder for RVC decoder config if any*/ + u16 predefined_rvc_config; + GF_DefaultDescriptor *rvc_config; + + GF_List *profileLevelIndicationIndexDescriptor; + /*pass through data for some modules*/ + void *udta; +} GF_DecoderConfig; + + +/*! Content Identification Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u8 compatibility; + u8 protectedContent; + u8 contentTypeFlag; + u8 contentIdentifierFlag; + u8 contentType; + u8 contentIdentifierType; + /*international code string*/ + char *contentIdentifier; +} GF_CIDesc; + +/*! Supplementary Content Identification Descriptor)*/ +typedef struct { + BASE_DESCRIPTOR + u32 languageCode; + char *supplContentIdentifierTitle; + char *supplContentIdentifierValue; +} GF_SCIDesc; + +/*! IPI (Intelectual Property Identification) Descriptor Pointer*/ +typedef struct { + BASE_DESCRIPTOR + u16 IPI_ES_Id; +} GF_IPIPtr; + +/*! IPMP Descriptor Pointer*/ +typedef struct { + BASE_DESCRIPTOR + u8 IPMP_DescriptorID; + u16 IPMP_DescriptorIDEx; + u16 IPMP_ES_ID; +} GF_IPMPPtr; + +/*! IPMPX control points*/ +enum +{ + /*no control point*/ + IPMP_CP_NONE = 0, + /*control point between DB and decoder*/ + IPMP_CP_DB = 1, + /*control point between decoder and CB*/ + IPMP_CP_CB = 2, + /*control point between CB and render*/ + IPMP_CP_CM = 3, + /*control point in BIFS tree (???)*/ + IPMP_CP_BIFS = 4 + /*the rest is reserved or forbidden(0xFF)*/ +}; + +/*! IPMPX base classe*/ +#define GF_IPMPX_BASE \ + u8 tag; \ + u8 version; \ + u32 dataID; \ + +/*! IPMPX base object used for type casting in many function*/ +typedef struct +{ + GF_IPMPX_BASE +} GF_GF_IPMPX_Base; + +/*! IPMP descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u8 IPMP_DescriptorID; + u16 IPMPS_Type; + /*if IPMPS_Type=0, NULL-terminated URL, else if IPMPS_Type is not IPMPX, opaque data*/ + u8 *opaque_data; + /*if IPMPS_Type=0, irrelevant (strlen(URL)), else if IPMPS_Type is not IPMPX, opaque data size*/ + u32 opaque_data_size; + + /*IPMPX specific*/ + u16 IPMP_DescriptorIDEx; + bin128 IPMP_ToolID; + u8 control_point; + u8 cp_sequence_code; + GF_List *ipmpx_data; +} GF_IPMP_Descriptor; + + +/*! IPMPX max number of tools*/ +#define MAX_IPMP_ALT_TOOLS 20 + +/*! IPMPX Tool*/ +typedef struct +{ + BASE_DESCRIPTOR + bin128 IPMP_ToolID; + /*if set, this is an alternate tool*/ + u32 num_alternate; + bin128 specificToolID[MAX_IPMP_ALT_TOOLS]; + + struct _tagIPMPXParamDesc *toolParamDesc; + char *tool_url; +} GF_IPMP_Tool; + + +/*! Elementary Mask of Bifs Config - parsing only */ +typedef struct { + BASE_DESCRIPTOR + u32 node_id; /* referenced nodeID */ + char *node_name; /* referenced node name */ +} GF_ElementaryMask; + +/*! BIFSConfig - parsing only, STORED IN ESD:DCD:DSI*/ +typedef struct __tag_bifs_config +{ + BASE_DESCRIPTOR + u32 version; + u16 nodeIDbits; + u16 routeIDbits; + u16 protoIDbits; + Bool pixelMetrics; + u16 pixelWidth, pixelHeight; + /*BIFS-Anim stuff*/ + Bool randomAccess; + GF_List *elementaryMasks; + /*internal extensions for encoding*/ + Bool useNames; +} GF_BIFSConfig; + +/*! flags for text style*/ +enum +{ + /*! normal*/ + GF_TXT_STYLE_NORMAL = 0, + /*! bold*/ + GF_TXT_STYLE_BOLD = 1, + /*! italic*/ + GF_TXT_STYLE_ITALIC = 1<<1, + /*! underlined*/ + GF_TXT_STYLE_UNDERLINED = 1<<2, + /*! strikethrough - not 3GPP/QT defined, GPAC only*/ + GF_TXT_STYLE_STRIKETHROUGH = 1<<3, +}; + +/*! text style record*/ +typedef struct +{ + u16 startCharOffset; + u16 endCharOffset; + u16 fontID; + u8 style_flags; + u8 font_size; + /*ARGB*/ + u32 text_color; +} GF_StyleRecord; + +/*! font record for text*/ +typedef struct +{ + u16 fontID; + char *fontName; +} GF_FontRecord; + +/*! positioning record for text*/ +typedef struct +{ + s16 top, left, bottom, right; +} GF_BoxRecord; + +/*! scroll flags for text*/ +enum +{ + GF_TXT_SCROLL_CREDITS = 0, + GF_TXT_SCROLL_MARQUEE = 1, + GF_TXT_SCROLL_DOWN = 2, + GF_TXT_SCROLL_RIGHT = 3 +}; + +/*! display flags for text*/ +enum +{ + GF_TXT_SCROLL_IN = 0x00000020, + GF_TXT_SCROLL_OUT = 0x00000040, + /*use one of the scroll flags, eg GF_TXT_SCROLL_DIRECTION | GF_TXT_SCROLL_CREDITS*/ + GF_TXT_SCROLL_DIRECTION = 0x00000180, + GF_TXT_KARAOKE = 0x00000800, + GF_TXT_VERTICAL = 0x00020000, + GF_TXT_FILL_REGION = 0x00040000, + + GF_TXT_NO_SCALE = 0x2, + GF_TXT_MOVIE_BACK_COLOR = 0x8, + GF_TXT_CONTINUOUS_SCROLL = 0x200, + GF_TXT_DROP_SHADOW = 0x1000, + GF_TXT_FILL_ANTIALIAS = 0x2000, + GF_TXT_SOME_SAMPLES_FORCED = 0x40000000, + GF_TXT_ALL_SAMPLES_FORCED = 0x80000000, +}; + +/*! Text sample description descriptor (eg mostly a copy of ISOBMF sample entry)*/ +typedef struct +{ + /*this is defined as a descriptor for parsing*/ + BASE_DESCRIPTOR + + u32 displayFlags; + /*left, top: 0 - centered: 1 - bottom, right: -1*/ + s8 horiz_justif, vert_justif; + /*ARGB*/ + u32 back_color; + GF_BoxRecord default_pos; + GF_StyleRecord default_style; + + u32 font_count; + GF_FontRecord *fonts; + + /*unused in isomedia but needed for streamingText*/ + u8 sample_index; +} GF_TextSampleDescriptor; + +/*! Text stream descriptor, internal only*/ +typedef struct +{ + BASE_DESCRIPTOR + /*only 0x10 shall be used for 3GP text stream*/ + u8 Base3GPPFormat; + /*only 0x10 shall be used for StreamingText*/ + u8 MPEGExtendedFormat; + /*only 0x10 shall be used for StreamingText (base profile, base level)*/ + u8 profileLevel; + u32 timescale; + /*0 forbidden, 1: out-of-band desc only, 2: in-band desc only, 3: both*/ + u8 sampleDescriptionFlags; + /*More negative layer values are towards the viewer*/ + s16 layer; + /*text track width & height*/ + u16 text_width; + u16 text_height; + /*compatible 3GP formats, same coding as 3GPPBaseFormat*/ + u8 nb_compatible_formats; + u8 compatible_formats[20]; + /*defined in isomedia.h*/ + GF_List *sample_descriptions; + + /*if true info below are valid (cf 3GPP for their meaning)*/ + Bool has_vid_info; + u16 video_width; + u16 video_height; + s16 horiz_offset; + s16 vert_offset; +} GF_TextConfig; + +/*! generic subtitle sample description descriptor*/ +typedef struct +{ + /*this is defined as a descriptor for parsing*/ + BASE_DESCRIPTOR + + /*unused in isomedia but needed for streamingText*/ + u8 sample_index; +} GF_GenericSubtitleSampleDescriptor; + +/*! generic subtitle descriptor*/ +typedef struct +{ + BASE_DESCRIPTOR + u32 timescale; + /*More negative layer values are towards the viewer*/ + s16 layer; + /*text track width & height*/ + u16 text_width; + u16 text_height; + /*defined in isomedia.h*/ + GF_List *sample_descriptions; + + /*if true info below are valid (cf 3GPP for their meaning)*/ + Bool has_vid_info; + u16 video_width; + u16 video_height; + s16 horiz_offset; + s16 vert_offset; +} GF_GenericSubtitleConfig; + + +/*! MuxInfo descriptor - parsing only, stored in ESD:extDescr*/ +typedef struct { + BASE_DESCRIPTOR + /*input location*/ + char *file_name; + /*input groupID for interleaving*/ + u32 GroupID; + /*input stream format (not required, guessed from file_name)*/ + char *streamFormat; + /*time offset in ms from first TS (appends an edit list in mp4)*/ + s32 startTime; + + /*media length to import in ms (from 0)*/ + u32 duration; + + /*SRT/SUB import extensions - only support for text and italic style*/ + char *textNode; + char *fontNode; + + /*video and SUB import*/ + Double frame_rate; + + /*same as importer flags, cf media.h*/ + u32 import_flags; + + /*indicates input file shall be destryed - used during SWF import*/ + Bool delete_file; + + /*carousel configuration*/ + u32 carousel_period_plus_one; + u16 aggregate_on_esid; + + /*original source URL*/ + char *src_url; +} GF_MuxInfo; + +/*! UI config descriptor for InputSensor streams*/ +typedef struct +{ + BASE_DESCRIPTOR + /*input type*/ + char *deviceName; + /*string sensor terminaison (validation) char*/ + char termChar; + /*string sensor deletion char*/ + char delChar; + /*device-specific data*/ + u8 *ui_data; + u32 ui_data_length; +} GF_UIConfig; + +/*! LASERConfig - parsing only, STORED IN ESD:DCD:DSI*/ +typedef struct __tag_laser_config +{ + BASE_DESCRIPTOR + u8 profile; + u8 level; + u8 pointsCodec; + u8 pathComponents; + u8 fullRequestHost; + u16 time_resolution; + u8 colorComponentBits; + s8 resolution; + u8 coord_bits; + u8 scale_bits_minus_coord_bits; + u8 newSceneIndicator; + u8 extensionIDBits; + + /*the rest of the structure is never coded, only used for the config of GPAC...*/ + Bool force_string_ids;/*forces all nodes to be defined with string IDs*/ +} GF_LASERConfig; + + +/*! QoS Tags */ +enum +{ + QoSMaxDelayTag = 0x01, + QoSPrefMaxDelayTag = 0x02, + QoSLossProbTag = 0x03, + QoSMaxGapLossTag = 0x04, + QoSMaxAUSizeTag = 0x41, + QoSAvgAUSizeTag = 0x42, + QoSMaxAURateTag = 0x43 +}; + +/*! QoS Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u8 predefined; + GF_List *QoS_Qualifiers; +} GF_QoS_Descriptor; + + +/*! macro defining a base QOS qualifier*/ +#define QOS_BASE_QUALIFIER \ + u8 tag; \ + u32 size; + +/*! QoS Default Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER +} GF_QoS_Default; + +/*! QoS Max Delay Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + u32 MaxDelay; +} GF_QoS_MaxDelay; + +/*! QoS preferred Max Delay Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + u32 PrefMaxDelay; +} GF_QoS_PrefMaxDelay; + +/*! QoS loss probability Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + Float LossProb; +} GF_QoS_LossProb; + +/*! QoS Max Gap Loss Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + u32 MaxGapLoss; +} GF_QoS_MaxGapLoss; + +/*! QoS Max AU Size Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + u32 MaxAUSize; +} GF_QoS_MaxAUSize; + +/*! QoS Average AU Size Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + u32 AvgAUSize; +} GF_QoS_AvgAUSize; + +/*! QoS AU rate Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + u32 MaxAURate; +} GF_QoS_MaxAURate; + +/*! QoS private Qualifier*/ +typedef struct { + QOS_BASE_QUALIFIER + /*! max size class is 2^28 - 1*/ + u32 DataLength; + u8 *Data; +} GF_QoS_Private; + + +/*! Registration Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 formatIdentifier; + u32 dataLength; + u8 *additionalIdentificationInfo; +} GF_Registration; + +/*! Language Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 langCode; + char *full_lang_code; +} GF_Language; + +/*! Elementary Stream Descriptor*/ +typedef struct +{ + BASE_DESCRIPTOR + u16 ESID; + u16 OCRESID; + u16 dependsOnESID; + u8 streamPriority; + char *URLString; + GF_DecoderConfig *decoderConfig; + GF_SLConfig *slConfig; + GF_IPIPtr *ipiPtr; + GF_QoS_Descriptor *qos; + GF_Registration *RegDescriptor; + /*! 0 or 1 lang desc*/ + GF_Language *langDesc; + + GF_List *IPIDataSet; + GF_List *IPMPDescriptorPointers; + GF_List *extensionDescriptors; + + //GPAC internals + + /*! 1 if this stream has scalable layers, 0 otherwise (GPAC internals)*/ + Bool has_scalable_layers; + /*! service URL (GPAC internals)*/ + const char *service_url; +} GF_ESD; + + +/*! Auxiliary Video Data Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 aux_video_type; + u32 position_offset_h; + u32 position_offset_v; + u32 knear; + u32 kfar; + u32 parallax_zero; + u32 parallax_scale; + u32 dref; + u32 wref; +} GF_AuxVideoDescriptor; + +/*! Content Classification Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 classificationEntity; + u16 classificationTable; + u32 dataLength; + char *contentClassificationData; +} GF_CCDescriptor; + + +/*! this structure is used in GF_KeyWord*/ +typedef struct { + char *keyWord; +} GF_KeyWordItem; + +/*! Key Word Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 languageCode; + u8 isUTF8; + GF_List *keyWordsList; +} GF_KeyWord; + +/*! Rating Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 ratingEntity; + u16 ratingCriteria; + u32 infoLength; + char *ratingInfo; +} GF_Rating; + + +/*! Short Textual Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 langCode; + u8 isUTF8; + char *eventName; + char *eventText; +} GF_ShortTextual; + + +/*! this structure is used in GF_ExpandedTextual*/ +typedef struct { + char *text; +} GF_ETD_ItemText; + +/*! Expanded Textual Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u32 langCode; + u8 isUTF8; + GF_List *itemDescriptionList; + GF_List *itemTextList; + char *NonItemText; +} GF_ExpandedTextual; + +/*! this structure is used in GF_CC_Name*/ +typedef struct { + u32 langCode; + u8 isUTF8; + char *contentCreatorName; +} GF_ContentCreatorInfo; + +/*! Content Creator Name GF_Descriptor +\note The desctructor will delete all the items in the list +(GF_ContentCreatorInfo items) */ +typedef struct { + BASE_DESCRIPTOR + GF_List *ContentCreators; +} GF_CC_Name; + +/*! Content Creation Date Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + char contentCreationDate[5]; +} GF_CC_Date; + + +/*! this structure is used in GF_OCICreators*/ +typedef struct { + u32 langCode; + u8 isUTF8; + char *OCICreatorName; +} GF_OCICreator_item; + +/*! OCI Creator Name Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + GF_List *OCICreators; +} GF_OCICreators; + +/*! OCI Creation Date Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + char OCICreationDate[5]; +} GF_OCI_Data; + + +/*! this structure is used in GF_SMPTECamera*/ +typedef struct { + u8 paramID; + u32 param; +} GF_SmpteParam; + +/*! Smpte Camera Position Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u8 cameraID; + GF_List *ParamList; +} GF_SMPTECamera; + + +/*! Extension Profile Level Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u8 profileLevelIndicationIndex; + u8 ODProfileLevelIndication; + u8 SceneProfileLevelIndication; + u8 AudioProfileLevelIndication; + u8 VisualProfileLevelIndication; + u8 GraphicsProfileLevelIndication; + u8 MPEGJProfileLevelIndication; +} GF_PLExt; + +/*! Profile Level Indication Index Descriptor*/ +typedef struct { + BASE_DESCRIPTOR + u8 profileLevelIndicationIndex; +} GF_PL_IDX; + +/*! used for storing NALU-based parameter set in AVC/HEVC/VVC configuration record*/ +typedef struct +{ + /*! size of nal*/ + u16 size; + /*! nal data*/ + u8 *data; + /*! ID of param set, used by some importers but not written in file*/ + s32 id; + /*! CRC of nal, used by some importers but not written in file*/ + u32 crc; +} GF_NALUFFParam; + +/*! pre v1.1 naming of NALU config record*/ +typedef GF_NALUFFParam GF_AVCConfigSlot; + +/*! AVC config record - not a real MPEG-4 descriptor +*/ +typedef struct +{ + u8 configurationVersion; + u8 AVCProfileIndication; + u8 profile_compatibility; + u8 AVCLevelIndication; + u8 nal_unit_size; + + GF_List *sequenceParameterSets; + GF_List *pictureParameterSets; + + /*for SVC*/ + u8 complete_representation; + + /*for high profiles*/ + u8 chroma_format; + u8 luma_bit_depth; + u8 chroma_bit_depth; + /*may be NULL*/ + GF_List *sequenceParameterSetExtensions; + + Bool write_annex_b; +} GF_AVCConfig; + + + +/*! used for storing HEVC SPS/PPS/VPS/SEI*/ +typedef struct +{ + u8 type; + u8 array_completeness; + GF_List *nalus; +} GF_NALUFFParamArray; + +/*! pre v1.1 naming of NALU param array*/ +typedef GF_NALUFFParamArray GF_HEVCParamArray; + +/*! HEVC config record - not a real MPEG-4 descriptor*/ +typedef struct +{ + u8 configurationVersion; + u8 profile_space; + u8 tier_flag; + u8 profile_idc; + u32 general_profile_compatibility_flags; + u8 progressive_source_flag; + u8 interlaced_source_flag; + u8 non_packed_constraint_flag; + u8 frame_only_constraint_flag; + /*only lowest 44 bits used*/ + u64 constraint_indicator_flags; + u8 level_idc; + u16 min_spatial_segmentation_idc; + + u8 parallelismType; + u8 chromaFormat; + u8 luma_bit_depth; + u8 chroma_bit_depth; + u16 avgFrameRate; + u8 constantFrameRate; + u8 numTemporalLayers; + u8 temporalIdNested; + + u8 nal_unit_size; + + GF_List *param_array; + //used in LHVC config + Bool complete_representation; + + //following are internal to libgpac and NEVER serialized + + //set by libisomedia at import/export/parsing time to differentiate between lhcC and hvcC time + Bool is_lhvc; + + Bool write_annex_b; + +} GF_HEVCConfig; + + + +/*! VVC config record - not a real MPEG-4 descriptor*/ +typedef struct +{ + u8 general_profile_idc; + u8 general_tier_flag; + u8 general_sub_profile_idc; + u8 num_constraint_info; + u8 *general_constraint_info; + u8 general_level_idc; + + u8 ptl_sublayer_present_mask; + u8 sublayer_level_idc[8]; + + u8 chroma_format; + u8 bit_depth; + u16 avgFrameRate; + u8 constantFrameRate; + u8 numTemporalLayers; + u16 maxPictureWidth, maxPictureHeight; + + Bool ptl_present, ptl_frame_only_constraint, ptl_multilayer_enabled; + u8 num_sub_profiles; + u32 *sub_profiles_idc; + + u16 ols_idx; + u8 nal_unit_size; + + GF_List *param_array; + + Bool write_annex_b; +} GF_VVCConfig; + + +/*! used for storing AV1 OBUs*/ +typedef struct +{ + u64 obu_length; + int obu_type; /*ObuType*/ + u8 *obu; +} GF_AV1_OBUArrayEntry; + +/*! AV1 config record - not a real MPEG-4 descriptor*/ +typedef struct +{ + Bool marker; + u8 version; /*7 bits*/ + u8 seq_profile; /*3 bits*/ + u8 seq_level_idx_0; /*5 bits*/ + Bool seq_tier_0; + Bool high_bitdepth; + Bool twelve_bit; + Bool monochrome; + Bool chroma_subsampling_x; + Bool chroma_subsampling_y; + u8 chroma_sample_position; /*2 bits*/ + + Bool initial_presentation_delay_present; + u8 initial_presentation_delay_minus_one; /*4 bits*/ + GF_List *obu_array; /*GF_AV1_OBUArrayEntry*/ +} GF_AV1Config; + +/*! max number of reference frames for VP9*/ +#define VP9_NUM_REF_FRAMES 8 + +/*! VP8-9 config vpcC */ +typedef struct +{ + u8 profile; + u8 level; + + u8 bit_depth; /*4 bits*/ + u8 chroma_subsampling; /*3 bits*/ + Bool video_fullRange_flag; + + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + /* MUST be 0 for VP8 and VP9 */ + u16 codec_initdata_size; + u8* codec_initdata; + + /* parsing state information - not used for vpcC*/ + int RefFrameWidth[VP9_NUM_REF_FRAMES]; + int RefFrameHeight[VP9_NUM_REF_FRAMES]; +} GF_VPConfig; + +/*! DolbyVision config dvcC/dvvC */ +typedef struct { + u8 dv_version_major; + u8 dv_version_minor; + u8 dv_profile; //7 bits + u8 dv_level; //6 bits + Bool rpu_present_flag; + Bool el_present_flag; + Bool bl_present_flag; + u8 dv_bl_signal_compatibility_id; //4 bits + //const unsigned int (28) reserved = 0; + //const unsigned int (32)[4] reserved = 0; + + //internal, force dvhe or dvh1 signaling + u8 force_dv; +} GF_DOVIDecoderConfigurationRecord; + +/*! Media Segment Descriptor used for Media Control Extensions*/ +typedef struct +{ + BASE_DESCRIPTOR + Double startTime; + Double Duration; + char *SegmentName; +} GF_Segment; + +/*! Media Time Descriptor used for Media Control Extensions*/ +typedef struct +{ + BASE_DESCRIPTOR + Double mediaTimeStamp; +} GF_MediaTime; + + +/*! MPEG-4 SYSTEMS OD Commands Tags */ +enum +{ + GF_ODF_OD_UPDATE_TAG = 0x01, + GF_ODF_OD_REMOVE_TAG = 0x02, + GF_ODF_ESD_UPDATE_TAG = 0x03, + GF_ODF_ESD_REMOVE_TAG = 0x04, + GF_ODF_IPMP_UPDATE_TAG = 0x05, + GF_ODF_IPMP_REMOVE_TAG = 0x06, + + /*file format reserved*/ + GF_ODF_ESD_REMOVE_REF_TAG = 0x07, + + GF_ODF_COM_ISO_BEGIN_TAG = 0x0D, + GF_ODF_COM_ISO_END_TAG = 0xBF, + + GF_ODF_COM_USER_BEGIN_TAG = 0xC0, + GF_ODF_COM_USER_END_TAG = 0xFE +}; + +/*! macro defining a base OD command*/ +#define BASE_OD_COMMAND \ + u8 tag; + +/*! MPEG-4 SYSTEMS OD - (abstract) base command. */ +typedef struct { + BASE_OD_COMMAND +} GF_ODCom; + +/*! MPEG-4 SYSTEMS OD - default command*/ +typedef struct { + BASE_OD_COMMAND + u32 dataSize; + u8 *data; +} GF_BaseODCom; + +/*! MPEG-4 SYSTEMS OD - Object Descriptor Update +NB: the list can contain OD or IOD, except internally in the File Format (only MP4_OD)*/ +typedef struct +{ + BASE_OD_COMMAND + GF_List *objectDescriptors; +} GF_ODUpdate; + +/*! MPEG-4 SYSTEMS OD - Object Descriptor Remove*/ +typedef struct +{ + BASE_OD_COMMAND + u32 NbODs; + u16 *OD_ID; +} GF_ODRemove; + +/*! MPEG-4 SYSTEMS OD - Elementary Stream Descriptor Update*/ +typedef struct +{ + BASE_OD_COMMAND + u16 ODID; + GF_List *ESDescriptors; +} GF_ESDUpdate; + +/*! MPEG-4 SYSTEMS OD - Elementary Stream Descriptor Remove*/ +typedef struct { + BASE_OD_COMMAND + u16 ODID; + u32 NbESDs; + u16 *ES_ID; +} GF_ESDRemove; + +/*! MPEG-4 SYSTEMS OD - IPMP Descriptor Update*/ +typedef struct { + BASE_OD_COMMAND + GF_List *IPMPDescList; +} GF_IPMPUpdate; + +/*! MPEG-4 SYSTEMS OD - IPMP Descriptor Remove*/ +typedef struct { + BASE_OD_COMMAND + u32 NbIPMPDs; + /*now this is bad: only IPMPv1 descriptors can be removed at run tim...*/ + u8 *IPMPDescID; +} GF_IPMPRemove; + + + + + + +/*! MPEG-4 SYSTEMS OD - OD API */ + +/*! OD CODEC object - just a simple reader/writer*/ +typedef struct tagODCoDec +{ + GF_BitStream *bs; + GF_List *CommandList; +} GF_ODCodec; + + +/*! OD codec construction +\return new codec object*/ +GF_ODCodec *gf_odf_codec_new(); +/*! OD codec destruction +\param codec OD codec to destroy + */ +void gf_odf_codec_del(GF_ODCodec *codec); +/*! add a command to the codec command list. +\param codec target codec +\param command command to add +\return error if any + */ +GF_Err gf_odf_codec_add_com(GF_ODCodec *codec, GF_ODCom *command); +/*! encode the current command list. +\param codec target codec +\param cleanup_type specifies what to do with the command after encoding. The following values are accepted: + 0: commands are removed from the list but not destroyed + 1: commands are removed from the list and destroyed + 2: commands are kept in the list and not destroyed +\return error if any +*/ +GF_Err gf_odf_codec_encode(GF_ODCodec *codec, u32 cleanup_type); +/*! get the encoded AU. +\param codec target codec +\param outAU output buffer allocated by the codec, user is responsible of freeing the allocated space +\param au_length size of the AU (allocated buffer) +\return error if any + */ +GF_Err gf_odf_codec_get_au(GF_ODCodec *codec, u8 **outAU, u32 *au_length); +/*! set the encoded AU to the codec +\param codec target codec +\param au target AU to decode +\param au_length size in bytes of the AU to decode +\return error if any + */ +GF_Err gf_odf_codec_set_au(GF_ODCodec *codec, const u8 *au, u32 au_length); +/*! decode the previously set-up AU +\param codec target codec +\return error if any + */ +GF_Err gf_odf_codec_decode(GF_ODCodec *codec); +/*! get the first OD command in the list. Once called, the command is removed +from the command list. Return NULL when commandList is empty +\param codec target codec +\return deocded command or NULL + */ +GF_ODCom *gf_odf_codec_get_com(GF_ODCodec *codec); + +/*! apply a command to the codec command list. Command is duplicated if needed +This is used for state maintenance and RAP generation. +\param codec target codec +\param command the command to apply +\return error if any + */ +GF_Err gf_odf_codec_apply_com(GF_ODCodec *codec, GF_ODCom *command); + + +/*! MPEG-4 SYSTEMS OD Command Creation +\param tag type of command to create +\return the created command or NULL + */ +GF_ODCom *gf_odf_com_new(u8 tag); +/*! MPEG-4 SYSTEMS OD Command Destruction +\param com the command to delete. Pointer is set back to NULL +\return error if any + */ +GF_Err gf_odf_com_del(GF_ODCom **com); + + +/************************************************************ + Descriptors Functions +************************************************************/ + +/*! Descriptors Creation +\param tag type of descriptor to create +\return created descriptor or NULL + */ +GF_Descriptor *gf_odf_desc_new(u8 tag); +/*! Descriptors Destruction +\param desc the descriptor to destroy + */ +void gf_odf_desc_del(GF_Descriptor *desc); + +/*! helper for building a preformatted GF_ESD with decoderConfig, decoderSpecificInfo with no data and +SLConfig descriptor with predefined +\param sl_predefined type of predefined sl config +\return the ESD created +*/ +GF_ESD *gf_odf_desc_esd_new(u32 sl_predefined); + +/*! special function for authoring - convert DSI to BIFSConfig +\param dsi BIFS decoder specific info +\param codecid BIFS codecid/object type indication +\return decoded BIFS Config descriptor - It is the caller responsability of freeing it + */ +GF_BIFSConfig *gf_odf_get_bifs_config(GF_DefaultDescriptor *dsi, u32 codecid); +/*! special function for authoring - convert DSI to LASERConfig +\param dsi LASER decoder specific info +\param cfg the LASER config object to be filled +\return error if any + */ +GF_Err gf_odf_get_laser_config(GF_DefaultDescriptor *dsi, GF_LASERConfig *cfg); +/*! special function for authoring - convert DSI to TextConfig +\param data TEXT decoder config block +\param data_len TEXT decoder config block size +\param codecid TEXT codecid/object type indication +\param cfg the text config object to be filled +\return error if any + */ +GF_Err gf_odf_get_text_config(u8 *data, u32 data_len, u32 codecid, GF_TextConfig *cfg); +/*! converts UIConfig to dsi - does not destroy input descr but does create output one +\param cfg the UI config object +\param out_dsi the decoder specific info created. It is the caller responsability of freeing it +\return error if any + */ +GF_Err gf_odf_encode_ui_config(GF_UIConfig *cfg, GF_DefaultDescriptor **out_dsi); + +/*! AVC config constructor +\return the created AVC config*/ +GF_AVCConfig *gf_odf_avc_cfg_new(); +/*! AVC config destructor +\param cfg the AVC config to destroy*/ +void gf_odf_avc_cfg_del(GF_AVCConfig *cfg); +/*! gets GF_AVCConfig from MPEG-4 DSI +\param dsi encoded AVC decoder specific info +\param dsi_size encoded AVC decoder specific info size +\return the decoded AVC config + */ +GF_AVCConfig *gf_odf_avc_cfg_read(u8 *dsi, u32 dsi_size); +/*! writes GF_AVCConfig +\param cfg the AVC config to encode +\param outData encoded dsi buffer - it is the caller responsability to free this +\param outSize encoded dsi buffer size +\return error if any + */ +GF_Err gf_odf_avc_cfg_write(GF_AVCConfig *cfg, u8 **outData, u32 *outSize); + +/*! writes GF_AVCConfig to bitstream +\param cfg the AVC config to encode +\param bs bitstream in WRITE mode +\return error if any + */ +GF_Err gf_odf_avc_cfg_write_bs(GF_AVCConfig *cfg, GF_BitStream *bs); + +/*! writes GF_TextSampleDescriptor +\param cfg the text config to encode +\param outData address of output buffer (internal alloc, user to free it) +\param outSize size of the allocated output +\return error if any + */ +GF_Err gf_odf_tx3g_write(GF_TextSampleDescriptor *cfg, u8 **outData, u32 *outSize); + +/*! gets GF_TextSampleDescriptor from buffer +\param dsi encoded text sample description +\param dsi_size size of encoded description +\return the decoded GF_TextSampleDescriptor config + */ +GF_TextSampleDescriptor *gf_odf_tx3g_read(u8 *dsi, u32 dsi_size); + +/*! HEVC config constructor +\return the created HEVC config*/ +GF_HEVCConfig *gf_odf_hevc_cfg_new(); + +/*! HEVC config destructor +\param cfg the HEVC config to destroy*/ +void gf_odf_hevc_cfg_del(GF_HEVCConfig *cfg); + +/*! writes GF_HEVCConfig as MPEG-4 DSI in a bitstream object +\param cfg the HEVC config to encode +\param bs output bitstream object in which the config is written +\return error if any + */ +GF_Err gf_odf_hevc_cfg_write_bs(GF_HEVCConfig *cfg, GF_BitStream *bs); + +/*! writes GF_HEVCConfig as MPEG-4 DSI +\param cfg the HEVC config to encode +\param outData encoded dsi buffer - it is the caller responsability to free this +\param outSize encoded dsi buffer size +\return error if any + */ +GF_Err gf_odf_hevc_cfg_write(GF_HEVCConfig *cfg, u8 **outData, u32 *outSize); + +/*! gets GF_HEVCConfig from bitstream MPEG-4 DSI +\param bs bitstream containing the encoded HEVC decoder specific info +\param is_lhvc if GF_TRUE, indicates if the dsi is LHVC +\return the decoded HEVC config + */ +GF_HEVCConfig *gf_odf_hevc_cfg_read_bs(GF_BitStream *bs, Bool is_lhvc); + +/*! gets GF_HEVCConfig from MPEG-4 DSI +\param dsi encoded HEVC decoder specific info +\param dsi_size encoded HEVC decoder specific info size +\param is_lhvc if GF_TRUE, indicates if the dsi is LHVC +\return the decoded HEVC config + */ +GF_HEVCConfig *gf_odf_hevc_cfg_read(u8 *dsi, u32 dsi_size, Bool is_lhvc); + + +/*! VVC config constructor +\return the created VVC config*/ +GF_VVCConfig *gf_odf_vvc_cfg_new(); + +/*! VVC config destructor +\param cfg the VVC config to destroy*/ +void gf_odf_vvc_cfg_del(GF_VVCConfig *cfg); + +/*! writes GF_VVCConfig as MPEG-4 DSI in a bitstream object +\param cfg the VVC config to encode +\param bs output bitstream object in which the config is written +\return error if any + */ +GF_Err gf_odf_vvc_cfg_write_bs(GF_VVCConfig *cfg, GF_BitStream *bs); + +/*! writes GF_VVCConfig as MPEG-4 DSI +\param cfg the VVC config to encode +\param outData encoded dsi buffer - it is the caller responsability to free this +\param outSize encoded dsi buffer size +\return error if any + */ +GF_Err gf_odf_vvc_cfg_write(GF_VVCConfig *cfg, u8 **outData, u32 *outSize); + +/*! gets GF_VVCConfig from bitstream MPEG-4 DSI +\param bs bitstream containing the encoded VVC decoder specific info +\return the decoded VVC config + */ +GF_VVCConfig *gf_odf_vvc_cfg_read_bs(GF_BitStream *bs); + +/*! gets GF_VVCConfig from MPEG-4 DSI +\param dsi encoded VVC decoder specific info +\param dsi_size encoded VVC decoder specific info size +\return the decoded VVC config + */ +GF_VVCConfig *gf_odf_vvc_cfg_read(u8 *dsi, u32 dsi_size); + +/*! AV1 config constructor +\return the created AV1 config*/ +GF_AV1Config *gf_odf_av1_cfg_new(); + +/*! AV1 config destructor +\param cfg the AV1 config to destroy*/ +void gf_odf_av1_cfg_del(GF_AV1Config *cfg); + +/*! writes AV1 config to buffer +\param cfg the AV1 config to write +\param outData set to an allocated encoded buffer - it is the caller responsability to free this +\param outSize set to the encoded dsi buffer size +\return error if any +*/ +GF_Err gf_odf_av1_cfg_write(GF_AV1Config *cfg, u8 **outData, u32 *outSize); + +/*! writes AV1 config to bitstream +\param cfg the AV1 config to write +\param bs bitstream containing the encoded AV1 decoder specific info +\return error code if any +*/ +GF_Err gf_odf_av1_cfg_write_bs(GF_AV1Config *cfg, GF_BitStream *bs); + +/*! gets AV1 config from bitstream +\param bs bitstream containing the encoded AV1 decoder specific info +\return the decoded AV1 config +*/ +GF_AV1Config *gf_odf_av1_cfg_read_bs(GF_BitStream *bs); + +/*! gets AV1 config to bitstream +\param bs bitstream containing the encoded AV1 decoder specific info +\param size size of encoded structure in the bitstream. A value of 0 means "until the end", equivalent to gf_odf_av1_cfg_read_bs +\return the decoded AV1 config +*/ +GF_AV1Config *gf_odf_av1_cfg_read_bs_size(GF_BitStream *bs, u32 size); + +/*! gets AV1 config from buffer +\param dsi encoded AV1 config +\param dsi_size size of encoded AV1 config +\return the decoded AV1 config +*/ +GF_AV1Config *gf_odf_av1_cfg_read(u8 *dsi, u32 dsi_size); + + +/*! creates a VPx (VP8, VP9) descriptor +\return a newly allocated descriptor +*/ +GF_VPConfig *gf_odf_vp_cfg_new(); +/*! destroys an VPx config +\param cfg the VPx config to destroy*/ +void gf_odf_vp_cfg_del(GF_VPConfig *cfg); +/*! writes VPx config to bitstream +\param cfg the VPx config to write +\param bs bitstream containing the encoded VPx decoder specific info +\param is_v0 if GF_TRUE, this is a version 0 config +\return error code if any +*/ +GF_Err gf_odf_vp_cfg_write_bs(GF_VPConfig *cfg, GF_BitStream *bs, Bool is_v0); +/*! writes VPx config to buffer +\param cfg the VPx config to write +\param outData set to an allocated encoded buffer - it is the caller responsability to free this +\param outSize set to the encoded buffer size +\param is_v0 if GF_TRUE, this is a version 0 config +\return error if any +*/ +GF_Err gf_odf_vp_cfg_write(GF_VPConfig *cfg, u8 **outData, u32 *outSize, Bool is_v0); +/*! gets VPx config to bitstream +\param bs bitstream containing the encoded VPx decoder specific info +\param is_v0 if GF_TRUE, this is a version 0 config +\return the decoded VPx config +*/ +GF_VPConfig *gf_odf_vp_cfg_read_bs(GF_BitStream *bs, Bool is_v0); +/*! gets VPx config from buffer +\param dsi encoded VPx config +\param dsi_size size of encoded VPx config +\return the decoded VPx config +*/ +GF_VPConfig *gf_odf_vp_cfg_read(u8 *dsi, u32 dsi_size); + +/*! gets DolbyVision config to bitstream +\param bs bitstream containing the encoded VPx decoder specific info +\return the decoded DolbyVision config +*/ +GF_DOVIDecoderConfigurationRecord *gf_odf_dovi_cfg_read_bs(GF_BitStream *bs); +/*! writes DolbyVision config to buffer +\param cfg the DolbyVision config to write +\param bs the bitstream object in which to write the config +\return error if any +*/ +GF_Err gf_odf_dovi_cfg_write_bs(GF_DOVIDecoderConfigurationRecord *cfg, GF_BitStream *bs); +/*! destroys a DolbyVision config +\param cfg the DolbyVision config to destroy*/ +void gf_odf_dovi_cfg_del(GF_DOVIDecoderConfigurationRecord *cfg); + + +/*! AC-3 config record extension for EAC-3 - see dolby specs*/ +typedef struct __ec3_stream +{ + /*! AC3 fs code*/ + u8 fscod; + /*! AC3 bsid code*/ + u8 bsid; + /*! AC3 bs mode*/ + u8 bsmod; + /*! AC3 ac mode*/ + u8 acmod; + /*! LF on*/ + u8 lfon; + /*! asvc mode, only for EC3*/ + u8 asvc; + /*! deps, only for EC3*/ + u8 nb_dep_sub; + /*! channel loc, only for EC3*/ + u8 chan_loc; +} GF_AC3StreamInfo; + +/*! AC3 config record*/ +typedef struct __ac3_config +{ + /*! indicates if ec3*/ + u8 is_ec3; + /*! if AC3 this is the bitrate code, otherwise cumulated data rate of EC3 streams*/ + u16 brcode; + /*! number of streams : + 1 for AC3 + max 8 for EC3, main stream is included + */ + u8 nb_streams; + /*! streams info*/ + GF_AC3StreamInfo streams[8]; + + //! \cond used in parsing not part of the AC3 config record + u32 bitrate; + u32 sample_rate; + u32 framesize; + u32 channels; + u16 substreams; //bit-mask, used for channel map > 5.1 + + //! \endcond private + +} GF_AC3Config; + + +/*! writes Dolby AC3/EAC3 config to buffer +\param cfg the Dolby AC3 config to write +\param bs the bitstream object in which to write the config +\return error if any +*/ +GF_Err gf_odf_ac3_cfg_write_bs(GF_AC3Config *cfg, GF_BitStream *bs); +/*! writes Dolby AC3/EAC3 config to buffer +\param cfg the Dolby AC3 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_ac3_cfg_write(GF_AC3Config *cfg, u8 **data, u32 *size); + + +/*! parses an AC3/EC3 sample description +\param dsi the encoded config +\param dsi_len the encoded config size +\param is_ec3 indicates that the encoded config is for an EC3 track +\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); + +/*! parses an AC3/EC3 sample description from bitstream +\param bs the bitstream object +\param is_ec3 indicates that the encoded config is for an EC3 track +\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); + +/*! destroy the descriptors in a list but not the list +\param descList descriptor list to destroy +\return error if any + */ +GF_Err gf_odf_desc_list_del(GF_List *descList); + +/*! use this function to decode a standalone descriptor +the raw descriptor MUST be formatted with tag and size field!!! +a new desc is created and you must delete it when done +\param raw_desc encoded descriptor to decode +\param descSize size of descriptor to decode +\param outDesc output decoded descriptor - it is the caller responsability to free this +\return error if any +*/ +GF_Err gf_odf_desc_read(u8 *raw_desc, u32 descSize, GF_Descriptor **outDesc); + +/*! use this function to encode a standalone descriptor +the desc will be formatted with tag and size field +the output buffer is allocated and you must delete it when done +\param desc descriptor to encode +\param outEncDesc output encoded descriptor - it is the caller responsability to free this +\param outSize size of encoded descriptor +\return error if any + */ +GF_Err gf_odf_desc_write(GF_Descriptor *desc, u8 **outEncDesc, u32 *outSize); + +/*! use this function to encode a standalone descriptor in a bitstream object +the desc will be formatted with tag and size field +\param desc descriptor to encode +\param bs the bitstream object in write mode +\return error if any + */ +GF_Err gf_odf_desc_write_bs(GF_Descriptor *desc, GF_BitStream *bs); + +/*! use this function to get the size of a standalone descriptor (including tag and size fields) +\param desc descriptor to encode +\return 0 if error or encoded desc size otherwise*/ +u32 gf_odf_desc_size(GF_Descriptor *desc); + +/*! duplicate descriptors +\param inDesc descriptor to copy +\param outDesc copied descriptor - it is the caller responsability to free this +\return error if any + */ +GF_Err gf_odf_desc_copy(GF_Descriptor *inDesc, GF_Descriptor **outDesc); + +/*! Adds a descriptor to a parent one. Handles internally what desc can be added to another desc +and adds it. NO DUPLICATION of the descriptor, so +once a desc is added to its parent, destroying the parent WILL DESTROY +this descriptor +\param parentDesc parent descriptor +\param newDesc descriptor to add to parent +\return error if any + */ +GF_Err gf_odf_desc_add_desc(GF_Descriptor *parentDesc, GF_Descriptor *newDesc); + +/*! Since IPMP V2, we introduce a new set of functions to read / write a list of descriptors +that have no containers (a bit like an OD command, but for descriptors) +This is useful for IPMPv2 DecoderSpecificInfo which contains a set of IPMP_Declarators +As it could be used for other purposes we keep it generic +you must create the list yourself, the functions just encode/decode from/to the list*/ + +/*! uncompress an encoded list of descriptors. You must pass an empty GF_List structure +to know exactly what was in the buffer +\param raw_list encoded list of descriptors +\param raw_size size of the encoded list of descriptors +\param descList list in which the decoded descriptors will be placed +\return error if any +*/ +GF_Err gf_odf_desc_list_read(u8 *raw_list, u32 raw_size, GF_List *descList); + +/*! compress all descriptors in the list into a single buffer. The buffer is allocated +by the lib and must be destroyed by your app +you must pass (outEncList != NULL && *outEncList == NULL) +\param descList list of descriptors to be encoded +\param outEncList buffer of encoded descriptors +\param outSize size of buffer of encoded descriptors +\return error if any + */ +GF_Err gf_odf_desc_list_write(GF_List *descList, u8 **outEncList, u32 *outSize); + +/*! returns size of encoded desc list +\param descList list of descriptors to be encoded +\param outSize size of buffer of encoded descriptors +\return error if any + */ +GF_Err gf_odf_desc_list_size(GF_List *descList, u32 *outSize); + + +//! @cond Doxygen_Suppress + +#ifndef GPAC_MINIMAL_ODF + + +/************************************************************ + QoS Qualifiers Functions +************************************************************/ + +/*! QoS Qualifiers constructor +\param tag tag of QoS descriptor to create +\return created QoS descriptor + */ +GF_QoS_Default *gf_odf_qos_new(u8 tag); +/*! QoS Qualifiers destructor +\param qos descriptor to destroy. The pointer is set back to NULL upon destruction +\return error if any + */ +GF_Err gf_odf_qos_del(GF_QoS_Default **qos); + +/*! READ/WRITE functions: QoS qualifiers are special descriptors but follow the same rules as descriptors. +therefore, use gf_odf_desc_read and gf_odf_desc_write for QoS*/ + +/*! Adds a QoS qualificator to a parent QoS descriptor +\param desc parent QoS descriptor +\param qualif QoS qualificator +\return error if any + */ +GF_Err gf_odf_qos_add_qualif(GF_QoS_Descriptor *desc, GF_QoS_Default *qualif); + + + +/* + OCI Stream AU is a list of OCI event (like OD AU is a list of OD commands) +*/ + +typedef struct __tag_oci_event OCIEvent; + +OCIEvent *gf_oci_event_new(u16 EventID); +void gf_oci_event_del(OCIEvent *event); + +GF_Err gf_oci_event_set_start_time(OCIEvent *event, u8 Hours, u8 Minutes, u8 Seconds, u8 HundredSeconds, u8 IsAbsoluteTime); +GF_Err gf_oci_event_set_duration(OCIEvent *event, u8 Hours, u8 Minutes, u8 Seconds, u8 HundredSeconds); +GF_Err gf_oci_event_add_desc(OCIEvent *event, GF_Descriptor *oci_desc); + +GF_Err gf_oci_event_get_id(OCIEvent *event, u16 *ID); +GF_Err gf_oci_event_get_start_time(OCIEvent *event, u8 *Hours, u8 *Minutes, u8 *Seconds, u8 *HundredSeconds, u8 *IsAbsoluteTime); +GF_Err gf_oci_event_get_duration(OCIEvent *event, u8 *Hours, u8 *Minutes, u8 *Seconds, u8 *HundredSeconds); +u32 gf_oci_event_get_desc_count(OCIEvent *event); +GF_Descriptor *gf_oci_event_get_desc(OCIEvent *event, u32 DescIndex); +GF_Err gf_oci_event_rem_desc(OCIEvent *event, u32 DescIndex); + + + +typedef struct __tag_oci_codec OCICodec; + +/*construction / destruction +IsEncoder specifies an OCI Event encoder +version is for future extensions, and only 0x01 is valid for now*/ +OCICodec *gf_oci_codec_new(u8 IsEncoder, u8 Version); +void gf_oci_codec_del(OCICodec *codec); + +/* ENCODER FUNCTIONS +add a command to the codec event list. +The event WILL BE DESTROYED BY THE CODEC after encoding*/ +GF_Err gf_oci_codec_add_event(OCICodec *codec, OCIEvent *event); + +/*encode AU. The memory allocation is done in place +WARNING: once this function called, the codec event List is empty +and events destroyed +you must set *outAU = NULL*/ +GF_Err gf_oci_codec_encode(OCICodec *codec, u8 **outAU, u32 *au_length); + + + +/*Decoder: decode the previously set-up AU +the input buffer is cleared once decoded*/ +GF_Err gf_oci_codec_decode(OCICodec *codec, u8 *au, u32 au_length); + +/*get the first OCI Event in the list. Once called, the event is removed +from the event list. Return NULL when the event List is empty +you MUST delete events */ +OCIEvent *gf_oci_codec_get_event(OCICodec *codec); + +/*! Dumps an OCI event +\param ev OCI event to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT + */ +GF_Err gf_oci_dump_event(OCIEvent *ev, FILE *trace, u32 indent, Bool XMTDump); +/*! Dumps an OCI AU +\param version version of the OCI stream +\param au OCI AU to dump +\param au_length size of the OCI AU to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT + */ +GF_Err gf_oci_dump_au(u8 version, u8 *au, u32 au_length, FILE *trace, u32 indent, Bool XMTDump); + +#endif /*GPAC_MINIMAL_ODF*/ + +//! @endcond + +#ifndef GPAC_DISABLE_OD_DUMP + +/*! Dumps an OD AU +\param com OD command to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\return error if any + */ +GF_Err gf_odf_dump_com(GF_ODCom *com, FILE *trace, u32 indent, Bool XMTDump); +/*! Dumps an OD Descriptor +\param desc descriptor to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\return error if any + */ +GF_Err gf_odf_dump_desc(GF_Descriptor *desc, FILE *trace, u32 indent, Bool XMTDump); +/*! Dumps an OD Descriptor +\param commandList descriptor list to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\return error if any + */ +GF_Err gf_odf_dump_com_list(GF_List *commandList, FILE *trace, u32 indent, Bool XMTDump); + +#endif /*GPAC_DISABLE_OD_DUMP*/ + + +/*! Gets descriptor tag by name +\param descName target descriptor name +\return descriptor tag or 0 if error + */ +u32 gf_odf_get_tag_by_name(char *descName); + +/*! field type for OD/QoS/IPMPX/etc*/ +typedef enum +{ + /*! regular type*/ + GF_ODF_FT_DEFAULT = 0, + /*! single descriptor type*/ + GF_ODF_FT_OD = 1, + /*! descriptor list type*/ + GF_ODF_FT_OD_LIST = 2, + /*! IPMP Data type*/ + GF_ODF_FT_IPMPX = 3, + /*! IPMP Data list type*/ + GF_ODF_FT_IPMPX_LIST = 4, + /*! IPMP ByteArray type*/ + GF_ODF_FT_IPMPX_BA = 5, + /*! IPMP ByteArray list type*/ + GF_ODF_FT_IPMPX_BA_LIST = 6 +} GF_ODF_FieldType; +/*! Gets ODF field type by name +\param desc target descriptor +\param fieldName descriptor field name +\return the descriptor field type +*/ +GF_ODF_FieldType gf_odf_get_field_type(GF_Descriptor *desc, char *fieldName); + +/*! Set non-descriptor field value - value string shall be presented without ' or " characters +\param desc target descriptor +\param fieldName descriptor field name +\param val field value to parse +\return error if any + */ +GF_Err gf_odf_set_field(GF_Descriptor *desc, char *fieldName, char *val); + +#ifndef GPAC_MINIMAL_ODF + +//! @cond Doxygen_Suppress + + +/* + IPMPX extensions - IPMP Data only (messages are not supported yet) +*/ + +/*! IPMPX base buffer object*/ +typedef struct +{ + u32 length; + u8 *data; +} GF_IPMPX_ByteArray; + +#define GF_IPMPX_AUTH_DESC \ + u8 tag; \ + +/*! IPMPX authentication descriptor*/ +typedef struct +{ + GF_IPMPX_AUTH_DESC +} GF_IPMPX_Authentication; + +enum +{ + GF_IPMPX_AUTH_Forbidden_Tag = 0x00, + GF_IPMPX_AUTH_AlgorithmDescr_Tag = 0x01, + GF_IPMPX_AUTH_KeyDescr_Tag = 0x02 +}; + +/*! IPMPX authentication key descriptor*/ +typedef struct +{ + GF_IPMPX_AUTH_DESC + u8 *keyBody; + u32 keyBodyLength; +} GF_IPMPX_AUTH_KeyDescriptor; + +/*! IPMPX authentication algorithm descriptor*/ +typedef struct +{ + GF_IPMPX_AUTH_DESC + /*used if no specAlgoID*/ + u16 regAlgoID; + GF_IPMPX_ByteArray *specAlgoID; + GF_IPMPX_ByteArray *OpaqueData; +} GF_IPMPX_AUTH_AlgorithmDescriptor; + + +/*! IPMPX data message types*/ +enum +{ + GF_IPMPX_OPAQUE_DATA_TAG = 0x01, + GF_IPMPX_AUDIO_WM_INIT_TAG = 0x02, + GF_IPMPX_VIDEO_WM_INIT_TAG = 0x03, + GF_IPMPX_SEL_DEC_INIT_TAG = 0x04, + GF_IPMPX_KEY_DATA_TAG = 0x05, + GF_IPMPX_AUDIO_WM_SEND_TAG = 0x06, + GF_IPMPX_VIDEO_WM_SEND_TAG = 0x07, + GF_IPMPX_RIGHTS_DATA_TAG = 0x08, + GF_IPMPX_SECURE_CONTAINER_TAG = 0x09, + GF_IPMPX_ADD_TOOL_LISTENER_TAG = 0x0A, + GF_IPMPX_REMOVE_TOOL_LISTENER_TAG = 0x0B, + GF_IPMPX_INIT_AUTHENTICATION_TAG = 0x0C, + GF_IPMPX_MUTUAL_AUTHENTICATION_TAG = 0x0D, + GF_IPMPX_USER_QUERY_TAG = 0x0E, + GF_IPMPX_USER_RESPONSE_TAG = 0x0F, + GF_IPMPX_PARAMETRIC_DESCRIPTION_TAG = 0x10, + GF_IPMPX_PARAMETRIC_CAPS_QUERY_TAG = 0x11, + GF_IPMPX_PARAMETRIC_CAPS_RESPONSE_TAG = 0x12, + /*NO ASSOCIATED STRUCTURE*/ + GF_IPMPX_GET_TOOLS_TAG = 0x13, + GF_IPMPX_GET_TOOLS_RESPONSE_TAG = 0x14, + GF_IPMPX_GET_TOOL_CONTEXT_TAG = 0x15, + GF_IPMPX_GET_TOOL_CONTEXT_RESPONSE_TAG = 0x16, + GF_IPMPX_CONNECT_TOOL_TAG = 0x17, + GF_IPMPX_DISCONNECT_TOOL_TAG = 0x18, + GF_IPMPX_NOTIFY_TOOL_EVENT_TAG = 0x19, + GF_IPMPX_CAN_PROCESS_TAG = 0x1A, + GF_IPMPX_TRUST_SECURITY_METADATA_TAG = 0x1B, + GF_IPMPX_TOOL_API_CONFIG_TAG = 0x1C, + + /*ISMA*/ + GF_IPMPX_ISMACRYP_TAG = 0xD0, + + /*intern ones for parsing (not real datas)*/ + GF_IPMPX_TRUSTED_TOOL_TAG = 0xA1, + GF_IPMPX_TRUST_SPECIFICATION_TAG = 0xA2, + /*emulate algo descriptors as base IPMP classes for parsing...*/ + GF_IPMPX_ALGORITHM_DESCRIPTOR_TAG = 0xA3, + GF_IPMPX_KEY_DESCRIPTOR_TAG = 0xA4, + GF_IPMPX_PARAM_DESCRIPTOR_ITEM_TAG = 0xA5, + GF_IPMPX_SEL_ENC_BUFFER_TAG = 0xA6, + GF_IPMPX_SEL_ENC_FIELD_TAG = 0xA7 +}; + +typedef char GF_IPMPX_Date[5]; + + +#define GF_IPMPX_DATA_BASE \ + u8 tag; \ + u8 Version; \ + u8 dataID; \ + +/*! Base IPMPX data*/ +typedef struct +{ + GF_IPMPX_DATA_BASE +} GF_IPMPX_Data; + +/*! IPMPX Init Authentiactaion data*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + u32 Context; + u8 AuthType; +} GF_IPMPX_InitAuthentication; + +/*! IPMPX Trust Specification data +NOT a real DATA, only used as data for parsing + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_Date startDate; + u8 attackerProfile; + u32 trustedDuration; + GF_IPMPX_ByteArray *CCTrustMetadata; +} GF_IPMPX_TrustSpecification; + +/*! IPMPX Trusted Tool data + NOT a real DATA, only used as data for parsing + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + bin128 toolID; + GF_IPMPX_Date AuditDate; + GF_List *trustSpecifications; +} GF_IPMPX_TrustedTool; + +/*! IPMPX Trust Security Metadata data +*/ +typedef struct _ipmpx_TrustSecurityMetadata +{ + GF_IPMPX_DATA_BASE + GF_List *TrustedTools; +} GF_IPMPX_TrustSecurityMetadata; + + +/*! IPMPX Mutual Authentication data +*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + Bool failedNegotiation; + + GF_List *candidateAlgorithms; + GF_List *agreedAlgorithms; + GF_IPMPX_ByteArray *AuthenticationData; + + /*inclAuthCodes will be set if any of the members is set (cf spec...)*/ + u32 certType; + /*GF_IPMPX_ByteArray list*/ + GF_List *certificates; + GF_IPMPX_AUTH_KeyDescriptor *publicKey; + GF_IPMPX_ByteArray *opaque; + GF_IPMPX_TrustSecurityMetadata *trustData; + GF_IPMPX_ByteArray *authCodes; +} GF_IPMPX_MutualAuthentication; + +/*! IPMPX Secure Container data +*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + /*if set MAC is part of the encrypted data*/ + Bool isMACEncrypted; + + GF_IPMPX_ByteArray *encryptedData; + GF_IPMPX_Data *protectedMsg; + GF_IPMPX_ByteArray *MAC; +} GF_IPMPX_SecureContainer; + +/*! IPMPX Tool Response container +*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_List *ipmp_tools; +} GF_IPMPX_GetToolsResponse; + +/*! IPMPX Parametric Description Item data +*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_ByteArray *main_class; + GF_IPMPX_ByteArray *subClass; + GF_IPMPX_ByteArray *typeData; + GF_IPMPX_ByteArray *type; + GF_IPMPX_ByteArray *addedData; +} GF_IPMPX_ParametricDescriptionItem; + +/*! IPMPX Parametric Description data + */ +typedef struct _tagIPMPXParamDesc +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_ByteArray *descriptionComment; + u8 majorVersion; + u8 minorVersion; + /*list of GF_IPMPX_ParametricDescriptionItem*/ + GF_List *descriptions; +} GF_IPMPX_ParametricDescription; + +/*! IPMPX Tool Capability Query data +*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_ParametricDescription *description; +} GF_IPMPX_ToolParamCapabilitiesQuery; + +/*! IPMPX Tool Capability Response data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + Bool capabilitiesSupported; +} GF_IPMPX_ToolParamCapabilitiesResponse; + + +/*! IPMPX Connected Tool data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMP_Descriptor *toolDescriptor; +} GF_IPMPX_ConnectTool; + +/*! IPMPX Disconnected Tool data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u32 IPMP_ToolContextID; +} GF_IPMPX_DisconnectTool; + + +/*! IPMPX Tool Context ID query data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 scope; + u16 IPMP_DescriptorIDEx; +} GF_IPMPX_GetToolContext; + + +/*! IPMPX Get Tool response data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u16 OD_ID; + u16 ESD_ID; + u32 IPMP_ToolContextID; +} GF_IPMPX_GetToolContextResponse; + +/*! GF_IPMPX_LISTEN_Types*/ +typedef enum +{ + GF_IPMPX_LISTEN_CONNECTED = 0x00, + GF_IPMPX_LISTEN_CONNECTIONFAILED = 0x01, + GF_IPMPX_LISTEN_DISCONNECTED = 0x02, + GF_IPMPX_LISTEN_DISCONNECTIONFAILED = 0x03, + GF_IPMPX_LISTEN_WATERMARKDETECTED = 0x04 +} GF_IPMPX_ListenType; + +/*! IPMPX Add Tool Listener data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 scope; + /*events to listen to*/ + u8 eventTypeCount; + GF_IPMPX_ListenType eventType[10]; +} GF_IPMPX_AddToolNotificationListener; + +/*! IPMPX Remove Tool Listener data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 eventTypeCount; + GF_IPMPX_ListenType eventType[10]; +} GF_IPMPX_RemoveToolNotificationListener; + +/*! IPMPX Tool Notify Event data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u16 OD_ID; + u16 ESD_ID; + u8 eventType; + u32 IPMP_ToolContextID; +} GF_IPMPX_NotifyToolEvent; + +/*! IPMPX Can Process data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + Bool canProcess; +} GF_IPMPX_CanProcess; + +/*! IPMPX Opaque Data container data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_ByteArray *opaqueData; +} GF_IPMPX_OpaqueData; + + +/*! IPMPX Key data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_ByteArray *keyBody; + /*flags meaning + hasStartDTS = 1; + hasStartPacketID = 1<<1; + hasExpireDTS = 1<<2; + hasExpirePacketID = 1<<3 + */ + u32 flags; + + u64 startDTS; + u32 startPacketID; + u64 expireDTS; + u32 expirePacketID; + GF_IPMPX_ByteArray *OpaqueData; +} GF_IPMPX_KeyData; + +/*! IPMPX Rights data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + GF_IPMPX_ByteArray *rightsInfo; +} GF_IPMPX_RightsData; + + +/*! IPMPX Selective Encryption Buffer data + not a real GF_IPMPX_Data in spec, but emulated as if for parsing + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + bin128 cipher_Id; + u8 syncBoundary; + /*block mode if stream cypher info is NULL*/ + u8 mode; + u16 blockSize; + u16 keySize; + GF_IPMPX_ByteArray *Stream_Cipher_Specific_Init_Info; +} GF_IPMPX_SelEncBuffer; + +/*! IPMPX Selective Encryption Field data +not a real GF_IPMPX_Data in spec, but emulated as if for parsing + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 field_Id; + u8 field_Scope; + u8 buf; + + u16 mappingTableSize; + u16 *mappingTable; + GF_IPMPX_ByteArray *shuffleSpecificInfo; +} GF_IPMPX_SelEncField; + + +/*! IPMPX mediaTypeExtension*/ +enum +{ + GF_IPMPX_SE_MT_ISO_IEC = 0x00, + GF_IPMPX_SE_MT_ITU = 0x01 + /*the rest is reserved or forbidden*/ +}; + +/*! IPMPX compliance*/ +enum +{ + GF_IPMPX_SE_COMP_FULLY = 0x00, + GF_IPMPX_SE_COMP_VIDEO_PACKETS = 0x01, + GF_IPMPX_SE_COMP_VIDEO_VOP = 0x02, + GF_IPMPX_SE_COMP_VIDEO_NONE = 0x03, + GF_IPMPX_SE_COMP_VIDEO_GOB = 0x04, + /*0x05-2F ISO Reserved for video*/ + GF_IPMPX_SE_COMP_AAC_DF = 0x30, + GF_IPMPX_SE_COMP_AAC_NONE = 0x31 + /* + 0x32 - 0x5F ISO Reserved for audio + 0x60 - 0xCF ISO Reserved + 0xD0 - 0xFE User Defined + 0xFF Forbidden + */ +}; + +/*! IPMPX syncBoundary*/ +enum +{ + GF_IPMPX_SE_SYNC_VID7EO_PACKETS = 0x00, + GF_IPMPX_SE_SYNC_VIDEO_VOP = 0x01, + GF_IPMPX_SE_SYNC_VIDEO_GOV = 0x02, + /*0x03-2F ISO Reserved for video,*/ + GF_IPMPX_SE_SYNC_AAC_DF = 0x30 + /*0x31 - 0x5F ISO Reserved for audio + 0x60 - 0xCF ISO Reserved + 0xD0 - 0xFE User Defined + 0xFF Forbidden + */ +}; + +/*! IPMPX field_Id for selective encryption*/ +enum +{ + GF_IPMPX_SE_FID_VIDEO_MV = 0x00, + GF_IPMPX_SE_FID_VIDEO_DC = 0x01, + GF_IPMPX_SE_FID_VIDEO_DCT_SIGN = 0x02, + GF_IPMPX_SE_FID_VIDEO_DQUANT = 0x03, + GF_IPMPX_SE_FID_VIDEO_DCT_COEF = 0x04, + GF_IPMPX_SE_FID_VIDEO_ALL = 0x05, + /*0x06-2F ISO Reserved for video*/ + GF_IPMPX_SE_FID_AAC_SIGN = 0x30, + GF_IPMPX_SE_FID_AAC_CODEWORDS = 0x31, + GF_IPMPX_SE_FID_AAC_SCALE = 0x32 + /*0x32 - 0x5F ISO Reserved for audio + 0x60 - 0xCF ISO Reserved + 0xD0 - 0xFE User Defined + 0xFF Forbidden*/ +}; + + +/*! IPMPX Selective Encryption Init data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 mediaTypeExtension; + u8 mediaTypeIndication; + u8 profileLevelIndication; + u8 compliance; + + GF_List *SelEncBuffer; + + GF_List *SelEncFields; + + u16 RLE_DataLength; + u16 *RLE_Data; +} GF_IPMPX_SelectiveDecryptionInit; + + +/*! IPMPX watermark init ops*/ +enum +{ + GF_IPMPX_WM_INSERT = 0, + GF_IPMPX_WM_EXTRACT = 1, + GF_IPMPX_WM_REMARK = 2, + GF_IPMPX_WM_DETECT_COMPRESSION = 3 +}; + +/*! IPMPX Watermark Init data +used for both audio and video WM init + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + /* + for audio: PCM defined (0x01) and all audio objectTypeIndications + for video: YUV defined (0x01) and all visual objectTypeIndications + */ + u8 inputFormat; + u8 requiredOp; + + /*valid for audio WM, inputFormat=0x01*/ + u8 nChannels; + u8 bitPerSample; + u32 frequency; + + /*valid for video WM, inputFormat=0x01*/ + u16 frame_horizontal_size; + u16 frame_vertical_size; + u8 chroma_format; + + u32 wmPayloadLen; + u8 *wmPayload; + + u16 wmRecipientId; + + u32 opaqueDataSize; + u8 *opaqueData; +} GF_IPMPX_WatermarkingInit; + + + +/*! IPMPX Watermark status*/ +enum +{ + GF_IPMPX_WM_PAYLOAD = 0, + GF_IPMPX_WM_NOPAYLOAD = 1, + GF_IPMPX_WM_NONE = 2, + GF_IPMPX_WM_UNKNOWN = 3 +}; + +/*! IPMPX compression status*/ +enum +{ + GF_IPMPX_WM_COMPRESSION = 0, + GF_IPMPX_WM_NO_COMPRESSION = 1, + GF_IPMPX_WM_COMPRESSION_UNKNOWN = 2 +}; + +/*! IPMPX Send Watermark data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 wm_status; + u8 compression_status; + /*if payload is set, status is FORCED to AUDIO_GF_IPMPX_WM_PAYLOAD*/ + GF_IPMPX_ByteArray *payload; + GF_IPMPX_ByteArray *opaqueData; +} GF_IPMPX_SendWatermark; + + +/*! IPMPX Tool API config data + */ +typedef struct +{ + GF_IPMPX_DATA_BASE + /*GPAC only supports non-0 IDs*/ + u32 Instantiation_API_ID; + u32 Messaging_API_ID; + GF_IPMPX_ByteArray *opaqueData; +} GF_IPMPX_ToolAPI_Config; + +/*! IPMPX ISMACryp data +*/ +typedef struct +{ + GF_IPMPX_DATA_BASE + u8 cryptoSuite; + u8 IV_length; + Bool use_selective_encryption; + u8 key_indicator_length; +} GF_IPMPX_ISMACryp; + + +/* constructor */ +GF_IPMPX_Data *gf_ipmpx_data_new(u8 tag); +/* destructor */ +void gf_ipmpx_data_del(GF_IPMPX_Data *p); + +/* parse from bitstream */ +GF_Err gf_ipmpx_data_parse(GF_BitStream *bs, GF_IPMPX_Data **out_data); +/*! get IPMP_Data contained size (eg without tag & sizeofinstance)*/ +u32 gf_ipmpx_data_size(GF_IPMPX_Data *p); +/*! get fulml IPMP_Data encoded size (eg with tag & sizeofinstance)*/ +u32 gf_ipmpx_data_full_size(GF_IPMPX_Data *p); +/*! writes IPMP_Data to buffer*/ +GF_Err gf_ipmpx_data_write(GF_BitStream *bs, GF_IPMPX_Data *_p); + +/*! returns GF_IPMPX_Tag based on name*/ +u8 gf_ipmpx_get_tag(char *dataName); +/*! return values: cf above */ +u32 gf_ipmpx_get_field_type(GF_IPMPX_Data *p, char *fieldName); +GF_Err gf_ipmpx_set_field(GF_IPMPX_Data *desc, char *fieldName, char *val); +/*! assign subdata*/ +GF_Err gf_ipmpx_set_sub_data(GF_IPMPX_Data *desc, char *fieldName, GF_IPMPX_Data *subdesc); +/*! assign bytearray*/ +GF_Err gf_ipmpx_set_byte_array(GF_IPMPX_Data *p, char *field, char *str); + +/*! ipmpx dumper*/ +GF_Err gf_ipmpx_dump_data(GF_IPMPX_Data *_p, FILE *trace, u32 indent, Bool XMTDump); + +//! @endcond + +#endif /*GPAC_MINIMAL_ODF*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_MPEG4_ODF_H_*/ diff --git a/include/gpac/mpegts.h b/include/gpac/mpegts.h new file mode 100644 index 0000000..9a5fa37 --- /dev/null +++ b/include/gpac/mpegts.h @@ -0,0 +1,1997 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre, Cyril Concolato, Romain Bouqueau + * Copyright (c) Telecom ParisTech 2006-2019 + * + * This file is part of GPAC / MPEG2-TS 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_MPEG_TS_H_ +#define _GF_MPEG_TS_H_ + +/*! +\file <gpac/mpegts.h> +\brief MPEG-TS demultiplexer and multiplexer APIs +*/ + +/*! +\addtogroup m2ts_grp MPEG-2 TS +\ingroup media_grp +\brief MPEG-TS demultiplexer and multiplexer APIs. + +This section documents the MPEG-TS demultiplexer and multiplexer APIs. + +@{ +*/ + + +#include <gpac/list.h> +#include <gpac/network.h> +#include <gpac/thread.h> +#include <gpac/internal/odf_dev.h> + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! metadata types for GF_M2TS_METADATA_POINTER_DESCRIPTOR*/ +enum { + GF_M2TS_META_ID3 = GF_4CC('I','D','3',' '), +}; + + + +/*! MPEG-2 Descriptor tags*/ +enum +{ + /* ... */ + GF_M2TS_VIDEO_STREAM_DESCRIPTOR = 0x02, + GF_M2TS_AUDIO_STREAM_DESCRIPTOR = 0x03, + GF_M2TS_HIERARCHY_DESCRIPTOR = 0x04, + GF_M2TS_REGISTRATION_DESCRIPTOR = 0x05, + GF_M2TS_DATA_STREAM_ALIGNEMENT_DESCRIPTOR = 0x06, + GF_M2TS_TARGET_BACKGROUND_GRID_DESCRIPTOR = 0x07, + GF_M2TS_VIEW_WINDOW_DESCRIPTOR = 0x08, + GF_M2TS_CA_DESCRIPTOR = 0x09, + 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_DVB_STREAM_LOCATION_DESCRIPTOR =0x13, + /* ... */ + GF_M2TS_STD_DESCRIPTOR = 0x17, + /* ... */ + GF_M2TS_MPEG4_VIDEO_DESCRIPTOR = 0x1B, + GF_M2TS_MPEG4_AUDIO_DESCRIPTOR = 0x1C, + GF_M2TS_MPEG4_IOD_DESCRIPTOR = 0x1D, + GF_M2TS_MPEG4_SL_DESCRIPTOR = 0x1E, + GF_M2TS_MPEG4_FMC_DESCRIPTOR = 0x1F, + /* ... */ + GF_M2TS_METADATA_POINTER_DESCRIPTOR = 0x25, + GF_M2TS_METADATA_DESCRIPTOR = 0x26, + /* ... */ + GF_M2TS_AVC_VIDEO_DESCRIPTOR = 0x28, + /* ... */ + GF_M2TS_AVC_TIMING_HRD_DESCRIPTOR = 0x2A, + /* ... */ + GF_M2TS_SVC_EXTENSION_DESCRIPTOR = 0x30, + /* ... */ + GF_M2TS_MPEG4_ODUPDATE_DESCRIPTOR = 0x35, + + GF_M2TS_HEVC_VIDEO_DESCRIPTOR = 0x38, + + /* 0x2D - 0x3F - ISO/IEC 13818-6 values */ + /* 0x40 - 0xFF - User Private values */ + GF_M2TS_DVB_NETWORK_NAME_DESCRIPTOR = 0x40, + GF_M2TS_DVB_SERVICE_LIST_DESCRIPTOR = 0x41, + GF_M2TS_DVB_STUFFING_DESCRIPTOR = 0x42, + GF_M2TS_DVB_SAT_DELIVERY_SYSTEM_DESCRIPTOR = 0x43, + GF_M2TS_DVB_CABLE_DELIVERY_SYSTEM_DESCRIPTOR = 0x44, + GF_M2TS_DVB_VBI_DATA_DESCRIPTOR = 0x45, + GF_M2TS_DVB_VBI_TELETEXT_DESCRIPTOR = 0x46, + GF_M2TS_DVB_BOUQUET_NAME_DESCRIPTOR = 0x47, + GF_M2TS_DVB_SERVICE_DESCRIPTOR = 0x48, + GF_M2TS_DVB_COUNTRY_AVAILABILITY_DESCRIPTOR = 0x49, + GF_M2TS_DVB_LINKAGE_DESCRIPTOR = 0x4A, + GF_M2TS_DVB_NVOD_REFERENCE_DESCRIPTOR = 0x4B, + GF_M2TS_DVB_TIME_SHIFTED_SERVICE_DESCRIPTOR = 0x4C, + GF_M2TS_DVB_SHORT_EVENT_DESCRIPTOR = 0x4D, + GF_M2TS_DVB_EXTENDED_EVENT_DESCRIPTOR = 0x4E, + GF_M2TS_DVB_TIME_SHIFTED_EVENT_DESCRIPTOR = 0x4F, + GF_M2TS_DVB_COMPONENT_DESCRIPTOR = 0x50, + GF_M2TS_DVB_MOSAIC_DESCRIPTOR = 0x51, + GF_M2TS_DVB_STREAM_IDENTIFIER_DESCRIPTOR = 0x52, + GF_M2TS_DVB_CA_IDENTIFIER_DESCRIPTOR = 0x53, + GF_M2TS_DVB_CONTENT_DESCRIPTOR = 0x54, + GF_M2TS_DVB_PARENTAL_RATING_DESCRIPTOR = 0x55, + GF_M2TS_DVB_TELETEXT_DESCRIPTOR = 0x56, + /* ... */ + GF_M2TS_DVB_LOCAL_TIME_OFFSET_DESCRIPTOR = 0x58, + GF_M2TS_DVB_SUBTITLING_DESCRIPTOR = 0x59, + GF_M2TS_DVB_PRIVATE_DATA_SPECIFIER_DESCRIPTOR = 0x5F, + /* ... */ + GF_M2TS_DVB_DATA_BROADCAST_DESCRIPTOR = 0x64, + /* ... */ + GF_M2TS_DVB_DATA_BROADCAST_ID_DESCRIPTOR = 0x66, + /* ... */ + GF_M2TS_DVB_AC3_DESCRIPTOR = 0x6A, + /* ... */ + GF_M2TS_DVB_TIME_SLICE_FEC_DESCRIPTOR = 0x77, + /* ... */ + GF_M2TS_DVB_EAC3_DESCRIPTOR = 0x7A, + GF_M2TS_DVB_LOGICAL_CHANNEL_DESCRIPTOR = 0x83, + + GF_M2TS_DOLBY_VISION_DESCRIPTOR = 0xB0 +}; + +/*! Reserved PID values */ +enum { + GF_M2TS_PID_PAT = 0x0000, + GF_M2TS_PID_CAT = 0x0001, + GF_M2TS_PID_TSDT = 0x0002, + /* reserved 0x0003 to 0x000F */ + GF_M2TS_PID_NIT_ST = 0x0010, + GF_M2TS_PID_SDT_BAT_ST = 0x0011, + GF_M2TS_PID_EIT_ST_CIT = 0x0012, + GF_M2TS_PID_RST_ST = 0x0013, + GF_M2TS_PID_TDT_TOT_ST = 0x0014, + GF_M2TS_PID_NET_SYNC = 0x0015, + GF_M2TS_PID_RNT = 0x0016, + /* reserved 0x0017 to 0x001B */ + GF_M2TS_PID_IN_SIG = 0x001C, + GF_M2TS_PID_MEAS = 0x001D, + GF_M2TS_PID_DIT = 0x001E, + GF_M2TS_PID_SIT = 0x001F +}; + +/*! max size includes first header, second header, payload and CRC */ +enum { + GF_M2TS_TABLE_ID_PAT = 0x00, + GF_M2TS_TABLE_ID_CAT = 0x01, + GF_M2TS_TABLE_ID_PMT = 0x02, + GF_M2TS_TABLE_ID_TSDT = 0x03, /* max size for section 1024 */ + GF_M2TS_TABLE_ID_MPEG4_BIFS = 0x04, /* max size for section 4096 */ + GF_M2TS_TABLE_ID_MPEG4_OD = 0x05, /* max size for section 4096 */ + GF_M2TS_TABLE_ID_METADATA = 0x06, + GF_M2TS_TABLE_ID_IPMP_CONTROL = 0x07, + /* 0x08 - 0x37 reserved */ + /* 0x38 - 0x3D DSM-CC defined */ + GF_M2TS_TABLE_ID_DSM_CC_ENCAPSULATED_DATA = 0x3A, + GF_M2TS_TABLE_ID_DSM_CC_UN_MESSAGE = 0x3B, /* used for MPE (only, not MPE-FEC) */ + GF_M2TS_TABLE_ID_DSM_CC_DOWNLOAD_DATA_MESSAGE = 0x3C, /* used for MPE (only, not MPE-FEC) */ + GF_M2TS_TABLE_ID_DSM_CC_STREAM_DESCRIPTION = 0x3D, /* used for MPE (only, not MPE-FEC) */ + GF_M2TS_TABLE_ID_DSM_CC_PRIVATE = 0x3E, /* used for MPE (only, not MPE-FEC) */ + /* 0x3F DSM-CC defined */ + GF_M2TS_TABLE_ID_NIT_ACTUAL = 0x40, /* max size for section 1024 */ + GF_M2TS_TABLE_ID_NIT_OTHER = 0x41, + GF_M2TS_TABLE_ID_SDT_ACTUAL = 0x42, /* max size for section 1024 */ + /* 0x43 - 0x45 reserved */ + GF_M2TS_TABLE_ID_SDT_OTHER = 0x46, /* max size for section 1024 */ + /* 0x47 - 0x49 reserved */ + GF_M2TS_TABLE_ID_BAT = 0x4a, /* max size for section 1024 */ + /* 0x4b reserved */ + GF_M2TS_TABLE_ID_INT = 0x4c, /* max size for section 4096 */ + /* 0x4d reserved */ + + GF_M2TS_TABLE_ID_EIT_ACTUAL_PF = 0x4E, /* max size for section 4096 */ + GF_M2TS_TABLE_ID_EIT_OTHER_PF = 0x4F, + /* 0x50 - 0x6f EIT SCHEDULE */ + GF_M2TS_TABLE_ID_EIT_SCHEDULE_MIN = 0x50, + GF_M2TS_TABLE_ID_EIT_SCHEDULE_ACTUAL_MAX= 0x5F, + GF_M2TS_TABLE_ID_EIT_SCHEDULE_MAX = 0x6F, + + GF_M2TS_TABLE_ID_TDT = 0x70, /* max size for section 1024 */ + GF_M2TS_TABLE_ID_RST = 0x71, /* max size for section 1024 */ + GF_M2TS_TABLE_ID_ST = 0x72, /* max size for section 4096 */ + GF_M2TS_TABLE_ID_TOT = 0x73, /* max size for section 1024 */ + GF_M2TS_TABLE_ID_AIT = 0x74, + GF_M2TS_TABLE_ID_CONT = 0x75, + GF_M2TS_TABLE_ID_RC = 0x76, + GF_M2TS_TABLE_ID_CID = 0x77, + GF_M2TS_TABLE_ID_MPE_FEC = 0x78, + GF_M2TS_TABLE_ID_RES_NOT = 0x79, + /* 0x7A - 0x7D reserved */ + GF_M2TS_TABLE_ID_DIT = 0x7E, + GF_M2TS_TABLE_ID_SIT = 0x7F, /* max size for section 4096 */ + /* 0x80 - 0xfe reserved */ + /* 0xff reserved */ +}; + +/*! MPEG-2 TS Media types*/ +typedef enum +{ + GF_M2TS_VIDEO_MPEG1 = 0x01, + GF_M2TS_VIDEO_MPEG2 = 0x02, + GF_M2TS_AUDIO_MPEG1 = 0x03, + GF_M2TS_AUDIO_MPEG2 = 0x04, + GF_M2TS_PRIVATE_SECTION = 0x05, + GF_M2TS_PRIVATE_DATA = 0x06, + GF_M2TS_MHEG = 0x07, + GF_M2TS_13818_1_DSMCC = 0x08, + GF_M2TS_H222_1 = 0x09, + GF_M2TS_13818_6_ANNEX_A = 0x0A, + GF_M2TS_13818_6_ANNEX_B = 0x0B, + GF_M2TS_13818_6_ANNEX_C = 0x0C, + GF_M2TS_13818_6_ANNEX_D = 0x0D, + GF_M2TS_13818_1_AUXILIARY = 0x0E, + GF_M2TS_AUDIO_AAC = 0x0F, + GF_M2TS_VIDEO_MPEG4 = 0x10, + GF_M2TS_AUDIO_LATM_AAC = 0x11, + GF_M2TS_SYSTEMS_MPEG4_PES = 0x12, + GF_M2TS_SYSTEMS_MPEG4_SECTIONS = 0x13, + GF_M2TS_SYNC_DOWNLOAD_PROTOCOL = 0x14, + GF_M2TS_METADATA_PES = 0x15, + GF_M2TS_METADATA_SECTION = 0x16, + GF_M2TS_METADATA_DATA_CAROUSEL = 0x17, + GF_M2TS_METADATA_OBJECT_CAROUSEL = 0x18, + GF_M2TS_METADATA_SYNC_DOWNLOAD_PROTOCOL = 0x19, + GF_M2TS_IPMP = 0x1A, + GF_M2TS_VIDEO_H264 = 0x1B, + GF_M2TS_MPEG4_AUDIO_NO_SYNTAX = 0x1C, + GF_M2TS_MPEG4_TEXT = 0x1D, + GF_M2TS_AUX_VIDEO_23002_2 = 0x1E, + GF_M2TS_VIDEO_SVC = 0x1F, + GF_M2TS_VIDEO_MVC = 0x20, + GF_M2TS_VIDEO_15444_1 = 0x21, + GF_M2TS_VIDEO_MPEG2_ADD_STEREO = 0x22, + GF_M2TS_VIDEO_H264_ADD_STEREO = 0x23, + GF_M2TS_VIDEO_HEVC = 0x24, + GF_M2TS_VIDEO_HEVC_TEMPORAL = 0x25, + GF_M2TS_VIDEO_MVCD = 0x26, + GF_M2TS_TEMI = 0x27, + GF_M2TS_VIDEO_SHVC = 0x28, + GF_M2TS_VIDEO_SHVC_TEMPORAL = 0x29, + GF_M2TS_VIDEO_MHVC = 0x2A, + GF_M2TS_VIDEO_MHVC_TEMPORAL = 0x2B, + GF_M2TS_GREEN = 0x2C, + GF_M2TS_MHAS_MAIN = 0x2D, + GF_M2TS_MHAS_AUX = 0x2E, + GF_M2TS_QUALITY_SEC = 0x2F, + GF_M2TS_MORE_SEC = 0x30, + GF_M2TS_VIDEO_HEVC_MCTS = 0x31, + GF_M2TS_JPEG_XS = 0x32, + GF_M2TS_VIDEO_VVC = 0x33, + GF_M2TS_VIDEO_VVC_TEMPORAL = 0x34, + + GF_M2TS_HLS_AC3_CRYPT = 0xc1, + GF_M2TS_HLS_EC3_CRYPT = 0xc2, + GF_M2TS_HLS_AAC_CRYPT = 0xcf, + GF_M2TS_HLS_AVC_CRYPT = 0xdb, + + /*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_DVB_TELETEXT = 0x152, + GF_M2TS_DVB_VBI = 0x153, + GF_M2TS_DVB_SUBTITLE = 0x154, + GF_M2TS_METADATA_ID3_HLS = 0x155, + +} GF_M2TSStreamType; + + +/*! 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_GPAC = GF_4CC('G','P','A','C') +}; + + +/*! MPEG-2 Descriptor tags*/ +enum +{ + GF_M2TS_AFDESC_TIMELINE_DESCRIPTOR = 0x04, + GF_M2TS_AFDESC_LOCATION_DESCRIPTOR = 0x05, + GF_M2TS_AFDESC_BASEURL_DESCRIPTOR = 0x06, +}; + +/*! header till the last bit of the section_length field */ +#define SECTION_HEADER_LENGTH 3 +/*! header from the last bit of the section_length field to the payload */ +#define SECTION_ADDITIONAL_HEADER_LENGTH 5 +/*! CRC32 length*/ +#define CRC_LENGTH 4 + + + +#ifndef GPAC_DISABLE_MPEG2TS +/*! MPEG-2 TS demuxer*/ +typedef struct tag_m2ts_demux GF_M2TS_Demuxer; +/*! MPEG-2 TS demuxer elementary stream*/ +typedef struct tag_m2ts_es GF_M2TS_ES; +/*! MPEG-2 TS demuxer elementary stream section*/ +typedef struct tag_m2ts_section_es GF_M2TS_SECTION_ES; + +/*! Maximum number of streams in a TS*/ +#define GF_M2TS_MAX_STREAMS 8192 + +/*! 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 + +/*! gets the stream name for an MPEG-2 stream type +\param streamType the target stream type +\return name of the stream type*/ +const char *gf_m2ts_get_stream_name(GF_M2TSStreamType streamType); + +/*! probes a file for MPEG-2 TS format +\param fileName name / path of the file to brobe +\return GF_TRUE if file is an MPEG-2 TS */ +Bool gf_m2ts_probe_file(const char *fileName); + +/*! probes a buffer for MPEG-2 TS format +\param data data buffer to probe +\param size size of buffer to probe +\return GF_TRUE if file is an MPEG-2 TS */ +Bool gf_m2ts_probe_data(const u8 *data, u32 size); + +/*! restamps a set of TS packets by shifting all timing by the given value +\param buffer data buffer to restamp +\param size size of buffer +\param ts_shift clock shift in 90 kHz +\param is_pes array of GF_M2TS_MAX_STREAMS u8 set to 1 for PES PIDs to be restamped, 0 to stream to left untouched +\return error if any +*/ +GF_Err gf_m2ts_restamp(u8 *buffer, u32 size, s64 ts_shift, u8 is_pes[GF_M2TS_MAX_STREAMS]); + +/*! PES data framing modes*/ +typedef enum +{ + /*skip pes processing: all transport packets related to this stream are discarded*/ + GF_M2TS_PES_FRAMING_SKIP, + /*same as GF_M2TS_PES_FRAMING_SKIP but keeps internal PES buffer alive*/ + GF_M2TS_PES_FRAMING_SKIP_NO_RESET, + /*don't use data framing: all packets are raw PES packets*/ + GF_M2TS_PES_FRAMING_RAW, + + //TODO - remove this one, we no longer reframe in the TS demuxer + /*use data framing: recompute start of AUs (data frames)*/ + GF_M2TS_PES_FRAMING_DEFAULT, +} GF_M2TSPesFraming; + +/*! PES packet flags*/ +enum +{ + /*! PES packet is RAP*/ + GF_M2TS_PES_PCK_RAP = 1, + /*! PES packet contains an AU start*/ + GF_M2TS_PES_PCK_AU_START = 1<<1, + /*! visual frame starting in this packet is an I frame or IDR (AVC/H264)*/ + GF_M2TS_PES_PCK_I_FRAME = 1<<2, + /*! visual frame starting in this packet is a P frame*/ + GF_M2TS_PES_PCK_P_FRAME = 1<<3, + /*! visual frame starting in this packet is a B frame*/ + GF_M2TS_PES_PCK_B_FRAME = 1<<4, + /*! Possible PCR discontinuity from this packet on*/ + GF_M2TS_PES_PCK_DISCONTINUITY = 1<<5 +}; + +/*! Events used by the MPEGTS demuxer*/ +enum +{ + /*! PAT has been found (service connection) - no associated parameter*/ + GF_M2TS_EVT_PAT_FOUND = 0, + /*! PAT has been updated - no associated parameter*/ + GF_M2TS_EVT_PAT_UPDATE, + /*! repeated PAT has been found (carousel) - no associated parameter*/ + GF_M2TS_EVT_PAT_REPEAT, + /*! PMT has been found (service tune-in) - associated parameter: new PMT*/ + GF_M2TS_EVT_PMT_FOUND, + /*! repeated PMT has been found (carousel) - associated parameter: updated PMT*/ + GF_M2TS_EVT_PMT_REPEAT, + /*! PMT has been changed - associated parameter: updated PMT*/ + GF_M2TS_EVT_PMT_UPDATE, + /*! SDT has been received - associated parameter: none*/ + GF_M2TS_EVT_SDT_FOUND, + /*! repeated SDT has been found (carousel) - associated parameter: none*/ + GF_M2TS_EVT_SDT_REPEAT, + /*! SDT has been received - associated parameter: none*/ + GF_M2TS_EVT_SDT_UPDATE, + /*! INT has been received - associated parameter: none*/ + GF_M2TS_EVT_INT_FOUND, + /*! repeated INT has been found (carousel) - associated parameter: none*/ + GF_M2TS_EVT_INT_REPEAT, + /*! INT has been received - associated parameter: none*/ + GF_M2TS_EVT_INT_UPDATE, + /*! PES packet has been received - associated parameter: PES packet*/ + GF_M2TS_EVT_PES_PCK, + /*! PCR has been received - associated parameter: PES packet with no data*/ + GF_M2TS_EVT_PES_PCR, + /*! PTS/DTS/PCR info - associated parameter: PES packet with no data*/ + GF_M2TS_EVT_PES_TIMING, + /*! An MPEG-4 SL Packet has been received in a section - associated parameter: SL packet */ + GF_M2TS_EVT_SL_PCK, + /*! An IP datagram has been received in a section - associated parameter: IP datagram */ + GF_M2TS_EVT_IP_DATAGRAM, + /*! Duration has been estimated - associated parameter: PES packet with no data, PTS is duration in msec*/ + GF_M2TS_EVT_DURATION_ESTIMATED, + /*! TS packet processed - associated parameter: pointer to a GF_M2TS_TSPCK structure*/ + GF_M2TS_EVT_PCK, + + /*! AAC config has been extracted - associated parameter: PES Packet with encoded M4ADecSpecInfo in its data + THIS MUST BE CLEANED UP + */ + GF_M2TS_EVT_AAC_CFG, +#if 0 + /*! An EIT message for the present or following event on this TS has been received */ + GF_M2TS_EVT_EIT_ACTUAL_PF, + /*! An EIT message for the schedule of this TS has been received */ + GF_M2TS_EVT_EIT_ACTUAL_SCHEDULE, + /*! An EIT message for the present or following event of an other TS has been received */ + GF_M2TS_EVT_EIT_OTHER_PF, + /*! An EIT message for the schedule of an other TS has been received */ + GF_M2TS_EVT_EIT_OTHER_SCHEDULE, +#endif + /*! A message to inform about the current date and time in the TS */ + GF_M2TS_EVT_TDT, + /*! A message to inform about the current time offset in the TS */ + GF_M2TS_EVT_TOT, + /*! A generic event message for EIT, TDT, TOT etc */ + GF_M2TS_EVT_DVB_GENERAL, + /*! MPE / MPE-FEC frame extraction and IP datagrams decryptation */ + GF_M2TS_EVT_DVB_MPE, + /*! CAT has been found (service tune-in) - associated parameter: new CAT*/ + GF_M2TS_EVT_CAT_FOUND, + /*! repeated CAT has been found (carousel) - associated parameter: updated CAT*/ + GF_M2TS_EVT_CAT_REPEAT, + /*! CAT has been changed - associated parameter: updated PMT*/ + GF_M2TS_EVT_CAT_UPDATE, + /*! AIT has been found (carousel) */ + GF_M2TS_EVT_AIT_FOUND, + /*! DSCM-CC has been found (carousel) */ + GF_M2TS_EVT_DSMCC_FOUND, + + /*! a TEMI locator has been found or repeated*/ + GF_M2TS_EVT_TEMI_LOCATION, + /*! a TEMI timecode has been found*/ + GF_M2TS_EVT_TEMI_TIMECODE, + /*! a stream is about to be removed - - associated parameter: pointer to GF_M2TS_ES being removed*/ + GF_M2TS_EVT_STREAM_REMOVED +}; + +/*! table parsing state*/ +enum +{ + /*! flag set for start of the table */ + GF_M2TS_TABLE_START = 1, + /*! flag set for end of the table */ + GF_M2TS_TABLE_END = 1<<1, + /*! flag set when first occurence of the table */ + GF_M2TS_TABLE_FOUND = 1<<2, + /*! flag set when update of the table */ + GF_M2TS_TABLE_UPDATE = 1<<3, + /*! flag set when repetition of the table - both update and repeat flags may be set if data has changed*/ + GF_M2TS_TABLE_REPEAT = 1<<4, +}; + +/*! section callback function +\param demux the target MPEG-2 TS demultiplexer +\param es the target section stream +\param sections the list of gathered sections +\param table_id the ID of the table +\param ex_table_id the extended ID of the table +\param version_number the version number of the table +\param last_section_number the last section number of the table for fragmented cases +\param status the parsing status flags +*/ +typedef void (*gf_m2ts_section_callback)(GF_M2TS_Demuxer *demux, GF_M2TS_SECTION_ES *es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status); + +/*! MPEG-2 TS demuxer section*/ +typedef struct __m2ts_demux_section +{ + /*! section data*/ + u8 *data; + /*! section data size in bytes*/ + u32 data_size; +} GF_M2TS_Section; + +/*! MPEG-2 TS demuxer table*/ +typedef struct __m2ts_demux_table +{ + /*! pointer to next table/section*/ + struct __m2ts_demux_table *next; + /*! set when first table completely received*/ + u8 is_init; + /*! set when repeated section*/ + u8 is_repeat; + /*! table id*/ + u8 table_id; + /*! extended table id*/ + u16 ex_table_id; + /*! current section version number*/ + u8 version_number; + /*! last received version number*/ + u8 last_version_number; + /*! current/next indicator (cf MPEG-2 TS spec)*/ + u8 current_next_indicator; + /*! current section number*/ + u8 section_number; + /*! last section number to get the complete table*/ + u8 last_section_number; + /*! list of sections for this table*/ + GF_List *sections; + /*! total table size*/ + u32 table_size; +} GF_M2TS_Table; + + +/*! MPEG-2 TS demuxer section filter*/ +typedef struct GF_M2TS_SectionFilter +{ + /*! section reassembler*/ + s16 cc; + /*! section buffer (max 4096)*/ + u8 *section; + /*! current section length as indicated in section header*/ + u16 length; + /*! number of bytes received from current section*/ + u16 received; + /*! section->table aggregator*/ + GF_M2TS_Table *table; + /*! indicates that the section and last_section_number do not need to be checked */ + Bool process_individual; + /*! indicates that the section header with table id and extended table id ... is + not parsed by the TS demuxer and left for the application */ + Bool direct_dispatch; + /*! this field is used for AIT sections, to link the AIT with the program */ + u32 service_id; + /*! section callback*/ + gf_m2ts_section_callback process_section; + /*! flag indicatin the demultiplexer has been restarted*/ + Bool demux_restarted; +} GF_M2TS_SectionFilter; + +/*! metadata carriage types*/ +enum metadata_carriage { + METADATA_CARRIAGE_SAME_TS = 0, + METADATA_CARRIAGE_DIFFERENT_TS = 1, + METADATA_CARRIAGE_PS = 2, + METADATA_CARRIAGE_OTHER = 3 +}; + +/*! MPEG-2 TS demuxer metadat pointer*/ +typedef struct tag_m2ts_metadata_pointer_descriptor { + u16 application_format; + u32 application_format_identifier; + u8 format; + u32 format_identifier; + u8 service_id; + Bool locator_record_flag; + u32 locator_length; + char *locator_data; + enum metadata_carriage carriage_flag; + u16 program_number; + u16 ts_location; + u16 ts_id; + u8 *data; + u32 data_size; +} GF_M2TS_MetadataPointerDescriptor; + +/*! MPEG-2 TS demuxer TEMI location*/ +typedef struct +{ + u32 pid; + u32 timeline_id; + //for now we only support one URL announcement + const char *external_URL; + Bool is_announce, is_splicing; + Bool reload_external; + GF_Fraction activation_countdown; +} GF_M2TS_TemiLocationDescriptor; + +/*! MPEG-2 TS demuxer TEMI timecode*/ +typedef struct +{ + u32 pid; + u32 timeline_id; + u32 media_timescale; + u64 media_timestamp; + u64 pes_pts; + Bool force_reload; + Bool is_paused; + Bool is_discontinuity; + u64 ntp; +} GF_M2TS_TemiTimecodeDescriptor; + + +/*! MPEG-2 TS program object*/ +typedef struct +{ + /*! parent demuxer*/ + GF_M2TS_Demuxer *ts; + /*! list of streams (PES and sections)*/ + GF_List *streams; + /*! PID of PMT*/ + u32 pmt_pid; + /*! PID of PCR*/ + u32 pcr_pid; + /*! program number*/ + u32 number; + /*! MPEG-4 IOD if any*/ + GF_InitialObjectDescriptor *pmt_iod; + /*! list of additional ODs found per program + this list is only created when MPEG-4 over MPEG-2 is detected + the list AND the ODs contained in it are destroyed when destroying the program/demuxer + */ + GF_List *additional_ods; + /*! first dts found on this program - this is used by parsers, but not setup by the lib*/ + u64 first_dts; + /*! Last PCR value received for this program and associated packet number */ + u64 last_pcr_value; + /*! packet number of last PCR value received*/ + u32 last_pcr_value_pck_number; + /*! PCR value before the last received one for this program and associated packet number + used to compute PCR interpolation value*/ + u64 before_last_pcr_value; + /*! packet number of before last PCR value received*/ + u32 before_last_pcr_value_pck_number; + /*! for hybrid use-cases we need to know if TDT has already been processed*/ + Bool tdt_found; + /*! indicates if PID is playing. Used in scalable streams only to toggle quality switch*/ + u32 pid_playing; + /*! */ + Bool is_scalable; + /*! metadata descriptor pointer*/ + GF_M2TS_MetadataPointerDescriptor *metadata_pointer_descriptor; + /*! continuity counter check for pure PCR PIDs*/ + s16 pcr_cc; + + void *user; +} GF_M2TS_Program; + +/*! ES flags*/ +enum +{ + /*! ES is a PES stream*/ + GF_M2TS_ES_IS_PES = 1, + /*! ES is a section stream*/ + GF_M2TS_ES_IS_SECTION = 1<<1, + /*! ES is an mpeg-4 flexmux stream*/ + GF_M2TS_ES_IS_FMC = 1<<2, + /*! ES is an mpeg-4 SL-packetized stream*/ + GF_M2TS_ES_IS_SL = 1<<3, + /*! ES is an mpeg-4 Object Descriptor SL-packetized stream*/ + GF_M2TS_ES_IS_MPEG4_OD = 1<<4, + /*! ES is a DVB MPE stream*/ + GF_M2TS_ES_IS_MPE = 1<<5, + /*! stream is used to send PCR to upper layer*/ + GF_M2TS_INHERIT_PCR = 1<<6, + /*! signals the stream is used to send the PCR, but is not the original PID carrying it*/ + GF_M2TS_FAKE_PCR = 1<<7, + /*! signals the stream type is a gpac codec id*/ + GF_M2TS_GPAC_CODEC_ID = 1<<8, + /*! signals the stream type is a gpac codec id*/ + GF_M2TS_ES_IS_PMT = 1<<9, + + /*! all flags above this mask are used by demultiplexer users*/ + GF_M2TS_ES_STATIC_FLAGS_MASK = 0x0000FFFF, + + /*! always send sections regardless of their version_number*/ + GF_M2TS_ES_SEND_REPEATED_SECTIONS = 1<<16, + /*! flag used to signal next discontinuity on stream should be ignored*/ + GF_M2TS_ES_IGNORE_NEXT_DISCONTINUITY = 1<<17, + /*! flag used by importers/readers to mark streams that have been seen already in PMT process (update/found)*/ + GF_M2TS_ES_ALREADY_DECLARED = 1<<18, + /*! flag indicates TEMI info is declared on this stream*/ + GF_M2TS_ES_TEMI_INFO = 1<<19 +}; + +/*! macro for abstract Section/PES stream object, only used for type casting*/ +#define ABSTRACT_ES \ + GF_M2TS_Program *program; \ + u32 flags; \ + u32 pid; \ + u32 stream_type; \ + u32 mpeg4_es_id; \ + GF_SLConfig *slcfg; \ + s16 component_tag; \ + void *user; \ + GF_List *props; \ + u64 first_dts; \ + Bool is_seg_start; \ + u32 service_id; + +/*! abstract Section/PES stream object*/ +struct tag_m2ts_es +{ + ABSTRACT_ES +}; + + +/*! MPEG-2 TS muxer PES header*/ +typedef struct +{ + /*! stream ID*/ + u8 id; + /*! packet len, 0 if unknown*/ + u16 pck_len; + /*! data alignment flag*/ + u8 data_alignment; + /*! packet PTS in 90khz*/ + u64 PTS; + /*! packet DTS in 90khz*/ + u64 DTS; + /*! size of PES header*/ + u8 hdr_data_len; +} GF_M2TS_PESHeader; + +/*! Section elementary stream*/ +struct tag_m2ts_section_es +{ + /*! derive from base M2TS stream*/ + ABSTRACT_ES + /*! section reassembler*/ + GF_M2TS_SectionFilter *sec; +}; + +/*! MPEG-2 TS muxer DVB subtitling descriptor*/ +typedef struct tag_m2ts_dvb_sub +{ + char language[3]; + u8 type; + u16 composition_page_id; + u16 ancillary_page_id; +} GF_M2TS_DVB_Subtitling_Descriptor; + +/*! MPEG-2 TS muxer DVB teletext descriptor*/ +typedef struct tag_m2ts_dvb_teletext +{ + char language[3]; + u8 type; + u8 magazine_number; + u8 page_number; +} GF_M2TS_DVB_Teletext_Descriptor; + +/*! MPEG-2 TS muxer metadata descriptor*/ +typedef struct tag_m2ts_metadata_descriptor { + u16 application_format; + u32 application_format_identifier; + u8 format; + u32 format_identifier; + u8 service_id; + u8 decoder_config_flags; + Bool dsmcc_flag; + u8 service_id_record_length; + char *service_id_record; + u8 decoder_config_length; + u8 *decoder_config; + u8 decoder_config_id_length; + u8 *decoder_config_id; + u8 decoder_config_service_id; +} GF_M2TS_MetadataDescriptor; + +//! @cond Doxygen_Suppress + +/*! MPEG-2 TS ES object*/ +typedef struct tag_m2ts_pes +{ + /*! derive from base M2TS stream*/ + ABSTRACT_ES + /*! continuity counter check*/ + s16 cc; + /*! language tag*/ + u32 lang; + /*! PID of stream this stream depends on*/ + u32 depends_on_pid; + + /*mpegts lib private - do not touch*/ + /*! PES re-assembler data*/ + u8 *pck_data; + /*! amount of bytes allocated for data */ + u32 pck_alloc_len; + /*! amount of bytes received in the current PES packet (NOT INCLUDING ANY PENDING BYTES)*/ + u32 pck_data_len; + /*! size of the PES packet being received, as indicated in pes header length field - can be 0 if unknown*/ + u32 pes_len; + /*! RAP flag*/ + Bool rap; + /*! PES PTS in 90khz*/ + u64 PTS; + /*! PES DTS in 90khz*/ + u64 DTS; + /*! bytes not consumed from previous PES - shall be less than 9*/ + u8 *prev_data; + /*! number of bytes not consumed from previous PES - shall be less than 9*/ + u32 prev_data_len; + /*! number of TS packet containing the start of the current PES*/ + u32 pes_start_packet_number; + /*! number of TS packet containing the end of the current PES*/ + u32 pes_end_packet_number; + /*! Last PCR value received for this program*/ + u64 last_pcr_value; + /*! packet number of last PCR*/ + u32 last_pcr_value_pck_number; + /*! PCR value before the last received one for this program (used to compute PCR interpolation value)*/ + u64 before_last_pcr_value; + /*! packet number of before last PCR*/ + u32 before_last_pcr_value_pck_number; + + + /*! PES reframer callback. If NULL, pes processing is skiped + + 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; + + /*! last received TEMI payload*/ + u8 *temi_tc_desc; + /*! last received TEMI payload size*/ + 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*/ + u8 dv_info[25]; +} GF_M2TS_PES; + +/*! reserved streamID for PES headers*/ +enum +{ + GF_M2_STREAMID_PROGRAM_STREAM_MAP = 0xBC, + GF_M2_STREAMID_PADDING = 0xBE, + GF_M2_STREAMID_PRIVATE_2 = 0xBF, + GF_M2_STREAMID_ECM = 0xF0, + GF_M2_STREAMID_EMM = 0xF1, + GF_M2_STREAMID_PROGRAM_STREAM_DIRECTORY = 0xFF, + GF_M2_STREAMID_DSMCC = 0xF2, + GF_M2_STREAMID_H222_TYPE_E = 0xF8 +}; + +/*! SDT (service description table) information*/ +typedef struct +{ + u16 original_network_id; + u16 transport_stream_id; + u32 service_id; + u32 EIT_schedule; + u32 EIT_present_following; + u32 running_status; + u32 free_CA_mode; + u8 service_type; + char *provider, *service; +} GF_M2TS_SDT; + +/*! NIT (network information table) information*/ +typedef struct +{ + u16 network_id; + unsigned char *network_name; + u16 original_network_id; + u16 transport_stream_id; + u16 service_id; + u32 service_type; + u32 logical_channel_number; +} GF_M2TS_NIT; + +/*! TDT/TOT (Time and Date table) information*/ +typedef struct +{ + u16 year; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; +} GF_M2TS_TDT_TOT; + +/*! DVB Content Description information*/ +typedef struct { + u8 content_nibble_level_1, content_nibble_level_2, user_nibble; +} GF_M2TS_DVB_Content_Descriptor; + +/*! DVB Rating information*/ +typedef struct { + char country_code[3]; + u8 value; +} GF_M2TS_DVB_Rating_Descriptor; + +/*! DVB Short Event information*/ +typedef struct { + u8 lang[3]; + char *event_name, *event_text; +} GF_M2TS_DVB_Short_Event_Descriptor; + +/*! DVB Extended Event entry*/ +typedef struct { + char *item; + char *description; +} GF_M2TS_DVB_Extended_Event_Item; + +/*! DVB Extended Event information*/ +typedef struct { + u8 lang[3]; + u32 last; + GF_List *items; + char *text; +} GF_M2TS_DVB_Extended_Event_Descriptor; + +/*! DVB EIT Date and Time information*/ +typedef struct +{ + time_t unix_time; + + /* local time offset descriptor data (unused ...) */ + u8 country_code[3]; + u8 country_region_id; + s32 local_time_offset_seconds; + time_t unix_next_toc; + s32 next_time_offset_seconds; + +} GF_M2TS_DateTime_Event; + +/*! DVB EIT component information*/ +typedef struct { + u8 stream_content; + u8 component_type; + u8 component_tag; + u8 language_code[3]; + char *text; +} GF_M2TS_Component; + +/*! DVB EIT Event information*/ +typedef struct +{ + u16 event_id; + time_t unix_start; + time_t unix_duration; + + + u8 running_status; + u8 free_CA_mode; + GF_List *short_events; + GF_List *extended_events; + GF_List *components; + GF_List *contents; + GF_List *ratings; +} GF_M2TS_EIT_Event; + +/*! DVB EIT information*/ +typedef struct +{ + u32 original_network_id; + u32 transport_stream_id; + u16 service_id; + GF_List *events; + u8 table_id; +} GF_M2TS_EIT; + +/*! MPEG-2 TS packet*/ +typedef struct +{ + u8 *data; + u32 data_len; + u32 flags; + u64 PTS, DTS; + /*parent stream*/ + GF_M2TS_PES *stream; +} GF_M2TS_PES_PCK; + +/*! MPEG-4 SL packet from MPEG-2 TS*/ +typedef struct +{ + u8 *data; + u32 data_len; + u8 version_number; + /*parent stream */ + GF_M2TS_ES *stream; +} GF_M2TS_SL_PCK; + +/*! MPEG-4 SL packet from MPEG-2 TS*/ +typedef struct +{ + /*packet start (first byte is TS sync marker)*/ + u8 *data; + /*packet PID*/ + u32 pid; + /*parent stream if any/already declared*/ + GF_M2TS_ES *stream; +} GF_M2TS_TSPCK; + +typedef struct +{ + u8 version_number; + u8 table_id; + u16 ex_table_id; +} GF_M2TS_SectionInfo; + +/*! MPEG-2 TS demuxer*/ +struct tag_m2ts_demux +{ + /*! PIDs*/ + GF_M2TS_ES *ess[GF_M2TS_MAX_STREAMS]; + /*! programs*/ + GF_List *programs; + /*! number of PMT received*/ + u32 nb_prog_pmt_received; + /*! set to GF_TRUE if all PMT received*/ + Bool all_prog_pmt_received; + /*! set to GF_TRUE if all programs are setup*/ + Bool all_prog_processed; + /*! received SDTs - keep it separate for now*/ + GF_List *SDTs; + /*! UTC time from both TDT and TOT (we currently ignore local offset)*/ + GF_M2TS_TDT_TOT *TDT_time; + + /*! user callback - MUST NOT BE NULL*/ + void (*on_event)(struct tag_m2ts_demux *ts, u32 evt_type, void *par); + /*! private user data*/ + void *user; + + /*! private resync buffer*/ + u8 *buffer; + /*! private resync size*/ + u32 buffer_size; + /*! private alloc size*/ + u32 alloc_size; + /*! PAT filter*/ + GF_M2TS_SectionFilter *pat; + /*! CAT filter*/ + GF_M2TS_SectionFilter *cat; + /*! NIT filter*/ + GF_M2TS_SectionFilter *nit; + /*! SDT filter*/ + GF_M2TS_SectionFilter *sdt; + /*! EIT filter*/ + GF_M2TS_SectionFilter *eit; + /*! TDT/TOT filter*/ + GF_M2TS_SectionFilter *tdt_tot; + /*! MPEG-4 over 2 is present*/ + Bool has_4on2; + /*! analyser output */ + FILE *pes_out; + + /*! when set, only pmt and PAT are parsed*/ + Bool seek_mode; + /*! 192 packet mode with prefix*/ + Bool prefix_present; + /*! MPE direct flag*/ + Bool direct_mpe; + /*! DVB-H demu enabled*/ + Bool dvb_h_demux; + /*! sends packet for at PES header start*/ + Bool notify_pes_timing; + + /*! user callback - MUST NOT BE NULL*/ + void (*on_mpe_event)(struct tag_m2ts_demux *ts, u32 evt_type, void *par); + /*! structure to hold all the INT tables if the TS contains IP streams */ + struct __gf_dvb_mpe_ip_platform *ip_platform; + /*! current TS packet number*/ + u32 pck_number; + + /*! remote file handling - created and destroyed by user*/ + struct __gf_download_session *dnload; + /*! channel config path*/ + const char *dvb_channels_conf_path; + + /*! AIT*/ + GF_List* ChannelAppList; + + /*! carousel enabled*/ + Bool process_dmscc; + /*! carousel root dir*/ + char* dsmcc_root_dir; + /*! DSM-CC objects*/ + GF_List* dsmcc_controler; + /*! 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; +}; + +//! @endcond + +/*! creates a new MPEG-2 TS demultiplexer +\return a new demultiplexer*/ +GF_M2TS_Demuxer *gf_m2ts_demux_new(); +/*! destroys a new MPEG-2 TS demultiplexer +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_demux_del(GF_M2TS_Demuxer *demux); + +/*! resets all parsers (PES, sections) of the demultiplexer and trash any pending data in the demux input +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_reset_parsers(GF_M2TS_Demuxer *demux); + +/*! set all streams is_seg_start variable to GF_TRUE +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_mark_seg_start(GF_M2TS_Demuxer *demux); + +/*! resets all parsers (PES, sections) of a given program +\param demux the target MPEG-2 TS demultiplexer +\param program the target MPEG-2 TS program +*/ +void gf_m2ts_reset_parsers_for_program(GF_M2TS_Demuxer *demux, GF_M2TS_Program *program); +/*! sets PES framing mode of a stream +\param pes the target MPEG-2 TS stream +\param mode the desired PES framing mode +\return error if any +*/ +GF_Err gf_m2ts_set_pes_framing(GF_M2TS_PES *pes, GF_M2TSPesFraming mode); + +/*! processes input buffer. This will resync the input data to a TS packet start if needed +\param demux the target MPEG-2 TS demultiplexer +\param data the data to process +\param data_size size of the date to process +\return error if any +*/ +GF_Err gf_m2ts_process_data(GF_M2TS_Demuxer *demux, u8 *data, u32 data_size); + +/*! initializes DSM-CC object carousel reception +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_demux_dmscc_init(GF_M2TS_Demuxer *demux); + +/*! gets SDT info for a given program +\param demux the target MPEG-2 TS demultiplexer +\param program_id ID of the target MPEG-2 TS program +\return an SDT description, or NULL if not found +*/ +GF_M2TS_SDT *gf_m2ts_get_sdt_info(GF_M2TS_Demuxer *demux, u32 program_id); + +/*! flushes a given stream. This is used to flush internal demultiplexer buffers on end of stream +\param demux the target MPEG-2 demultiplexer +\param pes the target stream to flush +\param force_flush if GF_TRUE, flushes all streams, otherwise do not flush stream with known PES length and not yet completed (for HLS) +*/ +void gf_m2ts_flush_pes(GF_M2TS_Demuxer *demux, GF_M2TS_PES *pes, Bool force_flush); + +/*! flushes all streams in the mux. This is used to flush internal demultiplexer buffers on end of stream +\param demux the target MPEG-2 demultiplexer +\param no_force_flush do not force a flush of incomplete PES (used for HLS) +*/ +void gf_m2ts_flush_all(GF_M2TS_Demuxer *demux, Bool no_force_flush); + + +/*! MPEG-2 TS packet header*/ +typedef struct +{ + /*! sync byte 0x47*/ + u8 sync; + /*! error indicator*/ + u8 error; + /*! payload start flag (start of PES or section)*/ + u8 payload_start; + /*! priority flag*/ + u8 priority; + /*! PID of packet */ + u16 pid; + /*! scrambling flag ( 0 not scrambled, 1/2 scrambled, 3 reserved)*/ + u8 scrambling_ctrl; + /*! adaptation field type*/ + u8 adaptation_field; + /*! continuity counter*/ + u8 continuity_counter; +} GF_M2TS_Header; + +/*! MPEG-2 TS packet adaptation field*/ +typedef struct +{ + /*! discontunity indicator (for timeline splicing)*/ + u32 discontinuity_indicator; + /*! random access indicator*/ + u32 random_access_indicator; + /*! priority indicator*/ + u32 priority_indicator; + /*! PCR present flag*/ + u32 PCR_flag; + /*! PCR base value*/ + u64 PCR_base; + /*! PCR extended value*/ + u64 PCR_ext; + /*! original PCR flag*/ + u32 OPCR_flag; + /*! OPCR base value*/ + u64 OPCR_base; + /*! OPCR extended value*/ + u64 OPCR_ext; + /*! splicing point flag*/ + u32 splicing_point_flag; + /*! transport private data flag*/ + u32 transport_private_data_flag; + /*! AF extension flag*/ + u32 adaptation_field_extension_flag; + /* + u32 splice_countdown; + u32 transport_private_data_length; + u32 adaptation_field_extension_length; + u32 ltw_flag; + u32 piecewise_rate_flag; + u32 seamless_splice_flag; + u32 ltw_valid_flag; + u32 ltw_offset; + u32 piecewise_rate; + u32 splice_type; + u32 DTS_next_AU; + */ +} GF_M2TS_AdaptationField; + + +#endif /*GPAC_DISABLE_MPEG2TS*/ + + +#ifndef GPAC_DISABLE_MPEG2TS_MUX + +/*! +\addtogroup esi_grp ES Interface +\ingroup media_grp +\brief Basic stream interface API used by MPEG-2 TS muxer. + +This section documents the ES interface used by the MPEG-2 TS muxer. This interface is used to +describe streams and packets consumed by the TS muxer independently from the rest of GPAC (filter packets) + +@{ +*/ + +/*! ESI input control commands*/ +enum +{ + /*! forces a data flush from interface to dest (caller) - used for non-threaded interfaces + corresponding parameter: unused + */ + GF_ESI_INPUT_DATA_FLUSH, + /*! pulls a COMPLETE AU from the stream + corresponding parameter: pointer to a GF_ESIPacket to fill. The input data_len in the packet is used to indicate any padding in bytes + */ + GF_ESI_INPUT_DATA_PULL, + /*! releases the currently pulled AU from the stream - AU cannot be pulled after that, unless seek happens + corresponding parameter: unused + */ + GF_ESI_INPUT_DATA_RELEASE, + + /*! destroys any allocated resource by the stream interface*/ + GF_ESI_INPUT_DESTROY, +}; + +/*! ESI output control commands*/ +enum +{ + /*! forces a data flush from interface to dest (caller) - used for non-threaded interfaces + corresponding parameter: GF_ESIPacket + */ + GF_ESI_OUTPUT_DATA_DISPATCH +}; + +/*! data packet flags*/ +enum +{ + /*! packet is an access unit start*/ + GF_ESI_DATA_AU_START = 1, + /*! packet is an access unit end*/ + GF_ESI_DATA_AU_END = 1<<1, + /*! packet has valid CTS*/ + GF_ESI_DATA_HAS_CTS = 1<<2, + /*! packet has valid DTS*/ + GF_ESI_DATA_HAS_DTS = 1<<3, + /*! packet contains a carousel repeated AU*/ + GF_ESI_DATA_REPEAT = 1<<4, +}; + +/*! stream interface for MPEG-2 TS muxer*/ +typedef struct __data_packet_ifce +{ + /*! packet flags*/ + u16 flags; + /*! SAP type*/ + u8 sap_type; + /*! payload*/ + u8 *data; + /*! payload size*/ + u32 data_len; + /*! DTS expressed in media timescale*/ + u64 dts; + /*! CTS/PTS expressed in media timescale*/ + u64 cts; + /*! duration expressed in media timescale*/ + u32 duration; + /*! packet sequence number*/ + u32 pck_sn; + /*! MPEG-4 Access Unit sequence number (use for carousel of BIFS/OD AU)*/ + u32 au_sn; + /*! ISM BSO for for packets using ISMACrypt/OMA/3GPP based crypto*/ + u32 isma_bso; + /*! serialized list of AF descriptors*/ + u8 *mpeg2_af_descriptors; + /*! size of serialized list of AF descriptors*/ + u32 mpeg2_af_descriptors_size; +} GF_ESIPacket; + +/*! video information for stream interface*/ +struct __esi_video_info +{ + /*! video width in pixels*/ + u32 width; + /*! video height in pixels*/ + u32 height; + /*! pixel aspect ratio as (num<<16)|den*/ + u32 par; + /*! video framerate*/ + Double FPS; +}; +/*! audio information for stream interface*/ +struct __esi_audio_info +{ + /*! sample rate*/ + u32 sample_rate; + /*! number of channels*/ + u32 nb_channels; +}; + +/*! ES interface capabilities*/ +enum +{ + /*! data can be pulled from this stream*/ + GF_ESI_AU_PULL_CAP = 1, + /*! no more data to expect from this stream*/ + GF_ESI_STREAM_IS_OVER = 1<<2, + /*! stream is not signaled through MPEG-4 Systems (OD stream) */ + GF_ESI_STREAM_WITHOUT_MPEG4_SYSTEMS = 1<<3, + /*! stream is not signaled through MPEG-4 Systems (OD stream) */ + GF_ESI_AAC_USE_LATM = 1<<4, + /*! temporrary end of stream (flush of segment)*/ + GF_ESI_STREAM_FLUSH = 1<<5, + /*! stream uses HLS SAES encryption*/ + GF_ESI_STREAM_HLS_SAES = 1<<6, + /*! stream uses non-backward DolbyVision signaling*/ + GF_ESI_FORCE_DOLBY_VISION = 1<<7, +}; + +/*! elementary stream information*/ +typedef struct __elementary_stream_ifce +{ + /*! misc caps of the stream*/ + u32 caps; + /*! matches PID for MPEG2, ES_ID for MPEG-4*/ + u32 stream_id; + /*! MPEG-4 ST*/ + u8 stream_type; + /*! codec ID*/ + u32 codecid; + /*! packed 3-char language code (4CC with last byte ' ')*/ + u32 lang; + /*! media timescale*/ + u32 timescale; + /*! duration in ms - 0 if unknown*/ + Double duration; + /*! average bit rate in bit/sec - 0 if unknown*/ + u32 bit_rate; + /*! repeat rate in ms for carrouseling - 0 if no repeat*/ + u32 repeat_rate; + /*! decoder configuration*/ + u8 *decoder_config; + /*! decoder configuration size*/ + u32 decoder_config_size; + + /*! MPEG-4 SL Config if any*/ + GF_SLConfig *sl_config; + + /*! input ES control from caller*/ + GF_Err (*input_ctrl)(struct __elementary_stream_ifce *_self, u32 ctrl_type, void *param); + /*! input user data of interface - usually set by interface owner*/ + void *input_udta; + + /*! output ES control of destination*/ + GF_Err (*output_ctrl)(struct __elementary_stream_ifce *_self, u32 ctrl_type, void *param); + /*! output user data of interface - usually set during interface setup*/ + void *output_udta; + /*! stream dependency ID*/ + u32 depends_on_stream; + + /*! dv info, not valid if first byte not 1*/ + u8 dv_info[24]; + + u32 ra_code; +} GF_ESInterface; + +/*! @} */ + +/* + MPEG-2 TS Multiplexer +*/ + +/*! adaptation field type*/ +enum { + /*! reserved*/ + GF_M2TS_ADAPTATION_RESERVED = 0, + /*! no adaptation (payload only)*/ + GF_M2TS_ADAPTATION_NONE = 1, + /*! adaptation only (no payload)*/ + GF_M2TS_ADAPTATION_ONLY = 2, + /*! adaptation and payload*/ + GF_M2TS_ADAPTATION_AND_PAYLOAD = 3, +}; + +/*! MPEG-2 TS muxer program*/ +typedef struct __m2ts_mux_program GF_M2TS_Mux_Program; +/*! MPEG-2 TS muxer*/ +typedef struct __m2ts_mux GF_M2TS_Mux; + +/*! MPEG-2 TS muxer section*/ +typedef struct __m2ts_section { + /*! pointer to next section*/ + struct __m2ts_section *next; + /*! section data*/ + u8 *data; + /*! section size*/ + u32 length; +} GF_M2TS_Mux_Section; + +/*! MPEG-2 TS muxer table*/ +typedef struct __m2ts_table { + /*! pointer to next table*/ + struct __m2ts_table *next; + /*! table ID (we don't handle extended table ID as part of the section muxer*/ + u8 table_id; + /*! version number of the table*/ + u8 version_number; + /*! list of sections for the table (one or more)*/ + struct __m2ts_section *section; +} GF_M2TS_Mux_Table; + +/*! MPEG-2 TS muxer time information*/ +typedef struct +{ + /*! seconds*/ + u32 sec; + /*! nanoseconds*/ + u32 nanosec; +} GF_M2TS_Time; + + +/*! MPEG-2 TS muxer packet*/ +typedef struct __m2ts_mux_pck +{ + /*! pointer to next packet (we queue packets if needed)*/ + struct __m2ts_mux_pck *next; + /*! payload*/ + u8 *data; + /*! payload size in bytes*/ + u32 data_len; + /*! flags*/ + u16 flags; + /*! sap type*/ + u8 sap_type; + /*! CTS in 90 khz*/ + u64 cts; + /*! DTS in 90 khz*/ + u64 dts; + /*! duration in 90 khz*/ + u32 duration; + /*! serialized list of adaptation field descriptors if any*/ + u8 *mpeg2_af_descriptors; + /*! size of serialized list of adaptation field descriptors*/ + u32 mpeg2_af_descriptors_size; +} GF_M2TS_Packet; + +/*! MPEG-2 TS muxer stream*/ +typedef struct __m2ts_mux_stream { + /*! next stream*/ + struct __m2ts_mux_stream *next; + /*! pid*/ + u32 pid; + /*! CC of the stream*/ + u8 continuity_counter; + /*! parent program*/ + struct __m2ts_mux_program *program; + /*! average stream bit-rate in bit/sec*/ + u32 bit_rate; + /*! multiplexer time - NOT THE PCR*/ + GF_M2TS_Time time; + /*! next time for packet*/ + GF_M2TS_Time next_time; + /*! allow PCR only packets*/ + Bool pcr_only_mode; + /*! tables for section PIDs*/ + GF_M2TS_Mux_Table *tables; + /*! total table sizes for bitrate estimation (PMT/PAT/...)*/ + u32 total_table_size; + /*! current table - used for on-the-fly packetization of sections */ + GF_M2TS_Mux_Table *current_table; + /*! active section*/ + GF_M2TS_Mux_Section *current_section; + /*! current section offset*/ + u32 current_section_offset; + /*! carousel rate in ms*/ + u32 refresh_rate_ms; + /*! init verision of table*/ + u8 initial_version_number; + /*! PES version of transport for this codec type is forced*/ + u8 force_pes; + /*! table needs updating*/ + Bool table_needs_update; + /*! table needs send*/ + Bool table_needs_send; + /*! force one AU per PES*/ + Bool force_single_au; + /*! force initial discontinuity*/ + Bool set_initial_disc; + /*! force registration descriptor*/ + Bool force_reg_desc; + + /*! minimal amount of bytes we are allowed to copy frome next AU in the current PES. If no more than this + 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*/ + u32 (*process)(struct __m2ts_mux *muxer, struct __m2ts_mux_stream *stream); + + /*! stream type*/ + GF_M2TSStreamType mpeg2_stream_type; + /*! stream ID*/ + u32 mpeg2_stream_id; + /*! priority*/ + u32 scheduling_priority; + + /*! current packet being processed - does not belong to the packet fifo*/ + GF_ESIPacket curr_pck; + /*! offset in packet*/ + u32 pck_offset; + /*! size of next payload, 0 if unknown*/ + u32 next_payload_size; + /*! number of bytes to copy from next packet*/ + u32 copy_from_next_packets; + /*! size of next next payload, 0 if unknown*/ + u32 next_next_payload_size; + /*! size of next next next payload, 0 if unknown*/ + u32 next_next_next_payload_size; + /*! size of packetized packet*/ + u32 pes_data_len; + /*! remaining bytes to send as TS packets*/ + u32 pes_data_remain; + /*! if set forces a new PES packet at next packet*/ + Bool force_new; + /*! flag set to indicate packet data shall be freed (rewrite of source packet)*/ + Bool discard_data; + /*! flags of next packet*/ + u16 next_pck_flags; + /*! SAP type of next packet*/ + u8 next_pck_sap; + /*! CTS of next packet*/ + u64 next_pck_cts; + /*! DTS of next packet*/ + u64 next_pck_dts; + /*! overhead of reframing in bytes (ex, ADTS header or LATM rewrite)*/ + u32 reframe_overhead; + /*! if set, forces a PES start at each RAP*/ + Bool start_pes_at_rap; + /*! if set, do not have more than one AU start in a PES packet*/ + Bool prevent_two_au_start_in_pes; + /*! ES interface of stream*/ + struct __elementary_stream_ifce *ifce; + /*! timescale scale factor (90khz / es_ifce.timeScale)*/ + GF_Fraction ts_scale; + + /*! packet fifo head*/ + GF_M2TS_Packet *pck_first; + /*! packet fifo tail*/ + GF_M2TS_Packet *pck_last; + /*! packet reassembler (PES packets are most of the time full frames)*/ + GF_M2TS_Packet *pck_reassembler; + /*! mutex if enabled*/ + GF_Mutex *mx; + /*! avg bitrate computed*/ + u64 last_br_time; + /*! number of bytes sent since last bitrate estimation*/ + u32 bytes_since_last_time; + /*! number of PES packets sent since last bitrate estimation*/ + u32 pes_since_last_time; + /*! last DTS sent*/ + u64 last_dts; + /*! MPEG-4 over MPEG-2 sections table id (BIFS, OD, ...)*/ + u8 table_id; + /*! MPEG-4 SyncLayer header of the packet*/ + GF_SLHeader sl_header; + /*! last time when the AAC config was sent for LATM*/ + u32 latm_last_aac_time; + /*! list of GF_M2TSDescriptor to add to the MPEG-2 stream. By default set to NULL*/ + GF_List *loop_descriptors; + + /*! packet SAP type when segmenting the TS*/ + u32 pck_sap_type; + /*! packet SAP time (=PTS) when segmenting the TS*/ + u64 pck_sap_time; + + /*! last process result*/ + u32 process_res; +} GF_M2TS_Mux_Stream; + +/*! MPEG-4 systems signaling mode*/ +enum { + /*! no MPEG-4 signaling*/ + GF_M2TS_MPEG4_SIGNALING_NONE = 0, + /*! Full MPEG-4 signaling*/ + GF_M2TS_MPEG4_SIGNALING_FULL, + /*MPEG-4 over MPEG-2 profile where PES media streams may be referred to by the scene without SL-packetization*/ + GF_M2TS_MPEG4_SIGNALING_SCENE +}; + +/*! MPEG-2 TS base descriptor*/ +typedef struct __m2ts_base_descriptor +{ + /*! descriptor tag*/ + u8 tag; + /*! descriptor size*/ + u8 data_len; + /*! descriptor payload*/ + u8 *data; +} GF_M2TSDescriptor; + +/*! MPEG-2 TS muxer program info */ +struct __m2ts_mux_program { + /*! next program*/ + struct __m2ts_mux_program *next; + /*! parent muxer*/ + struct __m2ts_mux *mux; + /*! program number*/ + u16 number; + /*! all streams but PMT*/ + GF_M2TS_Mux_Stream *streams; + /*! PMT stream*/ + GF_M2TS_Mux_Stream *pmt; + /*! pointer to PCR stream*/ + GF_M2TS_Mux_Stream *pcr; + /*! TS time at pcr init*/ + GF_M2TS_Time ts_time_at_pcr_init; + /*! pcr init time (rand or fixed by user)*/ + u64 pcr_init_time; + /*! number of TS packet at PCR init*/ + u64 num_pck_at_pcr_init; + /*! last PCR value in 27 mhz*/ + u64 last_pcr; + /*! last DTS value in 90 khz*/ + u64 last_dts; + /*! system clock in microseconds at last PCR*/ + u64 sys_clock_at_last_pcr; + /*! number of packets at last PCR*/ + u64 nb_pck_last_pcr; + /*! min initial DTS of all streams*/ + u64 initial_ts; + /*! indicates that min initial DTS of all streams is set*/ + u32 initial_ts_set; + /*! if set, injects a PCR offset*/ + Bool pcr_init_time_set; + /*! PCR offset to inject*/ + u64 pcr_offset; + /*! Forced value of first packet CTS*/ + u64 force_first_pts; + /*! indicates the initial discontinuity flag should be set on first packet of all streams*/ + Bool initial_disc_set; + /*! MPEG-4 IOD for 4on2*/ + GF_Descriptor *iod; + /*! list of GF_M2TSDescriptor to add to the program descriptor loop. By default set to NULL, if non null will be reset and destroyed upon cleanup*/ + GF_List *loop_descriptors; + /*! MPEG-4 signaling type*/ + u32 mpeg4_signaling; + /*! if set, use MPEG-4 signaling only for BIFS and OD streams*/ + Bool mpeg4_signaling_for_scene_only; + /*! program name (for SDT)*/ + char *name; + /*! program provider (for SDT)*/ + char *provider; + /*! CTS offset in 90khz*/ + u32 cts_offset; +}; + +/*! AU packing per pes configuration*/ +typedef enum +{ + /*! 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; + +/*! MPEG-2 TS muxer*/ +struct __m2ts_mux { + /*! list of programs*/ + GF_M2TS_Mux_Program *programs; + /*! PAT stream*/ + GF_M2TS_Mux_Stream *pat; + /*! SDT stream*/ + GF_M2TS_Mux_Stream *sdt; + /*! TS ID*/ + u16 ts_id; + /*! signals reconfigure is needed (PAT)*/ + Bool needs_reconfig; + + /*! used to indicate that the input data is pushed to the muxer (i.e. not read from a file) + or that the output data is sent on sockets (not written to a file) */ + Bool real_time; + + /*! indicates if the multiplexer shall target a fix bit rate (monitoring timing and produce padding packets) + or if the output stream will contain only input data*/ + Bool fixed_rate; + + /*! output bit-rate in bit/sec*/ + u32 bit_rate; + + /*! init value for PCRs on all streams if 0, random value is used*/ + u64 init_pcr_value; + /*! PCR update rate in milliseconds*/ + u32 pcr_update_ms; + /*! destination TS packet*/ + char dst_pck[188]; + /*! destination NULL TS packet*/ + char null_pck[188]; + + /*! multiplexer time, incremented each time a packet is sent + used to monitor the sending of muxer related data (PAT, ...) */ + GF_M2TS_Time time; + + /*! Time of the muxer when the first call to process is made (first packet sent?) */ + GF_M2TS_Time init_ts_time; + + /*! System time in microsenconds when the muxer is started */ + u64 init_sys_time; + /*! forces PAT injection at next packet*/ + Bool force_pat; + /*! AU per PES packing mode*/ + GF_M2TS_PackMode au_pes_mode; + /*! enable forced PCR (PCR only packets)*/ + Bool enable_forced_pcr; + /*! last rate estimation time in microseconds*/ + u64 last_br_time_us; + /*! number of TS packets over the bitrate estimation period*/ + u32 pck_sent_over_br_window; + /*! number of TS packets sent*/ + u64 tot_pck_sent; + /*! number of TS NULL packets sent*/ + u64 tot_pad_sent; + /*! number of PES bytes sent*/ + u64 tot_pes_pad_bytes; + + /*! average rate in kbps*/ + u32 average_birate_kbps; + + /*! if set, flushes all streams in a program when a PES RAP is found (eg sent remaining audios and co)*/ + Bool flush_pes_at_rap; + /*! state for forced injection of PAT/PMT/PCR*/ + u32 force_pat_pmt_state; + + /*! static write bitstream object for formatting packets*/ + GF_BitStream *pck_bs; + /*! PID to watch for SAP insertions*/ + u32 ref_pid; + /* if the packet output starts (first PES) with the first packet of a SAP AU (used when dashing), set to TRUE*/ + Bool sap_inserted; + /*! SAP time (used when dashing)*/ + u64 sap_time; + /*! SAP type (used when dashing)*/ + u32 sap_type; + /*! last PTS in 90khz (used when dashing to build sidx)*/ + u64 last_pts; + /*! last PID (used when dashing to build sidx)*/ + u32 last_pid; +}; + +/*! default refresh rate for PSI data*/ +#define GF_M2TS_PSI_DEFAULT_REFRESH_RATE 200 +/*! creates a new MPEG-2 TS multiplexer +\param mux_rate target mux rate in kbps, can be 0 for VBR +\param pat_refresh_rate PAT refresh rate in ms +\param real_time indicates if real-time production should be used +\return a new MPEG-2 TS multiplexer + */ +GF_M2TS_Mux *gf_m2ts_mux_new(u32 mux_rate, u32 pat_refresh_rate, Bool real_time); +/*! destroys a TS muxer +\param muxer the target MPEG-2 TS multiplexer +*/ +void gf_m2ts_mux_del(GF_M2TS_Mux *muxer); + +/*! sets max interval between two PCR. Default/max interval is 100 milliseconds +\param muxer the target MPEG-2 TS multiplexer +\param pcr_update_ms PCR interval in milliseconds +*/ +void gf_m2ts_mux_set_pcr_max_interval(GF_M2TS_Mux *muxer, u32 pcr_update_ms); +/*! adds a new program to the muxer +\param muxer the target MPEG-2 TS multiplexer +\param program_number number of program to add +\param pmt_pid value of program PMT packet identifier +\param pmt_refresh_rate refresh rate in milliseconds of PMT. 0 only sends PMT once +\param pcr_offset initial offset in 90 kHz to add to PCR values (usually to compensate for large frame sending times) +\param mpeg4_signaling type of MPEG-4 signaling used +\param pmt_version initial version of the PMT +\param initial_disc if GF_TRUE, signals packet discontinuity on the first packet of eact stream in the program +\param force_first_pts if not 0, the first PTS written in the program will have the indicated value (in 90khz) +\return a TS multiplexer program +*/ +GF_M2TS_Mux_Program *gf_m2ts_mux_program_add(GF_M2TS_Mux *muxer, u32 program_number, u32 pmt_pid, u32 pmt_refresh_rate, u64 pcr_offset, u32 mpeg4_signaling, u32 pmt_version, Bool initial_disc, u64 force_first_pts); +/*! adds a stream to a program +\param program the target program +\param ifce the stream interface object for packet and properties query +\param pid the packet identifier for the stream +\param is_pcr if GF_TRUE, this stream is the clock reference of the program +\param force_pes_mode if GF_TRUE, forces PES packetization (used for streams carried over PES or sections, such as metadata and MPEG-4) +\param needs_mutex indicates a mutex is required before calling the stream interface object +\return a TS multiplexer program +*/ +GF_M2TS_Mux_Stream *gf_m2ts_program_stream_add(GF_M2TS_Mux_Program *program, GF_ESInterface *ifce, u32 pid, Bool is_pcr, Bool force_pes_mode, Bool needs_mutex); +/*! updates muxer configuration +\param muxer the target MPEG-2 TS multiplexer +\param reset_time if GF_TRUE, resets multiplexer time to 0 +*/ +void gf_m2ts_mux_update_config(GF_M2TS_Mux *muxer, Bool reset_time); +/*! removes stream from program, triggering PMT update as well +\param stream the target stream to remove +*/ +void gf_m2ts_program_stream_remove(GF_M2TS_Mux_Stream *stream); + +/*! finds a program by its number +\param muxer the target MPEG-2 TS multiplexer +\param program_number number of program to retrieve +\return a TS multiplexer program or NULL of not found +*/ +GF_M2TS_Mux_Program *gf_m2ts_mux_program_find(GF_M2TS_Mux *muxer, u32 program_number); +/*! gets number of program in a multiplex +\param muxer the target MPEG-2 TS multiplexer +\return number of programs +*/ +u32 gf_m2ts_mux_program_count(GF_M2TS_Mux *muxer); +/*! gets number of stream in a program +\param program the target program +\return number of streams +*/ +u32 gf_m2ts_mux_program_get_stream_count(GF_M2TS_Mux_Program *program); +/*! gets the packet identifier of the PMT of a program +\param program the target program +\return PID of the PMT +*/ +u32 gf_m2ts_mux_program_get_pmt_pid(GF_M2TS_Mux_Program *program); +/*! gets the packet identifier of the PCR stream of a program +\param program the target program +\return PID of the PCR +*/ +u32 gf_m2ts_mux_program_get_pcr_pid(GF_M2TS_Mux_Program *program); + +/*! state of multiplexer*/ +typedef enum +{ + /*! multiplexer is idle, nothing to produce (real-time mux only)*/ + GF_M2TS_STATE_IDLE, + /*! multiplexer is sending data*/ + GF_M2TS_STATE_DATA, + /*! multiplexer is sending padding (no input data and fixed rate required)*/ + GF_M2TS_STATE_PADDING, + /*! multiplexer is done*/ + GF_M2TS_STATE_EOS, +} GF_M2TSMuxState; + +/*! produces one packet of the multiplex +\param muxer the target MPEG-2 TS multiplexer +\param status set to the current state of the multiplexer +\param usec_till_next the target MPEG-2 TS multiplexer +\return packet produced or NULL if error or idle +*/ +const u8 *gf_m2ts_mux_process(GF_M2TS_Mux *muxer, GF_M2TSMuxState *status, u32 *usec_till_next); +/*! gets the system clock of the multiplexer (time elapsed since start) +\param muxer the target MPEG-2 TS multiplexer +\return system clock of the multiplexer in milliseconds +*/ +u32 gf_m2ts_get_sys_clock(GF_M2TS_Mux *muxer); +/*! gets the multiplexer clock (computed from mux rate and packet sent) +\param muxer the target MPEG-2 TS multiplexer +\return multiplexer clock in milliseconds +*/ +u32 gf_m2ts_get_ts_clock(GF_M2TS_Mux *muxer); + +/*! gets the multiplexer clock (computed from mux rate and packet sent) +\param muxer the target MPEG-2 TS multiplexer +\return multiplexer clock in 90kHz scale +*/ +u64 gf_m2ts_get_ts_clock_90k(GF_M2TS_Mux *muxer); + +/*! set AU per PES packetization mode +\param muxer the target MPEG-2 TS multiplexer +\param au_pes_mode AU packetization mode to use +\return error if any +*/ +GF_Err gf_m2ts_mux_use_single_au_pes_mode(GF_M2TS_Mux *muxer, GF_M2TS_PackMode au_pes_mode); + +/*! 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 +\return error if any +*/ +GF_Err gf_m2ts_mux_set_initial_pcr(GF_M2TS_Mux *muxer, u64 init_pcr_value); +/*! enables pure PCR packets (no payload). This can be needed for some DVB constraints on PCR refresh rate (less than one frame duration) when next video frame is not yet ready to be muxed. + +\param muxer the target MPEG-2 TS multiplexer +\param enable_forced_pcr if GF_TRUE, the multiplexer may use PCR-only packets; otherwise, it will wait for a frame to be ready on the PCR stream before sending a PCR +\return error if any +*/ +GF_Err gf_m2ts_mux_enable_pcr_only_packets(GF_M2TS_Mux *muxer, Bool enable_forced_pcr); + +/*! sets program name and provider +\param program the target program +\param program_name the program name (UTF-8) +\param mux_provider_name the provider name (UTF-8) +*/ +void gf_m2ts_mux_program_set_name(GF_M2TS_Mux_Program *program, const char *program_name, const char *mux_provider_name); + +/*! enables sending DVB SDT +\param muxer the target program +\param refresh_rate_ms the refresh rate for the SDT. A value of 0 only sends the SDT once +*/ +void gf_m2ts_mux_enable_sdt(GF_M2TS_Mux *muxer, u32 refresh_rate_ms); + +#endif /*GPAC_DISABLE_MPEG2TS_MUX*/ + + +#ifndef GPAC_DISABLE_MPEG2TS + +#endif /*GPAC_DISABLE_MPEG2TS*/ + +/*! @} */ + + +#ifdef __cplusplus +} +#endif + + +#endif //_GF_MPEG_TS_H_ diff --git a/include/gpac/network.h b/include/gpac/network.h new file mode 100644 index 0000000..edcc36b --- /dev/null +++ b/include/gpac/network.h @@ -0,0 +1,688 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_NET_H_ +#define _GF_NET_H_ + +#ifdef __cplusplus +extern "C" { +#endif +#include <gpac/setup.h> +#include <gpac/tools.h> + +/*! +\file <gpac/network.h> +\brief Networking. +*/ + +/*! +\addtogroup net_grp +\brief Networking tools (URL resolution, TCP/UDP sockets) + +This section documents the IP network functions of the GPAC framework. + +\defgroup sock_grp Socket +\ingroup net_grp +\defgroup sockgrp_grp Socket Group +\ingroup net_grp +\defgroup nett_grp Network Time and helpers +\ingroup net_grp +\defgroup url_grp URL tools +\ingroup net_grp + +*/ + +/*! +\addtogroup url_grp +\brief URL manipulation tools +@{ +*/ + +/*! +\brief URL local test + +Tests whether a URL describes a local file or not +\param url the url to analyze +\return 1 if the URL describes a local file, 0 otherwise + */ +Bool gf_url_is_local(const char *url); + +/*! +\brief gets absolute file path + +Gets the absolute file path from a relative path and its parent absolute one. This can only be used with file paths. +\param pathName the relative path name of a file +\param parentPath the absolute parent path name +\return absolute path name of the file, or NULL if bad paths are provided. + \note the returned string must be freed by user + */ +char *gf_url_get_absolute_path(const char *pathName, const char *parentPath); + +/*concatenates URL and gets back full URL - returned string must be freed by user*/ +/*! +\brief URL concatenation + +Concatenates a relative URL with its parent URL +\param parentName URL of the parent service +\param pathName URL of the service +\return absolute path name of the service, or NULL if bad paths are provided or if the service path is already an absolute one. + \note the returned string must be freed by user + */ +char *gf_url_concatenate(const char *parentName, const char *pathName); + +/*! +\brief URL concatenation + +Same as \ref gf_url_concatenate but if both paths are relative, resolved url is relative to parent path. +\param parentName URL of the parent service +\param pathName URL of the service +\return absolute path name of the service, or NULL if bad paths are provided or if the service path is already an absolute one. + \note the returned string must be freed by user + */ +char *gf_url_concatenate_parent(const char *parentName, const char *pathName); + +/*! +\brief URL encoding + +Encodes URL by replacing special characters with their % encodings. +\param path URL of the service +\return encoded path name , or NULL if bad paths are provided. + \note the returned string must be freed by user + */ +char *gf_url_percent_encode(const char *path); + +/*! +\brief URL decoding + +Decodes URL by % encodings with the special characters they correspond to +\param path encoded URL of the service +\return decoded path name , or NULL if error + \note the returned string must be freed by user + */ +char *gf_url_percent_decode(const char *path); + +/*! +\brief URL to file system + +Converts a local URL to a file system value. Removes all white spaces and similar +\param url url to convert + */ +void gf_url_to_fs_path(char *url); + +/*! +\brief get first after a filename/path + +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') + +\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 +\return position of first colon, or NULL +*/ +char* gf_url_colon_suffix(const char* URL, char assign_sep); + +/*! +\brief Extract resource name from URL + +Extracts the resource name from the URL +\param url input url +\return resource name. + */ +const char *gf_url_get_resource_name(const char *url); + +/*! +\brief Gets resource path from URL + +Gets the resource path and name from the URL, stripping scheme, server ID, port... +\param url input url +\return the extracted path, or the original path if no scheme indication, or NULL of no path + */ +const char *gf_url_get_path(const char *url); + +/*! @} */ + +/*! +\addtogroup nett_grp +\brief Network tools (time, byte ordering, ...) +@{ +*/ + + +/*! +\brief gets ipv6 support + +Returns IPV6 support information. +\return 2 if the machine has IPV6 support, 1 if the library was compiled with IPV6 support, 0 otherwise + */ +u32 gf_net_has_ipv6(); + + +/*! +\brief checks address type + +Checks if an address is an IPV6 or IPV4 one. +\param address address to check +\return true 1 if address is IPV6 one, 0 otherwise + */ +Bool gf_net_is_ipv6(const char *address); + + +/*! +host to network conversion of integer + +\param val integrer to convert +\return converted integer + */ +u32 gf_htonl(u32 val); +/*! +network to host conversion of integer + +\param val integrer to convert +\return converted integer + */ +u32 gf_ntohl(u32 val); +/*! +host to network conversion of short integer +\param val short integrer to convert +\return converted integer + */ +u16 gf_htons(u16 val); +/*! +network to host conversion of short integer +\param val short integrer to convert +\return converted integer + */ +u16 gf_ntohs(u16 val); + +/*! @} */ + +/*! +\addtogroup time_grp +\brief Time manipulation tools +@{ +*/ + +/*! +\brief gets UTC time + +Gets UTC time since midnight Jan 1970 +\param sec number of seconds +\param msec number of milliseconds + */ +void gf_utc_time_since_1970(u32 *sec, u32 *msec); + + +/*! +\brief NTP seconds from 1900 to 1970 +\hideinitializer + +Macro giving the number of seconds from 1900 to 1970 +*/ +#define GF_NTP_SEC_1900_TO_1970 2208988800ul + +/*! +\brief gets NTP time + +Gets NTP (Network Time Protocol) in seconds and fractional side +\param sec NTP time in seconds +\param frac fractional NTP time expressed in 1 / (1<<32 - 1) seconds units + */ +void gf_net_get_ntp(u32 *sec, u32 *frac); + +/*! +*\brief gets NTP time in milliseconds + +\return NTP time in milliseconds +*/ +u64 gf_net_get_ntp_ms(); + +/*! +\brief offsets NTP time by a given amount of seconds + +Offsets NTP time of the system by a given amount of seconds in the future or the past (default value is 0). +\param sec seconds to add or remove to the system NTP + */ +void gf_net_set_ntp_shift(s32 sec); + +/*! +\brief gets NTP time + +Gets NTP (Network Time Protocol) timestamp (high 32 bit is seconds, low 32 bit is fraction) +\return NTP timestamp + */ +u64 gf_net_get_ntp_ts(); + +/*! + +Gets diff in milliseconds between NTP time and current time +\param ntp NTP timestamp +\return diff in milliseconds with the current time + */ +s32 gf_net_get_ntp_diff_ms(u64 ntp); + + +/*! + +Gets diff in milliseconds between two NTP times time and current time +\param ntp_a first NTP timestamp +\param ntp_b second NTP timestamp +\return diff ntp_a minus ntp_b in milliseconds + */ +s32 gf_net_ntp_diff_ms(u64 ntp_a, u64 ntp_b); + +/*! @} */ + +/*! +\addtogroup sock_grp +\brief Sockets (TCP, UDP unicast, multicast) +@{ +*/ + +/*! +\brief error code description + +Returns text description of given errno code +\param errnoval the error value to test +\return its description + */ +const char *gf_errno_str(int errnoval); + + +/*! +Socket options +\hideinitializer + */ +enum +{ + /*!Reuses port.*/ + 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 +}; + +/*! +\brief abstracted socket object + +The abstracted socket object allows you to build client and server applications very simply with support for unicast and multicast +Available tools are socket and socket groups and some helper functions for network time +*/ +typedef struct __tag_socket GF_Socket; + +/*! +\brief abstracted socket group object + +The abstracted socket group object allows querying multiple sockets in a group +*/ +typedef struct __tag_sock_group GF_SockGroup; + +/*!Buffer size to pass for IP address retrieval*/ +#define GF_MAX_IP_NAME_LEN 1025 + +/*!socket is a TCP socket*/ +#define GF_SOCK_TYPE_TCP 0x01 +/*!socket is a UDP socket*/ +#define GF_SOCK_TYPE_UDP 0x02 + +#ifdef GPAC_HAS_SOCK_UN +/*!socket is a TCP socket*/ +#define GF_SOCK_TYPE_TCP_UN 0x03 +/*!socket is a TCP socket*/ +#define GF_SOCK_TYPE_UDP_UN 0x04 +#endif + +/*! +\brief socket constructor + +Constructs a socket object +\param SocketType the socket type to create, either UDP or TCP +\return the socket object or NULL if network initialization failure + */ +GF_Socket *gf_sk_new(u32 SocketType); +/*! +\brief socket destructor + +Deletes a socket object +\param sock the socket object + */ +void gf_sk_del(GF_Socket *sock); + +/*! +\brief reset internal buffer + +Forces the internal socket buffer to be reseted (discarded) +\param sock the socket object + */ +void gf_sk_reset(GF_Socket *sock); +/*! +\brief socket buffer size control + +Sets the size of the internal buffer of the socket. The socket MUST be bound or connected before. +\warning This operation may fail depending on the provider, hardware... +\param sock the socket object +\param send_buffer if 0, sets the size of the reception buffer, otherwise sets the size of the emission buffer +\param new_size new size of the buffer in bytes. +\return error if any + */ +GF_Err gf_sk_set_buffer_size(GF_Socket *sock, Bool send_buffer, u32 new_size); + +/*! +\brief blocking mode control + +Sets the blocking mode of a socket on or off. A blocking socket will wait for the net operation to be possible +while a non-blocking one would return an error. By default, sockets are created in blocking mode +\param sock the socket object +\param NonBlockingOn set to 1 to use non-blocking sockets, 0 otherwise +\return error if any + */ +GF_Err gf_sk_set_block_mode(GF_Socket *sock, Bool NonBlockingOn); +/*! +\brief socket binding + +Binds the given socket to the specified port. +\param local_ip the local interface IP address if desired. If NULL, the default interface will be used. +\param sock the socket object +\param port port number to bind this socket to +\param peer_name the remote server address, if NULL, will use localhost +\param peer_port remote port number to connect the socket to +\param options list of option for the bind operation. +\return error if any + */ +GF_Err gf_sk_bind(GF_Socket *sock, const char *local_ip, u16 port, const char *peer_name, u16 peer_port, u32 options); +/*! +\brief connects a socket + +Connects a socket to a remote peer on a given port +\param sock the socket object +\param peer_name the remote server address (IP or DNS) +\param port remote port number to connect the socket to +\param local_ip the local (client) address (IP or DNS) if any, NULL otherwise. +\return error if any + */ +GF_Err gf_sk_connect(GF_Socket *sock, const char *peer_name, u16 port, const char *local_ip); +/*! +\brief data emission + +Sends a buffer on the socket. The socket must be in a bound or connected mode +\param sock the socket object +\param buffer the data buffer to send +\param length the data length to send +\return error if any + */ +GF_Err gf_sk_send(GF_Socket *sock, const u8 *buffer, u32 length); +/*! +\brief data reception + +Fetches data on a socket. The socket must be in a bound or connected state +\param sock the socket object +\param buffer the reception buffer where data is written. Passing NULL will only probe for data in socket (eg only do select() ). +\param length the allocated size of the reception buffer +\param read the actual number of bytes received +\return error if any, GF_IP_NETWORK_EMPTY if nothing to read + */ +GF_Err gf_sk_receive(GF_Socket *sock, u8 *buffer, u32 length, u32 *read); + +/*! +\brief socket listening + +Sets the socket in a listening state. This socket must have been bound to a port before +\param sock the socket object +\param max_conn the maximum number of simultaneous connection this socket will accept +\return error if any + */ +GF_Err gf_sk_listen(GF_Socket *sock, u32 max_conn); +/*! +\brief socket accept + +Accepts an incoming connection on a listening socket +\param sock the socket object +\param new_conn the resulting connection socket object +\return error if any, GF_IP_NETWORK_EMPTY if nothing to read + */ +GF_Err gf_sk_accept(GF_Socket *sock, GF_Socket **new_conn); + +/*! +\brief server socket mode + +Disable the Nable algo (e.g. set TCP_NODELAY) and set the KEEPALIVE on +\param sock the socket object +\param server_on sets server mode on or off +\return error if any +*/ +GF_Err gf_sk_server_mode(GF_Socket *sock, Bool server_on); + +/*! +\brief get local host name + +Retrieves local host name. +\param buffer destination buffer for name. Buffer must be GF_MAX_IP_NAME_LEN long +\return error if any + */ +GF_Err gf_sk_get_host_name(char *buffer); + +/*! +\brief get local IP + +Gets local IP address of a connected socket, typically used for server after an ACCEPT +\param sock the socket object +\param buffer destination buffer for IP address. Buffer must be GF_MAX_IP_NAME_LEN long +\return error if any + */ +GF_Err gf_sk_get_local_ip(GF_Socket *sock, char *buffer); +/*! +\brief get local info + +Gets local socket info of a socket +\param sock the socket object +\param port local port number of the socket +\param sock_type socket type (UDP or TCP) +\return error if any + */ +GF_Err gf_sk_get_local_info(GF_Socket *sock, u16 *port, u32 *sock_type); + +/*! +\brief get remote address + +Gets the remote address 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 +\return error if any + */ +GF_Err gf_sk_get_remote_address(GF_Socket *sock, char *buffer); + +/*! +\brief set remote address + +Sets the remote address of a socket. This is used by connectionless sockets using SendTo and ReceiveFrom +\param sock the socket object +\param address the remote peer address +\param port the remote peer port +\return error if any + */ +GF_Err gf_sk_set_remote(GF_Socket *sock, char *address, u16 port); + + +/*! +\brief multicast setup + +Performs multicast setup (BIND and JOIN) for the socket object +\param sock the socket object +\param multi_ip_add the multicast IP address +\param multi_port the multicast port number +\param TTL the multicast TTL (Time-To-Live) +\param no_bind if sets, only join the multicast +\param local_interface_ip the local interface IP address if desired. If NULL, the default interface will be used. +\return error if any + */ +GF_Err gf_sk_setup_multicast(GF_Socket *sock, const char *multi_ip_add, u16 multi_port, u32 TTL, Bool no_bind, char *local_interface_ip); +/*! + \brief multicast address test + +Tests whether an IP address is a multicast one or not +\param multi_ip_add the multicast IP address to test +\return 1 if the address is a multicast one, 0 otherwise + */ +u32 gf_sk_is_multicast_address(const char *multi_ip_add); + +/*! +\brief send data with wait delay + +Sends data with a max wait delay. This is used for http / ftp sockets mainly. The socket must be connected. +\param sock the socket object +\param buffer the data buffer to send +\param length the data length to send +\param delay_sec the maximum delay in second to wait before aborting +\return If the operation timed out, the function will return a GF_IP_SOCK_WOULD_BLOCK error. + */ +GF_Err gf_sk_send_wait(GF_Socket *sock, const u8 *buffer, u32 length, u32 delay_sec); +/* receive data with a max wait delay of Second - used for http / ftp sockets mainly*/ +/*! +\brief receive data with wait delay + +Fetches data with a max wait delay. This is used for http / ftp sockets mainly. The socket must be connected. +\param sock the socket object +\param buffer the reception buffer where data is written +\param length the allocated size of the reception buffer +\param read the actual number of bytes received +\param delay_sec the maximum delay in second to wait before aborting +\return If the operation timed out, the function will return a GF_IP_SOCK_WOULD_BLOCK error. + */ +GF_Err gf_sk_receive_wait(GF_Socket *sock, u8 *buffer, u32 length, u32 *read, u32 delay_sec); + +/*! +\brief gets socket handle + +Gets the socket low-level handle as used by OpenSSL. +\param sock the socket object +\return the socket handle + */ +s32 gf_sk_get_handle(GF_Socket *sock); + +/*! +Sets the socket wait time in microseconds. Default wait time is 500 microseconds. Any value >= 1000000 will reset to default. +\param sock the socket object +\param usec_wait wait time in microseconds + */ +void gf_sk_set_usec_wait(GF_Socket *sock, u32 usec_wait); + +/*! +Fetches data on a socket without performing any select (wait), to be used with socket group on sockets that are set in the selected socket group +\param sock the socket object +\param buffer the reception buffer where data is written +\param length the allocated size of the reception buffer +\param read the actual number of bytes received +\return error if any, GF_IP_NETWORK_EMPTY if nothing to read + */ +GF_Err gf_sk_receive_no_select(GF_Socket *sock, u8 *buffer, u32 length, u32 *read); + +/*! +Checks if connection has been closed by remote peer +\param sock the socket object +\return error if any (GF_IP_CONNECTION_CLOSED if connection is closed) + */ +GF_Err gf_sk_probe(GF_Socket *sock); + + +/*! socket selection mode*/ +typedef enum +{ + /*! select for both read and write operations */ + GF_SK_SELECT_BOTH=0, + /*! select for both read operations */ + GF_SK_SELECT_READ, + /*! select for both write operations */ + GF_SK_SELECT_WRITE, +} GF_SockSelectMode; + +/*! +Checks if socket can is ready for read or write +\param sock the socket object +\param mode the operation mode desired +\return GF_OK if ready, GF_IP_SOCK_WOULD_BLOCK if not ready, otherwise error if any (GF_IP_CONNECTION_CLOSED if connection is closed) + */ +GF_Err gf_sk_select(GF_Socket *sock, GF_SockSelectMode mode); + +/*! @} */ + +/*! +\addtogroup sockgrp_grp +\brief Socket IO polling +@{ +*/ + +/*! +Creates a new socket group +\return socket group object + */ +GF_SockGroup *gf_sk_group_new(); +/*! +Deletes a socket group +\param sg socket group object + */ +void gf_sk_group_del(GF_SockGroup *sg); +/*! +Registers a socket to a socket group +\param sg socket group object +\param sk socket object to register + */ +void gf_sk_group_register(GF_SockGroup *sg, GF_Socket *sk); +/*! +Unregisters a socket from a socket group +\param sg socket group object +\param sk socket object to unregister + */ +void gf_sk_group_unregister(GF_SockGroup *sg, GF_Socket *sk); + +/*! +Performs a select (wait) on the socket group +\param sg socket group object +\param wait_usec microseconds to wait (can be larger than one second) +\param mode the operation mode desired +\return error if any + */ +GF_Err gf_sk_group_select(GF_SockGroup *sg, u32 wait_usec, GF_SockSelectMode mode); + +/*! +Checks if given socket is selected and can be read. This shall be called after gf_sk_group_select +\param sg socket group object +\param sk socket object to check +\param mode the operation mode desired +\return GF_TRUE if socket is ready to read, 0 otherwise + */ +Bool gf_sk_group_sock_is_set(GF_SockGroup *sg, GF_Socket *sk, GF_SockSelectMode mode); + +/*! @} */ + + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_NET_H_*/ + diff --git a/include/gpac/nodes_mpeg4.h b/include/gpac/nodes_mpeg4.h new file mode 100644 index 0000000..f51260d --- /dev/null +++ b/include/gpac/nodes_mpeg4.h @@ -0,0 +1,3337 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Scene Graph 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. + * + */ + + +/* + DO NOT MOFIFY - File generated on GMT Tue Nov 08 09:10:57 2011 + + BY MPEG4Gen for GPAC Version 0.5.0 +*/ + +#ifndef _nodes_mpeg4_H +#define _nodes_mpeg4_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/scenegraph_vrml.h> + +#ifndef GPAC_DISABLE_VRML + +enum { + TAG_MPEG4_Anchor = GF_NODE_RANGE_FIRST_MPEG4, + TAG_MPEG4_AnimationStream, + TAG_MPEG4_Appearance, + TAG_MPEG4_AudioBuffer, + TAG_MPEG4_AudioClip, + TAG_MPEG4_AudioDelay, + TAG_MPEG4_AudioFX, + TAG_MPEG4_AudioMix, + TAG_MPEG4_AudioSource, + TAG_MPEG4_AudioSwitch, + TAG_MPEG4_Background, + TAG_MPEG4_Background2D, + TAG_MPEG4_Billboard, + TAG_MPEG4_Bitmap, + TAG_MPEG4_Box, + TAG_MPEG4_Circle, + TAG_MPEG4_Collision, + TAG_MPEG4_Color, + TAG_MPEG4_ColorInterpolator, + TAG_MPEG4_CompositeTexture2D, + TAG_MPEG4_CompositeTexture3D, + TAG_MPEG4_Conditional, + TAG_MPEG4_Cone, + TAG_MPEG4_Coordinate, + TAG_MPEG4_Coordinate2D, + TAG_MPEG4_CoordinateInterpolator, + TAG_MPEG4_CoordinateInterpolator2D, + TAG_MPEG4_Curve2D, + TAG_MPEG4_Cylinder, + TAG_MPEG4_CylinderSensor, + TAG_MPEG4_DirectionalLight, + TAG_MPEG4_DiscSensor, + TAG_MPEG4_ElevationGrid, + TAG_MPEG4_Expression, + TAG_MPEG4_Extrusion, + TAG_MPEG4_Face, + TAG_MPEG4_FaceDefMesh, + TAG_MPEG4_FaceDefTables, + TAG_MPEG4_FaceDefTransform, + TAG_MPEG4_FAP, + TAG_MPEG4_FDP, + TAG_MPEG4_FIT, + TAG_MPEG4_Fog, + TAG_MPEG4_FontStyle, + TAG_MPEG4_Form, + TAG_MPEG4_Group, + TAG_MPEG4_ImageTexture, + TAG_MPEG4_IndexedFaceSet, + TAG_MPEG4_IndexedFaceSet2D, + TAG_MPEG4_IndexedLineSet, + TAG_MPEG4_IndexedLineSet2D, + TAG_MPEG4_Inline, + TAG_MPEG4_LOD, + TAG_MPEG4_Layer2D, + TAG_MPEG4_Layer3D, + TAG_MPEG4_Layout, + TAG_MPEG4_LineProperties, + TAG_MPEG4_ListeningPoint, + TAG_MPEG4_Material, + TAG_MPEG4_Material2D, + TAG_MPEG4_MovieTexture, + TAG_MPEG4_NavigationInfo, + TAG_MPEG4_Normal, + TAG_MPEG4_NormalInterpolator, + TAG_MPEG4_OrderedGroup, + TAG_MPEG4_OrientationInterpolator, + TAG_MPEG4_PixelTexture, + TAG_MPEG4_PlaneSensor, + TAG_MPEG4_PlaneSensor2D, + TAG_MPEG4_PointLight, + TAG_MPEG4_PointSet, + TAG_MPEG4_PointSet2D, + TAG_MPEG4_PositionInterpolator, + TAG_MPEG4_PositionInterpolator2D, + TAG_MPEG4_ProximitySensor2D, + TAG_MPEG4_ProximitySensor, + TAG_MPEG4_QuantizationParameter, + TAG_MPEG4_Rectangle, + TAG_MPEG4_ScalarInterpolator, + TAG_MPEG4_Script, + TAG_MPEG4_Shape, + TAG_MPEG4_Sound, + TAG_MPEG4_Sound2D, + TAG_MPEG4_Sphere, + TAG_MPEG4_SphereSensor, + TAG_MPEG4_SpotLight, + TAG_MPEG4_Switch, + TAG_MPEG4_TermCap, + TAG_MPEG4_Text, + TAG_MPEG4_TextureCoordinate, + TAG_MPEG4_TextureTransform, + TAG_MPEG4_TimeSensor, + TAG_MPEG4_TouchSensor, + TAG_MPEG4_Transform, + TAG_MPEG4_Transform2D, + TAG_MPEG4_Valuator, + TAG_MPEG4_Viewpoint, + TAG_MPEG4_VisibilitySensor, + TAG_MPEG4_Viseme, + TAG_MPEG4_WorldInfo, + TAG_MPEG4_AcousticMaterial, + TAG_MPEG4_AcousticScene, + TAG_MPEG4_ApplicationWindow, + TAG_MPEG4_BAP, + TAG_MPEG4_BDP, + TAG_MPEG4_Body, + TAG_MPEG4_BodyDefTable, + TAG_MPEG4_BodySegmentConnectionHint, + TAG_MPEG4_DirectiveSound, + TAG_MPEG4_Hierarchical3DMesh, + TAG_MPEG4_MaterialKey, + TAG_MPEG4_PerceptualParameters, + TAG_MPEG4_TemporalTransform, + TAG_MPEG4_TemporalGroup, + TAG_MPEG4_ServerCommand, + TAG_MPEG4_InputSensor, + TAG_MPEG4_MatteTexture, + TAG_MPEG4_MediaBuffer, + TAG_MPEG4_MediaControl, + TAG_MPEG4_MediaSensor, + TAG_MPEG4_BitWrapper, + TAG_MPEG4_CoordinateInterpolator4D, + TAG_MPEG4_DepthImage, + TAG_MPEG4_FFD, + TAG_MPEG4_Implicit, + TAG_MPEG4_XXLFM_Appearance, + TAG_MPEG4_XXLFM_BlendList, + TAG_MPEG4_XXLFM_FrameList, + TAG_MPEG4_XXLFM_LightMap, + TAG_MPEG4_XXLFM_SurfaceMapList, + TAG_MPEG4_XXLFM_ViewMapList, + TAG_MPEG4_MeshGrid, + TAG_MPEG4_NonLinearDeformer, + TAG_MPEG4_NurbsCurve, + TAG_MPEG4_NurbsCurve2D, + TAG_MPEG4_NurbsSurface, + TAG_MPEG4_OctreeImage, + TAG_MPEG4_XXParticles, + TAG_MPEG4_XXParticleInitBox, + TAG_MPEG4_XXPlanarObstacle, + TAG_MPEG4_XXPointAttractor, + TAG_MPEG4_PointTexture, + TAG_MPEG4_PositionAnimator, + TAG_MPEG4_PositionAnimator2D, + TAG_MPEG4_PositionInterpolator4D, + TAG_MPEG4_ProceduralTexture, + TAG_MPEG4_Quadric, + TAG_MPEG4_SBBone, + TAG_MPEG4_SBMuscle, + TAG_MPEG4_SBSegment, + TAG_MPEG4_SBSite, + TAG_MPEG4_SBSkinnedModel, + TAG_MPEG4_SBVCAnimation, + TAG_MPEG4_ScalarAnimator, + TAG_MPEG4_SimpleTexture, + TAG_MPEG4_SolidRep, + TAG_MPEG4_SubdivisionSurface, + TAG_MPEG4_SubdivSurfaceSector, + TAG_MPEG4_WaveletSubdivisionSurface, + TAG_MPEG4_Clipper2D, + TAG_MPEG4_ColorTransform, + TAG_MPEG4_Ellipse, + TAG_MPEG4_LinearGradient, + TAG_MPEG4_PathLayout, + TAG_MPEG4_RadialGradient, + TAG_MPEG4_SynthesizedTexture, + TAG_MPEG4_TransformMatrix2D, + TAG_MPEG4_Viewport, + TAG_MPEG4_XCurve2D, + TAG_MPEG4_XFontStyle, + TAG_MPEG4_XLineProperties, + TAG_MPEG4_AdvancedAudioBuffer, + TAG_MPEG4_AudioChannelConfig, + TAG_MPEG4_DepthImageV2, + TAG_MPEG4_MorphShape, + TAG_MPEG4_MultiTexture, + TAG_MPEG4_PointTextureV2, + TAG_MPEG4_SBVCAnimationV2, + TAG_MPEG4_SimpleTextureV2, + TAG_MPEG4_SurroundingSound, + TAG_MPEG4_Transform3DAudio, + TAG_MPEG4_WideSound, + TAG_MPEG4_ScoreShape, + TAG_MPEG4_MusicScore, + TAG_MPEG4_FootPrintSetNode, + TAG_MPEG4_FootPrintNode, + TAG_MPEG4_BuildingPartNode, + TAG_MPEG4_RoofNode, + TAG_MPEG4_FacadeNode, + TAG_MPEG4_Shadow, + TAG_MPEG4_CacheTexture, + TAG_MPEG4_EnvironmentTest, + TAG_MPEG4_KeyNavigator, + TAG_MPEG4_SpacePartition, + TAG_MPEG4_Storage, + TAG_LastImplementedMPEG4 +}; + +typedef struct _tagAnchor +{ + BASE_NODE + VRML_CHILDREN + SFString description; /*exposedField*/ + MFString parameter; /*exposedField*/ + MFURL url; /*exposedField*/ + SFBool activate; /*eventIn*/ + void (*on_activate)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ +} M_Anchor; + + +typedef struct _tagAnimationStream +{ + BASE_NODE + SFBool loop; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + MFURL url; /*exposedField*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_AnimationStream; + + +typedef struct _tagAppearance +{ + BASE_NODE + GF_Node *material; /*exposedField*/ + GF_Node *texture; /*exposedField*/ + GF_Node *textureTransform; /*exposedField*/ +} M_Appearance; + + +typedef struct _tagAudioBuffer +{ + BASE_NODE + SFBool loop; /*exposedField*/ + SFFloat pitch; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + GF_ChildNodeItem *children; /*exposedField*/ + SFInt32 numChan; /*exposedField*/ + MFInt32 phaseGroup; /*exposedField*/ + SFFloat length; /*exposedField*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_AudioBuffer; + + +typedef struct _tagAudioClip +{ + BASE_NODE + SFString description; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFFloat pitch; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + MFURL url; /*exposedField*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_AudioClip; + + +typedef struct _tagAudioDelay +{ + BASE_NODE + VRML_CHILDREN + SFTime delay; /*exposedField*/ + SFInt32 numChan; /*field*/ + MFInt32 phaseGroup; /*field*/ +} M_AudioDelay; + + +typedef struct _tagAudioFX +{ + BASE_NODE + VRML_CHILDREN + SFString orch; /*exposedField*/ + SFString score; /*exposedField*/ + MFFloat params; /*exposedField*/ + SFInt32 numChan; /*field*/ + MFInt32 phaseGroup; /*field*/ +} M_AudioFX; + + +typedef struct _tagAudioMix +{ + BASE_NODE + VRML_CHILDREN + SFInt32 numInputs; /*exposedField*/ + MFFloat matrix; /*exposedField*/ + SFInt32 numChan; /*field*/ + MFInt32 phaseGroup; /*field*/ +} M_AudioMix; + + +typedef struct _tagAudioSource +{ + BASE_NODE + VRML_CHILDREN + MFURL url; /*exposedField*/ + SFFloat pitch; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFInt32 numChan; /*field*/ + MFInt32 phaseGroup; /*field*/ +} M_AudioSource; + + +typedef struct _tagAudioSwitch +{ + BASE_NODE + VRML_CHILDREN + MFInt32 whichChoice; /*exposedField*/ + SFInt32 numChan; /*field*/ + MFInt32 phaseGroup; /*field*/ +} M_AudioSwitch; + + +typedef struct _tagBackground +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat groundAngle; /*exposedField*/ + MFColor groundColor; /*exposedField*/ + MFURL backUrl; /*exposedField*/ + MFURL bottomUrl; /*exposedField*/ + MFURL frontUrl; /*exposedField*/ + MFURL leftUrl; /*exposedField*/ + MFURL rightUrl; /*exposedField*/ + MFURL topUrl; /*exposedField*/ + MFFloat skyAngle; /*exposedField*/ + MFColor skyColor; /*exposedField*/ + SFBool isBound; /*eventOut*/ +} M_Background; + + +typedef struct _tagBackground2D +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFColor backColor; /*exposedField*/ + MFURL url; /*exposedField*/ + SFBool isBound; /*eventOut*/ +} M_Background2D; + + +typedef struct _tagBillboard +{ + BASE_NODE + VRML_CHILDREN + SFVec3f axisOfRotation; /*exposedField*/ +} M_Billboard; + + +typedef struct _tagBitmap +{ + BASE_NODE + SFVec2f scale; /*exposedField*/ +} M_Bitmap; + + +typedef struct _tagBox +{ + BASE_NODE + SFVec3f size; /*field*/ +} M_Box; + + +typedef struct _tagCircle +{ + BASE_NODE + SFFloat radius; /*exposedField*/ +} M_Circle; + + +typedef struct _tagCollision +{ + BASE_NODE + VRML_CHILDREN + SFBool collide; /*exposedField*/ + GF_Node *proxy; /*field*/ + SFTime collideTime; /*eventOut*/ +} M_Collision; + + +typedef struct _tagColor +{ + BASE_NODE + MFColor color; /*exposedField*/ +} M_Color; + + +typedef struct _tagColorInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFColor keyValue; /*exposedField*/ + SFColor value_changed; /*eventOut*/ +} M_ColorInterpolator; + + +typedef struct _tagCompositeTexture2D +{ + BASE_NODE + VRML_CHILDREN + SFInt32 pixelWidth; /*exposedField*/ + SFInt32 pixelHeight; /*exposedField*/ + GF_Node *background; /*exposedField*/ + GF_Node *viewport; /*exposedField*/ + SFInt32 repeatSandT; /*field*/ +} M_CompositeTexture2D; + + +typedef struct _tagCompositeTexture3D +{ + BASE_NODE + VRML_CHILDREN + SFInt32 pixelWidth; /*exposedField*/ + SFInt32 pixelHeight; /*exposedField*/ + GF_Node *background; /*exposedField*/ + GF_Node *fog; /*exposedField*/ + GF_Node *navigationInfo; /*exposedField*/ + GF_Node *viewpoint; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ +} M_CompositeTexture3D; + + +typedef struct _tagConditional +{ + BASE_NODE + SFBool activate; /*eventIn*/ + void (*on_activate)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool reverseActivate; /*eventIn*/ + void (*on_reverseActivate)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFCommandBuffer buffer; /*exposedField*/ + SFBool isActive; /*eventOut*/ +} M_Conditional; + + +typedef struct _tagCone +{ + BASE_NODE + SFFloat bottomRadius; /*field*/ + SFFloat height; /*field*/ + SFBool side; /*field*/ + SFBool bottom; /*field*/ +} M_Cone; + + +typedef struct _tagCoordinate +{ + BASE_NODE + MFVec3f point; /*exposedField*/ +} M_Coordinate; + + +typedef struct _tagCoordinate2D +{ + BASE_NODE + MFVec2f point; /*exposedField*/ +} M_Coordinate2D; + + +typedef struct _tagCoordinateInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + MFVec3f value_changed; /*eventOut*/ +} M_CoordinateInterpolator; + + +typedef struct _tagCoordinateInterpolator2D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec2f keyValue; /*exposedField*/ + MFVec2f value_changed; /*eventOut*/ +} M_CoordinateInterpolator2D; + + +typedef struct _tagCurve2D +{ + BASE_NODE + GF_Node *point; /*exposedField*/ + SFFloat fineness; /*exposedField*/ + MFInt32 type; /*exposedField*/ +} M_Curve2D; + + +typedef struct _tagCylinder +{ + BASE_NODE + SFBool bottom; /*field*/ + SFFloat height; /*field*/ + SFFloat radius; /*field*/ + SFBool side; /*field*/ + SFBool top; /*field*/ +} M_Cylinder; + + +typedef struct _tagCylinderSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFFloat diskAngle; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFFloat maxAngle; /*exposedField*/ + SFFloat minAngle; /*exposedField*/ + SFFloat offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFRotation rotation_changed; /*eventOut*/ + SFVec3f trackPoint_changed; /*eventOut*/ +} M_CylinderSensor; + + +typedef struct _tagDirectionalLight +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFColor color; /*exposedField*/ + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFBool on; /*exposedField*/ +} M_DirectionalLight; + + +typedef struct _tagDiscSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFFloat maxAngle; /*exposedField*/ + SFFloat minAngle; /*exposedField*/ + SFFloat offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFFloat rotation_changed; /*eventOut*/ + SFVec2f trackPoint_changed; /*eventOut*/ +} M_DiscSensor; + + +typedef struct _tagElevationGrid +{ + BASE_NODE + MFFloat set_height; /*eventIn*/ + void (*on_set_height)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + MFFloat height; /*field*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFFloat creaseAngle; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + SFInt32 xDimension; /*field*/ + SFFloat xSpacing; /*field*/ + SFInt32 zDimension; /*field*/ + SFFloat zSpacing; /*field*/ +} M_ElevationGrid; + + +typedef struct _tagExpression +{ + BASE_NODE + SFInt32 expression_select1; /*exposedField*/ + SFInt32 expression_intensity1; /*exposedField*/ + SFInt32 expression_select2; /*exposedField*/ + SFInt32 expression_intensity2; /*exposedField*/ + SFBool init_face; /*exposedField*/ + SFBool expression_def; /*exposedField*/ +} M_Expression; + + +typedef struct _tagExtrusion +{ + BASE_NODE + MFVec2f set_crossSection; /*eventIn*/ + void (*on_set_crossSection)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFRotation set_orientation; /*eventIn*/ + void (*on_set_orientation)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFVec2f set_scale; /*eventIn*/ + void (*on_set_scale)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFVec3f set_spine; /*eventIn*/ + void (*on_set_spine)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool beginCap; /*field*/ + SFBool ccw; /*field*/ + SFBool convex; /*field*/ + SFFloat creaseAngle; /*field*/ + MFVec2f crossSection; /*field*/ + SFBool endCap; /*field*/ + MFRotation orientation; /*field*/ + MFVec2f scale; /*field*/ + SFBool solid; /*field*/ + MFVec3f spine; /*field*/ +} M_Extrusion; + + +typedef struct _tagFace +{ + BASE_NODE + GF_Node *fap; /*exposedField*/ + GF_Node *fdp; /*exposedField*/ + GF_Node *fit; /*exposedField*/ + GF_Node *ttsSource; /*exposedField*/ + GF_ChildNodeItem *renderedFace; /*exposedField*/ +} M_Face; + + +typedef struct _tagFaceDefMesh +{ + BASE_NODE + GF_Node *faceSceneGraphNode; /*field*/ + MFInt32 intervalBorders; /*field*/ + MFInt32 coordIndex; /*field*/ + MFVec3f displacements; /*field*/ +} M_FaceDefMesh; + + +typedef struct _tagFaceDefTables +{ + BASE_NODE + SFInt32 fapID; /*field*/ + SFInt32 highLevelSelect; /*field*/ + GF_ChildNodeItem *faceDefMesh; /*exposedField*/ + GF_ChildNodeItem *faceDefTransform; /*exposedField*/ +} M_FaceDefTables; + + +typedef struct _tagFaceDefTransform +{ + BASE_NODE + GF_Node *faceSceneGraphNode; /*field*/ + SFInt32 fieldId; /*field*/ + SFRotation rotationDef; /*field*/ + SFVec3f scaleDef; /*field*/ + SFVec3f translationDef; /*field*/ +} M_FaceDefTransform; + + +typedef struct _tagFAP +{ + BASE_NODE + GF_Node *viseme; /*exposedField*/ + GF_Node *expression; /*exposedField*/ + SFInt32 open_jaw; /*exposedField*/ + SFInt32 lower_t_midlip; /*exposedField*/ + SFInt32 raise_b_midlip; /*exposedField*/ + SFInt32 stretch_l_corner; /*exposedField*/ + SFInt32 stretch_r_corner; /*exposedField*/ + SFInt32 lower_t_lip_lm; /*exposedField*/ + SFInt32 lower_t_lip_rm; /*exposedField*/ + SFInt32 lower_b_lip_lm; /*exposedField*/ + SFInt32 lower_b_lip_rm; /*exposedField*/ + SFInt32 raise_l_cornerlip; /*exposedField*/ + SFInt32 raise_r_cornerlip; /*exposedField*/ + SFInt32 thrust_jaw; /*exposedField*/ + SFInt32 shift_jaw; /*exposedField*/ + SFInt32 push_b_lip; /*exposedField*/ + SFInt32 push_t_lip; /*exposedField*/ + SFInt32 depress_chin; /*exposedField*/ + SFInt32 close_t_l_eyelid; /*exposedField*/ + SFInt32 close_t_r_eyelid; /*exposedField*/ + SFInt32 close_b_l_eyelid; /*exposedField*/ + SFInt32 close_b_r_eyelid; /*exposedField*/ + SFInt32 yaw_l_eyeball; /*exposedField*/ + SFInt32 yaw_r_eyeball; /*exposedField*/ + SFInt32 pitch_l_eyeball; /*exposedField*/ + SFInt32 pitch_r_eyeball; /*exposedField*/ + SFInt32 thrust_l_eyeball; /*exposedField*/ + SFInt32 thrust_r_eyeball; /*exposedField*/ + SFInt32 dilate_l_pupil; /*exposedField*/ + SFInt32 dilate_r_pupil; /*exposedField*/ + SFInt32 raise_l_i_eyebrow; /*exposedField*/ + SFInt32 raise_r_i_eyebrow; /*exposedField*/ + SFInt32 raise_l_m_eyebrow; /*exposedField*/ + SFInt32 raise_r_m_eyebrow; /*exposedField*/ + SFInt32 raise_l_o_eyebrow; /*exposedField*/ + SFInt32 raise_r_o_eyebrow; /*exposedField*/ + SFInt32 squeeze_l_eyebrow; /*exposedField*/ + SFInt32 squeeze_r_eyebrow; /*exposedField*/ + SFInt32 puff_l_cheek; /*exposedField*/ + SFInt32 puff_r_cheek; /*exposedField*/ + SFInt32 lift_l_cheek; /*exposedField*/ + SFInt32 lift_r_cheek; /*exposedField*/ + SFInt32 shift_tongue_tip; /*exposedField*/ + SFInt32 raise_tongue_tip; /*exposedField*/ + SFInt32 thrust_tongue_tip; /*exposedField*/ + SFInt32 raise_tongue; /*exposedField*/ + SFInt32 tongue_roll; /*exposedField*/ + SFInt32 head_pitch; /*exposedField*/ + SFInt32 head_yaw; /*exposedField*/ + SFInt32 head_roll; /*exposedField*/ + SFInt32 lower_t_midlip_o; /*exposedField*/ + SFInt32 raise_b_midlip_o; /*exposedField*/ + SFInt32 stretch_l_cornerlip; /*exposedField*/ + SFInt32 stretch_r_cornerlip; /*exposedField*/ + SFInt32 lower_t_lip_lm_o; /*exposedField*/ + SFInt32 lower_t_lip_rm_o; /*exposedField*/ + SFInt32 raise_b_lip_lm_o; /*exposedField*/ + SFInt32 raise_b_lip_rm_o; /*exposedField*/ + SFInt32 raise_l_cornerlip_o; /*exposedField*/ + SFInt32 raise_r_cornerlip_o; /*exposedField*/ + SFInt32 stretch_l_nose; /*exposedField*/ + SFInt32 stretch_r_nose; /*exposedField*/ + SFInt32 raise_nose; /*exposedField*/ + SFInt32 bend_nose; /*exposedField*/ + SFInt32 raise_l_ear; /*exposedField*/ + SFInt32 raise_r_ear; /*exposedField*/ + SFInt32 pull_l_ear; /*exposedField*/ + SFInt32 pull_r_ear; /*exposedField*/ +} M_FAP; + + +typedef struct _tagFDP +{ + BASE_NODE + GF_Node *featurePointsCoord; /*exposedField*/ + GF_Node *textureCoord; /*exposedField*/ + GF_ChildNodeItem *faceDefTables; /*exposedField*/ + GF_ChildNodeItem *faceSceneGraph; /*exposedField*/ + SFBool useOrthoTexture; /*field*/ +} M_FDP; + + +typedef struct _tagFIT +{ + BASE_NODE + MFInt32 FAPs; /*exposedField*/ + MFInt32 Graph; /*exposedField*/ + MFInt32 numeratorExp; /*exposedField*/ + MFInt32 denominatorExp; /*exposedField*/ + MFInt32 numeratorImpulse; /*exposedField*/ + MFInt32 numeratorTerms; /*exposedField*/ + MFInt32 denominatorTerms; /*exposedField*/ + MFFloat numeratorCoefs; /*exposedField*/ + MFFloat denominatorCoefs; /*exposedField*/ +} M_FIT; + + +typedef struct _tagFog +{ + BASE_NODE + SFColor color; /*exposedField*/ + SFString fogType; /*exposedField*/ + SFFloat visibilityRange; /*exposedField*/ + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool isBound; /*eventOut*/ +} M_Fog; + + +typedef struct _tagFontStyle +{ + BASE_NODE + MFString family; /*exposedField*/ + SFBool horizontal; /*exposedField*/ + MFString justify; /*exposedField*/ + SFString language; /*exposedField*/ + SFBool leftToRight; /*exposedField*/ + SFFloat size; /*exposedField*/ + SFFloat spacing; /*exposedField*/ + SFString style; /*exposedField*/ + SFBool topToBottom; /*exposedField*/ +} M_FontStyle; + + +typedef struct _tagForm +{ + BASE_NODE + VRML_CHILDREN + SFVec2f size; /*exposedField*/ + MFInt32 groups; /*exposedField*/ + MFString constraints; /*exposedField*/ + MFInt32 groupsIndex; /*exposedField*/ +} M_Form; + + +typedef struct _tagGroup +{ + BASE_NODE + VRML_CHILDREN +} M_Group; + + +typedef struct _tagImageTexture +{ + BASE_NODE + MFURL url; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ +} M_ImageTexture; + + +typedef struct _tagIndexedFaceSet +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_normalIndex; /*eventIn*/ + void (*on_set_normalIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_texCoordIndex; /*eventIn*/ + void (*on_set_texCoordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool convex; /*field*/ + MFInt32 coordIndex; /*field*/ + SFFloat creaseAngle; /*field*/ + MFInt32 normalIndex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + MFInt32 texCoordIndex; /*field*/ +} M_IndexedFaceSet; + + +typedef struct _tagIndexedFaceSet2D +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_texCoordIndex; /*eventIn*/ + void (*on_set_texCoordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool convex; /*field*/ + MFInt32 coordIndex; /*field*/ + MFInt32 texCoordIndex; /*field*/ +} M_IndexedFaceSet2D; + + +typedef struct _tagIndexedLineSet +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + MFInt32 coordIndex; /*field*/ +} M_IndexedLineSet; + + +typedef struct _tagIndexedLineSet2D +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + MFInt32 coordIndex; /*field*/ +} M_IndexedLineSet2D; + + +typedef struct _tagInline +{ + BASE_NODE + MFURL url; /*exposedField*/ +} M_Inline; + + +typedef struct _tagLOD +{ + BASE_NODE + GF_ChildNodeItem *level; /*exposedField*/ + SFVec3f center; /*field*/ + MFFloat range; /*field*/ +} M_LOD; + + +typedef struct _tagLayer2D +{ + BASE_NODE + VRML_CHILDREN + SFVec2f size; /*exposedField*/ + GF_Node *background; /*exposedField*/ + GF_Node *viewport; /*exposedField*/ +} M_Layer2D; + + +typedef struct _tagLayer3D +{ + BASE_NODE + VRML_CHILDREN + SFVec2f size; /*exposedField*/ + GF_Node *background; /*exposedField*/ + GF_Node *fog; /*exposedField*/ + GF_Node *navigationInfo; /*exposedField*/ + GF_Node *viewpoint; /*exposedField*/ +} M_Layer3D; + + +typedef struct _tagLayout +{ + BASE_NODE + VRML_CHILDREN + SFBool wrap; /*exposedField*/ + SFVec2f size; /*exposedField*/ + SFBool horizontal; /*exposedField*/ + MFString justify; /*exposedField*/ + SFBool leftToRight; /*exposedField*/ + SFBool topToBottom; /*exposedField*/ + SFFloat spacing; /*exposedField*/ + SFBool smoothScroll; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFBool scrollVertical; /*exposedField*/ + SFFloat scrollRate; /*exposedField*/ + SFInt32 scrollMode; /*exposedField*/ +} M_Layout; + + +typedef struct _tagLineProperties +{ + BASE_NODE + SFColor lineColor; /*exposedField*/ + SFInt32 lineStyle; /*exposedField*/ + SFFloat width; /*exposedField*/ +} M_LineProperties; + + +typedef struct _tagListeningPoint +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool jump; /*exposedField*/ + SFRotation orientation; /*exposedField*/ + SFVec3f position; /*exposedField*/ + SFString description; /*field*/ + SFTime bindTime; /*eventOut*/ + SFBool isBound; /*eventOut*/ +} M_ListeningPoint; + + +typedef struct _tagMaterial +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFColor diffuseColor; /*exposedField*/ + SFColor emissiveColor; /*exposedField*/ + SFFloat shininess; /*exposedField*/ + SFColor specularColor; /*exposedField*/ + SFFloat transparency; /*exposedField*/ +} M_Material; + + +typedef struct _tagMaterial2D +{ + BASE_NODE + SFColor emissiveColor; /*exposedField*/ + SFBool filled; /*exposedField*/ + GF_Node *lineProps; /*exposedField*/ + SFFloat transparency; /*exposedField*/ +} M_Material2D; + + +typedef struct _tagMovieTexture +{ + BASE_NODE + SFBool loop; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + MFURL url; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_MovieTexture; + + +typedef struct _tagNavigationInfo +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat avatarSize; /*exposedField*/ + SFBool headlight; /*exposedField*/ + SFFloat speed; /*exposedField*/ + MFString type; /*exposedField*/ + SFFloat visibilityLimit; /*exposedField*/ + SFBool isBound; /*eventOut*/ +} M_NavigationInfo; + + +typedef struct _tagNormal +{ + BASE_NODE + MFVec3f vector; /*exposedField*/ +} M_Normal; + + +typedef struct _tagNormalInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + MFVec3f value_changed; /*eventOut*/ +} M_NormalInterpolator; + + +typedef struct _tagOrderedGroup +{ + BASE_NODE + VRML_CHILDREN + MFFloat order; /*exposedField*/ +} M_OrderedGroup; + + +typedef struct _tagOrientationInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFRotation keyValue; /*exposedField*/ + SFRotation value_changed; /*eventOut*/ +} M_OrientationInterpolator; + + +typedef struct _tagPixelTexture +{ + BASE_NODE + SFImage image; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ +} M_PixelTexture; + + +typedef struct _tagPlaneSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFVec2f maxPosition; /*exposedField*/ + SFVec2f minPosition; /*exposedField*/ + SFVec3f offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFVec3f trackPoint_changed; /*eventOut*/ + SFVec3f translation_changed; /*eventOut*/ +} M_PlaneSensor; + + +typedef struct _tagPlaneSensor2D +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFVec2f maxPosition; /*exposedField*/ + SFVec2f minPosition; /*exposedField*/ + SFVec2f offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFVec2f trackPoint_changed; /*eventOut*/ + SFVec2f translation_changed; /*eventOut*/ +} M_PlaneSensor2D; + + +typedef struct _tagPointLight +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFVec3f attenuation; /*exposedField*/ + SFColor color; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFBool on; /*exposedField*/ + SFFloat radius; /*exposedField*/ +} M_PointLight; + + +typedef struct _tagPointSet +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ +} M_PointSet; + + +typedef struct _tagPointSet2D +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ +} M_PointSet2D; + + +typedef struct _tagPositionInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + SFVec3f value_changed; /*eventOut*/ +} M_PositionInterpolator; + + +typedef struct _tagPositionInterpolator2D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec2f keyValue; /*exposedField*/ + SFVec2f value_changed; /*eventOut*/ +} M_PositionInterpolator2D; + + +typedef struct _tagProximitySensor2D +{ + BASE_NODE + SFVec2f center; /*exposedField*/ + SFVec2f size; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFVec2f position_changed; /*eventOut*/ + SFFloat orientation_changed; /*eventOut*/ + SFTime enterTime; /*eventOut*/ + SFTime exitTime; /*eventOut*/ +} M_ProximitySensor2D; + + +typedef struct _tagProximitySensor +{ + BASE_NODE + SFVec3f center; /*exposedField*/ + SFVec3f size; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFVec3f position_changed; /*eventOut*/ + SFRotation orientation_changed; /*eventOut*/ + SFTime enterTime; /*eventOut*/ + SFTime exitTime; /*eventOut*/ +} M_ProximitySensor; + + +typedef struct _tagQuantizationParameter +{ + BASE_NODE + SFBool isLocal; /*field*/ + SFBool position3DQuant; /*field*/ + SFVec3f position3DMin; /*field*/ + SFVec3f position3DMax; /*field*/ + SFInt32 position3DNbBits; /*field*/ + SFBool position2DQuant; /*field*/ + SFVec2f position2DMin; /*field*/ + SFVec2f position2DMax; /*field*/ + SFInt32 position2DNbBits; /*field*/ + SFBool drawOrderQuant; /*field*/ + SFFloat drawOrderMin; /*field*/ + SFFloat drawOrderMax; /*field*/ + SFInt32 drawOrderNbBits; /*field*/ + SFBool colorQuant; /*field*/ + SFFloat colorMin; /*field*/ + SFFloat colorMax; /*field*/ + SFInt32 colorNbBits; /*field*/ + SFBool textureCoordinateQuant; /*field*/ + SFFloat textureCoordinateMin; /*field*/ + SFFloat textureCoordinateMax; /*field*/ + SFInt32 textureCoordinateNbBits; /*field*/ + SFBool angleQuant; /*field*/ + SFFloat angleMin; /*field*/ + SFFloat angleMax; /*field*/ + SFInt32 angleNbBits; /*field*/ + SFBool scaleQuant; /*field*/ + SFFloat scaleMin; /*field*/ + SFFloat scaleMax; /*field*/ + SFInt32 scaleNbBits; /*field*/ + SFBool keyQuant; /*field*/ + SFFloat keyMin; /*field*/ + SFFloat keyMax; /*field*/ + SFInt32 keyNbBits; /*field*/ + SFBool normalQuant; /*field*/ + SFInt32 normalNbBits; /*field*/ + SFBool sizeQuant; /*field*/ + SFFloat sizeMin; /*field*/ + SFFloat sizeMax; /*field*/ + SFInt32 sizeNbBits; /*field*/ + SFBool useEfficientCoding; /*field*/ +} M_QuantizationParameter; + + +typedef struct _tagRectangle +{ + BASE_NODE + SFVec2f size; /*exposedField*/ +} M_Rectangle; + + +typedef struct _tagScalarInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFFloat keyValue; /*exposedField*/ + SFFloat value_changed; /*eventOut*/ +} M_ScalarInterpolator; + + +typedef struct _tagScript +{ + BASE_NODE + MFScript url; /*exposedField*/ + SFBool directOutput; /*field*/ + SFBool mustEvaluate; /*field*/ +} M_Script; + + +typedef struct _tagShape +{ + BASE_NODE + GF_Node *appearance; /*exposedField*/ + GF_Node *geometry; /*exposedField*/ +} M_Shape; + + +typedef struct _tagSound +{ + BASE_NODE + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFFloat maxBack; /*exposedField*/ + SFFloat maxFront; /*exposedField*/ + SFFloat minBack; /*exposedField*/ + SFFloat minFront; /*exposedField*/ + SFFloat priority; /*exposedField*/ + GF_Node *source; /*exposedField*/ + SFBool spatialize; /*field*/ +} M_Sound; + + +typedef struct _tagSound2D +{ + BASE_NODE + SFFloat intensity; /*exposedField*/ + SFVec2f location; /*exposedField*/ + GF_Node *source; /*exposedField*/ + SFBool spatialize; /*field*/ +} M_Sound2D; + + +typedef struct _tagSphere +{ + BASE_NODE + SFFloat radius; /*field*/ +} M_Sphere; + + +typedef struct _tagSphereSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFRotation offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFRotation rotation_changed; /*eventOut*/ + SFVec3f trackPoint_changed; /*eventOut*/ +} M_SphereSensor; + + +typedef struct _tagSpotLight +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFVec3f attenuation; /*exposedField*/ + SFFloat beamWidth; /*exposedField*/ + SFColor color; /*exposedField*/ + SFFloat cutOffAngle; /*exposedField*/ + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFBool on; /*exposedField*/ + SFFloat radius; /*exposedField*/ +} M_SpotLight; + + +typedef struct _tagSwitch +{ + BASE_NODE + GF_ChildNodeItem *choice; /*exposedField*/ + SFInt32 whichChoice; /*exposedField*/ +} M_Switch; + + +typedef struct _tagTermCap +{ + BASE_NODE + SFTime evaluate; /*eventIn*/ + void (*on_evaluate)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFInt32 capability; /*exposedField*/ + SFInt32 value; /*eventOut*/ +} M_TermCap; + + +typedef struct _tagText +{ + BASE_NODE + MFString string; /*exposedField*/ + MFFloat length; /*exposedField*/ + GF_Node *fontStyle; /*exposedField*/ + SFFloat maxExtent; /*exposedField*/ +} M_Text; + + +typedef struct _tagTextureCoordinate +{ + BASE_NODE + MFVec2f point; /*exposedField*/ +} M_TextureCoordinate; + + +typedef struct _tagTextureTransform +{ + BASE_NODE + SFVec2f center; /*exposedField*/ + SFFloat rotation; /*exposedField*/ + SFVec2f scale; /*exposedField*/ + SFVec2f translation; /*exposedField*/ +} M_TextureTransform; + + +typedef struct _tagTimeSensor +{ + BASE_NODE + SFTime cycleInterval; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFTime cycleTime; /*eventOut*/ + SFFloat fraction_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFTime time; /*eventOut*/ +} M_TimeSensor; + + +typedef struct _tagTouchSensor +{ + BASE_NODE + SFBool enabled; /*exposedField*/ + SFVec3f hitNormal_changed; /*eventOut*/ + SFVec3f hitPoint_changed; /*eventOut*/ + SFVec2f hitTexCoord_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFBool isOver; /*eventOut*/ + SFTime touchTime; /*eventOut*/ +} M_TouchSensor; + + +typedef struct _tagTransform +{ + BASE_NODE + VRML_CHILDREN + SFVec3f center; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + SFVec3f translation; /*exposedField*/ +} M_Transform; + + +typedef struct _tagTransform2D +{ + BASE_NODE + VRML_CHILDREN + SFVec2f center; /*exposedField*/ + SFFloat rotationAngle; /*exposedField*/ + SFVec2f scale; /*exposedField*/ + SFFloat scaleOrientation; /*exposedField*/ + SFVec2f translation; /*exposedField*/ +} M_Transform2D; + + +typedef struct _tagValuator +{ + BASE_NODE + SFBool inSFBool; /*eventIn*/ + void (*on_inSFBool)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFColor inSFColor; /*eventIn*/ + void (*on_inSFColor)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFColor inMFColor; /*eventIn*/ + void (*on_inMFColor)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat inSFFloat; /*eventIn*/ + void (*on_inSFFloat)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat inMFFloat; /*eventIn*/ + void (*on_inMFFloat)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFInt32 inSFInt32; /*eventIn*/ + void (*on_inSFInt32)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 inMFInt32; /*eventIn*/ + void (*on_inMFInt32)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFRotation inSFRotation; /*eventIn*/ + void (*on_inSFRotation)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFRotation inMFRotation; /*eventIn*/ + void (*on_inMFRotation)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFString inSFString; /*eventIn*/ + void (*on_inSFString)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFString inMFString; /*eventIn*/ + void (*on_inMFString)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFTime inSFTime; /*eventIn*/ + void (*on_inSFTime)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec2f inSFVec2f; /*eventIn*/ + void (*on_inSFVec2f)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFVec2f inMFVec2f; /*eventIn*/ + void (*on_inMFVec2f)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec3f inSFVec3f; /*eventIn*/ + void (*on_inSFVec3f)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFVec3f inMFVec3f; /*eventIn*/ + void (*on_inMFVec3f)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool outSFBool; /*eventOut*/ + SFColor outSFColor; /*eventOut*/ + MFColor outMFColor; /*eventOut*/ + SFFloat outSFFloat; /*eventOut*/ + MFFloat outMFFloat; /*eventOut*/ + SFInt32 outSFInt32; /*eventOut*/ + MFInt32 outMFInt32; /*eventOut*/ + SFRotation outSFRotation; /*eventOut*/ + MFRotation outMFRotation; /*eventOut*/ + SFString outSFString; /*eventOut*/ + MFString outMFString; /*eventOut*/ + SFTime outSFTime; /*eventOut*/ + SFVec2f outSFVec2f; /*eventOut*/ + MFVec2f outMFVec2f; /*eventOut*/ + SFVec3f outSFVec3f; /*eventOut*/ + MFVec3f outMFVec3f; /*eventOut*/ + SFFloat Factor1; /*exposedField*/ + SFFloat Factor2; /*exposedField*/ + SFFloat Factor3; /*exposedField*/ + SFFloat Factor4; /*exposedField*/ + SFFloat Offset1; /*exposedField*/ + SFFloat Offset2; /*exposedField*/ + SFFloat Offset3; /*exposedField*/ + SFFloat Offset4; /*exposedField*/ + SFBool Sum; /*exposedField*/ +} M_Valuator; + + +typedef struct _tagViewpoint +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat fieldOfView; /*exposedField*/ + SFBool jump; /*exposedField*/ + SFRotation orientation; /*exposedField*/ + SFVec3f position; /*exposedField*/ + SFString description; /*field*/ + SFTime bindTime; /*eventOut*/ + SFBool isBound; /*eventOut*/ +} M_Viewpoint; + + +typedef struct _tagVisibilitySensor +{ + BASE_NODE + SFVec3f center; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFVec3f size; /*exposedField*/ + SFTime enterTime; /*eventOut*/ + SFTime exitTime; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_VisibilitySensor; + + +typedef struct _tagViseme +{ + BASE_NODE + SFInt32 viseme_select1; /*exposedField*/ + SFInt32 viseme_select2; /*exposedField*/ + SFInt32 viseme_blend; /*exposedField*/ + SFBool viseme_def; /*exposedField*/ +} M_Viseme; + + +typedef struct _tagWorldInfo +{ + BASE_NODE + MFString info; /*field*/ + SFString title; /*field*/ +} M_WorldInfo; + + +typedef struct _tagAcousticMaterial +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFColor diffuseColor; /*exposedField*/ + SFColor emissiveColor; /*exposedField*/ + SFFloat shininess; /*exposedField*/ + SFColor specularColor; /*exposedField*/ + SFFloat transparency; /*exposedField*/ + MFFloat reffunc; /*field*/ + MFFloat transfunc; /*field*/ + MFFloat refFrequency; /*field*/ + MFFloat transFrequency; /*field*/ +} M_AcousticMaterial; + + +typedef struct _tagAcousticScene +{ + BASE_NODE + SFVec3f center; /*field*/ + SFVec3f Size; /*field*/ + MFTime reverbTime; /*field*/ + MFFloat reverbFreq; /*field*/ + SFFloat reverbLevel; /*exposedField*/ + SFTime reverbDelay; /*exposedField*/ +} M_AcousticScene; + + +typedef struct _tagApplicationWindow +{ + BASE_NODE + SFBool isActive; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFString description; /*exposedField*/ + MFString parameter; /*exposedField*/ + MFURL url; /*exposedField*/ + SFVec2f size; /*exposedField*/ +} M_ApplicationWindow; + + +typedef struct _tagBAP +{ + BASE_NODE + SFInt32 sacroiliac_tilt; /*exposedField*/ + SFInt32 sacroiliac_torsion; /*exposedField*/ + SFInt32 sacroiliac_roll; /*exposedField*/ + SFInt32 l_hip_flexion; /*exposedField*/ + SFInt32 r_hip_flexion; /*exposedField*/ + SFInt32 l_hip_abduct; /*exposedField*/ + SFInt32 r_hip_abduct; /*exposedField*/ + SFInt32 l_hip_twisting; /*exposedField*/ + SFInt32 r_hip_twisting; /*exposedField*/ + SFInt32 l_knee_flexion; /*exposedField*/ + SFInt32 r_knee_flexion; /*exposedField*/ + SFInt32 l_knee_twisting; /*exposedField*/ + SFInt32 r_knee_twisting; /*exposedField*/ + SFInt32 l_ankle_flexion; /*exposedField*/ + SFInt32 r_ankle_flexion; /*exposedField*/ + SFInt32 l_ankle_twisting; /*exposedField*/ + SFInt32 r_ankle_twisting; /*exposedField*/ + SFInt32 l_subtalar_flexion; /*exposedField*/ + SFInt32 r_subtalar_flexion; /*exposedField*/ + SFInt32 l_midtarsal_flexion; /*exposedField*/ + SFInt32 r_midtarsal_flexion; /*exposedField*/ + SFInt32 l_metatarsal_flexion; /*exposedField*/ + SFInt32 r_metatarsal_flexion; /*exposedField*/ + SFInt32 l_sternoclavicular_abduct; /*exposedField*/ + SFInt32 r_sternoclavicular_abduct; /*exposedField*/ + SFInt32 l_sternoclavicular_rotate; /*exposedField*/ + SFInt32 r_sternoclavicular_rotate; /*exposedField*/ + SFInt32 l_acromioclavicular_abduct; /*exposedField*/ + SFInt32 r_acromioclavicular_abduct; /*exposedField*/ + SFInt32 l_acromioclavicular_rotate; /*exposedField*/ + SFInt32 r_acromioclavicular_rotate; /*exposedField*/ + SFInt32 l_shoulder_flexion; /*exposedField*/ + SFInt32 r_shoulder_flexion; /*exposedField*/ + SFInt32 l_shoulder_abduct; /*exposedField*/ + SFInt32 r_shoulder_abduct; /*exposedField*/ + SFInt32 l_shoulder_twisting; /*exposedField*/ + SFInt32 r_shoulder_twisting; /*exposedField*/ + SFInt32 l_elbow_flexion; /*exposedField*/ + SFInt32 r_elbow_flexion; /*exposedField*/ + SFInt32 l_elbow_twisting; /*exposedField*/ + SFInt32 r_elbow_twisting; /*exposedField*/ + SFInt32 l_wrist_flexion; /*exposedField*/ + SFInt32 r_wrist_flexion; /*exposedField*/ + SFInt32 l_wrist_pivot; /*exposedField*/ + SFInt32 r_wrist_pivot; /*exposedField*/ + SFInt32 l_wrist_twisting; /*exposedField*/ + SFInt32 r_wrist_twisting; /*exposedField*/ + SFInt32 skullbase_roll; /*exposedField*/ + SFInt32 skullbase_torsion; /*exposedField*/ + SFInt32 skullbase_tilt; /*exposedField*/ + SFInt32 vc1roll; /*exposedField*/ + SFInt32 vc1torsion; /*exposedField*/ + SFInt32 vc1tilt; /*exposedField*/ + SFInt32 vc2roll; /*exposedField*/ + SFInt32 vc2torsion; /*exposedField*/ + SFInt32 vc2tilt; /*exposedField*/ + SFInt32 vc3roll; /*exposedField*/ + SFInt32 vc3torsion; /*exposedField*/ + SFInt32 vc3tilt; /*exposedField*/ + SFInt32 vc4roll; /*exposedField*/ + SFInt32 vc4torsion; /*exposedField*/ + SFInt32 vc4tilt; /*exposedField*/ + SFInt32 vc5roll; /*exposedField*/ + SFInt32 vc5torsion; /*exposedField*/ + SFInt32 vc5tilt; /*exposedField*/ + SFInt32 vc6roll; /*exposedField*/ + SFInt32 vc6torsion; /*exposedField*/ + SFInt32 vc6tilt; /*exposedField*/ + SFInt32 vc7roll; /*exposedField*/ + SFInt32 vc7torsion; /*exposedField*/ + SFInt32 vc7tilt; /*exposedField*/ + SFInt32 vt1roll; /*exposedField*/ + SFInt32 vt1torsion; /*exposedField*/ + SFInt32 vt1tilt; /*exposedField*/ + SFInt32 vt2roll; /*exposedField*/ + SFInt32 vt2torsion; /*exposedField*/ + SFInt32 vt2tilt; /*exposedField*/ + SFInt32 vt3roll; /*exposedField*/ + SFInt32 vt3torsion; /*exposedField*/ + SFInt32 vt3tilt; /*exposedField*/ + SFInt32 vt4roll; /*exposedField*/ + SFInt32 vt4torsion; /*exposedField*/ + SFInt32 vt4tilt; /*exposedField*/ + SFInt32 vt5roll; /*exposedField*/ + SFInt32 vt5torsion; /*exposedField*/ + SFInt32 vt5tilt; /*exposedField*/ + SFInt32 vt6roll; /*exposedField*/ + SFInt32 vt6torsion; /*exposedField*/ + SFInt32 vt6tilt; /*exposedField*/ + SFInt32 vt7roll; /*exposedField*/ + SFInt32 vt7torsion; /*exposedField*/ + SFInt32 vt7tilt; /*exposedField*/ + SFInt32 vt8roll; /*exposedField*/ + SFInt32 vt8torsion; /*exposedField*/ + SFInt32 vt8tilt; /*exposedField*/ + SFInt32 vt9roll; /*exposedField*/ + SFInt32 vt9torsion; /*exposedField*/ + SFInt32 vt9tilt; /*exposedField*/ + SFInt32 vt10roll; /*exposedField*/ + SFInt32 vt10torsion; /*exposedField*/ + SFInt32 vt10tilt; /*exposedField*/ + SFInt32 vt11roll; /*exposedField*/ + SFInt32 vt11torsion; /*exposedField*/ + SFInt32 vt11tilt; /*exposedField*/ + SFInt32 vt12roll; /*exposedField*/ + SFInt32 vt12torsion; /*exposedField*/ + SFInt32 vt12tilt; /*exposedField*/ + SFInt32 vl1roll; /*exposedField*/ + SFInt32 vl1torsion; /*exposedField*/ + SFInt32 vl1tilt; /*exposedField*/ + SFInt32 vl2roll; /*exposedField*/ + SFInt32 vl2torsion; /*exposedField*/ + SFInt32 vl2tilt; /*exposedField*/ + SFInt32 vl3roll; /*exposedField*/ + SFInt32 vl3torsion; /*exposedField*/ + SFInt32 vl3tilt; /*exposedField*/ + SFInt32 vl4roll; /*exposedField*/ + SFInt32 vl4torsion; /*exposedField*/ + SFInt32 vl4tilt; /*exposedField*/ + SFInt32 vl5roll; /*exposedField*/ + SFInt32 vl5torsion; /*exposedField*/ + SFInt32 vl5tilt; /*exposedField*/ + SFInt32 l_pinky0_flexion; /*exposedField*/ + SFInt32 r_pinky0_flexion; /*exposedField*/ + SFInt32 l_pinky1_flexion; /*exposedField*/ + SFInt32 r_pinky1_flexion; /*exposedField*/ + SFInt32 l_pinky1_pivot; /*exposedField*/ + SFInt32 r_pinky1_pivot; /*exposedField*/ + SFInt32 l_pinky1_twisting; /*exposedField*/ + SFInt32 r_pinky1_twisting; /*exposedField*/ + SFInt32 l_pinky2_flexion; /*exposedField*/ + SFInt32 r_pinky2_flexion; /*exposedField*/ + SFInt32 l_pinky3_flexion; /*exposedField*/ + SFInt32 r_pinky3_flexion; /*exposedField*/ + SFInt32 l_ring0_flexion; /*exposedField*/ + SFInt32 r_ring0_flexion; /*exposedField*/ + SFInt32 l_ring1_flexion; /*exposedField*/ + SFInt32 r_ring1_flexion; /*exposedField*/ + SFInt32 l_ring1_pivot; /*exposedField*/ + SFInt32 r_ring1_pivot; /*exposedField*/ + SFInt32 l_ring1_twisting; /*exposedField*/ + SFInt32 r_ring1_twisting; /*exposedField*/ + SFInt32 l_ring2_flexion; /*exposedField*/ + SFInt32 r_ring2_flexion; /*exposedField*/ + SFInt32 l_ring3_flexion; /*exposedField*/ + SFInt32 r_ring3_flexion; /*exposedField*/ + SFInt32 l_middle0_flexion; /*exposedField*/ + SFInt32 r_middle0_flexion; /*exposedField*/ + SFInt32 l_middle1_flexion; /*exposedField*/ + SFInt32 r_middle1_flexion; /*exposedField*/ + SFInt32 l_middle1_pivot; /*exposedField*/ + SFInt32 r_middle1_pivot; /*exposedField*/ + SFInt32 l_middle1_twisting; /*exposedField*/ + SFInt32 r_middle1_twisting; /*exposedField*/ + SFInt32 l_middle2_flexion; /*exposedField*/ + SFInt32 r_middle2_flexion; /*exposedField*/ + SFInt32 l_middle3_flexion; /*exposedField*/ + SFInt32 r_middle3_flexion; /*exposedField*/ + SFInt32 l_index0_flexion; /*exposedField*/ + SFInt32 r_index0_flexion; /*exposedField*/ + SFInt32 l_index1_flexion; /*exposedField*/ + SFInt32 r_index1_flexion; /*exposedField*/ + SFInt32 l_index1_pivot; /*exposedField*/ + SFInt32 r_index1_pivot; /*exposedField*/ + SFInt32 l_index1_twisting; /*exposedField*/ + SFInt32 r_index1_twisting; /*exposedField*/ + SFInt32 l_index2_flexion; /*exposedField*/ + SFInt32 r_index2_flexion; /*exposedField*/ + SFInt32 l_index3_flexion; /*exposedField*/ + SFInt32 r_index3_flexion; /*exposedField*/ + SFInt32 l_thumb1_flexion; /*exposedField*/ + SFInt32 r_thumb1_flexion; /*exposedField*/ + SFInt32 l_thumb1_pivot; /*exposedField*/ + SFInt32 r_thumb1_pivot; /*exposedField*/ + SFInt32 l_thumb1_twisting; /*exposedField*/ + SFInt32 r_thumb1_twisting; /*exposedField*/ + SFInt32 l_thumb2_flexion; /*exposedField*/ + SFInt32 r_thumb2_flexion; /*exposedField*/ + SFInt32 l_thumb3_flexion; /*exposedField*/ + SFInt32 r_thumb3_flexion; /*exposedField*/ + SFInt32 HumanoidRoot_tr_vertical; /*exposedField*/ + SFInt32 HumanoidRoot_tr_lateral; /*exposedField*/ + SFInt32 HumanoidRoot_tr_frontal; /*exposedField*/ + SFInt32 HumanoidRoot_rt_body_turn; /*exposedField*/ + SFInt32 HumanoidRoot_rt_body_roll; /*exposedField*/ + SFInt32 HumanoidRoot_rt_body_tilt; /*exposedField*/ + SFInt32 extensionBap187; /*exposedField*/ + SFInt32 extensionBap188; /*exposedField*/ + SFInt32 extensionBap189; /*exposedField*/ + SFInt32 extensionBap190; /*exposedField*/ + SFInt32 extensionBap191; /*exposedField*/ + SFInt32 extensionBap192; /*exposedField*/ + SFInt32 extensionBap193; /*exposedField*/ + SFInt32 extensionBap194; /*exposedField*/ + SFInt32 extensionBap195; /*exposedField*/ + SFInt32 extensionBap196; /*exposedField*/ + SFInt32 extensionBap197; /*exposedField*/ + SFInt32 extensionBap198; /*exposedField*/ + SFInt32 extensionBap199; /*exposedField*/ + SFInt32 extensionBap200; /*exposedField*/ + SFInt32 extensionBap201; /*exposedField*/ + SFInt32 extensionBap202; /*exposedField*/ + SFInt32 extensionBap203; /*exposedField*/ + SFInt32 extensionBap204; /*exposedField*/ + SFInt32 extensionBap205; /*exposedField*/ + SFInt32 extensionBap206; /*exposedField*/ + SFInt32 extensionBap207; /*exposedField*/ + SFInt32 extensionBap208; /*exposedField*/ + SFInt32 extensionBap209; /*exposedField*/ + SFInt32 extensionBap210; /*exposedField*/ + SFInt32 extensionBap211; /*exposedField*/ + SFInt32 extensionBap212; /*exposedField*/ + SFInt32 extensionBap213; /*exposedField*/ + SFInt32 extensionBap214; /*exposedField*/ + SFInt32 extensionBap215; /*exposedField*/ + SFInt32 extensionBap216; /*exposedField*/ + SFInt32 extensionBap217; /*exposedField*/ + SFInt32 extensionBap218; /*exposedField*/ + SFInt32 extensionBap219; /*exposedField*/ + SFInt32 extensionBap220; /*exposedField*/ + SFInt32 extensionBap221; /*exposedField*/ + SFInt32 extensionBap222; /*exposedField*/ + SFInt32 extensionBap223; /*exposedField*/ + SFInt32 extensionBap224; /*exposedField*/ + SFInt32 extensionBap225; /*exposedField*/ + SFInt32 extensionBap226; /*exposedField*/ + SFInt32 extensionBap227; /*exposedField*/ + SFInt32 extensionBap228; /*exposedField*/ + SFInt32 extensionBap229; /*exposedField*/ + SFInt32 extensionBap230; /*exposedField*/ + SFInt32 extensionBap231; /*exposedField*/ + SFInt32 extensionBap232; /*exposedField*/ + SFInt32 extensionBap233; /*exposedField*/ + SFInt32 extensionBap234; /*exposedField*/ + SFInt32 extensionBap235; /*exposedField*/ + SFInt32 extensionBap236; /*exposedField*/ + SFInt32 extensionBap237; /*exposedField*/ + SFInt32 extensionBap238; /*exposedField*/ + SFInt32 extensionBap239; /*exposedField*/ + SFInt32 extensionBap240; /*exposedField*/ + SFInt32 extensionBap241; /*exposedField*/ + SFInt32 extensionBap242; /*exposedField*/ + SFInt32 extensionBap243; /*exposedField*/ + SFInt32 extensionBap244; /*exposedField*/ + SFInt32 extensionBap245; /*exposedField*/ + SFInt32 extensionBap246; /*exposedField*/ + SFInt32 extensionBap247; /*exposedField*/ + SFInt32 extensionBap248; /*exposedField*/ + SFInt32 extensionBap249; /*exposedField*/ + SFInt32 extensionBap250; /*exposedField*/ + SFInt32 extensionBap251; /*exposedField*/ + SFInt32 extensionBap252; /*exposedField*/ + SFInt32 extensionBap253; /*exposedField*/ + SFInt32 extensionBap254; /*exposedField*/ + SFInt32 extensionBap255; /*exposedField*/ + SFInt32 extensionBap256; /*exposedField*/ + SFInt32 extensionBap257; /*exposedField*/ + SFInt32 extensionBap258; /*exposedField*/ + SFInt32 extensionBap259; /*exposedField*/ + SFInt32 extensionBap260; /*exposedField*/ + SFInt32 extensionBap261; /*exposedField*/ + SFInt32 extensionBap262; /*exposedField*/ + SFInt32 extensionBap263; /*exposedField*/ + SFInt32 extensionBap264; /*exposedField*/ + SFInt32 extensionBap265; /*exposedField*/ + SFInt32 extensionBap266; /*exposedField*/ + SFInt32 extensionBap267; /*exposedField*/ + SFInt32 extensionBap268; /*exposedField*/ + SFInt32 extensionBap269; /*exposedField*/ + SFInt32 extensionBap270; /*exposedField*/ + SFInt32 extensionBap271; /*exposedField*/ + SFInt32 extensionBap272; /*exposedField*/ + SFInt32 extensionBap273; /*exposedField*/ + SFInt32 extensionBap274; /*exposedField*/ + SFInt32 extensionBap275; /*exposedField*/ + SFInt32 extensionBap276; /*exposedField*/ + SFInt32 extensionBap277; /*exposedField*/ + SFInt32 extensionBap278; /*exposedField*/ + SFInt32 extensionBap279; /*exposedField*/ + SFInt32 extensionBap280; /*exposedField*/ + SFInt32 extensionBap281; /*exposedField*/ + SFInt32 extensionBap282; /*exposedField*/ + SFInt32 extensionBap283; /*exposedField*/ + SFInt32 extensionBap284; /*exposedField*/ + SFInt32 extensionBap285; /*exposedField*/ + SFInt32 extensionBap286; /*exposedField*/ + SFInt32 extensionBap287; /*exposedField*/ + SFInt32 extensionBap288; /*exposedField*/ + SFInt32 extensionBap289; /*exposedField*/ + SFInt32 extensionBap290; /*exposedField*/ + SFInt32 extensionBap291; /*exposedField*/ + SFInt32 extensionBap292; /*exposedField*/ + SFInt32 extensionBap293; /*exposedField*/ + SFInt32 extensionBap294; /*exposedField*/ + SFInt32 extensionBap295; /*exposedField*/ + SFInt32 extensionBap296; /*exposedField*/ +} M_BAP; + + +typedef struct _tagBDP +{ + BASE_NODE + GF_ChildNodeItem *bodyDefTables; /*exposedField*/ + GF_ChildNodeItem *bodySceneGraph; /*exposedField*/ +} M_BDP; + + +typedef struct _tagBody +{ + BASE_NODE + GF_Node *bdp; /*exposedField*/ + GF_Node *bap; /*exposedField*/ + GF_ChildNodeItem *renderedBody; /*exposedField*/ +} M_Body; + + +typedef struct _tagBodyDefTable +{ + BASE_NODE + SFString bodySceneGraphNodeName; /*exposedField*/ + MFInt32 bapIDs; /*exposedField*/ + MFInt32 vertexIds; /*exposedField*/ + MFInt32 bapCombinations; /*exposedField*/ + MFVec3f displacements; /*exposedField*/ + SFInt32 numInterpolateKeys; /*exposedField*/ +} M_BodyDefTable; + + +typedef struct _tagBodySegmentConnectionHint +{ + BASE_NODE + SFString firstSegmentNodeName; /*exposedField*/ + SFString secondSegmentNodeName; /*exposedField*/ + MFInt32 firstVertexIdList; /*exposedField*/ + MFInt32 secondVertexIdList; /*exposedField*/ +} M_BodySegmentConnectionHint; + + +typedef struct _tagDirectiveSound +{ + BASE_NODE + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + GF_Node *source; /*exposedField*/ + GF_Node *perceptualParameters; /*exposedField*/ + SFBool roomEffect; /*exposedField*/ + SFBool spatialize; /*exposedField*/ + MFFloat directivity; /*field*/ + MFFloat angles; /*field*/ + MFFloat frequency; /*field*/ + SFFloat speedOfSound; /*field*/ + SFFloat distance; /*field*/ + SFBool useAirabs; /*field*/ +} M_DirectiveSound; + + +typedef struct _tagHierarchical3DMesh +{ + BASE_NODE + SFInt32 triangleBudget; /*eventIn*/ + void (*on_triangleBudget)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat level; /*exposedField*/ + MFURL url; /*field*/ + SFBool doneLoading; /*eventOut*/ +} M_Hierarchical3DMesh; + + +typedef struct _tagMaterialKey +{ + BASE_NODE + SFBool isKeyed; /*exposedField*/ + SFBool isRGB; /*exposedField*/ + SFColor keyColor; /*exposedField*/ + SFFloat lowThreshold; /*exposedField*/ + SFFloat highThreshold; /*exposedField*/ + SFFloat transparency; /*exposedField*/ +} M_MaterialKey; + + +typedef struct _tagPerceptualParameters +{ + BASE_NODE + SFFloat sourcePresence; /*exposedField*/ + SFFloat sourceWarmth; /*exposedField*/ + SFFloat sourceBrilliance; /*exposedField*/ + SFFloat roomPresence; /*exposedField*/ + SFFloat runningReverberance; /*exposedField*/ + SFFloat envelopment; /*exposedField*/ + SFFloat lateReverberance; /*exposedField*/ + SFFloat heavyness; /*exposedField*/ + SFFloat liveness; /*exposedField*/ + MFFloat omniDirectivity; /*exposedField*/ + MFFloat directFilterGains; /*exposedField*/ + MFFloat inputFilterGains; /*exposedField*/ + SFFloat refDistance; /*exposedField*/ + SFFloat freqLow; /*exposedField*/ + SFFloat freqHigh; /*exposedField*/ + SFTime timeLimit1; /*exposedField*/ + SFTime timeLimit2; /*exposedField*/ + SFTime timeLimit3; /*exposedField*/ + SFTime modalDensity; /*exposedField*/ +} M_PerceptualParameters; + + +typedef struct _tagTemporalTransform +{ + BASE_NODE + VRML_CHILDREN + MFURL url; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime optimalDuration; /*exposedField*/ + SFBool active; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFVec2f scalability; /*exposedField*/ + MFInt32 stretchMode; /*exposedField*/ + MFInt32 shrinkMode; /*exposedField*/ + SFTime maxDelay; /*exposedField*/ + SFTime actualDuration; /*eventOut*/ +} M_TemporalTransform; + + +typedef struct _tagTemporalGroup +{ + BASE_NODE + VRML_CHILDREN + SFBool costart; /*field*/ + SFBool coend; /*field*/ + SFBool meet; /*field*/ + MFFloat priority; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFInt32 activeChild; /*eventOut*/ +} M_TemporalGroup; + + +typedef struct _tagServerCommand +{ + BASE_NODE + SFBool trigger; /*eventIn*/ + void (*on_trigger)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool enable; /*exposedField*/ + MFURL url; /*exposedField*/ + SFString command; /*exposedField*/ +} M_ServerCommand; + + +typedef struct _tagInputSensor +{ + BASE_NODE + SFBool enabled; /*exposedField*/ + SFCommandBuffer buffer; /*exposedField*/ + MFURL url; /*exposedField*/ + SFTime eventTime; /*eventOut*/ +} M_InputSensor; + + +typedef struct _tagMatteTexture +{ + BASE_NODE + GF_Node *surfaceA; /*field*/ + GF_Node *surfaceB; /*field*/ + GF_Node *alphaSurface; /*field*/ + SFString operation; /*exposedField*/ + SFBool overwrite; /*field*/ + SFFloat fraction; /*exposedField*/ + MFFloat parameter; /*exposedField*/ +} M_MatteTexture; + + +typedef struct _tagMediaBuffer +{ + BASE_NODE + SFFloat bufferSize; /*exposedField*/ + MFURL url; /*exposedField*/ + SFTime mediaStartTime; /*exposedField*/ + SFTime mediaStopTime; /*exposedField*/ + SFBool isBuffered; /*eventOut*/ + SFBool enabled; /*exposedField*/ +} M_MediaBuffer; + + +typedef struct _tagMediaControl +{ + BASE_NODE + MFURL url; /*exposedField*/ + SFTime mediaStartTime; /*exposedField*/ + SFTime mediaStopTime; /*exposedField*/ + SFFloat mediaSpeed; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFBool preRoll; /*exposedField*/ + SFBool mute; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFBool isPreRolled; /*eventOut*/ +} M_MediaControl; + + +typedef struct _tagMediaSensor +{ + BASE_NODE + MFURL url; /*exposedField*/ + SFTime mediaCurrentTime; /*eventOut*/ + SFTime streamObjectStartTime; /*eventOut*/ + SFTime mediaDuration; /*eventOut*/ + SFBool isActive; /*eventOut*/ + MFString info; /*eventOut*/ +} M_MediaSensor; + + +typedef struct _tagBitWrapper +{ + BASE_NODE + GF_Node *node; /*field*/ + SFInt32 type; /*field*/ + MFURL url; /*field*/ + SFString buffer; /*field*/ + /*GPAC private*/ + u32 buffer_len; +} M_BitWrapper; + + +typedef struct _tagCoordinateInterpolator4D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec4f keyValue; /*exposedField*/ + MFVec4f value_changed; /*eventOut*/ +} M_CoordinateInterpolator4D; + + +typedef struct _tagDepthImage +{ + BASE_NODE + GF_Node *diTexture; /*field*/ + SFFloat farPlane; /*field*/ + SFVec2f fieldOfView; /*field*/ + SFFloat nearPlane; /*field*/ + SFRotation orientation; /*field*/ + SFBool orthographic; /*field*/ + SFVec3f position; /*field*/ +} M_DepthImage; + + +typedef struct _tagFFD +{ + BASE_NODE + VRML_CHILDREN + MFVec4f controlPoint; /*exposedField*/ + SFInt32 uDimension; /*field*/ + MFFloat uKnot; /*field*/ + SFInt32 uOrder; /*field*/ + SFInt32 vDimension; /*field*/ + MFFloat vKnot; /*field*/ + SFInt32 vOrder; /*field*/ + SFInt32 wDimension; /*field*/ + MFFloat wKnot; /*field*/ + SFInt32 wOrder; /*field*/ +} M_FFD; + + +typedef struct _tagImplicit +{ + BASE_NODE + SFVec3f bboxSize; /*exposedField*/ + MFFloat c; /*exposedField*/ + MFInt32 densities; /*exposedField*/ + SFBool dual; /*exposedField*/ + SFBool solid; /*exposedField*/ +} M_Implicit; + + +typedef struct _tagXXLFM_Appearance +{ + BASE_NODE + GF_Node *blendList; /*exposedField*/ + GF_ChildNodeItem *lightMapList; /*exposedField*/ + GF_ChildNodeItem *tileList; /*exposedField*/ + GF_Node *vertexFrameList; /*exposedField*/ +} M_XXLFM_Appearance; + + +typedef struct _tagXXLFM_BlendList +{ + BASE_NODE + MFInt32 blendMode; /*exposedField*/ + MFInt32 lightMapIndex; /*exposedField*/ +} M_XXLFM_BlendList; + + +typedef struct _tagXXLFM_FrameList +{ + BASE_NODE + MFInt32 index; /*exposedField*/ + MFVec3f frame; /*exposedField*/ +} M_XXLFM_FrameList; + + +typedef struct _tagXXLFM_LightMap +{ + BASE_NODE + SFVec3f biasRGB; /*exposedField*/ + SFInt32 priorityLevel; /*exposedField*/ + SFVec3f scaleRGB; /*exposedField*/ + GF_Node *surfaceMapList; /*exposedField*/ + GF_Node *viewMapList; /*exposedField*/ +} M_XXLFM_LightMap; + + +typedef struct _tagXXLFM_SurfaceMapList +{ + BASE_NODE + MFInt32 tileIndex; /*exposedField*/ + GF_Node *triangleCoordinate; /*exposedField*/ + MFInt32 triangleIndex; /*exposedField*/ + MFInt32 viewMapIndex; /*exposedField*/ +} M_XXLFM_SurfaceMapList; + + +typedef struct _tagXXLFM_ViewMapList +{ + BASE_NODE + GF_Node *textureOrigin; /*exposedField*/ + GF_Node *textureSize; /*exposedField*/ + MFInt32 tileIndex; /*exposedField*/ + MFInt32 vertexIndex; /*exposedField*/ +} M_XXLFM_ViewMapList; + + +typedef struct _tagMeshGrid +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_normalIndex; /*eventIn*/ + void (*on_set_normalIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_texCoordIndex; /*eventIn*/ + void (*on_set_texCoordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + SFInt32 displayLevel; /*exposedField*/ + SFInt32 filterType; /*exposedField*/ + GF_Node *gridCoord; /*exposedField*/ + SFInt32 hierarchicalLevel; /*exposedField*/ + MFInt32 nLevels; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + MFInt32 nSlices; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + MFFloat vertexOffset; /*exposedField*/ + MFInt32 vertexLink; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + MFInt32 coordIndex; /*field*/ + MFInt32 normalIndex; /*field*/ + SFBool solid; /*field*/ + MFInt32 texCoordIndex; /*field*/ + SFBool isLoading; /*eventOut*/ + MFInt32 nVertices; /*eventOut*/ +} M_MeshGrid; + + +typedef struct _tagNonLinearDeformer +{ + BASE_NODE + SFVec3f axis; /*exposedField*/ + MFFloat extend; /*exposedField*/ + GF_Node *geometry; /*exposedField*/ + SFFloat param; /*exposedField*/ + SFInt32 type; /*exposedField*/ +} M_NonLinearDeformer; + + +typedef struct _tagNurbsCurve +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + MFVec4f controlPoint; /*exposedField*/ + SFInt32 tessellation; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + MFFloat knot; /*field*/ + SFInt32 order; /*field*/ +} M_NurbsCurve; + + +typedef struct _tagNurbsCurve2D +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + MFVec3f controlPoint; /*exposedField*/ + SFInt32 tessellation; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + MFFloat knot; /*field*/ + SFInt32 order; /*field*/ +} M_NurbsCurve2D; + + +typedef struct _tagNurbsSurface +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_texColorIndex; /*eventIn*/ + void (*on_set_texColorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + MFVec4f controlPoint; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFInt32 uTessellation; /*exposedField*/ + SFInt32 vTessellation; /*exposedField*/ + SFBool ccw; /*field*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool solid; /*field*/ + MFInt32 texColorIndex; /*field*/ + SFInt32 uDimension; /*field*/ + MFFloat uKnot; /*field*/ + SFInt32 uOrder; /*field*/ + SFInt32 vDimension; /*field*/ + MFFloat vKnot; /*field*/ + SFInt32 vOrder; /*field*/ +} M_NurbsSurface; + + +typedef struct _tagOctreeImage +{ + BASE_NODE + GF_ChildNodeItem *images; /*field*/ + MFInt32 octree; /*field*/ + SFInt32 octreeResolution; /*field*/ + MFInt32 voxelImageIndex; /*field*/ +} M_OctreeImage; + + +typedef struct _tagXXParticles +{ + BASE_NODE + SFFloat creationRate; /*exposedField*/ + SFFloat creationRateVariation; /*exposedField*/ + SFFloat emitAlpha; /*exposedField*/ + SFColor emitColor; /*exposedField*/ + SFColor emitColorVariation; /*exposedField*/ + SFVec3f emitterPosition; /*exposedField*/ + SFVec3f emitVelocity; /*exposedField*/ + SFVec3f emitVelocityVariation; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFFloat fadeAlpha; /*exposedField*/ + SFColor fadeColor; /*exposedField*/ + SFFloat fadeRate; /*exposedField*/ + SFVec3f force; /*exposedField*/ + GF_ChildNodeItem *influences; /*exposedField*/ + GF_Node *init; /*exposedField*/ + SFTime maxLifeTime; /*exposedField*/ + SFFloat maxLifeTimeVariation; /*exposedField*/ + SFInt32 maxParticles; /*exposedField*/ + SFFloat minRange; /*exposedField*/ + SFFloat maxRange; /*exposedField*/ + GF_Node *primitive; /*exposedField*/ + SFInt32 primitiveType; /*exposedField*/ + SFFloat particleRadius; /*exposedField*/ + SFFloat particleRadiusRate; /*exposedField*/ + SFFloat particleRadiusVariation; /*exposedField*/ +} M_XXParticles; + + +typedef struct _tagXXParticleInitBox +{ + BASE_NODE + SFFloat falloff; /*exposedField*/ + SFVec3f size; /*exposedField*/ +} M_XXParticleInitBox; + + +typedef struct _tagXXPlanarObstacle +{ + BASE_NODE + SFVec3f distance; /*exposedField*/ + SFVec3f normal; /*exposedField*/ + SFFloat reflection; /*exposedField*/ + SFFloat absorption; /*exposedField*/ +} M_XXPlanarObstacle; + + +typedef struct _tagXXPointAttractor +{ + BASE_NODE + SFFloat innerRadius; /*exposedField*/ + SFFloat outerRadius; /*exposedField*/ + SFVec3f position; /*exposedField*/ + SFFloat rate; /*exposedField*/ +} M_XXPointAttractor; + + +typedef struct _tagPointTexture +{ + BASE_NODE + MFColor color; /*field*/ + MFInt32 depth; /*field*/ + SFInt32 depthNbBits; /*field*/ + SFInt32 height; /*field*/ + SFInt32 width; /*field*/ +} M_PointTexture; + + +typedef struct _tagPositionAnimator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec2f fromTo; /*exposedField*/ + MFFloat key; /*exposedField*/ + MFRotation keyOrientation; /*exposedField*/ + SFInt32 keyType; /*exposedField*/ + MFVec2f keySpline; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + SFInt32 keyValueType; /*exposedField*/ + SFVec3f offset; /*exposedField*/ + MFFloat weight; /*exposedField*/ + SFVec3f endValue; /*eventOut*/ + SFRotation rotation_changed; /*eventOut*/ + SFVec3f value_changed; /*eventOut*/ +} M_PositionAnimator; + + +typedef struct _tagPositionAnimator2D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec2f fromTo; /*exposedField*/ + MFFloat key; /*exposedField*/ + SFInt32 keyOrientation; /*exposedField*/ + SFInt32 keyType; /*exposedField*/ + MFVec2f keySpline; /*exposedField*/ + MFVec2f keyValue; /*exposedField*/ + SFInt32 keyValueType; /*exposedField*/ + SFVec2f offset; /*exposedField*/ + MFFloat weight; /*exposedField*/ + SFVec2f endValue; /*eventOut*/ + SFFloat rotation_changed; /*eventOut*/ + SFVec2f value_changed; /*eventOut*/ +} M_PositionAnimator2D; + + +typedef struct _tagPositionInterpolator4D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec4f keyValue; /*exposedField*/ + SFVec4f value_changed; /*eventOut*/ +} M_PositionInterpolator4D; + + +typedef struct _tagProceduralTexture +{ + BASE_NODE + SFBool aSmooth; /*exposedField*/ + MFVec2f aWarpmap; /*exposedField*/ + MFFloat aWeights; /*exposedField*/ + SFBool bSmooth; /*exposedField*/ + MFVec2f bWarpmap; /*exposedField*/ + MFFloat bWeights; /*exposedField*/ + SFInt32 cellWidth; /*exposedField*/ + SFInt32 cellHeight; /*exposedField*/ + MFColor color; /*exposedField*/ + SFFloat distortion; /*exposedField*/ + SFInt32 height; /*exposedField*/ + SFInt32 roughness; /*exposedField*/ + SFInt32 seed; /*exposedField*/ + SFInt32 type; /*exposedField*/ + SFBool xSmooth; /*exposedField*/ + MFVec2f xWarpmap; /*exposedField*/ + SFBool ySmooth; /*exposedField*/ + MFVec2f yWarpmap; /*exposedField*/ + SFInt32 width; /*exposedField*/ + SFImage image_changed; /*eventOut*/ +} M_ProceduralTexture; + + +typedef struct _tagQuadric +{ + BASE_NODE + SFVec3f bboxSize; /*exposedField*/ + MFInt32 densities; /*exposedField*/ + SFBool dual; /*exposedField*/ + SFVec4f P0; /*exposedField*/ + SFVec4f P1; /*exposedField*/ + SFVec4f P2; /*exposedField*/ + SFVec4f P3; /*exposedField*/ + SFVec4f P4; /*exposedField*/ + SFVec4f P5; /*exposedField*/ + SFBool solid; /*exposedField*/ +} M_Quadric; + + +typedef struct _tagSBBone +{ + BASE_NODE + VRML_CHILDREN + SFInt32 boneID; /*exposedField*/ + SFVec3f center; /*exposedField*/ + SFVec3f endpoint; /*exposedField*/ + SFInt32 falloff; /*exposedField*/ + SFInt32 ikChainPosition; /*exposedField*/ + MFFloat ikPitchLimit; /*exposedField*/ + MFFloat ikRollLimit; /*exposedField*/ + MFFloat ikTxLimit; /*exposedField*/ + MFFloat ikTyLimit; /*exposedField*/ + MFFloat ikTzLimit; /*exposedField*/ + MFFloat ikYawLimit; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFInt32 rotationOrder; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + MFFloat sectionInner; /*exposedField*/ + MFFloat sectionOuter; /*exposedField*/ + MFFloat sectionPosition; /*exposedField*/ + MFInt32 skinCoordIndex; /*exposedField*/ + MFFloat skinCoordWeight; /*exposedField*/ + SFVec3f translation; /*exposedField*/ +} M_SBBone; + + +typedef struct _tagSBMuscle +{ + BASE_NODE + SFInt32 falloff; /*exposedField*/ + GF_Node *muscleCurve; /*exposedField*/ + SFInt32 muscleID; /*exposedField*/ + SFInt32 radius; /*exposedField*/ + MFInt32 skinCoordIndex; /*exposedField*/ + MFFloat skinCoordWeight; /*exposedField*/ +} M_SBMuscle; + + +typedef struct _tagSBSegment +{ + BASE_NODE + VRML_CHILDREN + SFVec3f centerOfMass; /*exposedField*/ + SFFloat mass; /*exposedField*/ + MFVec3f momentsOfInertia; /*exposedField*/ + SFString name; /*exposedField*/ +} M_SBSegment; + + +typedef struct _tagSBSite +{ + BASE_NODE + VRML_CHILDREN + SFVec3f center; /*exposedField*/ + SFString name; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + SFVec3f translation; /*exposedField*/ +} M_SBSite; + + +typedef struct _tagSBSkinnedModel +{ + BASE_NODE + GF_ChildNodeItem *bones; /*exposedField*/ + SFVec3f center; /*exposedField*/ + GF_ChildNodeItem *muscles; /*exposedField*/ + SFString name; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + GF_ChildNodeItem *segments; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + GF_ChildNodeItem *sites; /*exposedField*/ + GF_ChildNodeItem *skeleton; /*exposedField*/ + GF_ChildNodeItem *skin; /*exposedField*/ + GF_Node *skinCoord; /*exposedField*/ + GF_Node *skinNormal; /*exposedField*/ + SFVec3f translation; /*exposedField*/ + GF_Node *weighsComputationSkinCoord; /*exposedField*/ +} M_SBSkinnedModel; + + +typedef struct _tagSBVCAnimation +{ + BASE_NODE + MFURL url; /*exposedField*/ + GF_ChildNodeItem *virtualCharacters; /*exposedField*/ +} M_SBVCAnimation; + + +typedef struct _tagScalarAnimator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec2f fromTo; /*exposedField*/ + MFFloat key; /*exposedField*/ + SFInt32 keyType; /*exposedField*/ + MFVec2f keySpline; /*exposedField*/ + MFFloat keyValue; /*exposedField*/ + SFInt32 keyValueType; /*exposedField*/ + SFFloat offset; /*exposedField*/ + MFFloat weight; /*exposedField*/ + SFFloat endValue; /*eventOut*/ + SFFloat value_changed; /*eventOut*/ +} M_ScalarAnimator; + + +typedef struct _tagSimpleTexture +{ + BASE_NODE + GF_Node *depth; /*field*/ + GF_Node *texture; /*field*/ +} M_SimpleTexture; + + +typedef struct _tagSolidRep +{ + BASE_NODE + SFVec3f bboxSize; /*exposedField*/ + MFInt32 densityList; /*exposedField*/ + GF_Node *solidTree; /*exposedField*/ +} M_SolidRep; + + +typedef struct _tagSubdivisionSurface +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_cornerVertexIndex; /*eventIn*/ + void (*on_set_cornerVertexIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_creaseEdgeIndex; /*eventIn*/ + void (*on_set_creaseEdgeIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_creaseVertexIndex; /*eventIn*/ + void (*on_set_creaseVertexIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_dartVertexIndex; /*eventIn*/ + void (*on_set_dartVertexIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_texCoordIndex; /*eventIn*/ + void (*on_set_texCoordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + GF_ChildNodeItem *sectors; /*exposedField*/ + SFInt32 subdivisionLevel; /*exposedField*/ + SFInt32 subdivisionType; /*exposedField*/ + SFInt32 subdivisionSubType; /*exposedField*/ + MFInt32 invisibleEdgeIndex; /*field*/ + SFBool ccw; /*field*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool convex; /*field*/ + MFInt32 coordIndex; /*field*/ + MFInt32 cornerVertexIndex; /*field*/ + MFInt32 creaseEdgeIndex; /*field*/ + MFInt32 creaseVertexIndex; /*field*/ + MFInt32 dartVertexIndex; /*field*/ + SFBool solid; /*field*/ + MFInt32 texCoordIndex; /*field*/ +} M_SubdivisionSurface; + + +typedef struct _tagSubdivSurfaceSector +{ + BASE_NODE + SFFloat flatness; /*exposedField*/ + SFVec3f normal; /*exposedField*/ + SFFloat normalTension; /*exposedField*/ + SFInt32 _tag; /*exposedField*/ + SFFloat theta; /*exposedField*/ + SFInt32 faceIndex; /*field*/ + SFInt32 vertexIndex; /*field*/ +} M_SubdivSurfaceSector; + + +typedef struct _tagWaveletSubdivisionSurface +{ + BASE_NODE + GF_Node *baseMesh; /*exposedField*/ + SFFloat fieldOfView; /*exposedField*/ + SFFloat frequency; /*exposedField*/ + SFInt32 quality; /*exposedField*/ +} M_WaveletSubdivisionSurface; + + +typedef struct _tagClipper2D +{ + BASE_NODE + VRML_CHILDREN + GF_Node *geometry; /*exposedField*/ + SFBool inside; /*exposedField*/ + GF_Node *transform; /*exposedField*/ + SFBool XOR; /*exposedField*/ +} M_Clipper2D; + + +typedef struct _tagColorTransform +{ + BASE_NODE + VRML_CHILDREN + SFFloat mrr; /*exposedField*/ + SFFloat mrg; /*exposedField*/ + SFFloat mrb; /*exposedField*/ + SFFloat mra; /*exposedField*/ + SFFloat tr; /*exposedField*/ + SFFloat mgr; /*exposedField*/ + SFFloat mgg; /*exposedField*/ + SFFloat mgb; /*exposedField*/ + SFFloat mga; /*exposedField*/ + SFFloat tg; /*exposedField*/ + SFFloat mbr; /*exposedField*/ + SFFloat mbg; /*exposedField*/ + SFFloat mbb; /*exposedField*/ + SFFloat mba; /*exposedField*/ + SFFloat tb; /*exposedField*/ + SFFloat mar; /*exposedField*/ + SFFloat mag; /*exposedField*/ + SFFloat mab; /*exposedField*/ + SFFloat maa; /*exposedField*/ + SFFloat ta; /*exposedField*/ +} M_ColorTransform; + + +typedef struct _tagEllipse +{ + BASE_NODE + SFVec2f radius; /*exposedField*/ +} M_Ellipse; + + +typedef struct _tagLinearGradient +{ + BASE_NODE + SFVec2f endPoint; /*exposedField*/ + MFFloat key; /*exposedField*/ + MFColor keyValue; /*exposedField*/ + MFFloat opacity; /*exposedField*/ + SFInt32 spreadMethod; /*exposedField*/ + SFVec2f startPoint; /*exposedField*/ + GF_Node *transform; /*exposedField*/ +} M_LinearGradient; + + +typedef struct _tagPathLayout +{ + BASE_NODE + VRML_CHILDREN + GF_Node *geometry; /*exposedField*/ + MFInt32 alignment; /*exposedField*/ + SFFloat pathOffset; /*exposedField*/ + SFFloat spacing; /*exposedField*/ + SFBool reverseLayout; /*exposedField*/ + SFInt32 wrapMode; /*exposedField*/ + SFBool splitText; /*exposedField*/ +} M_PathLayout; + + +typedef struct _tagRadialGradient +{ + BASE_NODE + SFVec2f center; /*exposedField*/ + SFVec2f focalPoint; /*exposedField*/ + MFFloat key; /*exposedField*/ + MFColor keyValue; /*exposedField*/ + MFFloat opacity; /*exposedField*/ + SFFloat radius; /*exposedField*/ + SFInt32 spreadMethod; /*exposedField*/ + GF_Node *transform; /*exposedField*/ +} M_RadialGradient; + + +typedef struct _tagSynthesizedTexture +{ + BASE_NODE + MFVec3f translation; /*exposedField*/ + MFRotation rotation; /*exposedField*/ + SFInt32 pixelWidth; /*exposedField*/ + SFInt32 pixelHeight; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + MFURL url; /*exposedField*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_SynthesizedTexture; + + +typedef struct _tagTransformMatrix2D +{ + BASE_NODE + VRML_CHILDREN + SFFloat mxx; /*exposedField*/ + SFFloat mxy; /*exposedField*/ + SFFloat tx; /*exposedField*/ + SFFloat myx; /*exposedField*/ + SFFloat myy; /*exposedField*/ + SFFloat ty; /*exposedField*/ +} M_TransformMatrix2D; + + +typedef struct _tagViewport +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec2f position; /*exposedField*/ + SFVec2f size; /*exposedField*/ + SFFloat orientation; /*exposedField*/ + MFInt32 alignment; /*exposedField*/ + SFInt32 fit; /*exposedField*/ + SFString description; /*field*/ + SFTime bindTime; /*eventOut*/ + SFBool isBound; /*eventOut*/ +} M_Viewport; + + +typedef struct _tagXCurve2D +{ + BASE_NODE + GF_Node *point; /*exposedField*/ + SFFloat fineness; /*exposedField*/ + MFInt32 type; /*exposedField*/ +} M_XCurve2D; + + +typedef struct _tagXFontStyle +{ + BASE_NODE + MFString fontName; /*exposedField*/ + SFBool horizontal; /*exposedField*/ + MFString justify; /*exposedField*/ + SFString language; /*exposedField*/ + SFBool leftToRight; /*exposedField*/ + SFFloat size; /*exposedField*/ + SFString stretch; /*exposedField*/ + SFFloat letterSpacing; /*exposedField*/ + SFFloat wordSpacing; /*exposedField*/ + SFInt32 weight; /*exposedField*/ + SFBool fontKerning; /*exposedField*/ + SFString style; /*exposedField*/ + SFBool topToBottom; /*exposedField*/ + MFString featureName; /*exposedField*/ + MFInt32 featureStartOffset; /*exposedField*/ + MFInt32 featureLength; /*exposedField*/ + MFInt32 featureValue; /*exposedField*/ +} M_XFontStyle; + + +typedef struct _tagXLineProperties +{ + BASE_NODE + SFColor lineColor; /*exposedField*/ + SFInt32 lineStyle; /*exposedField*/ + SFBool isCenterAligned; /*exposedField*/ + SFBool isScalable; /*exposedField*/ + SFInt32 lineCap; /*exposedField*/ + SFInt32 lineJoin; /*exposedField*/ + SFFloat miterLimit; /*exposedField*/ + SFFloat transparency; /*exposedField*/ + SFFloat width; /*exposedField*/ + SFFloat dashOffset; /*exposedField*/ + MFFloat dashes; /*exposedField*/ + GF_Node *texture; /*exposedField*/ + GF_Node *textureTransform; /*exposedField*/ +} M_XLineProperties; + + +typedef struct _tagAdvancedAudioBuffer +{ + BASE_NODE + VRML_CHILDREN + SFBool loop; /*exposedField*/ + SFFloat pitch; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFTime startLoadTime; /*exposedField*/ + SFTime stopLoadTime; /*exposedField*/ + SFInt32 loadMode; /*exposedField*/ + SFInt32 numAccumulatedBlocks; /*exposedField*/ + SFInt32 deleteBlock; /*exposedField*/ + SFInt32 playBlock; /*exposedField*/ + SFFloat length; /*exposedField*/ + SFInt32 numChan; /*field*/ + MFInt32 phaseGroup; /*field*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_AdvancedAudioBuffer; + + +typedef struct _tagAudioChannelConfig +{ + BASE_NODE + VRML_CHILDREN + SFInt32 generalChannelFormat; /*exposedField*/ + SFInt32 fixedPreset; /*exposedField*/ + SFInt32 fixedPresetSubset; /*exposedField*/ + SFInt32 fixedPresetAddInf; /*exposedField*/ + MFInt32 channelCoordinateSystems; /*exposedField*/ + MFFloat channelSoundLocation; /*exposedField*/ + MFInt32 channelDirectionalPattern; /*exposedField*/ + MFVec3f channelDirection; /*exposedField*/ + SFInt32 ambResolution2D; /*exposedField*/ + SFInt32 ambResolution3D; /*exposedField*/ + SFInt32 ambEncodingConvention; /*exposedField*/ + SFFloat ambNfcReferenceDistance; /*exposedField*/ + SFFloat ambSoundSpeed; /*exposedField*/ + SFInt32 ambArrangementRule; /*exposedField*/ + SFInt32 ambRecombinationPreset; /*exposedField*/ + MFInt32 ambComponentIndex; /*exposedField*/ + MFFloat ambBackwardMatrix; /*exposedField*/ + MFInt32 ambSoundfieldResolution; /*exposedField*/ + SFInt32 numChannel; /*field*/ +} M_AudioChannelConfig; + + +typedef struct _tagDepthImageV2 +{ + BASE_NODE + GF_Node *diTexture; /*field*/ + SFFloat farPlane; /*field*/ + SFVec2f fieldOfView; /*field*/ + SFFloat nearPlane; /*field*/ + SFRotation orientation; /*field*/ + SFBool orthographic; /*field*/ + SFVec3f position; /*field*/ + SFVec2f splatMinMax; /*field*/ +} M_DepthImageV2; + + +typedef struct _tagMorphShape +{ + BASE_NODE + GF_Node *baseShape; /*exposedField*/ + SFInt32 morphID; /*exposedField*/ + GF_ChildNodeItem *targetShapes; /*exposedField*/ + MFFloat weights; /*exposedField*/ +} M_MorphShape; + + +typedef struct _tagMultiTexture +{ + BASE_NODE + SFFloat alpha; /*exposedField*/ + SFColor color; /*exposedField*/ + MFInt32 function; /*exposedField*/ + MFInt32 mode; /*exposedField*/ + MFInt32 source; /*exposedField*/ + GF_ChildNodeItem *texture; /*exposedField*/ + MFVec3f cameraVector; /*exposedField*/ + SFBool transparent; /*exposedField*/ +} M_MultiTexture; + + +typedef struct _tagPointTextureV2 +{ + BASE_NODE + MFColor color; /*field*/ + MFInt32 depth; /*field*/ + SFInt32 depthNbBits; /*field*/ + SFInt32 height; /*field*/ + GF_Node *normal; /*field*/ + MFVec3f splatU; /*field*/ + MFVec3f splatV; /*field*/ + SFInt32 width; /*field*/ +} M_PointTextureV2; + + +typedef struct _tagSBVCAnimationV2 +{ + BASE_NODE + MFInt32 activeUrlIndex; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFFloat transitionTime; /*exposedField*/ + MFURL url; /*exposedField*/ + GF_ChildNodeItem *virtualCharacters; /*exposedField*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ +} M_SBVCAnimationV2; + + +typedef struct _tagSimpleTextureV2 +{ + BASE_NODE + GF_Node *depth; /*field*/ + GF_Node *normal; /*field*/ + GF_Node *splatU; /*field*/ + GF_Node *splatV; /*field*/ + GF_Node *texture; /*field*/ +} M_SimpleTextureV2; + + +typedef struct _tagSurroundingSound +{ + BASE_NODE + GF_Node *source; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFFloat distance; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFFloat distortionFactor; /*exposedField*/ + SFRotation orientation; /*exposedField*/ + SFBool isTransformable; /*exposedField*/ +} M_SurroundingSound; + + +typedef struct _tagTransform3DAudio +{ + BASE_NODE + VRML_CHILDREN + SFFloat thirdCenterCoordinate; /*exposedField*/ + SFVec3f rotationVector; /*exposedField*/ + SFFloat thirdScaleCoordinate; /*exposedField*/ + SFVec3f scaleOrientationVector; /*exposedField*/ + SFFloat thirdTranslationCoordinate; /*exposedField*/ + SFRotation coordinateTransform; /*exposedField*/ +} M_Transform3DAudio; + + +typedef struct _tagWideSound +{ + BASE_NODE + GF_Node *source; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFBool spatialize; /*exposedField*/ + GF_Node *perceptualParameters; /*exposedField*/ + SFBool roomEffect; /*exposedField*/ + SFInt32 shape; /*exposedField*/ + MFFloat size; /*exposedField*/ + SFVec3f direction; /*exposedField*/ + SFFloat density; /*exposedField*/ + SFInt32 diffuseSelect; /*exposedField*/ + SFFloat decorrStrength; /*exposedField*/ + SFFloat speedOfSound; /*field*/ + SFFloat distance; /*field*/ + SFBool useAirabs; /*field*/ +} M_WideSound; + + +typedef struct _tagScoreShape +{ + BASE_NODE + GF_Node *score; /*exposedField*/ + GF_Node *geometry; /*exposedField*/ +} M_ScoreShape; + + +typedef struct _tagMusicScore +{ + BASE_NODE + SFBool executeCommand; /*eventIn*/ + void (*on_executeCommand)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFString gotoLabel; /*eventIn*/ + void (*on_gotoLabel)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFInt32 gotoMeasure; /*eventIn*/ + void (*on_gotoMeasure)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFTime highlightTimePosition; /*eventIn*/ + void (*on_highlightTimePosition)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFVec3f mousePosition; /*eventIn*/ + void (*on_mousePosition)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFString argumentsOnExecute; /*exposedField*/ + SFString commandOnExecute; /*exposedField*/ + SFInt32 firstVisibleMeasure; /*exposedField*/ + SFBool hyperlinkEnable; /*exposedField*/ + SFBool loop; /*exposedField*/ + MFString partsLyrics; /*exposedField*/ + MFInt32 partsShown; /*exposedField*/ + SFTime scoreOffset; /*exposedField*/ + SFVec2f size; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFFloat transpose; /*exposedField*/ + MFURL url; /*exposedField*/ + MFURL urlSA; /*exposedField*/ + SFString viewType; /*exposedField*/ + SFString activatedLink; /*eventOut*/ + MFString availableCommands; /*eventOut*/ + MFString availableLabels; /*eventOut*/ + MFString availableLyricLanguages; /*eventOut*/ + MFString availableViewTypes; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFVec3f highlightPosition; /*eventOut*/ + SFInt32 lastVisibleMeasure; /*eventOut*/ + SFInt32 numMeasures; /*eventOut*/ + MFString partNames; /*eventOut*/ +} M_MusicScore; + + +typedef struct _tagFootPrintSetNode +{ + BASE_NODE + VRML_CHILDREN +} M_FootPrintSetNode; + + +typedef struct _tagFootPrintNode +{ + BASE_NODE + SFInt32 index; /*exposedField*/ + GF_Node *footprint; /*exposedField*/ +} M_FootPrintNode; + + +typedef struct _tagBuildingPartNode +{ + BASE_NODE + SFInt32 index; /*exposedField*/ + GF_Node *footprint; /*exposedField*/ + SFInt32 buildingIndex; /*exposedField*/ + SFFloat height; /*exposedField*/ + SFFloat altitude; /*exposedField*/ + GF_ChildNodeItem *alternativeGeometry; /*exposedField*/ + GF_ChildNodeItem *roofs; /*exposedField*/ + GF_ChildNodeItem *facades; /*exposedField*/ +} M_BuildingPartNode; + + +typedef struct _tagRoofNode +{ + BASE_NODE + SFInt32 Type; /*exposedField*/ + SFFloat Height; /*exposedField*/ + MFFloat SlopeAngle; /*exposedField*/ + SFFloat EaveProjection; /*exposedField*/ + SFInt32 EdgeSupportIndex; /*exposedField*/ + SFURL RoofTextureURL; /*exposedField*/ + SFBool IsGenericTexture; /*exposedField*/ + SFFloat TextureXScale; /*exposedField*/ + SFFloat TextureYScale; /*exposedField*/ + SFFloat TextureXPosition; /*exposedField*/ + SFFloat TextureYPosition; /*exposedField*/ + SFFloat TextureRotation; /*exposedField*/ +} M_RoofNode; + + +typedef struct _tagFacadeNode +{ + BASE_NODE + SFFloat WidthRatio; /*exposedField*/ + SFFloat XScale; /*exposedField*/ + SFFloat YScale; /*exposedField*/ + SFFloat XPosition; /*exposedField*/ + SFFloat YPosition; /*exposedField*/ + SFFloat XRepeatInterval; /*exposedField*/ + SFFloat YRepeatInterval; /*exposedField*/ + SFBool Repeat; /*exposedField*/ + SFURL FacadePrimitive; /*exposedField*/ + SFInt32 NbStories; /*exposedField*/ + MFInt32 NbFacadeCellsByStorey; /*exposedField*/ + MFFloat StoreyHeight; /*exposedField*/ + GF_ChildNodeItem *FacadeCellsArray; /*exposedField*/ +} M_FacadeNode; + + +typedef struct _tagShadow +{ + BASE_NODE + VRML_CHILDREN + SFBool enabled; /*exposedField*/ + SFBool cast; /*exposedField*/ + SFBool receive; /*exposedField*/ + SFFloat penumbra; /*exposedField*/ +} M_Shadow; + + +typedef struct _tagCacheTexture +{ + BASE_NODE + SFInt32 objectTypeIndication; /*field*/ + SFString decoderSpecificInfo; /*field*/ + SFString image; /*field*/ + SFString cacheURL; /*field*/ + MFURL cacheOD; /*field*/ + SFInt32 expirationDate; /*field*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ + /*GPAC private*/ + u8 *data; + u32 data_len; +} M_CacheTexture; + + +typedef struct _tagEnvironmentTest +{ + BASE_NODE + SFBool evaluate; /*eventIn*/ + void (*on_evaluate)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool enabled; /*exposedField*/ + SFInt32 parameter; /*exposedField*/ + SFString compareValue; /*exposedField*/ + SFBool evaluateOnChange; /*exposedField*/ + SFBool valueLarger; /*eventOut*/ + SFBool valueEqual; /*eventOut*/ + SFBool valueSmaller; /*eventOut*/ + SFString parameterValue; /*eventOut*/ +} M_EnvironmentTest; + + +typedef struct _tagKeyNavigator +{ + BASE_NODE + SFBool setFocus; /*eventIn*/ + void (*on_setFocus)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *sensor; /*exposedField*/ + GF_Node *left; /*exposedField*/ + GF_Node *right; /*exposedField*/ + GF_Node *up; /*exposedField*/ + GF_Node *down; /*exposedField*/ + GF_Node *select; /*exposedField*/ + GF_Node *quit; /*exposedField*/ + SFFloat step; /*exposedField*/ + SFBool focusSet; /*eventOut*/ +} M_KeyNavigator; + + +typedef struct _tagSpacePartition +{ + BASE_NODE + VRML_CHILDREN + SFURL SPStream; /*exposedField*/ +} M_SpacePartition; + + +typedef struct _tagStorage +{ + BASE_NODE + SFBool forceSave; /*eventIn*/ + void (*on_forceSave)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool forceRestore; /*eventIn*/ + void (*on_forceRestore)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool _auto; /*exposedField*/ + SFInt32 expireAfter; /*field*/ + SFString name; /*field*/ + MFAttrRef storageList; /*field*/ +} M_Storage; + + +/*NodeDataType tags*/ +enum { + NDT_SFWorldNode = 1, + NDT_SF3DNode, + NDT_SF2DNode, + NDT_SFStreamingNode, + NDT_SFAppearanceNode, + NDT_SFAudioNode, + NDT_SFBackground3DNode, + NDT_SFBackground2DNode, + NDT_SFGeometryNode, + NDT_SFColorNode, + NDT_SFTextureNode, + NDT_SFCoordinateNode, + NDT_SFCoordinate2DNode, + NDT_SFExpressionNode, + NDT_SFFaceDefMeshNode, + NDT_SFFaceDefTablesNode, + NDT_SFFaceDefTransformNode, + NDT_SFFAPNode, + NDT_SFFDPNode, + NDT_SFFITNode, + NDT_SFFogNode, + NDT_SFFontStyleNode, + NDT_SFTopNode, + NDT_SFLinePropertiesNode, + NDT_SFMaterialNode, + NDT_SFNavigationInfoNode, + NDT_SFNormalNode, + NDT_SFTextureCoordinateNode, + NDT_SFTextureTransformNode, + NDT_SFViewpointNode, + NDT_SFVisemeNode, + NDT_SFViewportNode, + NDT_SFBAPNode, + NDT_SFBDPNode, + NDT_SFBodyDefTableNode, + NDT_SFBodySegmentConnectionHintNode, + NDT_SFPerceptualParameterNode, + NDT_SFTemporalNode, + NDT_SFDepthImageNode, + NDT_SFBlendListNode, + NDT_SFFrameListNode, + NDT_SFLightMapNode, + NDT_SFSurfaceMapNode, + NDT_SFViewMapNode, + NDT_SFParticleInitializerNode, + NDT_SFInfluenceNode, + NDT_SFDepthTextureNode, + NDT_SFSBBoneNode, + NDT_SFSBMuscleNode, + NDT_SFSBSegmentNode, + NDT_SFSBSiteNode, + NDT_SFBaseMeshNode, + NDT_SFSubdivSurfaceSectorNode, + NDT_SFMusicScoreNode, + NDT_LAST +}; + +/*All BIFS versions handled*/ +#define GF_BIFS_NUM_VERSION 10 + +enum { + GF_BIFS_V1 = 1, + GF_BIFS_V2, + GF_BIFS_V3, + GF_BIFS_V4, + GF_BIFS_V5, + GF_BIFS_V6, + GF_BIFS_V7, + GF_BIFS_V8, + GF_BIFS_V9, + GF_BIFS_V10, + GF_BIFS_LAST_VERSION = GF_BIFS_V10 +}; + + + +#endif /*GPAC_DISABLE_VRML*/ + +#ifdef __cplusplus +} +#endif + + + +#endif /*_nodes_mpeg4_H*/ + diff --git a/include/gpac/nodes_svg.h b/include/gpac/nodes_svg.h new file mode 100644 index 0000000..4e2e2bc --- /dev/null +++ b/include/gpac/nodes_svg.h @@ -0,0 +1,338 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato - Jean Le Feuvre + * Copyright (c)2004-2012 Telecom ParisTech - All rights reserved + * + * This file is part of GPAC / XML-based Scene Graph 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_XML_NODES_H +#define _GF_XML_NODES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/scenegraph_svg.h> + +enum { + TAG_SVG_a = GF_NODE_RANGE_FIRST_SVG, + TAG_SVG_animate, + TAG_SVG_animateColor, + TAG_SVG_animateMotion, + TAG_SVG_animateTransform, + TAG_SVG_animation, + TAG_SVG_audio, + TAG_SVG_clip_path, + TAG_SVG_circle, + TAG_SVG_defs, + TAG_SVG_desc, + TAG_SVG_discard, + TAG_SVG_ellipse, + TAG_SVG_font, + TAG_SVG_font_face, + TAG_SVG_font_face_src, + TAG_SVG_font_face_uri, + TAG_SVG_foreignObject, + TAG_SVG_g, + TAG_SVG_glyph, + TAG_SVG_handler, + TAG_SVG_hkern, + TAG_SVG_image, + TAG_SVG_line, + TAG_SVG_linearGradient, + TAG_SVG_listener, + TAG_SVG_metadata, + TAG_SVG_missing_glyph, + TAG_SVG_mpath, + TAG_SVG_path, + TAG_SVG_polygon, + TAG_SVG_polyline, + TAG_SVG_prefetch, + TAG_SVG_radialGradient, + TAG_SVG_rect, + TAG_SVG_script, + TAG_SVG_set, + TAG_SVG_solidColor, + TAG_SVG_stop, + TAG_SVG_svg, + TAG_SVG_switch, + TAG_SVG_tbreak, + TAG_SVG_text, + TAG_SVG_textArea, + TAG_SVG_title, + TAG_SVG_tspan, + TAG_SVG_use, + TAG_SVG_video, + + TAG_SVG_filter, + TAG_SVG_feDistantLight, + TAG_SVG_fePointLight, + TAG_SVG_feSpotLight, + TAG_SVG_feBlend, + TAG_SVG_feColorMatrix, + TAG_SVG_feComponentTransfer, + TAG_SVG_feFuncR, + TAG_SVG_feFuncG, + TAG_SVG_feFuncB, + TAG_SVG_feFuncA, + TAG_SVG_feComposite, + TAG_SVG_feConvolveMatrix, + TAG_SVG_feDiffuseLighting, + TAG_SVG_feDisplacementMap, + TAG_SVG_feFlood, + TAG_SVG_feGaussianBlur, + TAG_SVG_feImage, + TAG_SVG_feMerge, + TAG_SVG_feMorphology, + TAG_SVG_feOffset, + TAG_SVG_feSpecularLighting, + TAG_SVG_feTile, + TAG_SVG_feTurbulence, + + TAG_LSR_conditional, + TAG_LSR_cursorManager, + TAG_LSR_rectClip, + TAG_LSR_selector, + TAG_LSR_simpleLayout, + TAG_LSR_updates, + + /*undefined elements (when parsing) use this tag*/ + TAG_SVG_UndefinedElement +}; + +struct _all_atts { + XML_Space *xml_space; + XMLRI *xml_base; + SVG_ID *xml_id; + SVG_LanguageID *xml_lang; + + DOM_String *xlink_type; + XMLRI *xlink_role; + XMLRI *xlink_arcrole; + DOM_String *xlink_title; + XMLRI *xlink_href; + DOM_String *xlink_show; + DOM_String *xlink_actuate; + + XMLEV_Event *event; + XMLEV_Phase *phase; + XMLEV_Propagate *propagate; + XMLEV_DefaultAction *defaultAction; + XML_IDREF *observer; + XML_IDREF *listener_target; + XMLRI *handler; + + SVG_ID *id; + SVG_String *_class; + SVG_ListOfIRI *requiredFeatures; + SVG_ListOfIRI *requiredExtensions; + SVG_FormatList *requiredFormats; + SVG_FontList *requiredFonts; + SVG_LanguageIDs *systemLanguage; + SVG_Display *display; + SVG_Visibility *visibility; + SVG_RenderingHint *image_rendering; + SVG_PointerEvents *pointer_events; + SVG_RenderingHint *shape_rendering; + SVG_RenderingHint *text_rendering; + SVG_Number *audio_level; + SVG_Paint *viewport_fill; + SVG_Number *viewport_fill_opacity; + SVG_String *overflow; + SVG_Number *fill_opacity; + SVG_Number *stroke_opacity; + SVG_Paint *fill; + SVG_FillRule *fill_rule; + SVG_Paint *filter; + SVG_Paint *stroke; + SVG_StrokeDashArray *stroke_dasharray; + SVG_Length *stroke_dashoffset; + SVG_StrokeLineCap *stroke_linecap; + SVG_StrokeLineJoin *stroke_linejoin; + SVG_Number *stroke_miterlimit; + SVG_Length *stroke_width; + SVG_Paint *color; + SVG_RenderingHint *color_rendering; + SVG_VectorEffect *vector_effect; + SVG_SVGColor *solid_color; + SVG_Number *solid_opacity; + SVG_DisplayAlign *display_align; + SVG_Number *line_increment; + SVG_SVGColor *stop_color; + SVG_Number *stop_opacity; + SVG_FontFamily *font_family; + SVG_FontSize *font_size; + SVG_FontStyle *font_style; + SVG_FontVariant *font_variant; + SVG_FontWeight *font_weight; + SVG_TextAnchor *text_anchor; + SVG_TextAlign *text_align; + SVG_String *text_decoration; + SVG_FocusHighlight *focusHighlight; + SVG_Boolean *externalResourcesRequired; + SVG_Focusable *focusable; + SVG_Focus *nav_next; + SVG_Focus *nav_prev; + SVG_Focus *nav_up; + SVG_Focus *nav_up_right; + SVG_Focus *nav_right; + SVG_Focus *nav_down_right; + SVG_Focus *nav_down; + SVG_Focus *nav_down_left; + SVG_Focus *nav_left; + SVG_Focus *nav_up_left; + SVG_Transform *transform; + SVG_String *target; + SMIL_AttributeName *attributeName; + SMIL_AttributeType *attributeType; + SMIL_Times *begin; + SVG_Boolean *lsr_enabled; + SMIL_Duration *dur; + SMIL_Times *end; + SMIL_RepeatCount *repeatCount; + SMIL_Duration *repeatDur; + SMIL_Restart *restart; + SMIL_Fill *smil_fill; + SMIL_Duration *min; + SMIL_Duration *max; + SMIL_AnimateValue *to; + SMIL_CalcMode *calcMode; + SMIL_AnimateValues *values; + SMIL_KeyTimes *keyTimes; + SMIL_KeySplines *keySplines; + SMIL_AnimateValue *from; + SMIL_AnimateValue *by; + SMIL_Additive *additive; + SMIL_Accumulate *accumulate; + SVG_PathData *path; + SMIL_KeyPoints *keyPoints; + SVG_Rotate *rotate; + SVG_String *origin; + SVG_TransformType *transform_type; + SVG_Clock *clipBegin; + SVG_Clock *clipEnd; + SMIL_SyncBehavior *syncBehavior; + SMIL_SyncTolerance *syncTolerance; + SVG_Boolean *syncMaster; + XMLRI *syncReference; + SVG_Coordinate *x; + SVG_Coordinate *y; + SVG_Length *width; + SVG_Length *height; + SVG_PreserveAspectRatio *preserveAspectRatio; + SVG_InitialVisibility *initialVisibility; + SVG_ContentType *type; + SVG_Coordinate *cx; + SVG_Coordinate *cy; + SVG_Length *r; + SVG_Length *cursorManager_x; + SVG_Length *cursorManager_y; + SVG_Length *rx; + SVG_Length *ry; + SVG_Number *horiz_adv_x; + SVG_Number *horiz_origin_x; + SVG_String *font_stretch; + SVG_String *unicode_range; + SVG_String *panose_1; + SVG_String *widths; + SVG_String *bbox; + SVG_Number *units_per_em; + SVG_Number *stemv; + SVG_Number *stemh; + SVG_Number *slope; + SVG_Number *cap_height; + SVG_Number *x_height; + SVG_Number *accent_height; + SVG_Number *ascent; + SVG_Number *descent; + SVG_Number *ideographic; + SVG_Number *alphabetic; + SVG_Number *mathematical; + SVG_Number *hanging; + SVG_Number *underline_position; + SVG_Number *underline_thickness; + SVG_Number *strikethrough_position; + SVG_Number *strikethrough_thickness; + SVG_Number *overline_position; + SVG_Number *overline_thickness; + SVG_PathData *d; + SVG_String *unicode; + SVG_String *glyph_name; + SVG_String *arabic_form; + SVG_LanguageIDs *lang; + SVG_String *u1; + SVG_String *g1; + SVG_String *u2; + SVG_String *g2; + SVG_Number *k; + SVG_Number *opacity; + SVG_Coordinate *x1; + SVG_Coordinate *y1; + SVG_Coordinate *x2; + SVG_Coordinate *y2; + SVG_GradientUnit *gradientUnits; + SVG_GradientUnit *filterUnits; + SVG_SpreadMethod *spreadMethod; + SVG_Transform *gradientTransform; + SVG_Number *pathLength; + SVG_Points *points; + SVG_Number *mediaSize; + SVG_String *mediaTime; + SVG_String *mediaCharacterEncoding; + SVG_String *mediaContentEncodings; + SVG_Number *bandwidth; + SVG_Coordinate *fx; + SVG_Coordinate *fy; + LASeR_Size *size; + LASeR_Choice *choice; + LASeR_Size *delta; + SVG_Number *offset; + SMIL_SyncBehavior *syncBehaviorDefault; + SMIL_SyncTolerance *syncToleranceDefault; + SVG_ViewBox *viewBox; + SVG_ZoomAndPan *zoomAndPan; + SVG_String *version; + SVG_String *baseProfile; + SVG_ContentType *contentScriptType; + SVG_Clock *snapshotTime; + SVG_TimelineBegin *timelineBegin; + SVG_PlaybackOrder *playbackOrder; + SVG_Boolean *editable; + SVG_Coordinates *text_x; + SVG_Coordinates *text_y; + SVG_Numbers *text_rotate; + SVG_TransformBehavior *transformBehavior; + SVG_Overlay *overlay; + SVG_Boolean *fullscreen; + SVG_Motion *motionTransform; + SVG_ClipPath *clip_path; + + SVG_Boolean *gpac_useAsPrimary; + SVG_Number *gpac_depthOffset; + SVG_Number *gpac_depthGain; +}; +#ifdef __cplusplus +} +#endif + + + +#endif /*_GF_SVG_NODES_H*/ + diff --git a/include/gpac/nodes_x3d.h b/include/gpac/nodes_x3d.h new file mode 100644 index 0000000..080954b --- /dev/null +++ b/include/gpac/nodes_x3d.h @@ -0,0 +1,2020 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / X3D Scene Graph 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. + * + */ + + +/* + DO NOT MOFIFY - File generated on GMT Fri Jul 31 16:39:50 2009 + + BY X3DGen for GPAC Version 0.5.0 +*/ + +#ifndef _GF_X3D_NODES_H +#define _GF_X3D_NODES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/scenegraph_vrml.h> + +#ifndef GPAC_DISABLE_X3D + + + +enum { + TAG_X3D_Anchor = GF_NODE_RANGE_FIRST_X3D, + TAG_X3D_Appearance, + TAG_X3D_Arc2D, + TAG_X3D_ArcClose2D, + TAG_X3D_AudioClip, + TAG_X3D_Background, + TAG_X3D_Billboard, + TAG_X3D_BooleanFilter, + TAG_X3D_BooleanSequencer, + TAG_X3D_BooleanToggle, + TAG_X3D_BooleanTrigger, + TAG_X3D_Box, + TAG_X3D_Circle2D, + TAG_X3D_Collision, + TAG_X3D_Color, + TAG_X3D_ColorInterpolator, + TAG_X3D_ColorRGBA, + TAG_X3D_Cone, + TAG_X3D_Contour2D, + TAG_X3D_ContourPolyline2D, + TAG_X3D_Coordinate, + TAG_X3D_CoordinateDouble, + TAG_X3D_Coordinate2D, + TAG_X3D_CoordinateInterpolator, + TAG_X3D_CoordinateInterpolator2D, + TAG_X3D_Cylinder, + TAG_X3D_CylinderSensor, + TAG_X3D_DirectionalLight, + TAG_X3D_Disk2D, + TAG_X3D_ElevationGrid, + TAG_X3D_EspduTransform, + TAG_X3D_Extrusion, + TAG_X3D_FillProperties, + TAG_X3D_Fog, + TAG_X3D_FontStyle, + TAG_X3D_GeoCoordinate, + TAG_X3D_GeoElevationGrid, + TAG_X3D_GeoLocation, + TAG_X3D_GeoLOD, + TAG_X3D_GeoMetadata, + TAG_X3D_GeoOrigin, + TAG_X3D_GeoPositionInterpolator, + TAG_X3D_GeoTouchSensor, + TAG_X3D_GeoViewpoint, + TAG_X3D_Group, + TAG_X3D_HAnimDisplacer, + TAG_X3D_HAnimHumanoid, + TAG_X3D_HAnimJoint, + TAG_X3D_HAnimSegment, + TAG_X3D_HAnimSite, + TAG_X3D_ImageTexture, + TAG_X3D_IndexedFaceSet, + TAG_X3D_IndexedLineSet, + TAG_X3D_IndexedTriangleFanSet, + TAG_X3D_IndexedTriangleSet, + TAG_X3D_IndexedTriangleStripSet, + TAG_X3D_Inline, + TAG_X3D_IntegerSequencer, + TAG_X3D_IntegerTrigger, + TAG_X3D_KeySensor, + TAG_X3D_LineProperties, + TAG_X3D_LineSet, + TAG_X3D_LoadSensor, + TAG_X3D_LOD, + TAG_X3D_Material, + TAG_X3D_MetadataDouble, + TAG_X3D_MetadataFloat, + TAG_X3D_MetadataInteger, + TAG_X3D_MetadataSet, + TAG_X3D_MetadataString, + TAG_X3D_MovieTexture, + TAG_X3D_MultiTexture, + TAG_X3D_MultiTextureCoordinate, + TAG_X3D_MultiTextureTransform, + TAG_X3D_NavigationInfo, + TAG_X3D_Normal, + TAG_X3D_NormalInterpolator, + TAG_X3D_NurbsCurve, + TAG_X3D_NurbsCurve2D, + TAG_X3D_NurbsOrientationInterpolator, + TAG_X3D_NurbsPatchSurface, + TAG_X3D_NurbsPositionInterpolator, + TAG_X3D_NurbsSet, + TAG_X3D_NurbsSurfaceInterpolator, + TAG_X3D_NurbsSweptSurface, + TAG_X3D_NurbsSwungSurface, + TAG_X3D_NurbsTextureCoordinate, + TAG_X3D_NurbsTrimmedSurface, + TAG_X3D_OrientationInterpolator, + TAG_X3D_PixelTexture, + TAG_X3D_PlaneSensor, + TAG_X3D_PointLight, + TAG_X3D_PointSet, + TAG_X3D_Polyline2D, + TAG_X3D_Polypoint2D, + TAG_X3D_PositionInterpolator, + TAG_X3D_PositionInterpolator2D, + TAG_X3D_ProximitySensor, + TAG_X3D_ReceiverPdu, + TAG_X3D_Rectangle2D, + TAG_X3D_ScalarInterpolator, + TAG_X3D_Script, + TAG_X3D_Shape, + TAG_X3D_SignalPdu, + TAG_X3D_Sound, + TAG_X3D_Sphere, + TAG_X3D_SphereSensor, + TAG_X3D_SpotLight, + TAG_X3D_StaticGroup, + TAG_X3D_StringSensor, + TAG_X3D_Switch, + TAG_X3D_Text, + TAG_X3D_TextureBackground, + TAG_X3D_TextureCoordinate, + TAG_X3D_TextureCoordinateGenerator, + TAG_X3D_TextureTransform, + TAG_X3D_TimeSensor, + TAG_X3D_TimeTrigger, + TAG_X3D_TouchSensor, + TAG_X3D_Transform, + TAG_X3D_TransmitterPdu, + TAG_X3D_TriangleFanSet, + TAG_X3D_TriangleSet, + TAG_X3D_TriangleSet2D, + TAG_X3D_TriangleStripSet, + TAG_X3D_Viewpoint, + TAG_X3D_VisibilitySensor, + TAG_X3D_WorldInfo, + TAG_LastImplementedX3D +}; + +typedef struct _tagX3DAnchor +{ + BASE_NODE + VRML_CHILDREN + SFString description; /*exposedField*/ + MFString parameter; /*exposedField*/ + MFURL url; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Anchor; + + +typedef struct _tagX3DAppearance +{ + BASE_NODE + GF_Node *material; /*exposedField*/ + GF_Node *texture; /*exposedField*/ + GF_Node *textureTransform; /*exposedField*/ + GF_Node *fillProperties; /*exposedField*/ + GF_Node *lineProperties; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Appearance; + + +typedef struct _tagX3DArc2D +{ + BASE_NODE + SFFloat endAngle; /*field*/ + SFFloat radius; /*field*/ + SFFloat startAngle; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Arc2D; + + +typedef struct _tagX3DArcClose2D +{ + BASE_NODE + SFString closureType; /*field*/ + SFFloat endAngle; /*field*/ + SFFloat radius; /*field*/ + SFFloat startAngle; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_ArcClose2D; + + +typedef struct _tagX3DAudioClip +{ + BASE_NODE + SFString description; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFFloat pitch; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + MFURL url; /*exposedField*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFTime pauseTime; /*exposedField*/ + SFTime resumeTime; /*exposedField*/ + SFTime elapsedTime; /*eventOut*/ + SFBool isPaused; /*eventOut*/ +} X_AudioClip; + + +typedef struct _tagX3DBackground +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat groundAngle; /*exposedField*/ + MFColor groundColor; /*exposedField*/ + MFURL backUrl; /*exposedField*/ + MFURL bottomUrl; /*exposedField*/ + MFURL frontUrl; /*exposedField*/ + MFURL leftUrl; /*exposedField*/ + MFURL rightUrl; /*exposedField*/ + MFURL topUrl; /*exposedField*/ + MFFloat skyAngle; /*exposedField*/ + MFColor skyColor; /*exposedField*/ + SFBool isBound; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFTime bindTime; /*eventOut*/ +} X_Background; + + +typedef struct _tagX3DBillboard +{ + BASE_NODE + VRML_CHILDREN + SFVec3f axisOfRotation; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Billboard; + + +typedef struct _tagX3DBooleanFilter +{ + BASE_NODE + SFBool set_boolean; /*eventIn*/ + void (*on_set_boolean)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool inputFalse; /*eventOut*/ + SFBool inputNegate; /*eventOut*/ + SFBool inputTrue; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_BooleanFilter; + + +typedef struct _tagX3DBooleanSequencer +{ + BASE_NODE + SFBool next; /*eventIn*/ + void (*on_next)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool previous; /*eventIn*/ + void (*on_previous)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFBool keyValue; /*exposedField*/ + SFBool value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_BooleanSequencer; + + +typedef struct _tagX3DBooleanToggle +{ + BASE_NODE + SFBool set_boolean; /*eventIn*/ + void (*on_set_boolean)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool toggle; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_BooleanToggle; + + +typedef struct _tagX3DBooleanTrigger +{ + BASE_NODE + SFTime set_triggerTime; /*eventIn*/ + void (*on_set_triggerTime)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool triggerTrue; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_BooleanTrigger; + + +typedef struct _tagX3DBox +{ + BASE_NODE + SFVec3f size; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Box; + + +typedef struct _tagX3DCircle2D +{ + BASE_NODE + SFFloat radius; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Circle2D; + + +typedef struct _tagX3DCollision +{ + BASE_NODE + VRML_CHILDREN + SFBool enabled; /*exposedField*/ + GF_Node *proxy; /*field*/ + SFTime collideTime; /*eventOut*/ + SFBool isActive; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_Collision; + + +typedef struct _tagX3DColor +{ + BASE_NODE + MFColor color; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Color; + + +typedef struct _tagX3DColorInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFColor keyValue; /*exposedField*/ + SFColor value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_ColorInterpolator; + + +typedef struct _tagX3DColorRGBA +{ + BASE_NODE + MFColorRGBA color; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_ColorRGBA; + + +typedef struct _tagX3DCone +{ + BASE_NODE + SFFloat bottomRadius; /*field*/ + SFFloat height; /*field*/ + SFBool side; /*field*/ + SFBool bottom; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Cone; + + +typedef struct _tagX3DContour2D +{ + BASE_NODE + VRML_CHILDREN + GF_Node *metadata; /*exposedField*/ +} X_Contour2D; + + +typedef struct _tagX3DContourPolyline2D +{ + BASE_NODE + MFVec2f point; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_ContourPolyline2D; + + +typedef struct _tagX3DCoordinate +{ + BASE_NODE + MFVec3f point; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Coordinate; + + +typedef struct _tagX3DCoordinateDouble +{ + BASE_NODE + MFVec3d point; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_CoordinateDouble; + + +typedef struct _tagX3DCoordinate2D +{ + BASE_NODE + MFVec2f point; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Coordinate2D; + + +typedef struct _tagX3DCoordinateInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + MFVec3f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_CoordinateInterpolator; + + +typedef struct _tagX3DCoordinateInterpolator2D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec2f keyValue; /*exposedField*/ + MFVec2f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_CoordinateInterpolator2D; + + +typedef struct _tagX3DCylinder +{ + BASE_NODE + SFBool bottom; /*field*/ + SFFloat height; /*field*/ + SFFloat radius; /*field*/ + SFBool side; /*field*/ + SFBool top; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Cylinder; + + +typedef struct _tagX3DCylinderSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFFloat diskAngle; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFFloat maxAngle; /*exposedField*/ + SFFloat minAngle; /*exposedField*/ + SFFloat offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFRotation rotation_changed; /*eventOut*/ + SFVec3f trackPoint_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFString description; /*exposedField*/ + SFBool isOver; /*eventOut*/ +} X_CylinderSensor; + + +typedef struct _tagX3DDirectionalLight +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFColor color; /*exposedField*/ + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFBool on; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_DirectionalLight; + + +typedef struct _tagX3DDisk2D +{ + BASE_NODE + SFFloat innerRadius; /*field*/ + SFFloat outerRadius; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Disk2D; + + +typedef struct _tagX3DElevationGrid +{ + BASE_NODE + MFFloat set_height; /*eventIn*/ + void (*on_set_height)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + MFFloat height; /*field*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFFloat creaseAngle; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + SFInt32 xDimension; /*field*/ + SFFloat xSpacing; /*field*/ + SFInt32 zDimension; /*field*/ + SFFloat zSpacing; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_ElevationGrid; + + +typedef struct _tagX3DEspduTransform +{ + BASE_NODE + VRML_CHILDREN + SFFloat set_articulationParameterValue0; /*eventIn*/ + void (*on_set_articulationParameterValue0)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue1; /*eventIn*/ + void (*on_set_articulationParameterValue1)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue2; /*eventIn*/ + void (*on_set_articulationParameterValue2)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue3; /*eventIn*/ + void (*on_set_articulationParameterValue3)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue4; /*eventIn*/ + void (*on_set_articulationParameterValue4)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue5; /*eventIn*/ + void (*on_set_articulationParameterValue5)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue6; /*eventIn*/ + void (*on_set_articulationParameterValue6)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_articulationParameterValue7; /*eventIn*/ + void (*on_set_articulationParameterValue7)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFString address; /*exposedField*/ + SFInt32 applicationID; /*exposedField*/ + SFInt32 articulationParameterCount; /*exposedField*/ + MFInt32 articulationParameterDesignatorArray; /*exposedField*/ + MFInt32 articulationParameterChangeIndicatorArray; /*exposedField*/ + MFInt32 articulationParameterIdPartAttachedToArray; /*exposedField*/ + MFInt32 articulationParameterTypeArray; /*exposedField*/ + MFFloat articulationParameterArray; /*exposedField*/ + SFVec3f center; /*exposedField*/ + SFInt32 collisionType; /*exposedField*/ + SFInt32 deadReckoning; /*exposedField*/ + SFVec3f detonationLocation; /*exposedField*/ + SFVec3f detonationRelativeLocation; /*exposedField*/ + SFInt32 detonationResult; /*exposedField*/ + SFInt32 entityCategory; /*exposedField*/ + SFInt32 entityCountry; /*exposedField*/ + SFInt32 entityDomain; /*exposedField*/ + SFInt32 entityExtra; /*exposedField*/ + SFInt32 entityID; /*exposedField*/ + SFInt32 entityKind; /*exposedField*/ + SFInt32 entitySpecific; /*exposedField*/ + SFInt32 entitySubCategory; /*exposedField*/ + SFInt32 eventApplicationID; /*exposedField*/ + SFInt32 eventEntityID; /*exposedField*/ + SFInt32 eventNumber; /*exposedField*/ + SFInt32 eventSiteID; /*exposedField*/ + SFBool fired1; /*exposedField*/ + SFBool fired2; /*exposedField*/ + SFInt32 fireMissionIndex; /*exposedField*/ + SFFloat firingRange; /*exposedField*/ + SFInt32 firingRate; /*exposedField*/ + SFInt32 forceID; /*exposedField*/ + SFInt32 fuse; /*exposedField*/ + SFVec3f linearVelocity; /*exposedField*/ + SFVec3f linearAcceleration; /*exposedField*/ + SFString marking; /*exposedField*/ + SFString multicastRelayHost; /*exposedField*/ + SFInt32 multicastRelayPort; /*exposedField*/ + SFInt32 munitionApplicationID; /*exposedField*/ + SFVec3f munitionEndPoint; /*exposedField*/ + SFInt32 munitionEntityID; /*exposedField*/ + SFInt32 munitionQuantity; /*exposedField*/ + SFInt32 munitionSiteID; /*exposedField*/ + SFVec3f munitionStartPoint; /*exposedField*/ + SFString networkMode; /*exposedField*/ + SFInt32 port; /*exposedField*/ + SFTime readInterval; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + SFInt32 siteID; /*exposedField*/ + SFVec3f translation; /*exposedField*/ + SFInt32 warhead; /*exposedField*/ + SFTime writeInterval; /*exposedField*/ + SFBool rtpHeaderExpected; /*field*/ + SFFloat articulationParameterValue0_changed; /*eventOut*/ + SFFloat articulationParameterValue1_changed; /*eventOut*/ + SFFloat articulationParameterValue2_changed; /*eventOut*/ + SFFloat articulationParameterValue3_changed; /*eventOut*/ + SFFloat articulationParameterValue4_changed; /*eventOut*/ + SFFloat articulationParameterValue5_changed; /*eventOut*/ + SFFloat articulationParameterValue6_changed; /*eventOut*/ + SFFloat articulationParameterValue7_changed; /*eventOut*/ + SFTime collideTime; /*eventOut*/ + SFTime detonateTime; /*eventOut*/ + SFTime firedTime; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFBool isCollided; /*eventOut*/ + SFBool isDetonated; /*eventOut*/ + SFBool isNetworkReader; /*eventOut*/ + SFBool isNetworkWriter; /*eventOut*/ + SFBool isRtpHeaderHeard; /*eventOut*/ + SFBool isStandAlone; /*eventOut*/ + SFTime timestamp; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_EspduTransform; + + +typedef struct _tagX3DExtrusion +{ + BASE_NODE + MFVec2f set_crossSection; /*eventIn*/ + void (*on_set_crossSection)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFRotation set_orientation; /*eventIn*/ + void (*on_set_orientation)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFVec2f set_scale; /*eventIn*/ + void (*on_set_scale)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFVec3f set_spine; /*eventIn*/ + void (*on_set_spine)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool beginCap; /*field*/ + SFBool ccw; /*field*/ + SFBool convex; /*field*/ + SFFloat creaseAngle; /*field*/ + MFVec2f crossSection; /*field*/ + SFBool endCap; /*field*/ + MFRotation orientation; /*field*/ + MFVec2f scale; /*field*/ + SFBool solid; /*field*/ + MFVec3f spine; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Extrusion; + + +typedef struct _tagX3DFillProperties +{ + BASE_NODE + SFBool filled; /*exposedField*/ + SFColor hatchColor; /*exposedField*/ + SFBool hatched; /*exposedField*/ + SFInt32 hatchStyle; /*exposedField*/ +} X_FillProperties; + + +typedef struct _tagX3DFog +{ + BASE_NODE + SFColor color; /*exposedField*/ + SFString fogType; /*exposedField*/ + SFFloat visibilityRange; /*exposedField*/ + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool isBound; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFTime bindTime; /*eventOut*/ +} X_Fog; + + +typedef struct _tagX3DFontStyle +{ + BASE_NODE + MFString family; /*exposedField*/ + SFBool horizontal; /*exposedField*/ + MFString justify; /*exposedField*/ + SFString language; /*exposedField*/ + SFBool leftToRight; /*exposedField*/ + SFFloat size; /*exposedField*/ + SFFloat spacing; /*exposedField*/ + SFString style; /*exposedField*/ + SFBool topToBottom; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_FontStyle; + + +typedef struct _tagX3DGeoCoordinate +{ + BASE_NODE + MFVec3d point; /*exposedField*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoCoordinate; + + +typedef struct _tagX3DGeoElevationGrid +{ + BASE_NODE + MFDouble set_height; /*eventIn*/ + void (*on_set_height)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFFloat yScale; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFFloat creaseAngle; /*field*/ + SFString geoGridOrigin; /*field*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + MFDouble height; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + SFInt32 xDimension; /*field*/ + SFDouble xSpacing; /*field*/ + SFInt32 zDimension; /*field*/ + SFDouble zSpacing; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoElevationGrid; + + +typedef struct _tagX3DGeoLocation +{ + BASE_NODE + VRML_CHILDREN + SFVec3d geoCoords; /*exposedField*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoLocation; + + +typedef struct _tagX3DGeoLOD +{ + BASE_NODE + SFVec3d center; /*field*/ + MFURL child1Url; /*field*/ + MFURL child2Url; /*field*/ + MFURL child3Url; /*field*/ + MFURL child4Url; /*field*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + SFFloat range; /*field*/ + MFURL rootUrl; /*field*/ + GF_ChildNodeItem *rootNode; /*field*/ + GF_ChildNodeItem *children; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoLOD; + + +typedef struct _tagX3DGeoMetadata +{ + BASE_NODE + GF_ChildNodeItem *data; /*exposedField*/ + MFString summary; /*exposedField*/ + MFURL url; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoMetadata; + + +typedef struct _tagX3DGeoOrigin +{ + BASE_NODE + SFVec3d geoCoords; /*exposedField*/ + MFString geoSystem; /*exposedField*/ + SFBool rotateYUp; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoOrigin; + + +typedef struct _tagX3DGeoPositionInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3d keyValue; /*exposedField*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + SFVec3d geovalue_changed; /*eventOut*/ + SFVec3f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoPositionInterpolator; + + +typedef struct _tagX3DGeoTouchSensor +{ + BASE_NODE + SFBool enabled; /*exposedField*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + SFVec3f hitNormal_changed; /*eventOut*/ + SFVec3f hitPoint_changed; /*eventOut*/ + SFVec2f hitTexCoord_changed; /*eventOut*/ + SFVec3d hitGeoCoord_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFBool isOver; /*eventOut*/ + SFTime touchTime; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoTouchSensor; + + +typedef struct _tagX3DGeoViewpoint +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFString set_orientation; /*eventIn*/ + void (*on_set_orientation)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFString set_position; /*eventIn*/ + void (*on_set_position)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFString description; /*exposedField*/ + SFFloat fieldOfView; /*exposedField*/ + SFBool headlight; /*exposedField*/ + SFBool jump; /*exposedField*/ + MFString navType; /*exposedField*/ + SFTime bindTime; /*eventOut*/ + SFBool isBound; /*eventOut*/ + GF_Node *geoOrigin; /*field*/ + MFString geoSystem; /*field*/ + SFRotation orientation; /*field*/ + SFVec3d position; /*field*/ + SFFloat speedFactor; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_GeoViewpoint; + + +typedef struct _tagX3DGroup +{ + BASE_NODE + VRML_CHILDREN + GF_Node *metadata; /*exposedField*/ +} X_Group; + + +typedef struct _tagX3DHAnimDisplacer +{ + BASE_NODE + MFInt32 coordIndex; /*exposedField*/ + MFVec3f displacements; /*exposedField*/ + SFString name; /*exposedField*/ + SFFloat weight; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_HAnimDisplacer; + + +typedef struct _tagX3DHAnimHumanoid +{ + BASE_NODE + SFVec3f center; /*exposedField*/ + MFString info; /*exposedField*/ + GF_ChildNodeItem *joints; /*exposedField*/ + SFString name; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + GF_ChildNodeItem *segments; /*exposedField*/ + GF_ChildNodeItem *sites; /*exposedField*/ + GF_ChildNodeItem *skeleton; /*exposedField*/ + GF_ChildNodeItem *skin; /*exposedField*/ + GF_Node *skinCoord; /*exposedField*/ + GF_Node *skinNormal; /*exposedField*/ + SFVec3f translation; /*exposedField*/ + SFString version; /*exposedField*/ + GF_ChildNodeItem *viewpoints; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_HAnimHumanoid; + + +typedef struct _tagX3DHAnimJoint +{ + BASE_NODE + VRML_CHILDREN + SFVec3f center; /*exposedField*/ + GF_ChildNodeItem *displacers; /*exposedField*/ + SFRotation limitOrientation; /*exposedField*/ + MFFloat llimit; /*exposedField*/ + SFString name; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + MFInt32 skinCoordIndex; /*exposedField*/ + MFFloat skinCoordWeight; /*exposedField*/ + MFFloat stiffness; /*exposedField*/ + SFVec3f translation; /*exposedField*/ + MFFloat ulimit; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_HAnimJoint; + + +typedef struct _tagX3DHAnimSegment +{ + BASE_NODE + VRML_CHILDREN + SFVec3f centerOfMass; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_ChildNodeItem *displacers; /*exposedField*/ + SFFloat mass; /*exposedField*/ + MFFloat momentsOfInertia; /*exposedField*/ + SFString name; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_HAnimSegment; + + +typedef struct _tagX3DHAnimSite +{ + BASE_NODE + VRML_CHILDREN + SFVec3f center; /*exposedField*/ + SFString name; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + SFVec3f translation; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_HAnimSite; + + +typedef struct _tagX3DImageTexture +{ + BASE_NODE + MFURL url; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_ImageTexture; + + +typedef struct _tagX3DIndexedFaceSet +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_normalIndex; /*eventIn*/ + void (*on_set_normalIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_texCoordIndex; /*eventIn*/ + void (*on_set_texCoordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool convex; /*field*/ + MFInt32 coordIndex; /*field*/ + SFFloat creaseAngle; /*field*/ + MFInt32 normalIndex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + MFInt32 texCoordIndex; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_IndexedFaceSet; + + +typedef struct _tagX3DIndexedLineSet +{ + BASE_NODE + MFInt32 set_colorIndex; /*eventIn*/ + void (*on_set_colorIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFInt32 set_coordIndex; /*eventIn*/ + void (*on_set_coordIndex)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + MFInt32 colorIndex; /*field*/ + SFBool colorPerVertex; /*field*/ + MFInt32 coordIndex; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_IndexedLineSet; + + +typedef struct _tagX3DIndexedTriangleFanSet +{ + BASE_NODE + MFInt32 set_index; /*eventIn*/ + void (*on_set_index)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + MFInt32 index; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_IndexedTriangleFanSet; + + +typedef struct _tagX3DIndexedTriangleSet +{ + BASE_NODE + MFInt32 set_index; /*eventIn*/ + void (*on_set_index)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + MFInt32 index; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_IndexedTriangleSet; + + +typedef struct _tagX3DIndexedTriangleStripSet +{ + BASE_NODE + MFInt32 set_index; /*eventIn*/ + void (*on_set_index)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + SFFloat creaseAngle; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + MFInt32 index; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_IndexedTriangleStripSet; + + +typedef struct _tagX3DInline +{ + BASE_NODE + MFURL url; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ + SFBool load; /*exposedField*/ +} X_Inline; + + +typedef struct _tagX3DIntegerSequencer +{ + BASE_NODE + SFBool next; /*eventIn*/ + void (*on_next)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFBool previous; /*eventIn*/ + void (*on_previous)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFInt32 keyValue; /*exposedField*/ + SFInt32 value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_IntegerSequencer; + + +typedef struct _tagX3DIntegerTrigger +{ + BASE_NODE + SFBool set_boolean; /*eventIn*/ + void (*on_set_boolean)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFInt32 integerKey; /*exposedField*/ + SFInt32 triggerValue; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_IntegerTrigger; + + +typedef struct _tagX3DKeySensor +{ + BASE_NODE + SFBool enabled; /*exposedField*/ + SFInt32 actionKeyPress; /*eventOut*/ + SFInt32 actionKeyRelease; /*eventOut*/ + SFBool altKey; /*eventOut*/ + SFBool controlKey; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFString keyPress; /*eventOut*/ + SFString keyRelease; /*eventOut*/ + SFBool shiftKey; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_KeySensor; + + +typedef struct _tagX3DLineProperties +{ + BASE_NODE + SFBool applied; /*exposedField*/ + SFInt32 linetype; /*exposedField*/ + SFFloat linewidthScaleFactor; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_LineProperties; + + +typedef struct _tagX3DLineSet +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + MFInt32 vertexCount; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_LineSet; + + +typedef struct _tagX3DLoadSensor +{ + BASE_NODE + SFBool enabled; /*exposedField*/ + SFTime timeOut; /*exposedField*/ + GF_ChildNodeItem *watchList; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFBool isLoaded; /*eventOut*/ + SFTime loadTime; /*eventOut*/ + SFFloat progress; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_LoadSensor; + + +typedef struct _tagX3DLOD +{ + BASE_NODE + VRML_CHILDREN + SFVec3f center; /*field*/ + MFFloat range; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_LOD; + + +typedef struct _tagX3DMaterial +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFColor diffuseColor; /*exposedField*/ + SFColor emissiveColor; /*exposedField*/ + SFFloat shininess; /*exposedField*/ + SFColor specularColor; /*exposedField*/ + SFFloat transparency; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Material; + + +typedef struct _tagX3DMetadataDouble +{ + BASE_NODE + SFString name; /*exposedField*/ + SFString reference; /*exposedField*/ + MFDouble value; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MetadataDouble; + + +typedef struct _tagX3DMetadataFloat +{ + BASE_NODE + SFString name; /*exposedField*/ + SFString reference; /*exposedField*/ + MFFloat value; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MetadataFloat; + + +typedef struct _tagX3DMetadataInteger +{ + BASE_NODE + SFString name; /*exposedField*/ + SFString reference; /*exposedField*/ + MFInt32 value; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MetadataInteger; + + +typedef struct _tagX3DMetadataSet +{ + BASE_NODE + SFString name; /*exposedField*/ + SFString reference; /*exposedField*/ + GF_ChildNodeItem *value; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MetadataSet; + + +typedef struct _tagX3DMetadataString +{ + BASE_NODE + SFString name; /*exposedField*/ + SFString reference; /*exposedField*/ + MFString value; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MetadataString; + + +typedef struct _tagX3DMovieTexture +{ + BASE_NODE + SFBool loop; /*exposedField*/ + SFFloat speed; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + MFURL url; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ + SFTime duration_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFTime resumeTime; /*exposedField*/ + SFTime pauseTime; /*exposedField*/ + SFTime elapsedTime; /*eventOut*/ + SFBool isPaused; /*eventOut*/ +} X_MovieTexture; + + +typedef struct _tagX3DMultiTexture +{ + BASE_NODE + SFFloat alpha; /*exposedField*/ + SFColor color; /*exposedField*/ + MFString function; /*exposedField*/ + MFString mode; /*exposedField*/ + MFString source; /*exposedField*/ + GF_ChildNodeItem *texture; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MultiTexture; + + +typedef struct _tagX3DMultiTextureCoordinate +{ + BASE_NODE + GF_ChildNodeItem *texCoord; /*MultiTextureCoordinate*/ + GF_Node *metadata; /*exposedField*/ +} X_MultiTextureCoordinate; + + +typedef struct _tagX3DMultiTextureTransform +{ + BASE_NODE + GF_ChildNodeItem *textureTransform; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_MultiTextureTransform; + + +typedef struct _tagX3DNavigationInfo +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat avatarSize; /*exposedField*/ + SFBool headlight; /*exposedField*/ + SFFloat speed; /*exposedField*/ + MFString type; /*exposedField*/ + SFFloat visibilityLimit; /*exposedField*/ + SFBool isBound; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + MFString transitionType; /*exposedField*/ + SFTime bindTime; /*eventOut*/ +} X_NavigationInfo; + + +typedef struct _tagX3DNormal +{ + BASE_NODE + MFVec3f vector; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Normal; + + +typedef struct _tagX3DNormalInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + MFVec3f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_NormalInterpolator; + + +typedef struct _tagX3DNurbsCurve +{ + BASE_NODE + MFVec3f controlPoint; /*exposedField*/ + SFInt32 tessellation; /*exposedField*/ + MFDouble weight; /*exposedField*/ + SFBool closed; /*field*/ + MFFloat knot; /*field*/ + SFInt32 order; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsCurve; + + +typedef struct _tagX3DNurbsCurve2D +{ + BASE_NODE + MFVec2f controlPoint; /*exposedField*/ + SFInt32 tessellation; /*exposedField*/ + MFFloat weight; /*exposedField*/ + MFFloat knot; /*field*/ + SFInt32 order; /*field*/ + SFBool closed; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsCurve2D; + + +typedef struct _tagX3DNurbsOrientationInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *controlPoints; /*exposedField*/ + MFDouble knot; /*exposedField*/ + SFInt32 order; /*exposedField*/ + MFDouble weight; /*exposedField*/ + SFRotation value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsOrientationInterpolator; + + +typedef struct _tagX3DNurbsPatchSurface +{ + BASE_NODE + GF_Node *controlPoint; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFInt32 uTessellation; /*exposedField*/ + SFInt32 vTessellation; /*exposedField*/ + MFDouble weight; /*exposedField*/ + SFBool solid; /*field*/ + SFBool uClosed; /*field*/ + SFInt32 uDimension; /*field*/ + MFDouble uKnot; /*field*/ + SFInt32 uOrder; /*field*/ + SFBool vClosed; /*field*/ + SFInt32 vDimension; /*field*/ + MFDouble vKnot; /*field*/ + SFInt32 vOrder; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsPatchSurface; + + +typedef struct _tagX3DNurbsPositionInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *controlPoints; /*exposedField*/ + MFDouble knot; /*exposedField*/ + SFInt32 order; /*exposedField*/ + MFDouble weight; /*exposedField*/ + SFVec3f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsPositionInterpolator; + + +typedef struct _tagX3DNurbsSet +{ + BASE_NODE + GF_ChildNodeItem *addGeometry; /*eventIn*/ + void (*on_addGeometry)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_ChildNodeItem *removeGeometry; /*eventIn*/ + void (*on_removeGeometry)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_ChildNodeItem *geometry; /*exposedField*/ + SFFloat tessellationScale; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsSet; + + +typedef struct _tagX3DNurbsSurfaceInterpolator +{ + BASE_NODE + SFVec2f set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_Node *controlPoints; /*exposedField*/ + MFDouble weight; /*exposedField*/ + SFVec3f position_changed; /*eventOut*/ + SFVec3f normal_changed; /*eventOut*/ + SFInt32 uDimension; /*field*/ + MFDouble uKnot; /*field*/ + SFInt32 uOrder; /*field*/ + SFInt32 vDimension; /*field*/ + MFDouble vKnot; /*field*/ + SFInt32 vOrder; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsSurfaceInterpolator; + + +typedef struct _tagX3DNurbsSweptSurface +{ + BASE_NODE + GF_Node *crossSectionCurve; /*exposedField*/ + GF_Node *trajectoryCurve; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool solid; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsSweptSurface; + + +typedef struct _tagX3DNurbsSwungSurface +{ + BASE_NODE + GF_Node *profileCurve; /*exposedField*/ + GF_Node *trajectoryCurve; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool solid; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsSwungSurface; + + +typedef struct _tagX3DNurbsTextureCoordinate +{ + BASE_NODE + MFVec2f controlPoint; /*exposedField*/ + MFFloat weight; /*exposedField*/ + SFInt32 uDimension; /*field*/ + MFDouble uKnot; /*field*/ + SFInt32 uOrder; /*field*/ + SFInt32 vDimension; /*field*/ + MFDouble vKnot; /*field*/ + SFInt32 vOrder; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsTextureCoordinate; + + +typedef struct _tagX3DNurbsTrimmedSurface +{ + BASE_NODE + GF_ChildNodeItem *addTrimmingContour; /*eventIn*/ + void (*on_addTrimmingContour)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_ChildNodeItem *removeTrimmingContour; /*eventIn*/ + void (*on_removeTrimmingContour)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + GF_ChildNodeItem *trimmingContour; /*exposedField*/ + GF_Node *controlPoint; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFInt32 uTessellation; /*exposedField*/ + SFInt32 vTessellation; /*exposedField*/ + MFDouble weight; /*exposedField*/ + SFBool solid; /*field*/ + SFBool uClosed; /*field*/ + SFInt32 uDimension; /*field*/ + MFDouble uKnot; /*field*/ + SFInt32 uOrder; /*field*/ + SFBool vClosed; /*field*/ + SFInt32 vDimension; /*field*/ + MFDouble vKnot; /*field*/ + SFInt32 vOrder; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_NurbsTrimmedSurface; + + +typedef struct _tagX3DOrientationInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFRotation keyValue; /*exposedField*/ + SFRotation value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_OrientationInterpolator; + + +typedef struct _tagX3DPixelTexture +{ + BASE_NODE + SFImage image; /*exposedField*/ + SFBool repeatS; /*field*/ + SFBool repeatT; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_PixelTexture; + + +typedef struct _tagX3DPlaneSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFVec2f maxPosition; /*exposedField*/ + SFVec2f minPosition; /*exposedField*/ + SFVec3f offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFVec3f trackPoint_changed; /*eventOut*/ + SFVec3f translation_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFString description; /*exposedField*/ + SFBool isOver; /*eventOut*/ +} X_PlaneSensor; + + +typedef struct _tagX3DPointLight +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFVec3f attenuation; /*exposedField*/ + SFColor color; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFBool on; /*exposedField*/ + SFFloat radius; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_PointLight; + + +typedef struct _tagX3DPointSet +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_PointSet; + + +typedef struct _tagX3DPolyline2D +{ + BASE_NODE + MFVec2f lineSegments; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Polyline2D; + + +typedef struct _tagX3DPolypoint2D +{ + BASE_NODE + MFVec2f point; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Polypoint2D; + + +typedef struct _tagX3DPositionInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec3f keyValue; /*exposedField*/ + SFVec3f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_PositionInterpolator; + + +typedef struct _tagX3DPositionInterpolator2D +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFVec2f keyValue; /*exposedField*/ + SFVec2f value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_PositionInterpolator2D; + + +typedef struct _tagX3DProximitySensor +{ + BASE_NODE + SFVec3f center; /*exposedField*/ + SFVec3f size; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFVec3f position_changed; /*eventOut*/ + SFRotation orientation_changed; /*eventOut*/ + SFTime enterTime; /*eventOut*/ + SFTime exitTime; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFVec3f centerOfRotation_changed; /*eventOut*/ +} X_ProximitySensor; + + +typedef struct _tagX3DReceiverPdu +{ + BASE_NODE + SFString address; /*exposedField*/ + SFInt32 applicationID; /*exposedField*/ + SFInt32 entityID; /*exposedField*/ + SFString multicastRelayHost; /*exposedField*/ + SFInt32 multicastRelayPort; /*exposedField*/ + SFString networkMode; /*exposedField*/ + SFInt32 port; /*exposedField*/ + SFInt32 radioID; /*exposedField*/ + SFFloat readInterval; /*exposedField*/ + SFFloat receivedPower; /*exposedField*/ + SFInt32 receiverState; /*exposedField*/ + SFBool rtpHeaderExpected; /*exposedField*/ + SFInt32 siteID; /*exposedField*/ + SFInt32 transmitterApplicationID; /*exposedField*/ + SFInt32 transmitterEntityID; /*exposedField*/ + SFInt32 transmitterRadioID; /*exposedField*/ + SFInt32 transmitterSiteID; /*exposedField*/ + SFInt32 whichGeometry; /*exposedField*/ + SFFloat writeInterval; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFBool isNetworkReader; /*eventOut*/ + SFBool isNetworkWriter; /*eventOut*/ + SFBool isRtpHeaderHeard; /*eventOut*/ + SFBool isStandAlone; /*eventOut*/ + SFTime timestamp; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_ReceiverPdu; + + +typedef struct _tagX3DRectangle2D +{ + BASE_NODE + SFVec2f size; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Rectangle2D; + + +typedef struct _tagX3DScalarInterpolator +{ + BASE_NODE + SFFloat set_fraction; /*eventIn*/ + void (*on_set_fraction)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat key; /*exposedField*/ + MFFloat keyValue; /*exposedField*/ + SFFloat value_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_ScalarInterpolator; + + +typedef struct _tagX3DScript +{ + BASE_NODE + MFScript url; /*exposedField*/ + SFBool directOutput; /*field*/ + SFBool mustEvaluate; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Script; + + +typedef struct _tagX3DShape +{ + BASE_NODE + GF_Node *appearance; /*exposedField*/ + GF_Node *geometry; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Shape; + + +typedef struct _tagX3DSignalPdu +{ + BASE_NODE + SFString address; /*exposedField*/ + SFInt32 applicationID; /*exposedField*/ + MFInt32 data; /*exposedField*/ + SFInt32 dataLength; /*exposedField*/ + SFInt32 encodingScheme; /*exposedField*/ + SFInt32 entityID; /*exposedField*/ + SFString multicastRelayHost; /*exposedField*/ + SFInt32 multicastRelayPort; /*exposedField*/ + SFString networkMode; /*exposedField*/ + SFInt32 port; /*exposedField*/ + SFInt32 radioID; /*exposedField*/ + SFFloat readInterval; /*exposedField*/ + SFBool rtpHeaderExpected; /*exposedField*/ + SFInt32 sampleRate; /*exposedField*/ + SFInt32 samples; /*exposedField*/ + SFInt32 siteID; /*exposedField*/ + SFInt32 tdlType; /*exposedField*/ + SFInt32 whichGeometry; /*exposedField*/ + SFFloat writeInterval; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFBool isNetworkReader; /*eventOut*/ + SFBool isNetworkWriter; /*eventOut*/ + SFBool isRtpHeaderHeard; /*eventOut*/ + SFBool isStandAlone; /*eventOut*/ + SFTime timestamp; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_SignalPdu; + + +typedef struct _tagX3DSound +{ + BASE_NODE + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFFloat maxBack; /*exposedField*/ + SFFloat maxFront; /*exposedField*/ + SFFloat minBack; /*exposedField*/ + SFFloat minFront; /*exposedField*/ + SFFloat priority; /*exposedField*/ + GF_Node *source; /*exposedField*/ + SFBool spatialize; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Sound; + + +typedef struct _tagX3DSphere +{ + BASE_NODE + SFFloat radius; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_Sphere; + + +typedef struct _tagX3DSphereSensor +{ + BASE_NODE + SFBool autoOffset; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFRotation offset; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFRotation rotation_changed; /*eventOut*/ + SFVec3f trackPoint_changed; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFString description; /*exposedField*/ + SFBool isOver; /*eventOut*/ +} X_SphereSensor; + + +typedef struct _tagX3DSpotLight +{ + BASE_NODE + SFFloat ambientIntensity; /*exposedField*/ + SFVec3f attenuation; /*exposedField*/ + SFFloat beamWidth; /*exposedField*/ + SFColor color; /*exposedField*/ + SFFloat cutOffAngle; /*exposedField*/ + SFVec3f direction; /*exposedField*/ + SFFloat intensity; /*exposedField*/ + SFVec3f location; /*exposedField*/ + SFBool on; /*exposedField*/ + SFFloat radius; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_SpotLight; + + +typedef struct _tagX3DStaticGroup +{ + BASE_NODE + VRML_CHILDREN + GF_Node *metadata; /*exposedField*/ +} X_StaticGroup; + + +typedef struct _tagX3DStringSensor +{ + BASE_NODE + SFBool deletionAllowed; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFString enteredText; /*eventOut*/ + SFString finalText; /*eventOut*/ + SFBool isActive; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_StringSensor; + + +typedef struct _tagX3DSwitch +{ + BASE_NODE + VRML_CHILDREN + SFInt32 whichChoice; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Switch; + + +typedef struct _tagX3DText +{ + BASE_NODE + MFString string; /*exposedField*/ + MFFloat length; /*exposedField*/ + GF_Node *fontStyle; /*exposedField*/ + SFFloat maxExtent; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Text; + + +typedef struct _tagX3DTextureBackground +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + MFFloat groundAngle; /*exposedField*/ + MFColor groundColor; /*exposedField*/ + GF_Node *backTexture; /*exposedField*/ + GF_Node *bottomTexture; /*exposedField*/ + GF_Node *frontTexture; /*exposedField*/ + GF_Node *leftTexture; /*exposedField*/ + GF_Node *rightTexture; /*exposedField*/ + GF_Node *topTexture; /*exposedField*/ + MFFloat skyAngle; /*exposedField*/ + MFColor skyColor; /*exposedField*/ + MFFloat transparency; /*exposedField*/ + SFTime bindTime; /*exposedField*/ + SFBool isBound; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_TextureBackground; + + +typedef struct _tagX3DTextureCoordinate +{ + BASE_NODE + MFVec2f point; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_TextureCoordinate; + + +typedef struct _tagX3DTextureCoordinateGenerator +{ + BASE_NODE + SFString mode; /*exposedField*/ + MFFloat parameter; /*TextureCoordinateGenerator*/ + GF_Node *metadata; /*exposedField*/ +} X_TextureCoordinateGenerator; + + +typedef struct _tagX3DTextureTransform +{ + BASE_NODE + SFVec2f center; /*exposedField*/ + SFFloat rotation; /*exposedField*/ + SFVec2f scale; /*exposedField*/ + SFVec2f translation; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_TextureTransform; + + +typedef struct _tagX3DTimeSensor +{ + BASE_NODE + SFTime cycleInterval; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFBool loop; /*exposedField*/ + SFTime startTime; /*exposedField*/ + SFTime stopTime; /*exposedField*/ + SFTime cycleTime; /*eventOut*/ + SFFloat fraction_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFTime time; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFTime pauseTime; /*exposedField*/ + SFTime resumeTime; /*exposedField*/ + SFTime elapsedTime; /*eventOut*/ + SFBool isPaused; /*eventOut*/ +} X_TimeSensor; + + +typedef struct _tagX3DTimeTrigger +{ + BASE_NODE + SFBool set_boolean; /*eventIn*/ + void (*on_set_boolean)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFTime triggerTime; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_TimeTrigger; + + +typedef struct _tagX3DTouchSensor +{ + BASE_NODE + SFBool enabled; /*exposedField*/ + SFVec3f hitNormal_changed; /*eventOut*/ + SFVec3f hitPoint_changed; /*eventOut*/ + SFVec2f hitTexCoord_changed; /*eventOut*/ + SFBool isActive; /*eventOut*/ + SFBool isOver; /*eventOut*/ + SFTime touchTime; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFString description; /*exposedField*/ +} X_TouchSensor; + + +typedef struct _tagX3DTransform +{ + BASE_NODE + VRML_CHILDREN + SFVec3f center; /*exposedField*/ + SFRotation rotation; /*exposedField*/ + SFVec3f scale; /*exposedField*/ + SFRotation scaleOrientation; /*exposedField*/ + SFVec3f translation; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_Transform; + + +typedef struct _tagX3DTransmitterPdu +{ + BASE_NODE + SFString address; /*exposedField*/ + SFVec3f antennaLocation; /*exposedField*/ + SFInt32 antennaPatternLength; /*exposedField*/ + SFInt32 antennaPatternType; /*exposedField*/ + SFInt32 applicationID; /*exposedField*/ + SFInt32 cryptoKeyID; /*exposedField*/ + SFInt32 cryptoSystem; /*exposedField*/ + SFInt32 entityID; /*exposedField*/ + SFInt32 frequency; /*exposedField*/ + SFInt32 inputSource; /*exposedField*/ + SFInt32 lengthOfModulationParameters; /*exposedField*/ + SFInt32 modulationTypeDetail; /*exposedField*/ + SFInt32 modulationTypeMajor; /*exposedField*/ + SFInt32 modulationTypeSpreadSpectrum; /*exposedField*/ + SFInt32 modulationTypeSystem; /*exposedField*/ + SFString multicastRelayHost; /*exposedField*/ + SFInt32 multicastRelayPort; /*exposedField*/ + SFString networkMode; /*exposedField*/ + SFInt32 port; /*exposedField*/ + SFFloat power; /*exposedField*/ + SFInt32 radioEntityTypeCategory; /*exposedField*/ + SFInt32 radioEntityTypeCountry; /*exposedField*/ + SFInt32 radioEntityTypeDomain; /*exposedField*/ + SFInt32 radioEntityTypeKind; /*exposedField*/ + SFInt32 radioEntityTypeNomenclature; /*exposedField*/ + SFInt32 radioEntityTypeNomenclatureVersion; /*exposedField*/ + SFInt32 radioID; /*exposedField*/ + SFFloat readInterval; /*exposedField*/ + SFVec3f relativeAntennaLocation; /*exposedField*/ + SFBool rtpHeaderExpected; /*exposedField*/ + SFInt32 siteID; /*exposedField*/ + SFFloat transmitFrequencyBandwidth; /*exposedField*/ + SFInt32 transmitState; /*exposedField*/ + SFInt32 whichGeometry; /*exposedField*/ + SFFloat writeInterval; /*exposedField*/ + SFBool isActive; /*eventOut*/ + SFBool isNetworkReader; /*eventOut*/ + SFBool isNetworkWriter; /*eventOut*/ + SFBool isRtpHeaderHeard; /*eventOut*/ + SFBool isStandAlone; /*eventOut*/ + SFTime timestamp; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_TransmitterPdu; + + +typedef struct _tagX3DTriangleFanSet +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + MFInt32 fanCount; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_TriangleFanSet; + + +typedef struct _tagX3DTriangleSet +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_TriangleSet; + + +typedef struct _tagX3DTriangleSet2D +{ + BASE_NODE + MFVec2f vertices; /*exposedField*/ + GF_Node *metadata; /*exposedField*/ +} X_TriangleSet2D; + + +typedef struct _tagX3DTriangleStripSet +{ + BASE_NODE + GF_Node *color; /*exposedField*/ + GF_Node *coord; /*exposedField*/ + GF_Node *normal; /*exposedField*/ + MFInt32 stripCount; /*exposedField*/ + GF_Node *texCoord; /*exposedField*/ + SFBool ccw; /*field*/ + SFBool colorPerVertex; /*field*/ + SFBool normalPerVertex; /*field*/ + SFBool solid; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_TriangleStripSet; + + +typedef struct _tagX3DViewpoint +{ + BASE_NODE + SFBool set_bind; /*eventIn*/ + void (*on_set_bind)(GF_Node *pThis, struct _route *route); /*eventInHandler*/ + SFFloat fieldOfView; /*exposedField*/ + SFBool jump; /*exposedField*/ + SFRotation orientation; /*exposedField*/ + SFVec3f position; /*exposedField*/ + SFString description; /*field*/ + SFTime bindTime; /*eventOut*/ + SFBool isBound; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ + SFVec3f centerOfRotation; /*exposedField*/ +} X_Viewpoint; + + +typedef struct _tagX3DVisibilitySensor +{ + BASE_NODE + SFVec3f center; /*exposedField*/ + SFBool enabled; /*exposedField*/ + SFVec3f size; /*exposedField*/ + SFTime enterTime; /*eventOut*/ + SFTime exitTime; /*eventOut*/ + SFBool isActive; /*eventOut*/ + GF_Node *metadata; /*exposedField*/ +} X_VisibilitySensor; + + +typedef struct _tagX3DWorldInfo +{ + BASE_NODE + MFString info; /*field*/ + SFString title; /*field*/ + GF_Node *metadata; /*exposedField*/ +} X_WorldInfo; + + +#endif /*GPAC_DISABLE_X3D*/ + +#ifdef __cplusplus +} +#endif + + + +#endif /*_GF_X3D_NODES_H*/ + diff --git a/include/gpac/options.h b/include/gpac/options.h new file mode 100644 index 0000000..16c3371 --- /dev/null +++ b/include/gpac/options.h @@ -0,0 +1,336 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / Stream Management 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_OPTIONS_H_ +#define _GF_OPTIONS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/options.h> +\brief Compositor and terminal options. +*/ + +/*! +\addtogroup compose_grp Compositor +\ingroup playback_grp +\brief GPAC A/V/2D/3D compositor/rendering. + +This section documents the compositor of GPAC? in charge of assembling audio, images, video, text, 2D and 3D graphics with in a timed way + +@{ + */ + +/*! AspectRatio Type */ +enum +{ + /*! keep AR*/ + GF_ASPECT_RATIO_KEEP = 0, + /*! keep 16/9*/ + GF_ASPECT_RATIO_16_9, + /*! keep 4/3*/ + GF_ASPECT_RATIO_4_3, + /*! none (all rendering area used)*/ + GF_ASPECT_RATIO_FILL_SCREEN +}; + +/*! AntiAlias settings*/ +enum +{ + /*! no antialiasing*/ + GF_ANTIALIAS_NONE = 0, + /*! only text has antialiasing*/ + GF_ANTIALIAS_TEXT, + /*! full antialiasing*/ + GF_ANTIALIAS_FULL +}; + +/*! PlayState settings*/ +enum +{ + /*! terminal is playing (get only)*/ + GF_STATE_PLAYING = 0, + /*! terminal is paused (get only)*/ + GF_STATE_PAUSED, + /*! On set, terminal will pause after next frame (simulation tick). On get, indicates that rendering step hasn't performed yet*/ + GF_STATE_STEP_PAUSE, + /*! indicates resume shall restart from live point if any rather than pause point (set only)*/ + GF_STATE_PLAY_LIVE = 10 +}; + +/*! interaction level settings +\note GF_INTERACT_NORMAL and GF_INTERACT_NAVIGATION filter events. If set, any event processed by these 2 modules won't be forwarded to the user +*/ +enum +{ + /*! regular interactions enabled (touch sensors)*/ + GF_INTERACT_NORMAL = 1, + /*! InputSensor interactions enabled (mouse and keyboard)*/ + GF_INTERACT_INPUT_SENSOR = 2, + /*! all navigation interactions enabled (mouse and keyboard)*/ + GF_INTERACT_NAVIGATION = 4, +}; + +/*! BoundingVolume settings*/ +enum +{ + /*! doesn't draw bounding volume*/ + GF_BOUNDS_NONE = 0, + /*! draw object bounding box / rect*/ + GF_BOUNDS_BOX, + /*! draw object AABB tree (3D only) */ + GF_BOUNDS_AABB +}; + +/*! Wireframe settings*/ +enum +{ + /*! draw solid volumes*/ + GF_WIREFRAME_NONE = 0, + /*! draw only wireframe*/ + GF_WIREFRAME_ONLY, + /*! draw wireframe on solid object*/ + GF_WIREFRAME_SOLID +}; + + +/*! navigation type*/ +enum +{ + /*! navigation is disabled by content and cannot be forced by user*/ + GF_NAVIGATE_TYPE_NONE, + /*! 2D navigation modes only can be used*/ + GF_NAVIGATE_TYPE_2D, + /*! 3D navigation modes only can be used*/ + GF_NAVIGATE_TYPE_3D +}; + +/*! navigation modes - non-VRML ones are simply blaxxun contact ones*/ +enum +{ + /*! no navigation*/ + GF_NAVIGATE_NONE = 0, + /*! 3D navigation modes*/ + /*! walk navigation*/ + GF_NAVIGATE_WALK, + /*! fly navigation*/ + GF_NAVIGATE_FLY, + /*! pan navigation*/ + GF_NAVIGATE_PAN, + /*! game navigation*/ + GF_NAVIGATE_GAME, + /*! slide navigation, for 2D and 3D*/ + GF_NAVIGATE_SLIDE, + /*! all modes below disable collision detection & gravity in 3D*/ + /*examine navigation, for 2D and 3D */ + GF_NAVIGATE_EXAMINE, + /*! orbit navigation - 3D only*/ + GF_NAVIGATE_ORBIT, + /*! QT-VR like navigation - 3D only*/ + GF_NAVIGATE_VR, +}; + +/*! collision flags*/ +enum +{ + /*! no collision*/ + GF_COLLISION_NONE, + /*! regular collision*/ + GF_COLLISION_NORMAL, + /*! collision with camera displacement*/ + GF_COLLISION_DISPLACEMENT, +}; + +/*! TextTexturing settings*/ +enum +{ + /*! text drawn as texture in 3D mode, regular in 2D mode*/ + GF_TEXTURE_TEXT_DEFAULT = 0, + /*! text never drawn as texture*/ + GF_TEXTURE_TEXT_NEVER, + /*! text always drawn*/ + GF_TEXTURE_TEXT_ALWAYS +}; + +/*! Normal drawing settings*/ +enum +{ + /*! normals never drawn*/ + GF_NORMALS_NONE = 0, + /*! normals drawn per face (at barycenter)*/ + GF_NORMALS_FACE, + /*! normals drawn per vertex*/ + GF_NORMALS_VERTEX +}; + + +/*! Back-face culling mode*/ +enum +{ + /*! backface culling disabled*/ + GF_BACK_CULL_OFF = 0, + /*! backface culliong enabled*/ + GF_BACK_CULL_ON, + /*! backface culling enabled also for transparent meshes*/ + GF_BACK_CULL_ALPHA, +}; + +/*! 2D drawing mode*/ +enum +{ + /*! defer draw mode (only modified parts of the canvas are drawn)*/ + GF_DRAW_MODE_DEFER=0, + /*! immediate draw mode (canvas always redrawn)*/ + GF_DRAW_MODE_IMMEDIATE, + /*! defer debug draw mode (only modified parts of the canvas are drawn, the rest of the canvas is erased)*/ + GF_DRAW_MODE_DEFER_DEBUG, +}; + +/*! high-level options*/ +enum +{ + /*! set/get antialias flag (value: one of the AntiAlias enum) - may be ignored in OpenGL mode depending on graphic cards*/ + GF_OPT_ANTIALIAS =0, + /*! set/get fast mode (value: boolean) */ + GF_OPT_HIGHSPEED, + /*! set/get fullscreen flag (value: boolean) */ + GF_OPT_FULLSCREEN, + /*! reset top-level transform to original (value: boolean)*/ + GF_OPT_ORIGINAL_VIEW, + /*! overrides BIFS size info for simple AV - this is not recommended since + it will resize the window to the size of the biggest texture (thus some elements + may be lost)*/ + GF_OPT_OVERRIDE_SIZE, + /*! set / get audio volume (value is intensity between 0 and 100) */ + GF_OPT_AUDIO_VOLUME, + /*! set / get audio pan (value is pan between 0 (all left) and 100(all right) )*/ + GF_OPT_AUDIO_PAN, + /*! set / get audio mute*/ + GF_OPT_AUDIO_MUTE, + /*! get javascript flag (no set, depends on compil) - value: boolean, true if JS enabled in build*/ + GF_OPT_HAS_JAVASCRIPT, + /*! get selectable stream flag (no set) - value: boolean, true if audio/video/subtitle stream selection is + possible with content (if an MPEG-4 scene description is not present). Use regular OD browsing to get streams*/ + GF_OPT_CAN_SELECT_STREAMS, + /*! set/get control interaction, OR'ed combination of interaction flags*/ + GF_OPT_INTERACTION_LEVEL, + /*! set display window visible / get show/hide state*/ + GF_OPT_VISIBLE, + /*! set freeze display on/off / get freeze state freeze_display prevents any screen updates + needed when output driver uses direct video memory access*/ + GF_OPT_FREEZE_DISPLAY, + /*! Returns 1 if file playback is considered as done (all streams finished, no active time sensors + and no user interactions in the scene)*/ + GF_OPT_IS_FINISHED, + /*! Returns 1 if file timeline is considered as done (all streams finished, no active time sensors)*/ + GF_OPT_IS_OVER, + /*! set/get aspect ratio (value: one of AspectRatio enum) */ + GF_OPT_ASPECT_RATIO, + /*! send a redraw message (SetOption only): all graphics info (display list, vectorial path) is + recomputed, and textures are reloaded in HW*/ + GF_OPT_REFRESH, + /*! set/get stress mode (value: boolean) - in stress mode a GF_OPT_FORCE_REDRAW is emulated at each frame*/ + GF_OPT_STRESS_MODE, + /*! get/set bounding volume drawing (value: one of the above option)*/ + GF_OPT_DRAW_BOUNDS, + /*! get/set texture text option - when enabled and usable (that depends on content), text is first rendered + to a texture and only the texture is drawn, rather than drawing all the text each time (CPU intensive)*/ + GF_OPT_TEXTURE_TEXT, + /*! reload config file (set only), including drivers. Plugins configs are not reloaded*/ + GF_OPT_RELOAD_CONFIG, + /*! get: returns whether the content enable navigation and if it's 2D or 3D. + set: reset viewpoint (whatever value is given)*/ + GF_OPT_NAVIGATION_TYPE, + /*! get current navigation mode - set navigation mode if allowed by content - this is not a resident + option (eg not stored in cfg)*/ + GF_OPT_NAVIGATION, + /*! get/set Play state - cf above states for set*/ + GF_OPT_PLAY_STATE, + /*! get only: returns 1 if main addon is playing, 0 if regular scene is playing*/ + GF_OPT_MAIN_ADDON, + /*! get/set bench mode - if enabled, video frames are drawn as soon as possible witthout checking synchronisation*/ + GF_OPT_VIDEO_BENCH, + /*! get/set OpenGL force mode - returns error if OpenGL is not supported*/ + GF_OPT_USE_OPENGL, + /*! set/get draw mode*/ + GF_OPT_DRAW_MODE, + /*! set/get scalable zoom (value: boolean)*/ + GF_OPT_SCALABLE_ZOOM, + /*! set/get YUV acceleration (value: boolean) */ + GF_OPT_YUV_HARDWARE, + /*! get (set not supported yet) hardware YUV format (value: YUV 4CC) */ + GF_OPT_YUV_FORMAT, + /*! max video cache size in kbytes*/ + GF_OPT_VIDEO_CACHE_SIZE, + /*! max HTTP download rate in bits per second, 0 if no limit*/ + GF_OPT_HTTP_MAX_RATE, + /*! set only (value: boolean). If set, the main audio mixer can no longer be reconfigured. */ + GF_OPT_FORCE_AUDIO_CONFIG, + + /*! 3D ONLY OPTIONS */ + /*! set/get raster outline flag (value: boolean) - when set, no vectorial outlining is done, only OpenGL raster outline*/ + GF_OPT_RASTER_OUTLINES, + /*! set/get pow2 emulation flag (value: boolean) - when set, video textures with non power of 2 dimensions + are emulated as pow2 by expanding the video buffer (image is not scaled). Otherwise the entire image + is rescaled. This flag does not affect image textures, which are always rescaled*/ + GF_OPT_EMULATE_POW2, + /*! get/set polygon antialiasing flag (value: boolean) (may be ugly with some cards)*/ + GF_OPT_POLYGON_ANTIALIAS, + /*! get/set wireframe flag (value: cf above) (may be ugly with some cards)*/ + GF_OPT_WIREFRAME, + /*! get/set wireframe flag (value: cf above) (may be ugly with some cards)*/ + GF_OPT_NORMALS, + /*! disable backface culling*/ + GF_OPT_BACK_CULL, + /*! get/set RECT Ext flag (value: boolean) - when set, GL rectangular texture extension is not used + (but NPO2 texturing is if available)*/ + GF_OPT_NO_RECT_TEXTURE, + /*! set/get headlight (value: boolean)*/ + GF_OPT_HEADLIGHT, + /*! set/get collision (value: cf above)*/ + GF_OPT_COLLISION, + /*! set/get gravity*/ + GF_OPT_GRAVITY, + /*! get the number of offscreen views in stereo mode, or 1 if no offscreen stereo views are available*/ + GF_OPT_NUM_STEREO_VIEWS, + /*! set the mode of display of HEVC multiview videos, 0 to display the two views/layers and 1 to display just the first view/layer*/ + GF_OPT_MULTIVIEW_MODE, + /*! get orientation sensors flag, true if sensors are activated false if not*/ + GF_OPT_ORIENTATION_SENSORS_ACTIVE, +}; + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_USER_H_*/ + diff --git a/include/gpac/path2d.h b/include/gpac/path2d.h new file mode 100644 index 0000000..33547ef --- /dev/null +++ b/include/gpac/path2d.h @@ -0,0 +1,624 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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_PATH2D_H_ +#define _GF_PATH2D_H_ + +/*! +\file <gpac/path2d.h> +\brief 2D Vectorial Path. +*/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/maths.h> +#include <gpac/tools.h> + + +/*! +\addtogroup path_grp 2D Vector Path +\ingroup math_grp +\brief Vectorial 2D Path manipulation + +This section documents the 2D path object used in the GPAC framework. + +@{ +*/ + + +/*!\brief 2D Path Object + +The 2D path object is used to construct complex 2D shapes for later drawing or outlining. + */ +typedef struct +{ + /*! number of contours in path*/ + u32 n_contours; + /*! number of points in path and alloc size*/ + u32 n_points, n_alloc_points; + /*! path points */ + GF_Point2D *points; + /*! point tags (one per point)*/ + u8 *tags; + /*! contour end points*/ + u32 *contours; + /*! path bbox - NEVER USE WITHOUT FIRST CALLING \ref gf_path_get_bounds*/ + GF_Rect bbox; + /*! path flags*/ + s32 flags; + /*! fineness to use whenever flattening the path - default is \ref FIX_ONE*/ + Fixed fineness; +} GF_Path; + + +/*! +\brief path constructor + +Constructs an empty 2D path object +\return new path object + */ +GF_Path *gf_path_new(); +/*! +\brief path destructor + +Destructs a 2D path object +\param gp the target path + */ +void gf_path_del(GF_Path *gp); +/*! +\brief path reset + +Resets the 2D path object +\param gp the target path + */ +void gf_path_reset(GF_Path *gp); +/*! +\brief path copy constuctor + +Resets a copy of a 2D path object +\param gp the target path +\return new path copy + */ +GF_Path *gf_path_clone(GF_Path *gp); +/*! +\brief path close + +Closes current path contour +\param gp the target path +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_close(GF_Path *gp); +/*! +\brief path moveTo + +Starts a new contour from the specified point +\param gp the target path +\param x x-coordinate of the new point +\param y y-coordinate of the new point +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_move_to(GF_Path *gp, Fixed x, Fixed y); +/*! +\brief starts new contour + +Starts a new contour from the specified point +\param gp the target path +\param pt pointer to the new start point +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_move_to_vec(GF_Path *gp, GF_Point2D *pt); +/*! +\brief adds line to path + +Adds a line from the current point in path to the specified point +\param gp the target path +\param x x-coordinate of the line end +\param y y-coordinate of the line end +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_line_to(GF_Path *gp, Fixed x, Fixed y); +/*! +\brief adds line to path + +Adds a line from the current point in path to the specified point +\param gp the target path +\param pt line end +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_line_to_vec(GF_Path *gp, GF_Point2D *pt); +/*! +\brief adds cubic to path + +Adds a cubic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c1_x x-coordinate of the first control point of the cubic curve +\param c1_y y-coordinate of the first control point of the cubic curve +\param c2_x x-coordinate of the second control point of the cubic curve +\param c2_y y-coordinate of the second control point of the cubic curve +\param x x-coordinate of the end point of the cubic curve +\param y y-coordinate of the end point of the cubic curve +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_cubic_to(GF_Path *gp, Fixed c1_x, Fixed c1_y, Fixed c2_x, Fixed c2_y, Fixed x, Fixed y); +/*! +\brief adds cubic to path + +Adds a cubic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c1 first control point of the cubic curve +\param c2 second control point of the cubic curve +\param pt end point of the cubic curve +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_cubic_to_vec(GF_Path *gp, GF_Point2D *c1, GF_Point2D *c2, GF_Point2D *pt); +/*! +\brief adds quadratic to path + +Adds a quadratic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c_x x-coordinate of the control point of the quadratic curve +\param c_y y-coordinate of the control point of the quadratic curve +\param x x-coordinate of the end point of the cubic quadratic +\param y y-coordinate of the end point of the cubic quadratic +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_quadratic_to(GF_Path *gp, Fixed c_x, Fixed c_y, Fixed x, Fixed y); +/*! +\brief adds quadratic to path + +Adds a quadratic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c control point of the quadratic curve +\param pt end point of the cubic quadratic +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_quadratic_to_vec(GF_Path *gp, GF_Point2D *c, GF_Point2D *pt); +/*! +\brief adds rectangle to path + +Adds a rectangle contour to the path +\param gp the target path +\param cx x-coordinate of the rectangle center +\param cy y-coordinate of the rectangle center +\param w width of the rectangle +\param h height of the rectangle +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_rect_center(GF_Path *gp, Fixed cx, Fixed cy, Fixed w, Fixed h); +/*! +\brief adds rectangle to path + +Adds a rectangle contour to the path +\param gp the target path +\param ox left-most coordinate of the rectangle +\param oy top-most coordinate of the rectangle +\param w width of the rectangle +\param h height of the rectangle +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_rect(GF_Path *gp, Fixed ox, Fixed oy, Fixed w, Fixed h); +/*! +\brief adds ellipse to path + +Adds an ellipse contour to the path +\param gp the target path +\param cx x-coordinate of the ellipse center +\param cy y-coordinate of the ellipse center +\param a_axis length of the horizontal ellipse axis +\param b_axis length of the vertical ellipse axis +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_ellipse(GF_Path *gp, Fixed cx, Fixed cy, Fixed a_axis, Fixed b_axis); +/*! +\brief adds N-1 bezier curve to path + +Adds an N-degree bezier curve to the path, starting from the current point +\param gp the target path +\param pts points used to define the curve +\param nb_pts number of points used to define the curve. The degree of the curve is therefore (nb_pts-1). +\return error code if any error, \ref GF_OK otherwise +\note the fineness of the path must be set before calling this function. + */ +GF_Err gf_path_add_bezier(GF_Path *gp, GF_Point2D *pts, u32 nb_pts); +/*! +\brief adds arc as described in MPEG-4 BIFS to path + +Adds an arc contour to the path from focal and end points. +\param gp the target path +\param end_x x-coordinate of the arc end point +\param end_y y-coordinate of the arc end point +\param fa_x x-coordinate of the arc first focal point +\param fa_y y-coordinate of the arc first focal point +\param fb_x x-coordinate of the arc second focal point +\param fb_y y-coordinate of the arc second focal point +\param cw if 1, the arc will be clockwise, otherwise counter-clockwise. +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_arc_to(GF_Path *gp, Fixed end_x, Fixed end_y, Fixed fa_x, Fixed fa_y, Fixed fb_x, Fixed fb_y, Bool cw); +/*! +\brief adds arc as described in SVG to path + +Adds an arc contour to the path from end point, radii and 3 parameters. +\param gp the target path +\param end_x x-coordinate of the arc end point +\param end_y y-coordinate of the arc end point +\param r_x x-axis radius +\param r_y y-axis radius +\param x_axis_rotation angle for the x-axis +\param large_arc_flag large or short arc selection +\param sweep_flag if 1, the arc will be clockwise, otherwise counter-clockwise. +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_svg_arc_to(GF_Path *gp, Fixed end_x, Fixed end_y, Fixed r_x, Fixed r_y, Fixed x_axis_rotation, Bool large_arc_flag, Bool sweep_flag); + +/*! Close type for arc*/ +typedef enum +{ + /*! Arc is left open*/ + GF_PATH2D_ARC_OPEN=0, + /*! Arc is closed to its starting point*/ + GF_PATH2D_ARC_CLOSE, + /*! Arc is closed as a pie centered on arc center*/ + GF_PATH2D_ARC_PIE, +} GF_Path2DArcCloseType; +/*! +\brief adds arc to path + +Adds an arc contour to the path. +\param gp the target path +\param radius radius of the arc +\param start_angle start angle of the arc in radians +\param end_angle end angle of the arc in radians +\param close_type closing type: 0 for open arc, 1 for close arc, 2 for pie +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_arc(GF_Path *gp, Fixed radius, Fixed start_angle, Fixed end_angle, GF_Path2DArcCloseType close_type); + +/*! +\brief concatenates path + +Adds a sub-path to the path with a given transform. +\param gp the target path +\param subpath the path to add +\param mx Matrix for subpath +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_add_subpath(GF_Path *gp, GF_Path *subpath, GF_Matrix2D *mx); +/*! +\brief gets path control bounds + +Gets the path control bounds, i.e. the rectangle covering all lineTo and bezier control points. +\param gp the target path +\param rc pointer to rectangle receiving the control rectangle +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_get_control_bounds(GF_Path *gp, GF_Rect *rc); +/*! +\brief gets path bounds + +Gets the path bounds, i.e. the rectangle covering all points in path except bezier control points. +\param gp the target path +\param rc pointer to rectangle receiving the control rectangle +\return error code if any error, \ref GF_OK otherwise + */ +GF_Err gf_path_get_bounds(GF_Path *gp, GF_Rect *rc); +/*! +\brief flattens path + +Flattens the path, i.e. transform all bezier curves to lines according to the path flatness. +\param gp the target path + */ +void gf_path_flatten(GF_Path *gp); +/*! +\brief gets flatten copy of path + +Gets a flatten copy of the path. +\param gp the target path +\return the flatten path + */ +GF_Path *gf_path_get_flatten(GF_Path *gp); +/*! +\brief point over path testing + +Tests if a point is over a path or not, according to the path filling rule. +\param gp the target path +\param x x-coordinate of the point to check +\param y y-coordinate of the point to check +\return 1 if the point is over the path, 0 otherwise. + */ +Bool gf_path_point_over(GF_Path *gp, Fixed x, Fixed y); + +/*! +\brief path init testing + +Tests if the path is empty or not. +\param gp the target path +\return 1 if the path is empty, 0 otherwise. + */ +Bool gf_path_is_empty(GF_Path *gp); + +/*! +\brief path iterator + +The path iterator object is used to compute the length of a given path as well as transformation matrices along this path. + */ +typedef struct _path_iterator GF_PathIterator; + +/*! +\brief path iterator constructor + +Creates a new path iterator from a given path +\param gp the target path +\return the path iterator object. + */ +GF_PathIterator *gf_path_iterator_new(GF_Path *gp); +/*! +\brief path iterator destructor + +Destructs the path iterator object +\param it the target path iterator + */ +void gf_path_iterator_del(GF_PathIterator *it); + +/*! +\brief get path length + +Gets a path length from its iterator +\param it the target path iterator +\return the length of the path + */ +Fixed gf_path_iterator_get_length(GF_PathIterator *it); +/*! +\brief gets transformation matrix at given point on path + +Gets the transformation of a given point on the path, given by offset from origin. +The transform is so that a local system is translated to the given point, its x-axis tangent to the path and in the same direction. +The path direction is from first point to last point of the path. +\param it the target path iterator +\param offset length on the path in local system unit +\param follow_tangent indicates if transformation shall be computed if offset indicates a point outside the path (<0 or >path_length). In which case the path shall be virtually extended by the tangent at origin (offset <0) or at end (offset>path_length). Otherwise the transformation is not computed and 0 is returned. +\param mat matrix to be transformed (transformation shall be appended) - the matrix shall not be initialized +\param smooth_edges indicates if discontinuities shall be smoothed. If not set, the rotation angle THETA is the slope (DX/DY) of the current segment found. +\param length_after_point if set and smooth_edges is set, the amount of the object that lies on next segment shall be computed according to length_after_point. + + \code + Let: + len_last: length of current checked segment + len1: length of all previous segments so that len1 + len_last >= offset then if (offset + length_after_point > len1 + len_last) { + ratio = (len1 + len_last - offset) / length_after_point; + then THETA = ratio * slope(L1) + (1-ratio) * slope(L2) + + Of course care must be taken for PI/2 angles and similar situations + \endcode + +\return GF_TRUE if matrix has been updated, GF_FALSE otherwise, if failure or if point is out of path without tangent extension. + + */ +Bool gf_path_iterator_get_transform(GF_PathIterator *it, Fixed offset, Bool follow_tangent, GF_Matrix2D *mat, Bool smooth_edges, Fixed length_after_point); + + + +/*! brief gets convexity type for a 2D polygon +Gets the convexity type of the given 2D polygon +\param pts the points of the polygon +\param nb_pts number of points in the polygon +\return the convexity type of the polygon +*/ +u32 gf_polygone2d_get_convexity(GF_Point2D *pts, u32 nb_pts); + + +/* 2D Path constants */ + +/*! +2D Path point tags +\hideinitializer + */ +enum +{ + /*/! Point is on curve (moveTo, lineTo, end of splines)*/ + GF_PATH_CURVE_ON = 1, + /*! Point is a contour close*/ + GF_PATH_CLOSE = 5, + /*! Point is a quadratic control point*/ + GF_PATH_CURVE_CONIC = 0, + /*! Point is a cubic control point*/ + GF_PATH_CURVE_CUBIC = 2, +}; + + +/*! +2D Path flags +\hideinitializer + */ +enum +{ + /*! Path is filled using the zero-nonzero rule. If not set, filling uses odd/even rule*/ + GF_PATH_FILL_ZERO_NONZERO = 1, + /*! Path is filled using the odd-even rule but only even surface is filled*/ + GF_PATH_FILL_EVEN = 1<<1, + /*! When set bbox must be recomputed. + \note Read only, used to avoid wasting time on bounds calculation*/ + GF_PATH_BBOX_DIRTY = 1<<2, + /*! Indicates the path is flattened flattened + \note Read only, used to avoid wasting time on flattening*/ + GF_PATH_FLATTENED = 1<<3, +}; + +/*! +2D Polygon convexity type +\hideinitializer + */ +enum +{ + /*! Polygon is either complex or unknown*/ + GF_POLYGON_COMPLEX, + /*! Polygon is complex, starting in counter-clockwise order*/ + GF_POLYGON_COMPLEX_CCW, + /*! Polygon is complex, starting in clockwise order*/ + GF_POLYGON_COMPLEX_CW, + /*! Polygon is a counter-clockwise convex polygon*/ + GF_POLYGON_CONVEX_CCW, + /*! Polygon is a clockwise convex polygon*/ + GF_POLYGON_CONVEX_CW, + /*! Polygon is a convex line (degenerated path with all segments aligned)*/ + GF_POLYGON_CONVEX_LINE +}; + +/*! +Stencil alignment type for outlining +\hideinitializer + */ +enum +{ + /*! outline is centered on the path (default)*/ + GF_PATH_LINE_CENTER = 0, + /*! outline is inside the path*/ + GF_PATH_LINE_INSIDE, + /*! outline is outside the path*/ + GF_PATH_LINE_OUTSIDE, +}; + +/*! +Line cap type for outlining +\hideinitializer + */ +enum +{ + /*! End of line is flat (default)*/ + GF_LINE_CAP_FLAT = 0, + /*! End of line is round*/ + GF_LINE_CAP_ROUND, + /*! End of line is square*/ + GF_LINE_CAP_SQUARE, + /*! End of line is triangle*/ + GF_LINE_CAP_TRIANGLE, +}; + +/*! +Line join type for outlining +\hideinitializer + */ +enum +{ + /*! Line join is a miter join (default)*/ + GF_LINE_JOIN_MITER = 0, + /*! Line join is a round join*/ + GF_LINE_JOIN_ROUND, + /*! Line join is a bevel join*/ + GF_LINE_JOIN_BEVEL, + /*! Line join is a miter then bevel join*/ + GF_LINE_JOIN_MITER_SVG +}; + +/*! +Dash types for outlining +\hideinitializer + */ +enum +{ + /*! No dashing is used (default)*/ + GF_DASH_STYLE_PLAIN = 0, + /*! Predefined dash pattern is used*/ + GF_DASH_STYLE_DASH, + /*! Predefined dot pattern is used*/ + GF_DASH_STYLE_DOT, + /*! Predefined dash-dot pattern is used*/ + GF_DASH_STYLE_DASH_DOT, + /*! Predefined dash-dash-dot pattern is used*/ + GF_DASH_STYLE_DASH_DASH_DOT, + /*! Predefined dash-dot-dot pattern is used*/ + GF_DASH_STYLE_DASH_DOT_DOT, + /*! Custom pattern is used. Dash lengths are given in percentage of the pen width*/ + GF_DASH_STYLE_CUSTOM, + /*! SVG pattern is used. Dash lengths are given in the same unit as the pen width + and dash offset follows SVG specs (offset in dash pattern)*/ + GF_DASH_STYLE_SVG, +}; + + +/*!\brief Custom dash pattern +The custom dash pattern object is used to specify custom dashes when outlining a path. + */ +typedef struct +{ + /*beginning of the structure is casted in MFFloat in BIFS, DO NOT CHANGE ORDER*/ + + /*! Number of dashes in the pattern*/ + u32 num_dash; + /*! Value of the pattern dashes. Unit depends on the dash type*/ + Fixed *dashes; +} GF_DashSettings; + +/*!\brief Pen properties + +The pen properties object is used to specify several parameters used when building the vectorial outline of a path. + */ +typedef struct +{ + /*! The width of the outline*/ + Fixed width; + /*! The style of the lines ends*/ + u8 cap; + /*! The style of the lines joins*/ + u8 join; + /*! The alignment of the outline with regard to the path*/ + u8 align; + /*! The dash style of the line*/ + u8 dash; + /*! The miter limit of the line joins*/ + Fixed miterLimit; + /*! The initial dash offset in the outline. All points before this offset will be + * ignored when building the outline*/ + Fixed dash_offset; + /*! The dash pattern used for custom dashing*/ + GF_DashSettings *dash_set; + /*! The author-specified path length. Ignored if <= 0*/ + Fixed path_length; +} GF_PenSettings; + +/*! brief builds the vectorial outline of a path + +Builds the vectorial outline of a path for the given settings. The outline of a path is a path. +\param path the desired path to outline +\param pen the properties of the virtual pen used for outlining +\return the outline of the path +*/ +GF_Path *gf_path_get_outline(GF_Path *path, GF_PenSettings pen); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_PATH2D_H_*/ + diff --git a/include/gpac/route.h b/include/gpac/route.h new file mode 100644 index 0000000..cdbcc0f --- /dev/null +++ b/include/gpac/route.h @@ -0,0 +1,300 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / ROUTE (ATSC3, DVB-I) 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 + * 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_ROUTE_H_ +#define _GF_ROUTE_H_ + +#include <gpac/tools.h> + +#ifndef GPAC_DISABLE_ROUTE + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/route.h> +\brief Specific extensions for ROUTE ( ATSC3, DVB-I) protocol +*/ + +/*! +\addtogroup route_grp ROUTE +\ingroup media_grp +\brief ROUTE ATSC 3.0 reciever + +The ROUTE receiver implements part of the ATSC 3.0 specification, mostly low-level signaling and ROUTE reception. +It gathers objects from a ROUTE session and sends them back to the user through a callback, or deletes them if no callback is sent. +The route demuxer does not try to repairing files, it is the user responsability to do so. + +@{ +*/ + + +/*! ATSC3.0 bootstrap address for LLS*/ +#define GF_ATSC_MCAST_ADDR "224.0.23.60" +/*! ATSC3.0 bootstrap port for LLS*/ +#define GF_ATSC_MCAST_PORT 4937 + +/*!The GF_ROUTEDmx object.*/ +typedef struct __gf_routedmx GF_ROUTEDmx; + +/*!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*/ + 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*/ + GF_ROUTE_EVT_MPD, + /*! 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 + */ + 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, +} GF_ROUTEEventType; + +enum +{ + /*No-operation extension header*/ + GF_LCT_EXT_NOP = 0, + /*Authentication extension header*/ + GF_LCT_EXT_AUTH = 1, + /*Time extension header*/ + GF_LCT_EXT_TIME = 2, + /*FEC object transmission information extension header*/ + GF_LCT_EXT_FTI = 64, + /*Extension header for FDT - FLUTE*/ + GF_LCT_EXT_FDT = 192, + /*Extension header for FDT content encoding - FLUTE*/ + GF_LCT_EXT_CENC = 193, + /*TOL extension header - ROUTE - 24 bit payload*/ + GF_LCT_EXT_TOL24 = 194, + /*TOL extension header - ROUTE - HEL + 28 bit payload*/ + GF_LCT_EXT_TOL48 = 67, +}; + +/*! LCT fragment information*/ +typedef struct +{ + /*! offset in bytes of fragment in object / file*/ + u32 offset; + /*! size in bytes of fragment in object / file*/ + u32 size; +} GF_LCTFragInfo; + + +/*! Structure used to communicate file objects properties to the user*/ +typedef struct +{ + /*! original file name*/ + const char *filename; + /*! blob data pointer*/ + GF_Blob *blob; + /*! total size of object if known, 0 otherwise*/ + u32 total_size; + /*! object TSI*/ + u32 tsi; + /*! object TOI*/ + u32 toi; + /*! 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; + + /*! 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; +} GF_ROUTEEventFileInfo; + +/*! Creates a new ROUTE ATSC3.0 demultiplexer +\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 on_event the user callback function + \param udta the user data passed back by the callback +\return the ROUTE demultiplexer created +*/ +GF_ROUTEDmx *gf_route_atsc_dmx_new(const char *ifce, u32 sock_buffer_size, void (*on_event)(void *udta, GF_ROUTEEventType evt, u32 evt_param, GF_ROUTEEventFileInfo *finfo), void *udta); + +/*! Creates a new ROUTE demultiplexer +\param ip IP address of ROUTE session +\param port port of ROUTE session +\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 on_event the user callback function + \param udta the user data passed back by the callback +\return the ROUTE demultiplexer created +*/ +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 *finfo), void *udta); + +/*! Deletes an ROUTE demultiplexer +\param routedmx the ROUTE demultiplexer to delete +*/ +void gf_route_dmx_del(GF_ROUTEDmx *routedmx); + +/*! Processes demultiplexing, returns when nothing to read +\param routedmx the ROUTE demultiplexer +\return error code if any, GF_IP_NETWORK_EMPTY if nothing was read + */ +GF_Err gf_route_dmx_process(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). +\return error code if any + */ +GF_Err gf_route_set_reorder(GF_ROUTEDmx *routedmx, Bool force_reorder, u32 timeout_ms); + +/*! 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 +\return error code if any + */ +GF_Err gf_route_set_allow_progressive_dispatch(GF_ROUTEDmx *routedmx, Bool allow_progressive); + +/*! Sets the service ID to tune into for ATSC 3.0 +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to tune in. 0 means no service, 0xFFFFFFFF means all services and 0xFFFFFFFE means first service found +\param tune_others if set, will tune all non-selected services to get the MPD, but won't receive any media data +\return error code if any + */ +GF_Err gf_route_atsc3_tune_in(GF_ROUTEDmx *routedmx, u32 service_id, Bool tune_others); + + +/*! Gets the number of objects currently loaded in the service +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to query +\return number of objects in service + */ +u32 gf_route_dmx_get_object_count(GF_ROUTEDmx *routedmx, u32 service_id); + +/*! Removes an object with a given filename +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to query +\param fileName name of the file associated with the object +\param purge_previous if set, indicates that all objects with the same TSI and a TOI less than TOI of the deleted object will be removed +\return error if any, GF_NOT_FOUND if no such object + */ +GF_Err gf_route_dmx_remove_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName, Bool purge_previous); + +/*! Flags an object to be kept until \ref gf_route_dmx_remove_object_by_name is called +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to query +\param fileName name of the file associated with the object +\return error if any, GF_NOT_FOUND if no such object + */ +GF_Err gf_route_dmx_force_keep_object_by_name(GF_ROUTEDmx *routedmx, u32 service_id, char *fileName); + +/*! Removes the first object loaded in the service +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to query +\return GF_TRUE if success, GF_FALSE if no object could be removed (the object is in download) + */ +Bool gf_route_dmx_remove_first_object(GF_ROUTEDmx *routedmx, u32 service_id); + +/*! Checks existence of a service for atsc 3.0 +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to query +\return true if service is found, false otherwise + */ +Bool gf_route_dmx_find_atsc3_service(GF_ROUTEDmx *routedmx, u32 service_id); + +/*! Removes all non-signaling objects (ie TSI!=0), keeping only init segments and currently/last downloaded objects +\note this is mostly useful in case of looping session, or at MPD switch boundaries +\param routedmx the ROUTE demultiplexer +\param service_id ID of the service to cleanup + */ +void gf_route_dmx_purge_objects(GF_ROUTEDmx *routedmx, u32 service_id); + + +/*! Gets high resolution system time clock of the first packet received +\param routedmx the ROUTE demultiplexer +\return system clock in microseconds of first packet received + */ +u64 gf_route_dmx_get_first_packet_time(GF_ROUTEDmx *routedmx); + +/*! Gets high resolution system time clock of the last packet received +\param routedmx the ROUTE demultiplexer +\return system clock in microseconds of last packet received + */ +u64 gf_route_dmx_get_last_packet_time(GF_ROUTEDmx *routedmx); + +/*! Gets the number of packets received since start of the session, for all active services +\param routedmx the ROUTE demultiplexer +\return number of packets received + */ +u64 gf_route_dmx_get_nb_packets(GF_ROUTEDmx *routedmx); + +/*! Gets the number of bytes received since start of the session, for all active services +\param routedmx the ROUTE demultiplexer +\return number of bytes received + */ +u64 gf_route_dmx_get_recv_bytes(GF_ROUTEDmx *routedmx); + +/*! Gather only objects with given TSI (for debug purposes) +\param routedmx the ROUTE demultiplexer +\param tsi the target TSI, 0 for no filtering + */ +void gf_route_dmx_debug_tsi(GF_ROUTEDmx *routedmx, u32 tsi); + +/*! Sets udta for given service id +\param routedmx the ROUTE demultiplexer +\param service_id the target service +\param udta the target user data + */ +void gf_route_dmx_set_service_udta(GF_ROUTEDmx *routedmx, u32 service_id, void *udta); + +/*! Gets udta for given service id +\param routedmx the ROUTE demultiplexer +\param service_id the target service +\return the user data associated with the service + */ +void *gf_route_dmx_get_service_udta(GF_ROUTEDmx *routedmx, u32 service_id); + +/*! @} */ +#ifdef __cplusplus +} +#endif + +#endif /* GPAC_DISABLE_ROUTE */ + +#endif //_GF_ROUTE_H_ + diff --git a/include/gpac/rtp_streamer.h b/include/gpac/rtp_streamer.h new file mode 100644 index 0000000..fc88dc9 --- /dev/null +++ b/include/gpac/rtp_streamer.h @@ -0,0 +1,253 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * 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_RTPSTREAMER_H_ +#define _GF_RTPSTREAMER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/rtp_streamer.h> +\brief RTP streaming (packetizer and RTP socket). +*/ + +/*! +\addtogroup rtp_grp RTP Streamer +\ingroup media_grp +\brief RTPStreamer object + +This section documents the RTP streamer object of the GPAC framework. + +@{ + */ + + + +#include <gpac/ietf.h> + +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_ISOM) + +/*! RTP streamer object*/ +typedef struct __rtp_streamer GF_RTPStreamer; + + +/*! +\brief RTP Streamer constructor with extended parameters + +Constructs a new RTP file streamer +\param streamType type of the stream (GF_STREAM_* as defined in <gpac/constants.h>) +\param codecid codec ID for the stream (GF_CODEC_* as defined in <gpac/constants.h>) +\param timeScale unit to express timestamps of access units +\param ip_dest IP address of the destination +\param port port number of the destination +\param MTU Maximum Transmission Unit size to use +\param TTL Time To Leave +\param ifce_addr IP of the local interface to use (may be NULL) +\param flags set of RTP flags passed to the streamer +\param dsi MPEG-4 Decoder Specific Info for the stream +\param dsi_len length of the dsi parameter +\param PayloadType RTP payload type +\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 KI_length length of the key index +\param MinSize minimum AU size, 0 if unknown +\param MaxSize maximum AU size, 0 if unknown +\param avgTS average TS delta in timeScale, 0 if unknown +\param maxDTSDelta maximum DTS delta in timeScale, 0 if unknown +\param const_dur constant duration in timeScale, 0 if unknown +\param bandwidth bandwidth, 0 if unknown +\param max_ptime maximum packet duration in timeScale, 0 if unknown +\param au_sn_len length of the MPEG-4 SL descriptor AU sequence number field, 0 if unknown +\param for_rtsp indicates this is an RTP channel in an RTSP session, RTP channel will not be created, use \ref gf_rtp_streamer_init_rtsp +\return a new RTP streamer, or NULL of error or not supported + */ +GF_RTPStreamer *gf_rtp_streamer_new(u32 streamType, u32 codecid, u32 timeScale, + const char *ip_dest, u16 port, u32 MTU, u8 TTL, const char *ifce_addr, + u32 flags, const u8 *dsi, u32 dsi_len, + u32 PayloadType, u32 sample_rate, u32 nb_ch, + Bool is_crypted, u32 IV_length, u32 KI_length, + u32 MinSize, u32 MaxSize, u32 avgTS, u32 maxDTSDelta, u32 const_dur, u32 bandwidth, u32 max_ptime, u32 au_sn_len, Bool for_rtsp); + +/*! +\brief RTP file streamer destructor + +Destructs an RTP file streamer +\param streamer the target RTP streamer + */ +void gf_rtp_streamer_del(GF_RTPStreamer *streamer); + +/*! +\brief gets the SDP file + +Gets the SDP asscoiated with all media in the streaming session (only media parts are returned) +\param rtp the target RTP streamer +\param ESID The MPEG-4 elementary stream id of the stream to process +\param dsi The decoder specific info data +\param dsi_len length of the decoder specific info data +\param KMS_URI URI of the Key Management System +\param out_sdp_buffer location to the SDP buffer to allocate and fill +\return error if any + */ +GF_Err gf_rtp_streamer_append_sdp(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, char *KMS_URI, char **out_sdp_buffer); + +/*! +\brief gets the SDP file + +Gets the SDP asscoiated with all media in the streaming session (only media parts are returned) +\param rtp the target RTP streamer +\param ESID The MPEG-4 elementary stream id of the stream to process +\param dsi decoder specific info data +\param dsi_len length of the decoder specific info data +\param dsi_enh enhancement layer decoder specific info data +\param dsi_enh_len length of enhancement layer decoder specific info data +\param KMS_URI URI of the Key Management System +\param width video width +\param height video height +\param tw text window width +\param th text window height +\param tx text window horizontal offset +\param ty text window vertical offset +\param tl text window z-index +\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, Bool for_rtsp, char **out_sdp_buffer); + +/*! sends a full Access Unit over RTP +\param rtp the target RTP streamer +\param data AU payload +\param size AU payload size +\param cts composition timestamp in timeScale unit +\param dts decoding timestamp in timeScale unit +\param is_rap indicates if the AU is a random access +\return error if any +*/ +GF_Err gf_rtp_streamer_send_au(GF_RTPStreamer *rtp, u8 *data, u32 size, u64 cts, u64 dts, Bool is_rap); + +/*! sends a full Access Unit over RTP +\param rtp the target RTP streamer +\param data AU payload +\param size AU payload size +\param cts composition timestamp in timeScale unit +\param dts decoding timestamp in timeScale unit +\param is_rap indicates if the AU is a random access +\param inc_au_sn increments the AU sequence number by the given value before packetizing (used for MPEG-4 systems carousel) +\return error if any +*/ +GF_Err gf_rtp_streamer_send_au_with_sn(GF_RTPStreamer *rtp, u8 *data, u32 size, u64 cts, u64 dts, Bool is_rap, u32 inc_au_sn); + +/*! sends a full or partial Access Unit over RTP +\param streamer the target RTP streamer +\param data AU payload +\param size AU payload size +\param fullsize the size of the complete AU +\param cts composition timestamp in timeScale unit +\param dts decoding timestamp in timeScale unit +\param is_rap indicates if the AU is a random access +\param au_start indicates if this chunk is the start of the AU +\param au_end indicates if this chunk is the end of the AU +\param au_sn indicates the AU sequence number +\param duration indicates the AU duration +\param sampleDescriptionIndex indicates the ISOBMFF sampleDescriptionIndex for this AU (needed for 3GPP timed text) +\return error if any +*/ +GF_Err gf_rtp_streamer_send_data(GF_RTPStreamer *streamer, u8 *data, u32 size, u32 fullsize, u64 cts, u64 dts, Bool is_rap, Bool au_start, Bool au_end, u32 au_sn, u32 duration, u32 sampleDescriptionIndex); + +/*! formats a generic SDP header +\param app_name application name, may be NULL +\param ip_dest destination IP address, shall NOT be NULL +\param session_name session name, may be NULL +\param iod64 base64 encoded MPEG-4 IOD, may be NULL +\return the SDP header - shall be destroyed by caller +*/ +char *gf_rtp_streamer_format_sdp_header(char *app_name, char *ip_dest, char *session_name, char *iod64); + +/*! disables RTCP keepalive +\param streamer the target RTP streamer +*/ +void gf_rtp_streamer_disable_auto_rtcp(GF_RTPStreamer *streamer); + +/*! sends RTCP bye packet +\param streamer the target RTP streamer +\return error if any +*/ +GF_Err gf_rtp_streamer_send_bye(GF_RTPStreamer *streamer); + +/*! sends RTCP server report +\param streamer the target RTP streamer +\param force_ts if GF_TRUE, forces using the indicatet ts +\param rtp_ts the force RTP timestamp to use +\param force_ntp_type if 0, computes NTP while sending. If 1 or 2, uses ntp_sec and ntp_frac for report. If 2, forces sending reports right away +\param ntp_sec NTP seconds +\param ntp_frac NTP fractional part +\return error if any +*/ +GF_Err gf_rtp_streamer_send_rtcp(GF_RTPStreamer *streamer, Bool force_ts, u32 rtp_ts, u32 force_ntp_type, u32 ntp_sec, u32 ntp_frac); + +/*! gets associated payload type +\param streamer the target RTP streamer +\return the payload type +*/ +u8 gf_rtp_streamer_get_payload_type(GF_RTPStreamer *streamer); + +/*! initializes the RTP channel for RTSP setup +\param streamer the target RTP streamer +\param path_mtu MTU path size in bytes +\param tr the RTSP transport description +\param ifce_addr IP address of network interface to use +\return error if any +*/ +GF_Err gf_rtp_streamer_init_rtsp(GF_RTPStreamer *streamer, u32 path_mtu, GF_RTSPTransport *tr, const char *ifce_addr); + +/*! gets the sequence number of the next RTP packet to be sent +\param streamer the target RTP streamer +\return the sequence number +*/ +u16 gf_rtp_streamer_get_next_rtp_sn(GF_RTPStreamer *streamer); + +/*! sets callback functions for RTP over RTSP sending +\param streamer the target RTP streamer +\param RTP_TCPCallback the callback function +\param cbk1 first opaque data passed to callback function +\param cbk2 second opaque data passed to callback function +\return error if any +*/ +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); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif //GPAC_DISABLE_ISOM && GPAC_DISABLE_STREAMING + +#endif /*_GF_RTPSTREAMER_H_*/ + diff --git a/include/gpac/scene_engine.h b/include/gpac/scene_engine.h new file mode 100644 index 0000000..b23fafd --- /dev/null +++ b/include/gpac/scene_engine.h @@ -0,0 +1,175 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Cyril Concolato - Jean le Feuvre + * Copyright (c) Telecom ParisTech 2005-2019 + * All rights reserved + * + * This file is part of GPAC + * + * 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_SCENEENGINE_H_ +#define _GF_SCENEENGINE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/scene_engine.h> +\brief Live scene encoding engine with RAP generation support. +*/ + +/*! +\addtogroup seng Scene Engine +\ingroup scene_grp +\brief Live scene encoding engine with RAP generation support. + +This section documents the live scene encoding tools of GPAC. + +@{ + */ + +#include <gpac/scene_manager.h> + +#ifndef GPAC_DISABLE_SENG + +/*! scene encoding engine object*/ +typedef struct __tag_scene_engine GF_SceneEngine; + +/*! callback function prototype for scene engine*/ +typedef void (*gf_seng_callback)(void *udta, u16 ESID, u8 *data, u32 size, u64 ts); + +/*! creates a scene engine +\param calling_object is the calling object on which call back will be called +\param inputContext is the name of a scene file (bt, xmt or mp4) to initialize the coding context +\param load_type is the preferred loader type for the content (e.g. SVG vs DIMS) +\param dump_path is the path where scenes are dumped +\param embed_resources indicates if images and scripts should be encoded inline with the content +\return e scene engine object +*/ +GF_SceneEngine *gf_seng_init(void *calling_object, char *inputContext, u32 load_type, char *dump_path, Bool embed_resources); + +/*! get the number of streams in the scene +\param seng the target scene engine +\return the number of streams +*/ +u32 gf_seng_get_stream_count(GF_SceneEngine *seng); + +/*! gets carousel nformation for a stream +\param seng the target scene engine +\param ESID ID of the stream to query +\param carousel_period set to the carousel_period in millisenconds +\param aggregate_on_es_id set to the target carousel stream ID +\return error if any + */ +GF_Err gf_seng_get_stream_carousel_info(GF_SceneEngine *seng, u16 ESID, u32 *carousel_period, u16 *aggregate_on_es_id); + +/*! gets the stream decoder configuration +\param seng the target scene engine +\param idx stream index +\param ESID set to the stream ID +\param config set to the encoded BIFS config (memory is not allocated) +\param config_len length of the buffer +\param streamType pointer to get stream type +\param objectType pointer to get object type +\param timeScale pointer to get time scale +\return error if any +*/ +GF_Err gf_seng_get_stream_config(GF_SceneEngine *seng, u32 idx, u16 *ESID, const u8 **config, u32 *config_len, u32 *streamType, u32 *objectType, u32 *timeScale); + +/*! Encodes the AU context which is not encoded when calling BENC_EncodeAUFromString/File. This should be called after Aggregate. +\param seng the target scene engine +\param callback pointer on a callback function to get the result of the coding the AU using the current context +\return error if any +*/ +GF_Err gf_seng_encode_context(GF_SceneEngine *seng, gf_seng_callback callback); + +/*! Encodes an AU from file +\param seng the target scene engine +\param ESID target streams when no indication is present in the file (eg, no atES_ID ) +\param disable_aggregation do not aggregate the access unit on its target stream +\param auFile name of a file containing a description for an access unit (BT or XMT) +\param callback pointer on a callback function to get the result of the coding the AU using the current context +\return error if any +*/ +GF_Err gf_seng_encode_from_file(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, char *auFile, gf_seng_callback callback); + +/*! Encodes an AU from string +\param seng the target scene engine +\param ESID target streams when no indication is present in the file (eg, no atES_ID ) +\param disable_aggregation +\param auString a char string to encode (must one or several complete nodes in BT +\param callback pointer on a callback function to get the result of the coding the AU using the current context +\return error if any +*/ +GF_Err gf_seng_encode_from_string(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, char *auString, gf_seng_callback callback); + + +/*! saves the live context to a file. Use BENC_AggregateCurrentContext before to save an aggregated context. +\param seng the target scene engine +\param ctxFileName name of the file to save the current state of the BIFS scene to +\return error if any +*/ +GF_Err gf_seng_save_context(GF_SceneEngine *seng, char *ctxFileName); + +/*! marks the stream as carrying its own "rap" in the first AU of the stream +\param seng the target scene engine +\param ESID stream ID +\param onESID set stream aggragation on to the specified stream, or off if onESID is 0 +\return error if any +*/ +GF_Err gf_seng_enable_aggregation(GF_SceneEngine *seng, u16 ESID, u16 onESID); + +/*! aggregates the current context of the seng, creates a scene replace +\param seng the target scene engine +\param ESID stream ID. If not 0, only aggregate commands for this stream; otherwise aggregates for the all streams +\return error if any +*/ +GF_Err gf_seng_aggregate_context(GF_SceneEngine *seng, u16 ESID); + +/*! destroys the scene engine +\param seng the target scene engine +*/ +void gf_seng_terminate(GF_SceneEngine *seng); + +/*! encodes the IOD for this scene engine into Base64 +\param seng the target scene engine +\return the base64 encoded IOD, shall be freed by the caller + */ +char *gf_seng_get_base64_iod(GF_SceneEngine *seng); + +/*! returns the IOD for a scene engine +\param seng the target scene engine +\return the IOD object - shall be destroyed by caller +*/ +GF_Descriptor *gf_seng_get_iod(GF_SceneEngine *seng); + +#endif /*GPAC_DISABLE_SENG*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif // __cplusplus + + +#endif /*_GF_SCENEENGINE_H_*/ + diff --git a/include/gpac/scene_manager.h b/include/gpac/scene_manager.h new file mode 100644 index 0000000..c85d86e --- /dev/null +++ b/include/gpac/scene_manager.h @@ -0,0 +1,583 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Authoring 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_SCENE_MANAGER_H_ +#define _GF_SCENE_MANAGER_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/scene_manager.h> +\brief Scene management for importing/encoding of BIFS, XMT, LASeR scenes +*/ + +/*! +\addtogroup smgr Scene Manager +\ingroup scene_grp +\brief Scene management for importing/encoding of BIFS, XMT, LASeR scenes. + +This section documents the Scene manager used for importing/encoding of BIFS, XMT, LASeR scenes. + +@{ + */ + +#include <gpac/isomedia.h> +#include <gpac/mpeg4_odf.h> +#include <gpac/scenegraph_vrml.h> + +/* + Memory scene management + +*/ + +/*! Node data type check. This handles prototype nodes, and assumes undefined nodes always belong to the desired NDT +\param node the node to check +\param NDTType the parent NDT type +\return GF_TRUE if node belongs to given NDT. +*/ +Bool gf_node_in_table(GF_Node *node, u32 NDTType); + +/*! Scene manager AU flags*/ +enum +{ + /*AU is RAP - random access indication - may be overridden by encoder*/ + GF_SM_AU_RAP = 1, + /*AU will not be aggregated (only used by scene engine)*/ + GF_SM_AU_NOT_AGGREGATED = 1<<1, + /*AU has been modified (only used by scene engine)*/ + GF_SM_AU_MODIFIED = 1<<2, + /*AU is a carousel (only used by scene engine)*/ + GF_SM_AU_CAROUSEL = 1<<3 +}; + +/*! Generic systems access unit context*/ +typedef struct +{ + /*AU timing in TimeStampResolution*/ + u64 timing; + /*timing in sec - used if timing isn't set*/ + Double timing_sec; + /*opaque command list per stream type*/ + GF_List *commands; + + u32 flags; + + /*pointer to owner stream*/ + struct _stream_context *owner; +} GF_AUContext; + +/*! Generic stream context*/ +typedef struct _stream_context +{ + /*ESID of stream, or 0 if unknown in which case it is automatically updated at encode stage*/ + u16 ESID; + /*stream name if any (XMT), NULL otherwise*/ + char *name; + + /*stream type - used as a hint, the encoder(s) may override it*/ + u8 streamType; + u32 codec_id; + u32 timeScale; + GF_List *AUs; + + /*last stream AU time, when playing the context directly*/ + u64 last_au_time; + /*set if stream is part of root OD (playback only)*/ + Bool in_root_od; + /*number of previous AUs (used in live scene encoder only)*/ + u32 current_au_count; + u8 *dec_cfg; + u32 dec_cfg_len; + + /*time offset when exporting (dumping), max AU time created when importing*/ + u64 imp_exp_time; + + u16 aggregate_on_esid; + u32 carousel_period; + Bool disable_aggregation; +} GF_StreamContext; + +/*! Generic presentation context*/ +typedef struct +{ + /*the one and only scene graph used by the scene manager.*/ + GF_SceneGraph *scene_graph; + + /*all systems streams used in presentation*/ + GF_List *streams; + /*(initial) object descriptor if any - if not set the encoder will generate it*/ + GF_ObjectDescriptor *root_od; + + /*scene resolution*/ + u32 scene_width, scene_height; + Bool is_pixel_metrics; + + /*BIFS encoding - these is needed for: + - protos in protos which define subscene graph, hence separate namespace, but are coded with the same IDs + - route insertions which are not tracked by the scene graph + we could do this by hand (counting protos & route insert) but let's be lazy + */ + u32 max_node_id, max_route_id, max_proto_id; +} GF_SceneManager; + +/*! scene manager constructor +\param scene_graph scene graph used by the manager +\return a new scene manager object*/ +GF_SceneManager *gf_sm_new(GF_SceneGraph *scene_graph); +/*! scene manager destructor - does not destroy the attached scene graph +\param sman the target scene manager*/ +void gf_sm_del(GF_SceneManager *sman); +/*! retrive or create a stream context in the presentation context +\warning if a stream with the same streamType and no ESID already exists in the context, it is assigned the requested ES_ID - this is needed to solve base layer +\param sman the target scene manager +\param ES_ID ID of the new stream +\param streamType stream type of the new stream +\param codec_id codec ID of the new stream +\return a new scene manager stream context +*/ +GF_StreamContext *gf_sm_stream_new(GF_SceneManager *sman, u16 ES_ID, u8 streamType, u32 codec_id); +/*! locates a stream based on its id +\param sman the target scene manager +\param ES_ID ID of the new stream +\return the stream context or NULL if not found +*/ +GF_StreamContext *gf_sm_stream_find(GF_SceneManager *sman, u16 ES_ID); +/*! create a new AU context in the given stream context +\param stream the traget stream context +\param timing the timing in stream timescale +\param time_ms the timing in ms +\param isRap indicates of the AU is a RAP or not +\return a new scene manager AU context +*/ +GF_AUContext *gf_sm_stream_au_new(GF_StreamContext *stream, u64 timing, Double time_ms, Bool isRap); + +/*! locates a MuxInfo descriptor in an EDD +\param src the target ESD +\return a mux info descriptor or NULL if none found*/ +GF_MuxInfo *gf_sm_get_mux_info(GF_ESD *src); + +/*! applies all commands in all streams (only BIFS for now): the context manager will only have maximum one AU per +stream, this AU being a random access for the stream + +\param sman the target scene manager +\param ESID if set, aggregation is only performed on the given stream, otherwise on all streams +\return error if any +*/ +GF_Err gf_sm_aggregate(GF_SceneManager *sman, u16 ESID); + +/*! translates SRT/SUB (TTXT not supported) source into BIFS command stream source + +\param sman the target scene manager +\param src GF_ESD of new stream (MUST be created before to store TS resolution) +\param mux GF_MuxInfo of src stream - shall contain a valid file, and at least the textNode member set +\return error if any +*/ +GF_Err gf_sm_import_bifs_subtitle(GF_SceneManager *sman, GF_ESD *src, GF_MuxInfo *mux); + + +/*! SWF to MPEG-4 flags*/ +enum +{ + /*all data in dictionary is in first frame*/ + GF_SM_SWF_STATIC_DICT = 1, + /*remove all text*/ + GF_SM_SWF_NO_TEXT = (1<<1), + /*remove embedded fonts (force device font usage)*/ + GF_SM_SWF_NO_FONT = (1<<2), + /*forces XCurve2D which supports quadratic bezier*/ + GF_SM_SWF_QUAD_CURVE = (1<<3), + /*forces line remove*/ + GF_SM_SWF_NO_LINE = (1<<4), + /*forces XLineProperties (supports scalable lines)*/ + GF_SM_SWF_SCALABLE_LINE = (1<<5), + /*forces gradient remove (using center color) */ + GF_SM_SWF_NO_GRADIENT = (1<<6), + /*use a dedicated BIFS stream to control display list. This allows positioning in the movie + (jump to frame, etc..) as well as looping from inside the movie (set by default)*/ + GF_SM_SWF_SPLIT_TIMELINE = (1<<7), + /*enable appearance reuse*/ + GF_SM_SWF_REUSE_APPEARANCE = (1<<9), + /*enable IndexedCurve2D proto*/ + GF_SM_SWF_USE_IC2D = (1<<10), + /*enable SVG output*/ + GF_SM_SWF_USE_SVG = (1<<11), +}; + +/*! general loader flags*/ +enum +{ + /*if set, always load MPEG-4 nodes, otherwise X3D versions are used for vrml/x3d*/ + GF_SM_LOAD_MPEG4_STRICT = 1, + /*signal loading is done for playback: + scrips will be queued in their parent command for later loading + SFTime (MPEG-4 only) fields will be handled correctly when inserting/creating nodes based on AU timing + */ + GF_SM_LOAD_FOR_PLAYBACK = 1<<1, + + /*special flag indicating that the context is already loaded & valid (eg no default stream creations & co) + this is used when performing diff encoding (eg the file to load only has updates). + When set, gf_sm_load_init will NOT attempt to parse first frame*/ + GF_SM_LOAD_CONTEXT_READY = 1<<2, + + /* in this mode, each root svg tag will be interpreted as a REPLACE SCENE */ + GF_SM_LOAD_CONTEXT_STREAMING = 1<<3, + /*indicates that external resources in the content should be embedded as if possible*/ + GF_SM_LOAD_EMBEDS_RES = 1<<4 +}; + +/*! loader type, usually detected based on file ext*/ +typedef enum +{ + GF_SM_LOAD_BT = 1, /*BT loader*/ + GF_SM_LOAD_VRML, /*VRML97 loader*/ + GF_SM_LOAD_X3DV, /*X3D VRML loader*/ + GF_SM_LOAD_XMTA, /*XMT-A loader*/ + GF_SM_LOAD_X3D, /*X3D XML loader*/ + GF_SM_LOAD_SVG, /*SVG loader*/ + GF_SM_LOAD_XSR, /*LASeR+XML loader*/ + GF_SM_LOAD_DIMS, /*DIMS LASeR+XML loader*/ + GF_SM_LOAD_SWF, /*SWF->MPEG-4 converter*/ + GF_SM_LOAD_QT, /*MOV->MPEG-4 converter (only cubic QTVR for now)*/ + GF_SM_LOAD_MP4, /*MP4 memory loader*/ +} GF_SceneManager_LoadType; + +/*! Scenegraph loader*/ +typedef struct __scene_loader +{ + /*! loader type, one of the above value. If not set, detected based on file extension*/ + GF_SceneManager_LoadType type; + + /*! scene graph worked on - may be NULL if ctx is present*/ + GF_SceneGraph *scene_graph; + /*! inline scene*/ + struct _gf_scene *is; + + /*! context manager to load (MUST BE RESETED BEFORE if needed) - may be NULL for loaders not using commands, + in which case the graph will be directly updated*/ + GF_SceneManager *ctx; + /*! file to import except IsoMedia files*/ + const char *fileName; + //! original URL for the file or NULL if same as fileName + const char *src_url; +#ifndef GPAC_DISABLE_ISOM + /*! IsoMedia file to import (we need to be able to load from an opened file for scene stats)*/ + GF_ISOFile *isom; +#endif + /*! swf import flags*/ + u32 swf_import_flags; + /*! swf flatten limit: angle limit below which 2 lines are considered as aligned, + in which case the lines are merged as one. If 0, no flattening happens*/ + Float swf_flatten_limit; + /*! swf extraction path: if set, swf media (mp3, jpeg) are extracted to this path. If not set + media are extracted to original file directory*/ + const char *localPath; + + /*! carrying svgOutFile when the loader is used by a SceneDumper */ + const char *svgOutFile; + + /*! loader flags*/ + u32 flags; + + /*! force stream ID*/ + u16 force_es_id; + +//! @cond Doxygen_Suppress + /*private to loader*/ + void *loader_priv; + GF_Err (*process)(struct __scene_loader *loader); + void (*done)(struct __scene_loader *loader); + GF_Err (*parse_string)(struct __scene_loader *loader, const char *str); + GF_Err (*suspend)(struct __scene_loader *loader, Bool suspend); +//! @endcond + +} GF_SceneLoader; + +/*! initializes the context loader - this will load any IOD and the first frame of the main scene +\param sload a preallocated, uninitialized loader +\return error if any +*/ +GF_Err gf_sm_load_init(GF_SceneLoader *sload); +/*! completely loads context +\param sload the target scene loader +\return error if any +*/ +GF_Err gf_sm_load_run(GF_SceneLoader *sload); +/*! terminates the context loader +\param sload the target scene loader +*/ +void gf_sm_load_done(GF_SceneLoader *sload); + +/*! parses memory scene (any textural format) into the context +\warning LOADER TYPE MUST BE ASSIGNED (BT/WRL/XMT/X3D/SVG only) + +\param sload the target scene loader +\param str the string to load; MUST be at least 4 bytes long in order to detect BOM (unicode encoding); can be either UTF-8 or UTF-16 data +\param clean_at_end if GF_TRUE, associated parser is terminated. Otherwise, a call to gf_sm_load_done must be done to clean resources (needed for SAX progressive loading) +\return error if any +*/ +GF_Err gf_sm_load_string(GF_SceneLoader *sload, const char *str, Bool clean_at_end); + +#ifndef GPAC_DISABLE_SCENE_ENCODER + +/*! encoding flags*/ +enum +{ + /*if flag set, DEF names are encoded*/ + GF_SM_ENCODE_USE_NAMES = 1, + /*if flag set, RAP are generated inband rather than as redundant samples*/ + GF_SM_ENCODE_RAP_INBAND = 2, + /*if flag set, RAP are generated inband rather than as sync shadow samples*/ + GF_SM_ENCODE_RAP_SHADOW = 4, +}; + +/*! Scenegraph Encoder options*/ +typedef struct +{ + /*encoding flags*/ + u32 flags; + /*delay between 2 RAP in ms. If 0 RAPs are not forced - BIFS and LASeR only for now*/ + u32 rap_freq; + /*if set, any unknown stream in the scene will be looked for in @mediaSource (MP4 only)*/ + char *mediaSource; + /*LASeR */ + /*resolution*/ + s32 resolution; + /*coordBits, scaleBits*/ + u32 coord_bits, scale_bits; + /*auto quantification type: + 0: none + 1: LASeR + 2: BIFS + */ + u32 auto_quant; + + const char *src_url; +} GF_SMEncodeOptions; + +/*! encodes scene context into a destination MP4 file +\param sman the target scene manager +\param mp4 the destination ISOBMFF file +\param opt the encoding options +\return error if any +*/ +GF_Err gf_sm_encode_to_file(GF_SceneManager *sman, GF_ISOFile *mp4, GF_SMEncodeOptions *opt); + +#endif /*GPAC_DISABLE_SCENE_ENCODER*/ + + +/*Dumping tools*/ +#ifndef GPAC_DISABLE_SCENE_DUMP + +/*! Scenegraph dump mode*/ +typedef enum +{ + /*BT*/ + GF_SM_DUMP_BT = 0, + /*XMT-A*/ + GF_SM_DUMP_XMTA, + /*VRML Text (WRL)*/ + GF_SM_DUMP_VRML, + /*X3D Text (x3dv)*/ + GF_SM_DUMP_X3D_VRML, + /*X3D XML*/ + GF_SM_DUMP_X3D_XML, + /*LASeR XML*/ + GF_SM_DUMP_LASER, + /*SVG dump (only dumps svg root of the first LASeR unit*/ + GF_SM_DUMP_SVG, + /*blind XML dump*/ + GF_SM_DUMP_XML, + /*automatic selection of MPEG4 vs X3D, text mode*/ + GF_SM_DUMP_AUTO_TXT, + /*automatic selection of MPEG4 vs X3D, xml mode*/ + GF_SM_DUMP_AUTO_XML, + /* disables dumping the scene */ + GF_SM_DUMP_NONE +} GF_SceneDumpFormat; + +/*! dumps scene context to a given format +\param sman the target scene manager +\param rad_name file name & loc without extension - if NULL dump will happen in stdout +\param is_final_name if set, no extension is added to the filename +\param dump_mode scene dum mode +\return error if any +*/ +GF_Err gf_sm_dump(GF_SceneManager *sman, char *rad_name, Bool is_final_name, GF_SceneDumpFormat dump_mode); + +/*! Scenegraph dumper*/ +typedef struct _scenedump GF_SceneDumper; + +/*! creates a scene dumper +\param graph scene graph being dumped +\param rad_name file radical (NULL for stdout) - if not NULL MUST BE GF_MAX_PATH length +\param is_final_name if set, rad_name is the final name (no extension added) +\param indent_char indent format +\param dump_mode if set, dumps in XML format otherwise regular text +\return a new scene dumper object, or NULL if error +*/ +GF_SceneDumper *gf_sm_dumper_new(GF_SceneGraph *graph, char *rad_name, Bool is_final_name, char indent_char, GF_SceneDumpFormat dump_mode); +/*! destroys a scene dumper +\param sdump the target scene dumper*/ +void gf_sm_dumper_del(GF_SceneDumper *sdump); +/*! adds extra graph to scene dumper (all graphs will be dumped) +\param sdump the target scene dumper +\param extra scene graph to be added +*/ +void gf_sm_dumper_set_extra_graph(GF_SceneDumper *sdump, GF_SceneGraph *extra); + +/*! gets a pointer to the filename (rad+ext) of the dumped file +\param sdump the target scene dumper +\return null if no file has been dumped +*/ +char *gf_sm_dump_get_name(GF_SceneDumper *sdump); + +/*! dumps commands list +\param sdump the target scene dumper +\param comList list of commands to dump +\param indent indent to use +\param skip_first_replace if GF_TRUE and dumping in BT mode, the initial REPLACE SCENE header is skipped +\return error if any +*/ +GF_Err gf_sm_dump_command_list(GF_SceneDumper *sdump, GF_List *comList, u32 indent, Bool skip_first_replace); + +/*! dumps complete graph +\param sdump the target scene dumper +\param skip_proto proto declarations are skipped +\param skip_routes routes are not dumped +\return error if any +*/ +GF_Err gf_sm_dump_graph(GF_SceneDumper *sdump, Bool skip_proto, Bool skip_routes); + +#endif /*GPAC_DISABLE_SCENE_DUMP*/ + +#ifndef GPAC_DISABLE_SCENE_STATS + +/*! Node statistics object*/ +typedef struct +{ + /*node type or protoID*/ + u32 tag; + const char *name; + /*number of created nodes*/ + u32 nb_created; + /*number of used nodes*/ + u32 nb_used; + /*number of used nodes*/ + u32 nb_del; +} GF_NodeStats; + +/*! Scene statistics object*/ +typedef struct _scenestat +{ + GF_List *node_stats; + + GF_List *proto_stats; + + /*ranges of all SFVec2fs for points only (MFVec2fs)*/ + SFVec2f max_2d, min_2d; + /* resolution of 2D points (nb bits for integer part and decimal part)*/ + u32 int_res_2d, frac_res_2d; + /* resolution of scale coefficient (nb bits for integer part)*/ + u32 scale_int_res_2d, scale_frac_res_2d; + + Fixed max_fixed, min_fixed; + + /*number of parsed 2D points*/ + u32 count_2d; + /*number of deleted 2D points*/ + u32 rem_2d; + + /*ranges of all SFVec3fs for points only (MFVec3fs)*/ + SFVec3f max_3d, min_3d; + u32 count_3d; + /*number of deleted 3D points*/ + u32 rem_3d; + + u32 count_float, rem_float; + u32 count_color, rem_color; + /*all SFVec2f other than MFVec2fs elements*/ + u32 count_2f; + /*all SFVec3f other than MFVec3fs elements*/ + u32 count_3f; + + u32 nb_svg_attributes; + + GF_StreamContext *base_layer; +} GF_SceneStatistics; + +/*! Scenegraph statistics manager*/ +typedef struct _statman GF_StatManager; + +/*! creates new statitistics manager +\return a new statitistics manager*/ +GF_StatManager *gf_sm_stats_new(); +/*! destroys a statitistics manager +\param sstat the target statitistics manager*/ +void gf_sm_stats_del(GF_StatManager *sstat); +/*! resets statistics +\param sstat the target statitistics manager*/ +void gf_sm_stats_reset(GF_StatManager *sstat); + +/*! gets statistics report +\param sstat the target statitistics manager +\return a scene statistic report or NULL if error*/ +const GF_SceneStatistics *gf_sm_stats_get(GF_StatManager *sstat); + +/*! computes statistitics for a complete graph +\param sstat the target statitistics manager +\param sg the scene graph to analyze +\return error if any +*/ +GF_Err gf_sm_stats_for_graph(GF_StatManager *sstat, GF_SceneGraph *sg); + +/*! computes statistitics for the full scene +\param sstat the target statitistics manager +\param sman the scene manager to analyze +\return error if any +*/ +GF_Err gf_sm_stats_for_scene(GF_StatManager *sstat, GF_SceneManager *sman); + +/*! computes statistitics for the given command +\param sstat the target statitistics manager +\param com the scene command to analyze +\return error if any +*/ +GF_Err gf_sm_stats_for_command(GF_StatManager *sstat, GF_Command *com); + +#endif /*GPAC_DISABLE_SCENE_STATS*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_SCENE_MANAGER_H_*/ + diff --git a/include/gpac/scenegraph.h b/include/gpac/scenegraph.h new file mode 100644 index 0000000..c2f1f2d --- /dev/null +++ b/include/gpac/scenegraph.h @@ -0,0 +1,1031 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Scene Graph 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_SCENEGRAPH_H_ +#define _GF_SCENEGRAPH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/scenegraph.h> +\brief Scenegraph used for manipulating scenes (parsing, traversing, cleaning node status, ...) +*/ + + +/*! +\addtogroup scene_grp Scene Graph +\brief Scene graph management. +*/ + +/*! +\addtogroup sscene Base Scenegraph +\ingroup scene_grp +\brief Scenegraph used for manipulating scenes. + +This section documents the Scenegraph used in GPAC for all interactive scenes. + +@{ + */ + + +#include <gpac/list.h> +#include <gpac/maths.h> + +/*! Tags of scene graph nodes +TAG definitions are static, in order to be able to mix nodes from different standard in a single scenegraph. +These TAGs are only used internally (they do not match any binary encoding) +*/ +enum { + /*undefined node: just the base node class, used for parsing*/ + TAG_UndefinedNode = 0, + /*all MPEG-4/VRML/X3D proto instances have this tag*/ + TAG_ProtoNode, + + /*reserved TAG ranges per standard*/ + + /*range for MPEG4*/ + GF_NODE_RANGE_FIRST_MPEG4, + GF_NODE_RANGE_LAST_MPEG4 = GF_NODE_RANGE_FIRST_MPEG4+512, + + /*range for X3D*/ + GF_NODE_RANGE_FIRST_X3D, + GF_NODE_RANGE_LAST_X3D = GF_NODE_RANGE_FIRST_X3D+512, + + /*all nodes after this are always parent nodes*/ + GF_NODE_RANGE_LAST_VRML, + + /*DOM container for BIFS/LASeR/etc updates*/ + TAG_DOMUpdates, + + /*all nodes below MUST be parent nodes*/ + GF_NODE_FIRST_PARENT_NODE_TAG, + + /*DOM text node*/ + TAG_DOMText, + /*all nodes below MUST use the base DOM structure (with dyn attribute list)*/ + GF_NODE_FIRST_DOM_NODE_TAG, + + /*full node*/ + TAG_DOMFullNode = GF_NODE_FIRST_DOM_NODE_TAG, + + /*range for SVG*/ + GF_NODE_RANGE_FIRST_SVG, + GF_NODE_RANGE_LAST_SVG = GF_NODE_RANGE_FIRST_SVG+100, + +}; + + + +/*! macro for defining base node (apply to all nodes)*/ +#define BASE_NODE struct _nodepriv *sgprivate; + +/*! base node type*/ +typedef struct _base_node +{ + BASE_NODE +} GF_Node; + +/*! child storage + This is not integrated in the base node, because of VRML/MPEG-4 USE: a node +may be present at different places in the tree, hence have different "next" siblings.*/ +typedef struct _child_node +{ + struct _child_node *next; + GF_Node *node; +} GF_ChildNodeItem; + +/*! grouping nodes macro + children: list of children SFNodes +*/ +#define CHILDREN \ + struct _child_node *children; + +/*! generic parent node*/ +typedef struct +{ + BASE_NODE + CHILDREN +} GF_ParentNode; + +/*! adds a child to a given container +\param list pointer to target child list +\param n node to add +\return error if any +*/ +GF_Err gf_node_list_add_child(GF_ChildNodeItem **list, GF_Node *n); +/*! adds a child to a given container, updating last position +\param list pointer to target child list +\param n node to add +\param last_child set to position of add child +\return error if any +*/ +GF_Err gf_node_list_add_child_last(GF_ChildNodeItem **list, GF_Node *n, GF_ChildNodeItem **last_child); +/*! inserts a child to a given container - if pos doesn't match, append the child +\param list pointer to target child list +\param n node to insert +\param pos 0-based index at which to insert +\return error if any +*/ +GF_Err gf_node_list_insert_child(GF_ChildNodeItem **list, GF_Node *n, u32 pos); +/*! removes a child to a given container +\param list pointer to target child list +\param n node to remove +\return GF_TRUE if OK, GF_FALSE if not found +*/ +Bool gf_node_list_del_child(GF_ChildNodeItem **list, GF_Node *n); +/*! finds a child in a given container +\param list target child list +\param n node to find +\return 0-based index if found, -1 otherwise +*/ +s32 gf_node_list_find_child(GF_ChildNodeItem *list, GF_Node *n); +/*! finds a child in a given container given its index. if pos is <0, returns the last child +\param list target child list +\param pos 0-based index at which to insert +\return the child or NULL if not found +*/ +GF_Node *gf_node_list_get_child(GF_ChildNodeItem *list, s32 pos); +/*! gets the number of children in a given list +\param list target child list +\return the number of children +*/ +u32 gf_node_list_get_count(GF_ChildNodeItem *list); +/*! removes node at given idx +\param list pointer to target child list +\param pos 0-based index at which to insert +\return the removed node, or NULL if not found +*/ +GF_Node *gf_node_list_del_child_idx(GF_ChildNodeItem **list, u32 pos); + + + +/*! gets tag of node (tag is set upon creation and cannot be modified) +\param n the target node +\return the node tag +*/ +u32 gf_node_get_tag(GF_Node*n); + +/*! set node ID/def +If a different node with the same ID exists, returns error. +You may change the node ID by recalling the function with a different ID value. You may get a node ID by calling \ref gf_sg_get_next_available_node_id + +\param n the target node +\param nodeID ID to set to the node, ignored if 0. +\param nodeDEFName optional readable name (script, MPEGJ). To change the name, recall the function with a different name and the same ID +\return error if any +*/ +GF_Err gf_node_set_id(GF_Node*n, u32 nodeID, const char *nodeDEFName); +/*! gets def name of the node +\param n the target node +\return node name or NULL if not set +*/ +const char *gf_node_get_name(GF_Node*n); +/*! gets def name of the node, or the string representation of the node pointer if not set +\param n the target node +\return node name or NULL if error +*/ +const char *gf_node_get_log_name(GF_Node*n); +/*! gets def ID of the node +\param n the target node +\return the ID of the node, 0 if node not def +*/ +u32 gf_node_get_id(GF_Node*n); +/*! gets node built-in name (eg 'Appearance', ..) +\param n the target node +\return node class name +*/ +const char *gf_node_get_class_name(GF_Node *n); + +/*! unsets the node ID +\param n the target node +\return error if any +*/ +GF_Err gf_node_remove_id(GF_Node *n); + +/*! gets user private of node +\param n the target node +\return user data if any, NULL if error or not assigned +*/ +void *gf_node_get_private(GF_Node*n); +/*! sets user private of node +\param n the target node +\param udta user data to assign to the node +*/ +void gf_node_set_private(GF_Node*n, void *udta); + +/*! node callback function +\param n the target node +\param traverse_state opaque data passed during traversal +\param is_destroy set when the node is about to be destroyed +*/ +typedef void (*gf_sg_node_callback)(GF_Node *n, void *traverse_state, Bool is_destroy); + +/*! sets traversal callback function. If a node has no associated callback, the traversing of the +graph won't propagate below it. It is the app responsability to setup traversing functions as needed +VRML/MPEG4: Instantiated Protos are handled internally as well as interpolators, valuators and scripts +\param n the target node +\param NodeFunction the callback function +\return error if any +*/ +GF_Err gf_node_set_callback_function(GF_Node *n, gf_sg_node_callback NodeFunction); + +/*! registers a node (DEFed or not), specifying parent if any. +A node must be registered whenever used by something (a parent node, a command, whatever) to prevent its +destruction (think of it as a reference counting). +\warning NODES ARE CREATED WITHOUT BEING REGISTERED +\param n the target node +\param parent_node the parent node this node should be registered with +\return error if any +*/ +GF_Err gf_node_register(GF_Node *n, GF_Node *parent_node); + +/*! unregister a node from parent (node may or not be DEF'ed). Parent may be NULL (DEF root node, commands). +This MUST be called whenever a node is destroyed (removed from a parent node) +If this is the last instance of the node, the node is destroyed +\warning NODES ARE CREATED WITHOUT BEING REGISTERED, hence they MUST be registered at least once before +being destroyed +\param n the target node +\param parent_node the parent node this node should be unregistered from +\return error if any +*/ +GF_Err gf_node_unregister(GF_Node *n, GF_Node *parent_node); + +/*! unregisters all children in the given list +\param node the target parent node owning the list +\param childrenlist the list of children to unregister +*/ +void gf_node_unregister_children(GF_Node *node, GF_ChildNodeItem *childrenlist); + +/*! gets all parents of the node and replace the old_node by the new node in all parents +\note if the new node is not DEFed, only the first instance of "old_node" will be replaced, the other ones deleted +\param old_node old node to replace +\param new_node new node to replace with +\param updateOrderedGroup if GF_TRUE, update the order field of parent OrderdedGroup nodes +\return error if any +*/ +GF_Err gf_node_replace(GF_Node *old_node, GF_Node *new_node, Bool updateOrderedGroup); + +/*! gets the number of node instances +\param n the target node +\return the number of node instances +*/ +u32 gf_node_get_num_instances(GF_Node *n); + + +/*! calls node traverse callback routine on this node +\param n the target node +\param udta opaque data passed to the node traverse callback +*/ +void gf_node_traverse(GF_Node *n, void *udta); + +/*! allows a node to be re-rendered - by default a node in its render callback will never be retraversed a second time. +Use this function to enable a second traverse for this node while traversing it +\param n the target node +*/ +void gf_node_allow_cyclic_traverse(GF_Node *n); + +/*! sets the cyclic traverse flag +\param n the target node +\param on indicates if cyclic traverse shall be turned on or off +\return GF_FALSE if flag was already set, GF_TRUE otherwise*/ +Bool gf_node_set_cyclic_traverse_flag(GF_Node *n, Bool on); + +/*! blindly calls traverse callback on all children nodes +\param n the target node +\param udta opaque data passed to the node traverse callback +*/ +void gf_node_traverse_children(GF_Node *n, void *udta); +/*! get the number of parent for this node (parent are kept regardless of DEF state) +\param n the target node +\return the number of parents*/ +u32 gf_node_get_parent_count(GF_Node *n); +/*! returns desired parent for this node (parent are kept regardless of DEF state) +\param n the target node +\param idx 0-based parent index +\return the parent node, or NULL of error*/ +GF_Node *gf_node_get_parent(GF_Node *n, u32 idx); + +/*! checks if a node belongs to a subtree +\param parent the target parent node +\param target the target node +\return GF_TRUE if target is in the subtree below parent, GF_FALSE otherwise*/ +Bool gf_node_parent_of(GF_Node *parent, GF_Node *target); + +/*! node dirty flags*/ +enum +{ + /*flag set whenever a field of the node has been modified*/ + GF_SG_NODE_DIRTY = 1, + /*flag set whenever a child node of this node has been modified + \note Unloaded extern protos always invalidate their parent subgraph to get a chance + of being loaded. It is the user responsability to clear the CHILD_DIRTY flag before traversing + if relying on this flag for sub-tree discarding (eg, culling or similar)*/ + GF_SG_CHILD_DIRTY = 1<<1, + + /*flag set by bindable nodes to indicate a modification of the bindable stack. This is + only used for offscreen rendering of Layer3D*/ + GF_SG_VRML_BINDABLE_DIRTY = 1<<2, + + /*flag set whenever a ColorTransform node is removed from a parent node*/ + GF_SG_VRML_COLOR_DIRTY = 1<<3, + + + /*SVG-specific flags due to mix of geometry and appearance & co attributes*/ + /*SVG geometry changed is the same as base flag*/ + GF_SG_SVG_GEOMETRY_DIRTY = GF_SG_NODE_DIRTY, + GF_SG_SVG_COLOR_DIRTY = 1<<2, + GF_SG_SVG_DISPLAYALIGN_DIRTY = 1<<3, + GF_SG_SVG_FILL_DIRTY = 1<<4, + GF_SG_SVG_FILLOPACITY_DIRTY = 1<<5, + GF_SG_SVG_FILLRULE_DIRTY = 1<<6, + GF_SG_SVG_FONTFAMILY_DIRTY = 1<<7, + GF_SG_SVG_FONTSIZE_DIRTY = 1<<8, + GF_SG_SVG_FONTSTYLE_DIRTY = 1<<9, + GF_SG_SVG_FONTVARIANT_DIRTY = 1<<10, + GF_SG_SVG_FONTWEIGHT_DIRTY = 1<<11, + GF_SG_SVG_LINEINCREMENT_DIRTY = 1<<12, + GF_SG_SVG_OPACITY_DIRTY = 1<<13, + GF_SG_SVG_SOLIDCOLOR_OR_OPACITY_DIRTY = 1<<14, + GF_SG_SVG_STOPCOLOR_OR_OPACITY_DIRTY = 1<<15, + GF_SG_SVG_STROKE_DIRTY = 1<<16, + GF_SG_SVG_STROKEDASHARRAY_DIRTY = 1<<17, + GF_SG_SVG_STROKEDASHOFFSET_DIRTY= 1<<18, + GF_SG_SVG_STROKELINECAP_DIRTY = 1<<19, + GF_SG_SVG_STROKELINEJOIN_DIRTY = 1<<20, + GF_SG_SVG_STROKEMITERLIMIT_DIRTY= 1<<21, + GF_SG_SVG_STROKEOPACITY_DIRTY = 1<<22, + GF_SG_SVG_STROKEWIDTH_DIRTY = 1<<23, + GF_SG_SVG_TEXTPOSITION_DIRTY = 1<<24, + GF_SG_SVG_DISPLAY_DIRTY = 1<<25, + GF_SG_SVG_VECTOREFFECT_DIRTY = 1<<26, + GF_SG_SVG_XLINK_HREF_DIRTY = 1<<27, +}; + +/*! sets dirty flags +\param n the target node +\param flags if 0, sets the base flags on (GF_SG_NODE_DIRTY); otherwise, adds the flags to the node dirty state +\param dirty_parents if GF_TRUE, all parent subtrees for this node are marked as GF_SG_CHILD_DIRTY +\note parent subtree marking aborts if a node in the subtree is already marked with GF_SG_CHILD_DIRTY +which means tat if you never clean the dirty flags, no propagation will take place +*/ +void gf_node_dirty_set(GF_Node *n, u32 flags, Bool dirty_parents); + +/*! marks all parent subtrees for this node as GF_SG_CHILD_DIRTY +\note parent subtree marking aborts if a node in the subtree is already marked with GF_SG_CHILD_DIRTY +which means that if you never clean the dirty flags, no propagation will take place +\param n the target node +*/ +void gf_node_dirty_parents(GF_Node *n); + +/*! sets dirty flag off. It is the user responsability to clear dirty flags +\param n the target node +\param flags if 0, all flags are set off; otherwise, removes the indicated flags from the node dirty state +*/ +void gf_node_dirty_clear(GF_Node *n, u32 flags); + +/*! reset dirty state of a node +\param n the target node +\param reset_children if GF_TRUE and node was dirty, resets the state of all its children +*/ +void gf_node_dirty_reset(GF_Node *n, Bool reset_children); + +/*! gets dirty flag value +\param n the target node +\return the dirty fkags +*/ +u32 gf_node_dirty_get(GF_Node *n); + +/*! VRML/BIFS route object*/ +typedef struct _route GF_Route; + + +/*! Node Field/attribute information for VRML/BIFS/SVG +\note all scene graph implementations should answer node field query with this interface. +In case an implementation does not use this: + - the implementation shall handle the parent node dirty flag itself most of the time + - the implementation shall NOT allow referencing of a graph node in a parent graph node (when inlining +content) otherwise the app is guaranteed to crash. + +other fieldTypes may be ignored by implmentation not using VRML/MPEG4 native types +*/ +typedef struct +{ + /*! 0-based index of the field in the node*/ + u32 fieldIndex; + /*! field type - VRML/MPEG4 types are listed in scenegraph_vrml.h*/ + u32 fieldType; + /*! far ptr to the field (eg GF_Node **, GF_List**, MFInt32 *, ...)*/ + void *far_ptr; + /*! field name*/ + const char *name; + /*! NDT type in case of SF/MFNode field - cf BIFS specific tools*/ + u32 NDTtype; + /*! event type*/ + u32 eventType; + /*! eventin handler if any*/ + void (*on_event_in)(GF_Node *pNode, GF_Route *from_route); +} GF_FieldInfo; + +/*! returns number of field for this node. +For BIFS/VRML/X3D, this is the number of defined fields by the spec. +For SVG/DOM, this is the number of attributes defined for the node. +\param n the target node +\return the number of defined fields +*/ +u32 gf_node_get_field_count(GF_Node *n); + +/*! fills the field info structure for the given field +\param n the target node +\param FieldIndex the 0-based index of the target field +\param info filled with field info +\return error if any +*/ +GF_Err gf_node_get_field(GF_Node *n, u32 FieldIndex, GF_FieldInfo *info); + +/*! gets the field by its name +\param n the target node +\param name name of the target field +\param field filled with field info +\return error if any +*/ +GF_Err gf_node_get_field_by_name(GF_Node *n, char *name, GF_FieldInfo *field); + +/*! Scenegraph structure*/ +typedef struct __tag_scene_graph GF_SceneGraph; + +/*! creates a new scene graph +\return a new scene graph +*/ +GF_SceneGraph *gf_sg_new(); + +/*! creates a sub scene graph (typically used with Inline node): independent graph with same private stack, +and user callbacks as parent. All routes triggered in this subgraph are executed in the parent graph (this +means you only have to activate routes on the main graph) +\note The resulting graph is not destroyed when the parent graph is +\param scene the parent scene graph +\return a new scene graph +*/ +GF_SceneGraph *gf_sg_new_subscene(GF_SceneGraph *scene); + +/*! destroys a scene graph +\param sg the target scene graph +*/ +void gf_sg_del(GF_SceneGraph *sg); +/*! resets the graph - all nodes, routes and protos are destroyed +\param sg the target scene graph +*/ +void gf_sg_reset(GF_SceneGraph *sg); + +/*! sets user private data for scene graph +\param sg the target scene graph +\param udta user private data to set +*/ +void gf_sg_set_private(GF_SceneGraph *sg, void *udta); +/*! gets user private data of scene graph +\param sg the target scene graph +\return user private data +*/ +void *gf_sg_get_private(GF_SceneGraph *sg); + +/*! sets the scene time query callback (functions returns time in sec) +\param sg the target scene graph +\param GetSceneTime the scene time query callback +*/ +void gf_sg_set_scene_time_callback(GF_SceneGraph *sg, Double (*GetSceneTime)(void *user_priv) ); + +/*! gets the parent scene graph of a graph +\param sg the target scene graph +\return the parent graph or NULL if none defined +*/ +GF_SceneGraph *gf_sg_get_parent(GF_SceneGraph *sg); + +/*! node callback type*/ +typedef enum +{ + /*function called upon node creation. + ctxdata is not used*/ + GF_SG_CALLBACK_INIT = 0, + /*function called upon node modification. You typically will set some of the dirty flags here. + ctxdata is the fieldInfo pointer of the modified field*/ + GF_SG_CALLBACK_MODIFIED, + /*function called when the a "set dirty" propagates to root node of the graph + ctxdata is not used*/ + GF_SG_CALLBACK_GRAPH_DIRTY, + //node is being destroyed + GF_SG_CALLBACK_NODE_DESTROY, +} GF_SGNodeCbkType; + +/*! node callback function for scene graph +\param udta user private data of scene graph, see \ref gf_sg_set_private +\param type the type of callback +\param node the target node for the callback +\param ctxdata associated data, type depends on the callback type +*/ +typedef void (*gf_sg_node_init_callback)(void *udta, GF_SGNodeCbkType type, GF_Node *node, void *ctxdata); + +/*! sets node callback: function called upon node creation. +Application should instantiate the node rendering stack and any desired callback +\param sg the target scene graph +\param NodeCallback the node callback function +*/ +void gf_sg_set_node_callback(GF_SceneGraph *sg, gf_sg_node_init_callback NodeCallback); + +/*! gets the root node of the graph +\param sg the target scene graph +\return root node of the scene graph, NULL otherwise +*/ +GF_Node *gf_sg_get_root_node(GF_SceneGraph *sg); +/*! sets the root node of the graph +\param sg the target scene graph +\param node root node of the scene graph +*/ +void gf_sg_set_root_node(GF_SceneGraph *sg, GF_Node *node); + +/*! finds a registered node by ID +\param sg the target scene graph +\param nodeID ID of the node to find +\return node if found, NULL otherwise +*/ +GF_Node *gf_sg_find_node(GF_SceneGraph *sg, u32 nodeID); +/*! finds a registered node by DEF name +\param sg the target scene graph +\param name name of the node to find +\return node if found, NULL otherwise +*/ +GF_Node *gf_sg_find_node_by_name(GF_SceneGraph *sg, char *name); + +/*! signals node has been modified, indicating which field is modified +\note this is exposed for BIFS codec and BIFS/VRML rendering, it should not be needed by other apps +\param n the target node +\param fieldChanged the modified field, may be NULL of global node modification +*/ +void gf_node_changed(GF_Node *n, GF_FieldInfo *fieldChanged); + +/*! gets the scene graph of a node +\param n the target node +\return the parent scene graph +*/ +GF_SceneGraph *gf_node_get_graph(GF_Node *n); + +/*! sets size info for the graph - by default graphs have no size and are in meter metrics (VRML like) +if any of width or height is 0, the graph has no size info +\param sg the target scene graph +\param width width in pixels +\param height height in pixels +\param usePixelMetrics indicates coordinates in graph are given in pixels +*/ +void gf_sg_set_scene_size_info(GF_SceneGraph *sg, u32 width, u32 height, Bool usePixelMetrics); +/*! checks if pixel metrics is used +\param sg the target scene graph +\return GF_TRUE if pixelMetrics*/ +Bool gf_sg_use_pixel_metrics(GF_SceneGraph *sg); + +/*! gets size information of graph +\param sg the target scene graph +\param width set to width of scene +\param height set to height of scene +\return GF_FALSE if no size info, otherwise GF_TRUE and set width/height*/ +Bool gf_sg_get_scene_size_info(GF_SceneGraph *sg, u32 *width, u32 *height); + +/*! creates a node of the given tag. sg is the parent scenegraph of the node, +eg the root one for scene nodes or the proto one for proto code (cf proto) +\note + - NODE IS NOT REGISTERED (no instances) AND CANNOT BE DESTROYED UNTIL REGISTERED + - this doesn't perform application setup for the node, this must be done by the caller + +\param sg the target scene graph +\param tag tag of node to create +\return new node +*/ +GF_Node *gf_node_new(GF_SceneGraph *sg, u32 tag); +/*! inits node (either internal stack or user-defined) - usually called once the node has been fully loaded +\param n the target node +*/ +void gf_node_init(GF_Node *n); + +/*! clones a node in the given graph and register with parent cloned. +\param inScene the target scene graph in which the node should be cloned +\param orig the target node to clone +\param cloned_parent the parent of the node to clone +\param id_suffix if NULL, all IDs are removed from the cloned subtree, (each node instance will become a hard copy). If empty string "", ID will be kept exactly as they where in the original subtree (this may lead to errors due to the presence of the same ID depending on the standard). Otherwise, all IDs are translated ($(name) -> $(name)id_suffix) and binary IDs are generated on the fly +\param deep clones children as well +\return the cloned node +*/ +GF_Node *gf_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *id_suffix, Bool deep); + +/*! gets scene time for scene this node belongs too +\param n the target node +\return the scene time for the node, 0 if timeline not specified*/ +Double gf_node_get_scene_time(GF_Node *n); + +/*! gets next available NodeID +\param sg the target scene graph +\return next available NodeID +*/ +u32 gf_sg_get_next_available_node_id(GF_SceneGraph *sg); +/*! gets max ID used in graph +\param sg the target scene graph +\return max NodeID +*/ +u32 gf_sg_get_max_node_id(GF_SceneGraph *sg); + +/*! gets node ID and name +\param n the target node +\param ID set to the node ID or to 0 if not defined +\return the node name or NULL if not defined +*/ +const char *gf_node_get_name_and_id(GF_Node*n, u32 *ID); + +/*! SVG focus types*/ +enum +{ + GF_SG_FOCUS_AUTO = 1, + GF_SG_FOCUS_NEXT, + GF_SG_FOCUS_PREV, + GF_SG_FOCUS_NORTH, + GF_SG_FOCUS_NORTH_EAST, + GF_SG_FOCUS_EAST, + GF_SG_FOCUS_SOUTH_EAST, + GF_SG_FOCUS_SOUTH, + GF_SG_FOCUS_SOUTH_WEST, + GF_SG_FOCUS_WEST, + GF_SG_FOCUS_NORTH_WEST +}; + +/*! JS API Url structure*/ +typedef struct +{ + /*for GF_JSAPI_OP_RESOLVE_URI, + set by caller to the URI to resolve. + If NULL, the return URI is the unresolved parent scene one. + Otherwise, the input URL will be reolved to its local name (eg for ZIP/... packages) + upon return, ALLOCATED by the callee and must be freed by the caller + */ + char *url; + const char **params; + u32 nb_params; +} GF_JSAPIURI; + +/*! JS API structure for GPAC config file*/ +typedef struct +{ + const char *section; + const char *key; + const char *key_val; +} GF_JSAPIOPT; + +/*! JS API structure for script message*/ +typedef struct +{ + GF_Err e; + const char *msg; +} GF_JSAPIINFO; + +/*! JS API parameter type*/ +typedef union +{ + u32 opt; + Fixed val; + GF_Point2D pt; + GF_Rect rc; + Double time; + GF_BBox bbox; + GF_Matrix mx; + GF_JSAPIURI uri; + GF_JSAPIOPT gpac_cfg; + GF_Node *node; + struct __gf_download_manager *dnld_man; + GF_SceneGraph *scene; + void *compositor; + GF_JSAPIINFO info; +} GF_JSAPIParam; + +/*! JS API action types*/ +typedef enum +{ + /*!push message from script engine.*/ + GF_JSAPI_OP_MESSAGE, + /*!resolves a given URI.*/ + GF_JSAPI_OP_RESOLVE_URI, + /*!get current user agent scale.*/ + GF_JSAPI_OP_GET_SCALE, + /*!set current user agent scale.*/ + GF_JSAPI_OP_SET_SCALE, + /*!get current user agent rotation.*/ + GF_JSAPI_OP_GET_ROTATION, + /*!set current user agent rotation.*/ + GF_JSAPI_OP_SET_ROTATION, + /*!get current user agent translation.*/ + GF_JSAPI_OP_GET_TRANSLATE, + /*!set current user agent translation.*/ + GF_JSAPI_OP_SET_TRANSLATE, + /*!get node time.*/ + GF_JSAPI_OP_GET_TIME, + /*!set node time.*/ + GF_JSAPI_OP_SET_TIME, + /*!get current viewport.*/ + GF_JSAPI_OP_GET_VIEWPORT, + /*!get object bounding box in object local coord system.*/ + GF_JSAPI_OP_GET_LOCAL_BBOX, + /*!get object bounding box in world (screen) coord system.*/ + GF_JSAPI_OP_GET_SCREEN_BBOX, + /*!get transform matrix at object.*/ + GF_JSAPI_OP_GET_TRANSFORM, + /*!move focus according to opt value.*/ + GF_JSAPI_OP_MOVE_FOCUS, + /*!set focus to given node.*/ + GF_JSAPI_OP_GET_FOCUS, + /*!set focus to given node.*/ + GF_JSAPI_OP_SET_FOCUS, + /*!replace target scene URL*/ + GF_JSAPI_OP_LOAD_URL, + /*!get option by section and key*/ + GF_JSAPI_OP_GET_OPT, + /*!get option by section and key*/ + GF_JSAPI_OP_SET_OPT, + /*!retrieve download manager*/ + GF_JSAPI_OP_GET_DOWNLOAD_MANAGER, + /*!get navigation speed if any*/ + GF_JSAPI_OP_GET_SPEED, + /*!get current frame rate*/ + GF_JSAPI_OP_GET_FPS, + /*!set current title*/ + GF_JSAPI_OP_SET_TITLE, + /*!gets subscene for current node if any*/ + GF_JSAPI_OP_GET_SUBSCENE, + /*!resolves relative Xlink based on xml:base*/ + GF_JSAPI_OP_RESOLVE_XLINK, + + /*!gets parent filter*/ + GF_JSAPI_OP_GET_COMPOSITOR, + + /*!pauses an SVG element*/ + GF_JSAPI_OP_PAUSE_SVG, + /*!resumes an SVG ELEMENT*/ + GF_JSAPI_OP_RESUME_SVG, + /*!restarts an SVG ELEMENT: this restarts all the media tunning on the main timeline*/ + GF_JSAPI_OP_RESTART_SVG, + /*!sets scene speed*/ + GF_JSAPI_OP_SET_SCENE_SPEED, + /*!gets the DPI*/ + GF_JSAPI_OP_GET_DPI_X, + GF_JSAPI_OP_GET_DPI_Y, +} GF_JSAPIActionType; + +/*! interface to various get/set options: +\param callback opaque data passed to the function +\param type operand type, one of the above +\param node target node, scene root node or NULL +\param param i/o param, depending on operand type +\return GF_TRUE if success, GF_FALSE otherwise +*/ +typedef Bool (*gf_sg_script_action)(void *callback, GF_JSAPIActionType type, GF_Node *node, GF_JSAPIParam *param); + +/*! assigns API to scene graph - by default, sub-graphs inherits the API if set +\param sg the target scene graph +\param script_act the script action callback +\param cbk opaque data to pass to the script action callback +*/ +void gf_sg_set_script_action(GF_SceneGraph *sg, gf_sg_script_action script_act, void *cbk); + +/*! loads script into engine - this should be called only for script in main scene, loading of scripts +in protos is done internally when instanciating the proto +\param script the target script node*/ +void gf_sg_script_load(GF_Node *script); + +/*! checks if scripting is supported in GPAC (built-in and run-time enabled) +\return GF_TRUE if javascript is supported*/ +Bool gf_sg_has_scripting(); + +/*! Scene command tags + +scene graph command tools used for BIFS and LASeR +These are used to store updates in memory without applying changes to the graph, for dumpers, encoders ... +The commands can then be applied through this lib +*/ +enum +{ + GF_SG_RESERVED = 0, + +#ifndef GPAC_DISABLE_VRML + + /*BIFS commands*/ + GF_SG_SCENE_REPLACE, + GF_SG_NODE_REPLACE, + GF_SG_FIELD_REPLACE, + GF_SG_INDEXED_REPLACE, + GF_SG_ROUTE_REPLACE, + GF_SG_NODE_DELETE, + GF_SG_INDEXED_DELETE, + GF_SG_ROUTE_DELETE, + GF_SG_NODE_INSERT, + GF_SG_INDEXED_INSERT, + GF_SG_ROUTE_INSERT, + /*extended updates (BIFS-only)*/ + GF_SG_PROTO_INSERT, + GF_SG_PROTO_DELETE, + GF_SG_PROTO_DELETE_ALL, + GF_SG_MULTIPLE_REPLACE, + GF_SG_MULTIPLE_INDEXED_REPLACE, + GF_SG_GLOBAL_QUANTIZER, + /*same as NodeDelete, and also updates OrderedGroup.order when deleting a child*/ + GF_SG_NODE_DELETE_EX, + + /*BIFS*/ + GF_SG_XREPLACE, + +#endif + GF_SG_LAST_BIFS_COMMAND, + + + /*LASER commands*/ + GF_SG_LSR_NEW_SCENE, + GF_SG_LSR_REFRESH_SCENE, + GF_SG_LSR_ADD, + GF_SG_LSR_CLEAN, + GF_SG_LSR_REPLACE, + GF_SG_LSR_DELETE, + GF_SG_LSR_INSERT, + GF_SG_LSR_RESTORE, + GF_SG_LSR_SAVE, + GF_SG_LSR_SEND_EVENT, + GF_SG_LSR_ACTIVATE, + GF_SG_LSR_DEACTIVATE, + + GF_SG_UNDEFINED +}; + + +/*! structure used to store field info, pos and static pointers to GF_Node/MFNode in commands*/ +typedef struct +{ + u32 fieldIndex; + /*field type*/ + u32 fieldType; + /*field pointer for multiple replace/multiple indexed replace - if multiple indexed replace, must be the SF field being changed*/ + void *field_ptr; + /*replace/insert/delete pos - -1 is append except in multiple indexed replace*/ + s32 pos; + + /*Whenever field pointer is of type GF_Node, store the node here and set the far pointer to this address.*/ + GF_Node *new_node; + /*Whenever field pointer is of type MFNode, store the node list here and set the far pointer to this address.*/ + GF_ChildNodeItem *node_list; +} GF_CommandField; + +/*! structure used to store decoded BIFS command + +\note In order to maintain node registry, the nodes replaced/inserted MUST be registered with + their parents even when the command is never applied. Registering shall be performed + with gf_node_register (see below). + If you fail to do so, a node may be destroyed when destroying a command while still used + in another command or in the graph - this will just crash. + +*/ +typedef struct +{ + GF_SceneGraph *in_scene; + u32 tag; + + /*node the command applies to - may be NULL*/ + GF_Node *node; + + /*list of GF_CommandField for all field commands replace/ index insert / index replace / index delete / MultipleReplace / MultipleIndexedreplace + the content is destroyed when deleting the command*/ + GF_List *command_fields; + + /*may be NULL, and may be present with any command inserting a node*/ + GF_List *scripts_to_load; + /*for authoring purposes - must be cleaned by user*/ + Bool unresolved; + char *unres_name; + + + union { + /*scene replace command: + root node is stored in com->node + protos are stored in com->new_proto_list + routes are stored as RouteInsert in the same frame + BIFS only + */ + Bool use_names; + u32 RouteID; + s32 ChildNodeTag; + }; + + /*proto list to insert - BIFS only*/ + GF_List *new_proto_list; + /*proto ID list to delete - BIFS only*/ + u32 *del_proto_list; + union { + u32 del_proto_list_size; + u32 child_field; + }; + + union { + char *def_name; + char *send_event_string; + }; + union { + //route insertion - fromNodeID is also used to identify operandElementId in LASeR Add/Replace + u32 fromNodeID; + s32 send_event_integer; + }; + union { + u32 fromFieldIndex; + u32 send_event_name; + }; + + union { + u32 toNodeID; + s32 send_event_x; + }; + union { + u32 toFieldIndex; + s32 send_event_y; + }; + Bool aggregated; + /*some commands need to never be applied; for instance when building an aggregate carousel*/ + Bool never_apply; +} GF_Command; + + +/*! creates a command - graph is only needed for SceneReplace +\param sg parent scene graph of the command, only needed for SceneReplace +\param tag the command tag +\return a new command +*/ +GF_Command *gf_sg_command_new(GF_SceneGraph *sg, u32 tag); +/*! destroys a command +\param com the target command +*/ +void gf_sg_command_del(GF_Command *com); +/*! applies command to graph - the command content is kept unchanged for authoring purposes - THIS NEEDS TESTING AND FIXING +\param sg the target scene graph where to apply the command +\param com the target command +\param time_offset time offset in seconds for time fields if desired +\return error if any +*/ +GF_Err gf_sg_command_apply(GF_SceneGraph *sg, GF_Command *com, Double time_offset); + +/*! applies list if command to graph - the command content is kept unchanged for authoring purposes +\param sg the target scene graph where to apply the command +\param comList the list of commands +\param time_offset time offset in seconds for time fields if desired +\return error if any +*/ +GF_Err gf_sg_command_apply_list(GF_SceneGraph *sg, GF_List *comList, Double time_offset); + +/*! creates a new command field structire (some commands may target multiple fields at once) and registers it with command +\param com the parent command +\return new commandFieldInfo structure*/ +GF_CommandField *gf_sg_command_field_new(GF_Command *com); + +/*! XML node from DOM parser */ +typedef struct _xml_node *GF_DOMXMLNODE; + +/*! Creates a GF_SceneGraph from an XML root node. This is mostly used to allow using DOM API on an XML doc + \param document an empty scene graph object + \param root_node the root node of an XML document + \return error if any + */ +GF_Err gf_sg_init_from_xml_node(GF_SceneGraph *document, GF_DOMXMLNODE root_node); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + + +#endif /*_GF_SCENEGRAPH_H_*/ + + diff --git a/include/gpac/scenegraph_svg.h b/include/gpac/scenegraph_svg.h new file mode 100644 index 0000000..82f9280 --- /dev/null +++ b/include/gpac/scenegraph_svg.h @@ -0,0 +1,1167 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre - Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2019 + * All rights reserved + * + * This file is part of GPAC / SVG Scene Graph 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_SG_SVG_H_ +#define _GF_SG_SVG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/scenegraph_svg.h> +\brief Scenegraph for SVG files +*/ + +/*! +\addtogroup ssvg SVG Scenegraph +\ingroup scene_grp +\brief Scenegraph for SVG files. + +This section documents the Scenegraph for SVG files. + +@{ + */ + + +#include <gpac/scenegraph.h> +#include <gpac/svg_types.h> + + +/******************************************************************************* + * + * DOM base scene graph + * + *******************************************************************************/ +/*! DOM attributes tags for xmlspace, xmlev, xlink, SVG and LASeR*/ +enum +{ + /*should never be used, this is only a parsing error*/ + TAG_DOM_ATTRIBUTE_NULL, + /*this tag is set for a full dom attribute only - attribute name is then available*/ + TAG_DOM_ATT_any, + + TAG_XML_ATT_RANGE_FIRST, + TAG_XML_ATT_id = TAG_XML_ATT_RANGE_FIRST, + TAG_XML_ATT_base, + TAG_XML_ATT_lang, + TAG_XML_ATT_space, + TAG_XML_ATT_RANGE_LAST, + TAG_XLINK_ATT_RANGE_FIRST, + + TAG_XLINK_ATT_type = TAG_XLINK_ATT_RANGE_FIRST, + TAG_XLINK_ATT_role, + TAG_XLINK_ATT_arcrole, + TAG_XLINK_ATT_title, + TAG_XLINK_ATT_href, + TAG_XLINK_ATT_show, + TAG_XLINK_ATT_actuate, + TAG_XLINK_ATT_RANGE_LAST, + + TAG_XMLEV_ATT_RANGE_FIRST, + TAG_XMLEV_ATT_event, + TAG_XMLEV_ATT_phase, + TAG_XMLEV_ATT_propagate, + TAG_XMLEV_ATT_defaultAction, + TAG_XMLEV_ATT_observer, + TAG_XMLEV_ATT_target, + TAG_XMLEV_ATT_handler, + TAG_XMLEV_ATT_RANGE_LAST, + + TAG_LSR_ATT_RANGE_FIRST, + TAG_LSR_ATT_enabled, + TAG_LSR_ATT_RANGE_LAST, + /*these attribute types are only use for binary purpose*/ + TAG_LSR_ATT_children, + TAG_LSR_ATT_overflow, + TAG_LSR_ATT_rotation, + TAG_LSR_ATT_scale, + TAG_LSR_ATT_translation, + TAG_LSR_ATT_svg_width, + TAG_LSR_ATT_svg_height, + TAG_LSR_ATT_textContent, + TAG_LSR_ATT_text_display, + + TAG_SVG_ATT_RANGE_FIRST, + + TAG_SVG_ATT_id = TAG_SVG_ATT_RANGE_FIRST, + TAG_SVG_ATT__class, + TAG_SVG_ATT_requiredFeatures, + TAG_SVG_ATT_requiredExtensions, + TAG_SVG_ATT_requiredFormats, + TAG_SVG_ATT_requiredFonts, + TAG_SVG_ATT_systemLanguage, + TAG_SVG_ATT_display, + TAG_SVG_ATT_visibility, + TAG_SVG_ATT_image_rendering, + TAG_SVG_ATT_pointer_events, + TAG_SVG_ATT_shape_rendering, + TAG_SVG_ATT_text_rendering, + TAG_SVG_ATT_audio_level, + TAG_SVG_ATT_viewport_fill, + TAG_SVG_ATT_viewport_fill_opacity, + TAG_SVG_ATT_overflow, + TAG_SVG_ATT_fill_opacity, + TAG_SVG_ATT_stroke_opacity, + TAG_SVG_ATT_fill, + TAG_SVG_ATT_fill_rule, + TAG_SVG_ATT_filter, + TAG_SVG_ATT_stroke, + TAG_SVG_ATT_stroke_dasharray, + TAG_SVG_ATT_stroke_dashoffset, + TAG_SVG_ATT_stroke_linecap, + TAG_SVG_ATT_stroke_linejoin, + TAG_SVG_ATT_stroke_miterlimit, + TAG_SVG_ATT_stroke_width, + TAG_SVG_ATT_color, + TAG_SVG_ATT_color_rendering, + TAG_SVG_ATT_vector_effect, + TAG_SVG_ATT_solid_color, + TAG_SVG_ATT_solid_opacity, + TAG_SVG_ATT_display_align, + TAG_SVG_ATT_line_increment, + TAG_SVG_ATT_stop_color, + TAG_SVG_ATT_stop_opacity, + TAG_SVG_ATT_font_family, + TAG_SVG_ATT_font_size, + TAG_SVG_ATT_font_style, + TAG_SVG_ATT_font_variant, + TAG_SVG_ATT_font_weight, + TAG_SVG_ATT_text_anchor, + TAG_SVG_ATT_text_align, + TAG_SVG_ATT_text_decoration, + TAG_SVG_ATT_focusHighlight, + TAG_SVG_ATT_externalResourcesRequired, + TAG_SVG_ATT_focusable, + TAG_SVG_ATT_nav_next, + TAG_SVG_ATT_nav_prev, + TAG_SVG_ATT_nav_up, + TAG_SVG_ATT_nav_up_right, + TAG_SVG_ATT_nav_right, + TAG_SVG_ATT_nav_down_right, + TAG_SVG_ATT_nav_down, + TAG_SVG_ATT_nav_down_left, + TAG_SVG_ATT_nav_left, + TAG_SVG_ATT_nav_up_left, + TAG_SVG_ATT_transform, + TAG_SVG_ATT_target, + TAG_SVG_ATT_attributeName, + TAG_SVG_ATT_attributeType, + TAG_SVG_ATT_begin, + TAG_SVG_ATT_dur, + TAG_SVG_ATT_end, + TAG_SVG_ATT_repeatCount, + TAG_SVG_ATT_repeatDur, + TAG_SVG_ATT_restart, + TAG_SVG_ATT_smil_fill, + TAG_SVG_ATT_min, + TAG_SVG_ATT_max, + TAG_SVG_ATT_to, + TAG_SVG_ATT_calcMode, + TAG_SVG_ATT_values, + TAG_SVG_ATT_keyTimes, + TAG_SVG_ATT_keySplines, + TAG_SVG_ATT_from, + TAG_SVG_ATT_by, + TAG_SVG_ATT_additive, + TAG_SVG_ATT_accumulate, + TAG_SVG_ATT_path, + TAG_SVG_ATT_keyPoints, + TAG_SVG_ATT_rotate, + TAG_SVG_ATT_origin, + TAG_SVG_ATT_transform_type, + TAG_SVG_ATT_clipBegin, + TAG_SVG_ATT_clipEnd, + TAG_SVG_ATT_syncBehavior, + TAG_SVG_ATT_syncTolerance, + TAG_SVG_ATT_syncMaster, + TAG_SVG_ATT_syncReference, + TAG_SVG_ATT_x, + TAG_SVG_ATT_y, + TAG_SVG_ATT_width, + TAG_SVG_ATT_height, + TAG_SVG_ATT_preserveAspectRatio, + TAG_SVG_ATT_initialVisibility, + TAG_SVG_ATT_type, + TAG_SVG_ATT_cx, + TAG_SVG_ATT_cy, + TAG_SVG_ATT_r, + TAG_SVG_ATT_cursorManager_x, + TAG_SVG_ATT_cursorManager_y, + TAG_SVG_ATT_rx, + TAG_SVG_ATT_ry, + TAG_SVG_ATT_horiz_adv_x, + TAG_SVG_ATT_horiz_origin_x, + TAG_SVG_ATT_font_stretch, + TAG_SVG_ATT_unicode_range, + TAG_SVG_ATT_panose_1, + TAG_SVG_ATT_widths, + TAG_SVG_ATT_bbox, + TAG_SVG_ATT_units_per_em, + TAG_SVG_ATT_stemv, + TAG_SVG_ATT_stemh, + TAG_SVG_ATT_slope, + TAG_SVG_ATT_cap_height, + TAG_SVG_ATT_x_height, + TAG_SVG_ATT_accent_height, + TAG_SVG_ATT_ascent, + TAG_SVG_ATT_descent, + TAG_SVG_ATT_ideographic, + TAG_SVG_ATT_alphabetic, + TAG_SVG_ATT_mathematical, + TAG_SVG_ATT_hanging, + TAG_SVG_ATT_underline_position, + TAG_SVG_ATT_underline_thickness, + TAG_SVG_ATT_strikethrough_position, + TAG_SVG_ATT_strikethrough_thickness, + TAG_SVG_ATT_overline_position, + TAG_SVG_ATT_overline_thickness, + TAG_SVG_ATT_d, + TAG_SVG_ATT_unicode, + TAG_SVG_ATT_glyph_name, + TAG_SVG_ATT_arabic_form, + TAG_SVG_ATT_lang, + TAG_SVG_ATT_u1, + TAG_SVG_ATT_g1, + TAG_SVG_ATT_u2, + TAG_SVG_ATT_g2, + TAG_SVG_ATT_k, + TAG_SVG_ATT_opacity, + TAG_SVG_ATT_x1, + TAG_SVG_ATT_y1, + TAG_SVG_ATT_x2, + TAG_SVG_ATT_y2, + TAG_SVG_ATT_filterUnits, + TAG_SVG_ATT_gradientUnits, + TAG_SVG_ATT_spreadMethod, + TAG_SVG_ATT_gradientTransform, + TAG_SVG_ATT_pathLength, + TAG_SVG_ATT_points, + TAG_SVG_ATT_mediaSize, + TAG_SVG_ATT_mediaTime, + TAG_SVG_ATT_mediaCharacterEncoding, + TAG_SVG_ATT_mediaContentEncodings, + TAG_SVG_ATT_bandwidth, + TAG_SVG_ATT_fx, + TAG_SVG_ATT_fy, + TAG_SVG_ATT_size, + TAG_SVG_ATT_choice, + TAG_SVG_ATT_delta, + TAG_SVG_ATT_offset, + TAG_SVG_ATT_syncBehaviorDefault, + TAG_SVG_ATT_syncToleranceDefault, + TAG_SVG_ATT_viewBox, + TAG_SVG_ATT_zoomAndPan, + TAG_SVG_ATT_version, + TAG_SVG_ATT_baseProfile, + TAG_SVG_ATT_contentScriptType, + TAG_SVG_ATT_snapshotTime, + TAG_SVG_ATT_timelineBegin, + TAG_SVG_ATT_playbackOrder, + TAG_SVG_ATT_editable, + TAG_SVG_ATT_text_x, + TAG_SVG_ATT_text_y, + TAG_SVG_ATT_text_rotate, + TAG_SVG_ATT_transformBehavior, + TAG_SVG_ATT_overlay, + TAG_SVG_ATT_fullscreen, + TAG_SVG_ATT_motionTransform, + TAG_SVG_ATT_clip_path, + + TAG_SVG_ATT_filter_transfer_type, + TAG_SVG_ATT_filter_table_values, + TAG_SVG_ATT_filter_intercept, + TAG_SVG_ATT_filter_amplitude, + TAG_SVG_ATT_filter_exponent, + + + TAG_GSVG_ATT_useAsPrimary, + TAG_GSVG_ATT_depthOffset, + TAG_GSVG_ATT_depthGain, +}; + +/*! macro for DOM base attribute*/ +#define GF_DOM_BASE_ATTRIBUTE \ + u16 tag; /*attribute identifier*/ \ + u16 data_type; /*attribute datatype*/ \ + void *data; /*data pointer*/ \ + struct __dom_base_attribute *next; /*next attribute*/ + +/*! macro for DOM full attribute*/ +#define GF_DOM_FULL_ATTRIBUTE \ + GF_DOM_ATTRIBUTE \ + +/*! DOM attribute*/ +typedef struct __dom_base_attribute +{ + GF_DOM_BASE_ATTRIBUTE +} GF_DOMAttribute; + +/*! DOM full attribute*/ +typedef struct __dom_full_attribute +{ + GF_DOM_BASE_ATTRIBUTE + u32 xmlns; + char *name; /*attribute name - in this case, the data field is the attribute literal value*/ +} GF_DOMFullAttribute; + +/*! macro for DOM base node*/ +#define GF_DOM_BASE_NODE \ + BASE_NODE \ + CHILDREN \ + GF_DOMAttribute *attributes; + +/*! DOM base node*/ +typedef struct __dom_base_node +{ + GF_DOM_BASE_NODE +} GF_DOMNode; + +/*! DOM full node*/ +typedef struct __dom_full_node +{ + GF_DOM_BASE_NODE + char *name; + u32 ns; +} GF_DOMFullNode; + +/*! DOM built-in namespaces*/ +typedef enum +{ + /*XMLNS is undefined*/ + GF_XMLNS_UNDEFINED = 0, + + GF_XMLNS_XML, + GF_XMLNS_XLINK, + GF_XMLNS_XMLEV, + GF_XMLNS_LASER, + GF_XMLNS_SVG, + GF_XMLNS_XBL, + + GF_XMLNS_SVG_GPAC_EXTENSION, + + /*any other namespace uses the CRC32 of the namespace as an identifier*/ +} GF_NamespaceType; + +/*! gets built-in XMLNS id for this namespace +\param name name of the namespace +\return namespace ID if known, otherwise GF_XMLNS_UNDEFINED*/ +GF_NamespaceType gf_xml_get_namespace_id(char *name); +/*! adds a new namespace +\param sg the target scene graph +\param name name of the namespace (full URL) +\param qname QName of the namespace (short name in doc, eg "ev") +\return error if any +*/ +GF_Err gf_sg_add_namespace(GF_SceneGraph *sg, char *name, char *qname); +/*! removes a new namespace +\param sg the target scene graph +\param name name of the namespace +\param qname QName of the namespace +\return error if any +*/ +GF_Err gf_sg_remove_namespace(GF_SceneGraph *sg, char *name, char *qname); +/*! gets namespace code +\param sg the target scene graph +\param qname QName of the namespace +\return namespace code +*/ +GF_NamespaceType gf_sg_get_namespace_code(GF_SceneGraph *sg, char *qname); +/*! gets namespace code from name +\param sg the target scene graph +\param name name of the namespace +\return namespace code +*/ +GF_NamespaceType gf_sg_get_namespace_code_from_name(GF_SceneGraph *sg, char *name); + +/*! gets namespace qname from ID +\param sg the target scene graph +\param xmlns_id ID of the namespace +\return namespace qname +*/ +const char *gf_sg_get_namespace_qname(GF_SceneGraph *sg, GF_NamespaceType xmlns_id); +/*! gets namespace from ID +\param sg the target scene graph +\param xmlns_id ID of the namespace +\return namespace name +*/ +const char *gf_sg_get_namespace(GF_SceneGraph *sg, GF_NamespaceType xmlns_id); +/*! push namespace parsing state +\param elt the current node being parsed +*/ +void gf_xml_push_namespaces(GF_DOMNode *elt); +/*! pop namespace parsing state +\param elt the current node being parsed +*/ +void gf_xml_pop_namespaces(GF_DOMNode *elt); + +/*! gets namespace of an element +\param n the target node +\return namespace ID +*/ +GF_NamespaceType gf_xml_get_element_namespace(GF_Node *n); + + +/*! DOM text node type*/ +enum +{ + /*! regular text*/ + GF_DOM_TEXT_REGULAR = 0, + /*! CDATA section*/ + GF_DOM_TEXT_CDATA, + /*! inserted text node (typically external script)*/ + GF_DOM_TEXT_INSERTED +}; + +/*! DOM text node*/ +typedef struct +{ + BASE_NODE + CHILDREN + char *textContent; + u32 type; +} GF_DOMText; + +/*! creates a new text node, assign string (does NOT duplicate it) and register node with parent if desired +\param parent the target parent node +\param text_data UTF-8 data to add as a text node +\return the new inserted DOM text node or NULL if error +*/ +GF_DOMText *gf_dom_add_text_node(GF_Node *parent, char *text_data); + +/*! replaces text content of node by the specified string +\param n the target DOM node +\param text the replacement string in UTF-8. If NULL, only resets the children of the node data to add as a text node +*/ +void gf_dom_set_textContent(GF_Node *n, char *text); + +/*! flattens text content of the node +\param n the target DOM node +\return the flattened text - shall be free'ed by the caller*/ +char *gf_dom_flatten_textContent(GF_Node *n); + +/*! creates a new text node - this DOES NOT register the node +\param sg the target scene graph for the node +\return the new text node*/ +GF_DOMText *gf_dom_new_text_node(GF_SceneGraph *sg); + +/*! DOM update (DIMS/LASeR) node*/ +typedef struct +{ + BASE_NODE + CHILDREN + char *data; + u32 data_size; + GF_List *updates; +} GF_DOMUpdates; + +/*! creates a new updates node and register node with parent +\param parent the target parent node +\return a new DOM updates node +*/ +GF_DOMUpdates *gf_dom_add_updates_node(GF_Node *parent); + +/*! DOM Event phases*/ +typedef enum +{ + GF_DOM_EVENT_PHASE_CAPTURE = 1, + GF_DOM_EVENT_PHASE_AT_TARGET = 2, + GF_DOM_EVENT_PHASE_BUBBLE = 3, + + GF_DOM_EVENT_CANCEL_MASK = 0xE0, + /*special phase indicating the event has been canceled*/ + GF_DOM_EVENT_PHASE_CANCEL = 1<<5, + /*special phase indicating the event has been canceled immediately*/ + GF_DOM_EVENT_PHASE_CANCEL_ALL = 1<<6, + /*special phase indicating the default action of the event is prevented*/ + GF_DOM_EVENT_PHASE_PREVENT = 1<<7, +} GF_DOMEventPhase; + +/*! DOM Event possible targets*/ +typedef enum +{ + GF_DOM_EVENT_TARGET_NODE, + GF_DOM_EVENT_TARGET_DOCUMENT, + GF_DOM_EVENT_TARGET_MSE_MEDIASOURCE, + GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFERLIST, + GF_DOM_EVENT_TARGET_MSE_SOURCEBUFFER, + GF_DOM_EVENT_TARGET_XHR, +} GF_DOMEventTargetType; + + +/*! DOM EventTarget Interface*/ +typedef struct +{ + /*! list of SVG Listener nodes attached to this Event Target*/ + GF_List *listeners; + /*! pointer to the object implementing the DOM Event Target Interface*/ + void *ptr; + /*! type of the object implementing the DOM Event Target Interface*/ + GF_DOMEventTargetType ptr_type; +} GF_DOMEventTarget; + +/*! creates a new event target +\param type the type of the event target +\param obj opaque data passed to the event target +\return a new DOM event target +*/ +GF_DOMEventTarget *gf_dom_event_target_new(GF_DOMEventTargetType type, void *obj); +/*! associates a listener node and a event target node + - adds the listener node in the list of event listener nodes for the target node + - sets the target node as the user of the listener +\param listener the target listener +\param evt_target the event target +\return error if any +*/ +GF_Err gf_sg_listener_associate(GF_Node *listener, GF_DOMEventTarget *evt_target); + +/*! DOM Event media information*/ +typedef struct +{ + Bool bufferValid; + u32 level; + Fixed remaining_time; + u16 status; + const char *session_name; + u64 loaded_size, total_size; +} GF_DOMMediaEvent; + +/*! DOM Event structure*/ +typedef struct +{ + /*event type, as defined in <gpac/events.h>*/ + GF_EventType type; + /*event phase type, READ-ONLY + 0: at target, 1: bubbling, 2: capturing , 3: canceled + */ + u8 event_phase; + u8 bubbles; + u8 cancelable; + /*output only - indicates UI events (mouse) have been detected*/ + u8 has_ui_events; + + /*we don't use a GF_DOMEventTarget here since the structure is only created when events are attached */ + void *target; + GF_DOMEventTargetType target_type; + + GF_DOMEventTarget *currentTarget; + Double timestamp; + /*UIEvent extension. + For mouse extensions: number of clicks + For key event: the key code + For SMIL event: number of iteration (repeat) + */ + u32 detail; + + /*MouseEvent extension*/ + s32 screenX, screenY; + s32 clientX, clientY; + u32 button; + /*key flags*/ + u32 key_flags; + /*key hardware code*/ + u32 key_hw_code; + GF_Node *relatedTarget; + /*Zoom event*/ + GF_Rect screen_rect; + GF_Point2D prev_translate, new_translate; + Fixed prev_scale, new_scale; + /* CPU */ + u32 cpu_percentage; + /* Battery */ + Bool onBattery; + u32 batteryState, batteryLevel; + /*smil event time*/ + Double smil_event_time; + /* mutation event */ + GF_Node *relatedNode; + + /*DOM event used in VRML (GPAC's internal)*/ + Bool is_vrml; + /*media event*/ + GF_DOMMediaEvent media_event; + + /*number of listeners triggered by the event*/ + u32 consumed; + + /*for GF_EVENT_ATTR_MODIFIED*/ + GF_FieldInfo *attr; + GF_Err error_state; + + /* ADDON_DETECTED event*/ + const char *addon_url; +} GF_DOM_Event; + +/*! fires an event on the specified node +\warning event execution may very well destroy ANY node, especially the event target node !! +\param node the target node +\param event the DOM event +\return GF_TRUE if event is consumed/aborted after this call, GF_FALSE otherwise (event now pending) +*/ +Bool gf_dom_event_fire(GF_Node *node, GF_DOM_Event *event); +/*! fires a DOM event on the specified node +\warning event execution may very well destroy ANY node, especially the event target node !! +\param et the event target +\param event the dom event +\param sg the parent graph +\param n the event current target, can be NULL +\return GF_TRUE if event is consumed/aborted after this call, GF_FALSE otherwise (event now pending) +*/ +Bool gf_sg_fire_dom_event(GF_DOMEventTarget *et, GF_DOM_Event *event, GF_SceneGraph *sg, GF_Node *n); + +/*! fires event on the specified node +\warning event execution may very well destroy ANY node, especially the event target node !! +\param node the target node +\param event the DOM event +\param use_stack a list of parent node/use node pairs for bubbling phase - may be NULL +\return GF_TRUE if event is consumed/aborted after this call, GF_FALSE otherwise (event now pending) +*/ +Bool gf_dom_event_fire_ex(GF_Node *node, GF_DOM_Event *event, GF_List *use_stack); + +/*! gets event type by name +\param name the event name +\return the event type*/ +GF_EventType gf_dom_event_type_by_name(const char *name); +/*! gets event name by type +\param type the event type +\return the event name*/ +const char *gf_dom_event_get_name(GF_EventType type); + +/*! gets key name by type +\param key_identifier the key type +\return the key name*/ +const char *gf_dom_get_key_name(GF_KeyCode key_identifier); +/*! gets key type by name +\param key_name the key name +\return the key type*/ +GF_KeyCode gf_dom_get_key_type(char *key_name); + + +/*! macro for DOM listener +DOM listener is simply a node added to the node events list. +Only one observer can be attached to a listener. The listener will remove itself from the observer +event list when destructed.*/ +#define GF_DOM_BASE_LISTENER \ + /* JavaScript context in which the listener is applicable */ \ + struct js_handler_context *js_data;\ + /* text content of the callback */ \ + char *callback; \ + /* parent timed elt */ \ + GF_Node *timed_elt; + +/*! DOM Event handler*/ +typedef struct __xml_ev_handler +{ + GF_DOM_BASE_NODE + /*! handler callback function*/ + void (*handle_event)(GF_Node *hdl, GF_DOM_Event *event, GF_Node *observer); + GF_DOM_BASE_LISTENER +} GF_DOMHandler; + +/*! DOM Event category*/ +typedef enum +{ + GF_DOM_EVENT_UNKNOWN_CATEGORY, + /*basic DOM events*/ + GF_DOM_EVENT_DOM = 1, + /*DOM mutation events*/ + GF_DOM_EVENT_MUTATION = 1<<1, + /*DOM mouse events*/ + GF_DOM_EVENT_MOUSE = 1<<2, + /*DOM focus events*/ + GF_DOM_EVENT_FOCUS = 1<<3, + /*DOM key events*/ + GF_DOM_EVENT_KEY = 1<<4, + /*DOM/SVG/HTML UI events (resize, scroll, ...)*/ + GF_DOM_EVENT_UI = 1<<5, + /*text events*/ + GF_DOM_EVENT_TEXT = 1<<6, + /*SVG events*/ + GF_DOM_EVENT_SVG = 1<<7, + /*SMIL events*/ + GF_DOM_EVENT_SMIL = 1<<8, + /*LASeR events*/ + GF_DOM_EVENT_LASER = 1<<9, + /*HTML Media events*/ + GF_DOM_EVENT_MEDIA = 1<<10, + /*HTML Media Source events*/ + GF_DOM_EVENT_MEDIASOURCE = 1<<11, + + /*Internal GPAC events*/ + GF_DOM_EVENT_GPAC = 1<<30, + /*fake events - these events are NEVER fired*/ + GF_DOM_EVENT_FAKE = 0x80000000 //1<<31 +} GF_DOMEventCategory; + +/*! gets category of DOM event +\param type type of event +\return event category +*/ +GF_DOMEventCategory gf_dom_event_get_category(GF_EventType type); + +/*! registers an event category with the scene graph. Event with unregistered categories will not be fired +\param sg the target scene graph +\param category the DOM event category to add +*/ +void gf_sg_register_event_type(GF_SceneGraph *sg, GF_DOMEventCategory category); +/*! unregisters an event category with the scene graph +\param sg the target scene graph +\param category the DOM event category to add +*/ +void gf_sg_unregister_event_type(GF_SceneGraph *sg, GF_DOMEventCategory category); + +/*! adds a listener to the node. +\param n the target node +\param listener a listenerElement (XML event). The listener node is NOT registered with the node (it may very well not be a direct child of the node) +\return error if any +*/ +GF_Err gf_node_dom_listener_add(GF_Node *n, GF_Node *listener); +/*! gets number of listener of a node +\param n the target node +\return number of listeners +*/ +u32 gf_dom_listener_count(GF_Node *n); +/*! gets a listener of a node +\param n the target node +\param idx 0-based index of the listener to query +\return listener node or NULL if error +*/ +GF_Node *gf_dom_listener_get(GF_Node *n, u32 idx); + +/*! creates a default listener/handler for the given event on the given node +Listener/handler are stored at the node level +\param observer the observer node +\param event_type the event type +\param event_param the event parameter +\return the created handler element to allow for handler function override +*/ +GF_DOMHandler *gf_dom_listener_build(GF_Node *observer, GF_EventType event_type, u32 event_param); + +/*! registers a XML IRI with the scene graph - this is needed to handle replacement of IRI (SMIL anim, DOM / LASeR updates) +\warning \ref gf_node_unregister_iri shall be called when the parent node of the IRI is destroyed +\param sg the target scene graph +\param iri the IRI to register +*/ +void gf_node_register_iri(GF_SceneGraph *sg, XMLRI *iri); +/*! unregisters a XML IRI with the scene graph +\param sg the target scene graph +\param iri the IRI to unregister +*/ +void gf_node_unregister_iri(GF_SceneGraph *sg, XMLRI *iri); +/*! gets number of animation targeting this node +\param n the target node +\return the number of animations +*/ +u32 gf_node_animation_count(GF_Node *n); + +/*! writes data embedded in IRI (such as base64 data) to the indicated cahce directory, and updates the IRI accordingly +\param iri the IRI to store +\param cache_dir location of the directory to which the data should be extracted +\param base_filename location of the file the IRI was declared in +\return error if any +*/ +GF_Err gf_node_store_embedded_data(XMLRI *iri, const char *cache_dir, const char *base_filename); + + +/************************************************** + * SVG's styling properties (see 6.1 in REC 1.1) * + *************************************************/ + +/*! SVG properties of node*/ +typedef struct { + /* Tiny 1.2 properties*/ + SVG_Paint *color; + SVG_Paint *fill; + SVG_Paint *stroke; + SVG_Paint *solid_color; + SVG_Paint *stop_color; + SVG_Paint *viewport_fill; + + SVG_Number *fill_opacity; + SVG_Number *solid_opacity; + SVG_Number *stop_opacity; + SVG_Number *stroke_opacity; + SVG_Number *viewport_fill_opacity; + SVG_Number *opacity; /* Restricted property in Tiny 1.2 */ + + SVG_Number *audio_level; + Fixed computed_audio_level; + + SVG_RenderingHint *color_rendering; + SVG_RenderingHint *image_rendering; + SVG_RenderingHint *shape_rendering; + SVG_RenderingHint *text_rendering; + + SVG_Display *display; + SVG_Visibility *visibility; + SVG_Overflow *overflow; /* Restricted property in Tiny 1.2 */ + + SVG_FontFamily *font_family; + SVG_FontSize *font_size; + SVG_FontStyle *font_style; + SVG_FontWeight *font_weight; + SVG_FontVariant *font_variant; + SVG_Number *line_increment; + SVG_TextAnchor *text_anchor; + SVG_DisplayAlign *display_align; + SVG_TextAlign *text_align; + + SVG_PointerEvents *pointer_events; + + SVG_FillRule *fill_rule; + + SVG_StrokeDashArray *stroke_dasharray; + SVG_Length *stroke_dashoffset; + SVG_StrokeLineCap *stroke_linecap; + SVG_StrokeLineJoin *stroke_linejoin; + SVG_Number *stroke_miterlimit; + SVG_Length *stroke_width; + SVG_VectorEffect *vector_effect; + + /* Full 1.1 props, i.e. not implemented */ + /* + SVG_String *font; + SVG_String *font_size_adjust; + SVG_String *font_stretch; + SVG_String *direction; + SVG_String *letter_spacing; + SVG_String *text_decoration; + SVG_String *unicode_bidi; + SVG_String *word_spacing; + SVG_String *clip; + SVG_String *cursor; + SVG_String *clip_path; + SVG_String *clip_rule; + SVG_String *mask; + SVG_String *enable_background; + SVG_String *filter; + SVG_String *flood_color; + SVG_String *flood_opacity; + SVG_String *lighting_color; + SVG_String *color_interpolation; + SVG_String *color_interpolation_filters; + SVG_String *color_profile; + SVG_String *marker; + SVG_String *marker_end; + SVG_String *marker_mid; + SVG_String *marker_start; + SVG_String *alignment_baseline; + SVG_String *baseline_shift; + SVG_String *dominant_baseline; + SVG_String *glyph_orientation_horizontal; + SVG_String *glyph_orientation_vertical; + SVG_String *kerning; + SVG_String *writing_mode; + */ +} SVGPropertiesPointers; + + +/*! initializes an SVGPropertiesPointers +\param svg_props pointer to structure to initialize +*/ +void gf_svg_properties_init_pointers(SVGPropertiesPointers *svg_props); +/*! resets an SVGPropertiesPointers +\param svg_props pointer to structure to reset +*/ +void gf_svg_properties_reset_pointers(SVGPropertiesPointers *svg_props); + +/*! applies animations for the node +\param n the target node +\param render_svg_props pointer to rendering SVG properties of the node +*/ +void gf_svg_apply_animations(GF_Node *n, SVGPropertiesPointers *render_svg_props); +/*! check if appearance flag is set +\param flags flags to test +\return GF_TRUE if appearance dirty flag is set +*/ +Bool gf_svg_has_appearance_flag_dirty(u32 flags); + +/*! checks if node tag indicates a transformable elemnt +\param tag tag to check +\return GF_TRUE if element is transformable +*/ +Bool gf_svg_is_element_transformable(u32 tag); +/*! creates an SVG attribute value for the given type +\param attribute_type type of attribute to delete +\return newly allocated attribute value*/ +void *gf_svg_create_attribute_value(u32 attribute_type); +/*! destroys an SVG attribute value +\param attribute_type type of attribute to delete +\param value the value to destroy +\param sg the parent scenegraph (needed for IRI registration) +*/ +void gf_svg_delete_attribute_value(u32 attribute_type, void *value, GF_SceneGraph *sg); + +/*! checks if two SVG attributes are equal +\param a first attribute +\param b second attribute +\return GF_TRUE if equal +*/ +Bool gf_svg_attributes_equal(GF_FieldInfo *a, GF_FieldInfo *b); +/*! copies attributes (a = b) +\param a destination attribute +\param b source attribute +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any +*/ +GF_Err gf_svg_attributes_copy(GF_FieldInfo *a, GF_FieldInfo *b, Bool clamp); +/*! adds attributes ( c = a + b) +\param a first attribute +\param b second attribute +\param c destination attribute +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any + */ +GF_Err gf_svg_attributes_add(GF_FieldInfo *a, GF_FieldInfo *b, GF_FieldInfo *c, Bool clamp); +/*! checks if attribute type can be interpolated +\param type attribute type +\return GF_TRUE if type can be interpolation*/ +Bool gf_svg_attribute_is_interpolatable(u32 type); +/*! interpolates attributes ( c = coef * a + (1 - coef) * b ) +\param a first attribute +\param b second attribute +\param c destination attribute +\param coef interpolation coefficient +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any +*/ +GF_Err gf_svg_attributes_interpolate(GF_FieldInfo *a, GF_FieldInfo *b, GF_FieldInfo *c, Fixed coef, Bool clamp); +/*! multiply and add attributes (c = alpha * a + beta * b) +\param a first attribute +\param alpha first multiplier +\param b second attribute +\param beta second multiplier +\param c destination attribute +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any +*/ +GF_Err gf_svg_attributes_muladd(Fixed alpha, GF_FieldInfo *a, Fixed beta, GF_FieldInfo *b, GF_FieldInfo *c, Bool clamp); +/*! gets an attribute by its tag (built-in name) +\param n the target node +\param attribute_tag the attribute tag +\param create_if_not_found if GF_TRUE, adds the attribute if not found +\param set_default if GF_TRUE, sets the attribute to default when creating it +\param field set to the attribute information +\return error if any +*/ +GF_Err gf_node_get_attribute_by_tag(GF_Node *n, u32 attribute_tag, Bool create_if_not_found, Bool set_default, GF_FieldInfo *field); + +/*! gets name of an attribute type +\param att_type the attribute type +\return the attribute name*/ +const char *gf_svg_attribute_type_to_string(u32 att_type); +/*! parses an attribute +\param n the target node +\param info the attribute information +\param attribute_content the UTF-8 string to parse +\param anim_value_type the SMIL animation value type for this attribute, 0 if not animatable +\return error if any +*/ +GF_Err gf_svg_parse_attribute(GF_Node *n, GF_FieldInfo *info, char *attribute_content, u8 anim_value_type); +/*! parses an SVG style +\param n the target node +\param style the UTF-8 style string to parse +*/ +void gf_svg_parse_style(GF_Node *n, char *style); + +/*! dumps an SVG attribute +\param n the target node +\param info the attribute information +\return the textural representation of the value - shall be destroyed by caller +*/ +char *gf_svg_dump_attribute(GF_Node *n, GF_FieldInfo *info); +/*! dumps an SVG indexed attribute (special 1-D version of a N dimensional attribute, used by LASeR and DOM updates) +\param n the target node +\param info the attribute information +\return the textural representation of the value - shall be destroyed by caller +*/ +char *gf_svg_dump_attribute_indexed(GF_Node *n, GF_FieldInfo *info); + +#if USE_GF_PATH +/*! builds a path from its SVG representation +\param path a 2D graphical path object +\param commands a list of SVG path commands +\param points a list of SVG path points +*/ +void gf_svg_path_build(GF_Path *path, GF_List *commands, GF_List *points); +#endif +/*! parses an SVG element ID +\param n the target node +\param nodename the node name to parse +\param warning_if_defined if GF_TRUE, throws a warning when the node name is already defined +\return error if any +*/ +GF_Err gf_svg_parse_element_id(GF_Node *n, const char *nodename, Bool warning_if_defined); + +/*! gets the name of a paint server type +\param paint_type the paint server type +\return paint server name*/ +const char *gf_svg_get_system_paint_server_name(u32 paint_type); +/*! gets the type of a paint server by name +\param name the paint server name +\return paint server type*/ +u32 gf_svg_get_system_paint_server_type(const char *name); + +/*! notifies the scene time to all the timed elements in the given scene graph (including sub-scenes). +\param sg the target scene graph +\return the number of active timed elements, or 0 if no changes in SMIL timing nodes (no redraw/reevaluate needed) +*/ +Bool gf_smil_notify_timed_elements(GF_SceneGraph *sg); +/*! inserts a new resolved time instant in the begin or end attribute. + The insertion preserves the sorting and removes the previous insertions which have become obsolete +\warning Only used for inserting time when an <a> element, whose target is a timed element, is activated +\param n the target element +\param is_end if GF_TRUE, the SMIL clock is an end clock +\param clock clock time in seconds + */ +void gf_smil_timing_insert_clock(GF_Node *n, Bool is_end, Double clock); +/*! parses a transformation list into a 2D matrix +\param mat the matrix to fill +\param attribute_content the UTF8 string representin the SVG transformation +\return GF_TRUE if success*/ +Bool gf_svg_parse_transformlist(GF_Matrix2D *mat, char *attribute_content); + +/*! SMIL runtime timing information*/ +typedef struct _smil_timing_rti SMIL_Timing_RTI; + +/*! SMIL timing evaluation state*/ +typedef enum +{ + SMIL_TIMING_EVAL_NONE = 0, + SMIL_TIMING_EVAL_UPDATE, + SMIL_TIMING_EVAL_FREEZE, + SMIL_TIMING_EVAL_REMOVE, + SMIL_TIMING_EVAL_REPEAT, + SMIL_TIMING_EVAL_FRACTION, + SMIL_TIMING_EVAL_DISCARD, + /*signaled the animation element has been inserted in the DOM tree*/ + SMIL_TIMING_EVAL_ACTIVATE, + /*signaled the animation element has been removed from the DOM tree*/ + SMIL_TIMING_EVAL_DEACTIVATE, +} GF_SGSMILTimingEvalState; + +/*! SMIL timing evaluation callback +\param rti SMIL runtime info +\param normalized_simple_time SMIL normalized time +\param state SMIL evaluation state +*/ +typedef void gf_sg_smil_evaluate(struct _smil_timing_rti *rti, Fixed normalized_simple_time, GF_SGSMILTimingEvalState state); +/*! sets the SMIL evaluation callback for a node +\param smil_time the target SMIL node +\param smil_evaluate the callback function +*/ +void gf_smil_set_evaluation_callback(GF_Node *smil_time, gf_sg_smil_evaluate smil_evaluate); +/*! assigns media duration for a SMIL node +\param rti the target SMIL runtime info of the node +\param media_duration the media duration in seconds*/ +void gf_smil_set_media_duration(SMIL_Timing_RTI *rti, Double media_duration); +/*! gets media duration of a SMIL node +\param rti the target SMIL runtime info of the node +\return the media duration in seconds*/ +Double gf_smil_get_media_duration(SMIL_Timing_RTI *rti); +/*! gets node from a SMIL runtime info +\param rti the target SMIL runtime info of the node +\return the associated node*/ +GF_Node *gf_smil_get_element(SMIL_Timing_RTI *rti); +/*! checks if a SMIL node is active +\param node the target node +\return GF_TRUE if node is active*/ +Bool gf_smil_timing_is_active(GF_Node *node); +/*! notifies a SMIL node that it has been modified +\param node the target SMIL node +\param field the field information describing the modified attribute, may be NULL +*/ +void gf_smil_timing_modified(GF_Node *node, GF_FieldInfo *field); + +/******************************************************************************* + * + * SVG Scene Graph for dynamic allocation of attributes * + * + *******************************************************************************/ + +/*SVG attributes are just DOM ones*/ +/*! SVG attribute - same as DOM attribute*/ +typedef struct __dom_base_attribute SVGAttribute; +/*! SVG extended attribute - same as DOM extended attribute*/ +typedef struct __dom_full_attribute SVGExtendedAttribute; +/*! SVG node - same as DOM node*/ +typedef struct __dom_base_node SVG_Element; +/*! SVG handler node - same as DOM Event handler*/ +typedef struct __xml_ev_handler SVG_handlerElement; +/*! SVG all attributes*/ +typedef struct _all_atts SVGAllAttributes; + +/*! flattens all SVG attributes in a structure +\param n the target SVG node +\param all_atts filled with all SVG attributes defined fo node +*/ +void gf_svg_flatten_attributes(SVG_Element *n, SVGAllAttributes *all_atts); +/*! gets SVG attribute name for a node +\param n the target SVG node +\param tag the attribute tag +\return the attribute name +*/ +const char *gf_svg_get_attribute_name(GF_Node *n, u32 tag); +/*! applies inheritance of SVG attributes to a render_svg_props +\note Some properties (audio-level, display, opacity, solid*, stop*, vector-effect, viewport*) are inherited only when they are specified with the value 'inherit' otherwise they default to their initial value which for the function below means NULL, the compositor will take care of the rest + +\param all_atts the flatten SVG attributes of the node +\param render_svg_props the set of properties applying to the parent node (initialize this one to 0 for root element) +\return the set of inherited flags (from node dirty flags) +*/ +u32 gf_svg_apply_inheritance(SVGAllAttributes *all_atts, SVGPropertiesPointers *render_svg_props) ; + +/*! creates a DOM Attribute for the given tag +\warning the attribute is not added to the node's attributes +\param n the target node +\param tag the target built-in tag +\return a new DOM Attribute +*/ +GF_DOMAttribute *gf_xml_create_attribute(GF_Node *n, u32 tag); +/*! gets the type of a built-in XML/SVG/SMIL attribute tag +\param tag the target attribute tag +\return the attribute type*/ +u32 gf_xml_get_attribute_type(u32 tag); + +/*! gets the built-in tag of a node's attribute +\param n the target node +\param attribute_name the target attribute name +\param ns the target namespace +\return the attribute tag*/ +u32 gf_xml_get_attribute_tag(GF_Node *n, char *attribute_name, GF_NamespaceType ns); + +/*! gets the built-in tag of a node +\param node_name the target node name +\param xmlns the target namespace +\return the node tag*/ +u32 gf_xml_get_element_tag(const char *node_name, u32 xmlns); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif //_GF_SG_SVG_H_ diff --git a/include/gpac/scenegraph_vrml.h b/include/gpac/scenegraph_vrml.h new file mode 100644 index 0000000..c356625 --- /dev/null +++ b/include/gpac/scenegraph_vrml.h @@ -0,0 +1,958 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Scene Graph 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_SG_VRML_H_ +#define _GF_SG_VRML_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/scenegraph_vrml.h> +\brief Scenegraph for VRML files +*/ + +/*! +\addtogroup svrml BIFS/VRML/X3D Scenegraph +\ingroup scene_grp +\brief Scenegraph for VRML files. + +This section documents the Scenegraph for VRML files. + +@{ + */ + + +#include <gpac/scenegraph.h> +#include <gpac/maths.h> + +/* + All extensions for VRML/MPEG-4/X3D graph structure +*/ + +/*! reserved NDT for MPEG4 (match binary coding)*/ +#define MPEG4_RESERVED_NDT 200 + +/*! the NDTs used in X3D not defined in MPEG4*/ +enum +{ + NDT_SFMetadataNode = MPEG4_RESERVED_NDT+1, + NDT_SFFillPropertiesNode, + NDT_SFX3DLinePropertiesNode, + NDT_SFGeoOriginNode, + NDT_SFHAnimNode, + NDT_SFHAnimDisplacerNode, + NDT_SFNurbsControlCurveNode, + NDT_SFNurbsSurfaceNode, + NDT_SFNurbsCurveNode +}; + +/* + VRML / BIFS TYPES DEFINITION +*/ + +/*! event types of fields, as defined in the specs + this should not be needed by non binary codecs +*/ +enum +{ + GF_SG_EVENT_FIELD = 0, + GF_SG_EVENT_EXPOSED_FIELD = 1, + GF_SG_EVENT_IN = 2, + GF_SG_EVENT_OUT = 3, + GF_SG_EVENT_UNKNOWN = 4 +}; +/*! gets the event type name +\param EventType the event type +\param forX3D for X3D dumping +\return the event name +*/ +const char *gf_sg_vrml_get_event_type_name(u32 EventType, Bool forX3D); + +/*! field coding mode + + BIFS defines the bitstream syntax contextually, and therefore sometimes refer to fields as indexed + in the node ("all" mode) or just as a sub-set (in, out, def, dyn modes) of similar types +*/ +enum +{ + /*all fields and events*/ + GF_SG_FIELD_CODING_ALL = 0, + /*defined fields (exposedField and Field)*/ + GF_SG_FIELD_CODING_DEF = 1, + /*input field (exposedField and eventIn)*/ + GF_SG_FIELD_CODING_IN = 2, + /*output field (exposedField and eventOut)*/ + GF_SG_FIELD_CODING_OUT = 3, + /*field that can be animated (subset of inFields) used in BIFS_Anim only*/ + GF_SG_FIELD_CODING_DYN = 4 +}; + +/*! gets the number of field in the given mode (BIFS specific) +\param n the target node +\param IndexMode the field indexing mode +\return the number of field in this mode*/ +u32 gf_node_get_num_fields_in_mode(GF_Node *n, u8 IndexMode); + +/* SF Types */ +/*! Boolean*/ +typedef u32 SFBool; +/*! Integer*/ +typedef s32 SFInt32; +/*! Integer*/ +typedef s32 SFInt; +/*! Float*/ +typedef Fixed SFFloat; +/*! Double*/ +typedef Double SFDouble; + +/*! String*/ +typedef struct +{ + char* buffer; +} SFString; + +/*! Time*/ +typedef Double SFTime; + +/*! RGB color*/ +typedef struct { + Fixed red; + Fixed green; + Fixed blue; +} SFColor; + +/*! RGBA color*/ +typedef struct { + Fixed red; + Fixed green; + Fixed blue; + Fixed alpha; +} SFColorRGBA; + +/*! URL*/ +typedef struct { + u32 OD_ID; + char *url; +} SFURL; + +/*! 2D vector (double)*/ +typedef struct { + Double x; + Double y; +} SFVec2d; + +/*! 3D vector (double)*/ +typedef struct { + Double x; + Double y; + Double z; +} SFVec3d; + +/*typedef's to main math tools*/ + +/*! 2D vector (float)*/ +typedef struct __vec2f SFVec2f; +/*! 3D vector (float)*/ +typedef struct __vec3f SFVec3f; +/*! rotation (float)*/ +typedef struct __vec4f SFRotation; +/*! 4D vector (float)*/ +typedef struct __vec4f SFVec4f; + +/*! Image data (rgb pixels)*/ +typedef struct { + u32 width; + u32 height; + u8 numComponents; + unsigned char* pixels; +} SFImage; +/*! BIFS Command Buffer*/ +typedef struct { + u32 bufferSize; + u8* buffer; + /*uncompressed command list*/ + GF_List *commandList; +} SFCommandBuffer; + +/*! Script + \note The javascript or vrml script is handled in its decompressed (textual) format +since most JS interpreter work with text*/ +typedef struct { + char* script_text; +} SFScript; + +/*! BIFS Attribute Reference*/ +typedef struct { + GF_Node *node; + u32 fieldIndex; +} SFAttrRef; + +/* MF Types */ + +/*! generic MF field: all MF fields use the same syntax except MFNode which uses GF_List. You can thus use +this structure to safely typecast MF field pointers*/ +typedef struct { + u32 count; + u8 *array; +} GenMFField; +/*! Interger array*/ +typedef struct { + u32 count; + s32* vals; +} MFInt32; +/*! Interger array*/ +typedef struct { + u32 count; + s32* vals; +} MFInt; +/*! Float array*/ +typedef struct { + u32 count; + Fixed *vals; +} MFFloat; +/*! Double array*/ +typedef struct { + u32 count; + Double *vals; +} MFDouble; +/*! Boolean array*/ +typedef struct { + u32 count; + u32* vals; +} MFBool; +/*! Color RGB array*/ +typedef struct { + u32 count; + SFColor* vals; +} MFColor; +/*! Color RGBA array*/ +typedef struct { + u32 count; + SFColorRGBA* vals; +} MFColorRGBA; +/*! Rotation array*/ +typedef struct { + u32 count; + SFRotation* vals; +} MFRotation; +/*! Time array*/ +typedef struct { + u32 count; + Double* vals; +} MFTime; +/*! 2D Vector (float) array*/ +typedef struct { + u32 count; + SFVec2f* vals; +} MFVec2f; +/*! 2D Vector (double) array*/ +typedef struct { + u32 count; + SFVec2d* vals; +} MFVec2d; +/*! 3D Vector (float) array*/ +typedef struct { + u32 count; + SFVec3f* vals; +} MFVec3f; +/*! 3D Vector (double) array*/ +typedef struct { + u32 count; + SFVec3d* vals; +} MFVec3d; +/*! 4D Vector (float) array*/ +typedef struct { + u32 count; + SFVec4f* vals; +} MFVec4f; + +/*! URL array*/ +typedef struct { + u32 count; + SFURL* vals; +} MFURL; +/*! String array*/ +typedef struct { + u32 count; + char** vals; +} MFString; +/*! Script array*/ +typedef struct { + u32 count; + SFScript *vals; +} MFScript; + +/*! Attribute Reference array*/ +typedef struct { + u32 count; + SFAttrRef* vals; +} MFAttrRef; + +/*! converts an SFColor to an SFColorRGBA by setting the alpha component to 1 +\param val the input color +\return the RGBA color*/ +SFColorRGBA gf_sg_sfcolor_to_rgba(SFColor val); + +/*! field types, as defined in BIFS encoding (used for scripts and proto coding)*/ +enum +{ + GF_SG_VRML_SFBOOL = 0, + GF_SG_VRML_SFFLOAT = 1, + GF_SG_VRML_SFTIME = 2, + GF_SG_VRML_SFINT32 = 3, + GF_SG_VRML_SFSTRING = 4, + GF_SG_VRML_SFVEC3F = 5, + GF_SG_VRML_SFVEC2F = 6, + GF_SG_VRML_SFCOLOR = 7, + GF_SG_VRML_SFROTATION = 8, + GF_SG_VRML_SFIMAGE = 9, + GF_SG_VRML_SFNODE = 10, + /*TO CHECK*/ + GF_SG_VRML_SFVEC4F = 11, + + /*used types in GPAC but not defined in the MPEG4 spec*/ + GF_SG_VRML_SFURL, + GF_SG_VRML_SFSCRIPT, + GF_SG_VRML_SFCOMMANDBUFFER, + /*used types in X3D*/ + GF_SG_VRML_SFDOUBLE, + GF_SG_VRML_SFCOLORRGBA, + GF_SG_VRML_SFVEC2D, + GF_SG_VRML_SFVEC3D, + + GF_SG_VRML_FIRST_MF = 32, + GF_SG_VRML_MFBOOL = GF_SG_VRML_FIRST_MF, + GF_SG_VRML_MFFLOAT, + GF_SG_VRML_MFTIME, + GF_SG_VRML_MFINT32, + GF_SG_VRML_MFSTRING, + GF_SG_VRML_MFVEC3F, + GF_SG_VRML_MFVEC2F, + GF_SG_VRML_MFCOLOR, + GF_SG_VRML_MFROTATION, + GF_SG_VRML_MFIMAGE, + GF_SG_VRML_MFNODE, + GF_SG_VRML_MFVEC4F, + + GF_SG_VRML_SFATTRREF = 45, + GF_SG_VRML_MFATTRREF = 46, + + /*used types in GPAC but not defined in the MPEG4 spec*/ + GF_SG_VRML_MFURL, + GF_SG_VRML_MFSCRIPT, + GF_SG_VRML_MFCOMMANDBUFFER, + + /*used types in X3D*/ + GF_SG_VRML_MFDOUBLE, + GF_SG_VRML_MFCOLORRGBA, + GF_SG_VRML_MFVEC2D, + GF_SG_VRML_MFVEC3D, + + /*special event only used in routes for binding eventOut/exposedFields to script functions. + A route with ToField.FieldType set to this value holds a pointer to a function object. + */ + GF_SG_VRML_SCRIPT_FUNCTION, + + /*special event only used in routes for binding eventOut/exposedFields to generic functions. + A route with ToField.FieldType set to this value holds a pointer to a function object. + */ + GF_SG_VRML_GENERIC_FUNCTION, + + GF_SG_VRML_UNKNOWN +}; + +/*! checks if a field is an SF field +\param FieldType the tragte filed type +\return GF_TRUE if field is a single field*/ +Bool gf_sg_vrml_is_sf_field(u32 FieldType); + +/*! translates MF/SF to SF type +\param FieldType the tragte filed type +\return SF field type*/ +u32 gf_sg_vrml_get_sf_type(u32 FieldType); + +/*! inserts (+alloc) a slot in the MFField with a specified position for insertion and sets the ptr to the newly created slot +\param mf pointer to the MF field +\param FieldType the MF field type +\param new_ptr set to the allocated slot (do not free) +\param InsertAt is the 0-based index for the new slot +\return error if any +*/ +GF_Err gf_sg_vrml_mf_insert(void *mf, u32 FieldType, void **new_ptr, u32 InsertAt); +/*! removes all items of the MFField +\param mf pointer to the MF field +\param FieldType the MF field type +\return error if any +*/ +GF_Err gf_sg_vrml_mf_reset(void *mf, u32 FieldType); + +/*! deletes an MFUrl field +\note exported for URL handling in compositor +\param url the MF url field to reset +*/ +void gf_sg_mfurl_del(MFURL url); +/*! copies MFUrl field +\note exported for URL handling in compositor +\param dst the destination MF url field to copy +\param src the source MF url field to copy +*/ +void gf_sg_vrml_copy_mfurl(MFURL *dst, MFURL *src); +/*! interpolates SFRotation +\note exported for 3D camera in compositor +\param kv1 start value for interpolation +\param kv2 end value for interpolation +\param f interpolation factor +\return the interpolated result +*/ +SFRotation gf_sg_sfrotation_interpolate(SFRotation kv1, SFRotation kv2, Fixed f); + +/*! adds a new node to the "children" field +\warning DOES NOT CHECK CHILD/PARENT type compatibility +\param parent the target parent node +\param new_child the child to insert +\param pos the 0-BASED index in the list of children, -1 means end of list (append) +\return error if any +*/ +GF_Err gf_node_insert_child(GF_Node *parent, GF_Node *new_child, s32 pos); + +/*! removes and replace given child by specified node. If node is NULL, only delete target node +\warning DOES NOT CHECK CHILD/PARENT type compatibility +\param node the target node to replace +\param container the container list +\param pos the 0-BASED index in the list of children, -1 means end of list (append) +\param newNode the new node to use +\return error if any +*/ +GF_Err gf_node_replace_child(GF_Node *node, GF_ChildNodeItem **container, s32 pos, GF_Node *newNode); + + +/*! internal prototype*/ +#define GF_SG_INTERNAL_PROTO (PTR_TO_U_CAST -1) + + +#ifndef GPAC_DISABLE_VRML + + +/*! VRML grouping nodes macro - note we have inverted the children field to be +compatible with the base GF_ParentNode node +All grouping nodes (with "children" field) implement the following: + +addChildren: chain containing nodes to add passed as eventIn - handled internally through ROUTE +void (*on_addChildren)(GF_Node *pNode): add feventIn signaler - this is handled internally by the scene_graph and SHALL +NOT BE overridden since it takes care of node(s) routing + +removeChildren: chain containing nodes to remove passed as eventIn - handled internally through ROUTE + +void (*on_removeChildren)(GF_Node *pNode): remove eventIn signaler - this is handled internally by the scene_graph and SHALL +NOT BE overridden since it takes care of node(s) routing + +children: list of children SFNodes +*/ +#define VRML_CHILDREN \ + CHILDREN \ + GF_ChildNodeItem *addChildren; \ + void (*on_addChildren)(GF_Node *pNode, struct _route *route); \ + GF_ChildNodeItem *removeChildren; \ + void (*on_removeChildren)(GF_Node *pNode, struct _route *route); \ + +/*! generic VRML parent node*/ +typedef struct +{ + BASE_NODE + VRML_CHILDREN +} GF_VRMLParent; + +/*! setup a vrml parent +\param n the target node*/ +void gf_sg_vrml_parent_setup(GF_Node *n); +/*! resets all children in a vrml parent node (but does not destroy the node) +\param n the target node*/ +void gf_sg_vrml_parent_destroy(GF_Node *n); + +/*! checks if a given node tag is in a given NDT table +\param tag the node tag +\param NDTType the NDT type +\return GF_TRUE if node tag is in the NDT*/ +Bool gf_node_in_table_by_tag(u32 tag, u32 NDTType); +/*! gets field type name +\param FieldType the field type +\return the field name*/ +const char *gf_sg_vrml_get_field_type_name(u32 FieldType); + +/*! allocates a new field +\note GF_SG_VRML_MFNODE will return a pointer to a GF_List structure (eg GF_List *), GF_SG_VRML_SFNODE will return NULL +\param FieldType the field type +\return the new field pointer*/ +void *gf_sg_vrml_field_pointer_new(u32 FieldType); +/*! deletes a field pointer (including SF an,d MF nodes) +\param field the field pointer value +\param FieldType the field type +*/ +void gf_sg_vrml_field_pointer_del(void *field, u32 FieldType); + +/*! adds at the end of an MF field +\param mf pointer to the MF field +\param FieldType the MF field type +\param new_ptr set to the allocated SF field slot - do not destroy +\return error if any +*/ +GF_Err gf_sg_vrml_mf_append(void *mf, u32 FieldType, void **new_ptr); +/*! removes the desired item of an MF field +\param mf pointer to the MF field +\param FieldType the MF field type +\param RemoveFrom the 0-based index of item to remove +\return error if any +*/ +GF_Err gf_sg_vrml_mf_remove(void *mf, u32 FieldType, u32 RemoveFrom); +/*! allocates an MF array +\param mf pointer to the MF field +\param FieldType the MF field type +\param NbItems number of items to allocate +\return error if any +*/ +GF_Err gf_sg_vrml_mf_alloc(void *mf, u32 FieldType, u32 NbItems); +/*! gets the item in the MF array +\param mf pointer to the MF field +\param FieldType the MF field type +\param new_ptr set to the SF field slot - do not destroy +\param ItemPos the 0-based index of item to remove +\return error if any +*/ +GF_Err gf_sg_vrml_mf_get_item(void *mf, u32 FieldType, void **new_ptr, u32 ItemPos); + +/*! copies a field content EXCEPT SF/MFNode. Pointers to field shall be used +\param dest pointer to the MF field +\param orig pointer to the MF field +\param FieldType the MF field type +*/ +void gf_sg_vrml_field_copy(void *dest, void *orig, u32 FieldType); + +/*! clones a field content EXCEPT SF/MFNode. Pointers to field shall be used +\param dest pointer to the MF field +\param orig pointer to the MF field +\param FieldType the MF field type +\param inScene target scene graph for SFCommandBuffers cloning +*/ +void gf_sg_vrml_field_clone(void *dest, void *orig, u32 FieldType, GF_SceneGraph *inScene); + +/*! indicates whether 2 fields of same type EXCEPT SF/MFNode are equal +\param dest pointer to the MF field +\param orig pointer to the MF field +\param FieldType the MF field type +\return GF_TRUE if fields equal +*/ +Bool gf_sg_vrml_field_equal(void *dest, void *orig, u32 FieldType); + + +/*GF_Route manipultaion : routes are used to pass events between nodes. Event handling is managed by the scene graph +however only the nodes overloading the EventIn handler associated with the event will process the eventIn*/ + +/*! creates a new route +\note routes are automatically destroyed if either the target or origin node of the route is destroyed +\param sg the target scene graph of the route +\param fromNode the source node triggering the event out +\param fromField the source field triggering the event out +\param toNode the destination node accepting the event in +\param toField the destination field accepting the event in +\return a new route object +*/ +GF_Route *gf_sg_route_new(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, GF_Node *toNode, u32 toField); + +/*! destroys a route +\param route the target route +*/ +void gf_sg_route_del(GF_Route *route); +/*! destroys a route by ID +\param sg the scene graph of the route +\param routeID the ID of the route to destroy +\return error if any +*/ +GF_Err gf_sg_route_del_by_id(GF_SceneGraph *sg,u32 routeID); + +/*! locate a route by ID +\param sg the scene graph of the route +\param RouteID the ID of the route +\return the route object or NULL if not found +*/ +GF_Route *gf_sg_route_find(GF_SceneGraph *sg, u32 RouteID); +/*! locate a route by name +\param sg the scene graph of the route +\param name the name of the route +\return the route object or NULL if not found +*/ +GF_Route *gf_sg_route_find_by_name(GF_SceneGraph *sg, char *name); +/*! assigns a route ID +\param route the target route +\param ID the ID to assign +\return error if any - fails if a route with same ID already exists*/ +GF_Err gf_sg_route_set_id(GF_Route *route, u32 ID); + +/*! assign a route name +\param route the target route +\param name the name to assign +\return error if any - fails if a route with same name already exists +*/ +GF_Err gf_sg_route_set_name(GF_Route *route, char *name); +/*! gets route name +\param route the target route +\return the route name or NULL if not set +*/ +char *gf_sg_route_get_name(GF_Route *route); + +/*! retuns next available RouteID +\note this doesn't track inserted routes, that's the caller responsability +\param sg the target scene graph of the route +\return the next available ID for routes +*/ +u32 gf_sg_get_next_available_route_id(GF_SceneGraph *sg); +/*! sets max defined route ID used in the scene - used to handle RouteInsert commands +note that this must be called by the user to be effective,; otherwise the max route ID is computed +from the routes present in scene +\param sg the target scene graph of the route +\param ID the value of the max defined route ID +*/ +void gf_sg_set_max_defined_route_id(GF_SceneGraph *sg, u32 ID); + +/*! creates a new route from a node output to a given callback/function +\param sg the target scene graph of the route +\param fromNode the source node emiting the event out +\param fromField the source field emiting the event out +\param cbk opaque data to pass to the callback +\param route_callback route callback function to call +*/ +void gf_sg_route_new_to_callback(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, void *cbk, void ( *route_callback) (void *param, GF_FieldInfo *from_field) ); + +/*! activates all routes currently triggered - this follows the event cascade model of VRML/MPEG4: + - routes are collected during eventOut generation + - routes are activated. If eventOuts are generated during activation the cycle goes on. + + A route cannot be activated twice in the same simulation tick, hence this function shall be called + ONCE AND ONLY ONCE per simulation tick + +Note that children scene graphs register their routes with the top-level graph, so only the main +scene graph needs to be activated +\param sg the target scene graph of the route +*/ +void gf_sg_activate_routes(GF_SceneGraph *sg); + + +/* + proto handling + + The lib allows you to construct prototype nodes as defined in VRML/MPEG4 by constructing + proto interfaces and instantiating them. An instantiated proto is handled as a single node for + rendering, thus an application will never handle proto instances for rendering +*/ + +/*! proto object*/ +typedef struct _proto GF_Proto; +/*! proto field object*/ +typedef struct _protofield GF_ProtoFieldInterface; + + +/*! retuns next available proto ID +\param sg the target scene graph of the proto +\return the next available proto ID +*/ +u32 gf_sg_get_next_available_proto_id(GF_SceneGraph *sg); + +/*! constructs a new proto identified by ID/name in the given scene +2 protos in the same scene may not have the same ID/name + +\param sg the target scene graph in which the proto is created +\param ProtoID ID of the proto to create +\param name name of the proto to create +\param unregistered if GF_TRUE, the proto is not stored in the graph main proto list but in an alternate list (used for memory handling of scene graph only). Several protos with the same ID/Name can be stored unregistered +\return a new proto object +*/ +GF_Proto *gf_sg_proto_new(GF_SceneGraph *sg, u32 ProtoID, char *name, Bool unregistered); + +/*! destroys a proto - can be used even if instances of the proto are still present +\param proto the target proto +\return error if any +*/ +GF_Err gf_sg_proto_del(GF_Proto *proto); + +/*! returns the graph associated with this proto. Such a graph cannot be used for rendering but is needed during +construction of proto dictionaries in case of nested protos +\param proto the target proto +\return associated scene graph proto the target proto +*/ +GF_SceneGraph *gf_sg_proto_get_graph(GF_Proto *proto); + +/*! adds node code - a proto is build of several nodes, the first node is used for rendering +and the others are kept private. This set of nodes is referred to as the proto "node code" +\param proto the target proto +\param n the node to add to the proto code +\return error if any +*/ +GF_Err gf_sg_proto_add_node_code(GF_Proto *proto, GF_Node *n); + +/*! gets number of field in the proto interface +\param proto the target proto +\return the number of fields +*/ +u32 gf_sg_proto_get_field_count(GF_Proto *proto); +/*! locates a field declaration by name +\param proto the target proto +\param fieldName the name of the field +\return the proto field interface or NULL if not found +*/ +GF_ProtoFieldInterface *gf_sg_proto_field_find_by_name(GF_Proto *proto, char *fieldName); +/*! locates field declaration by index +\param proto the target proto +\param fieldIndex 0-based index of the field to query +\return the proto field interface or NULL if not found +*/ +GF_ProtoFieldInterface *gf_sg_proto_field_find(GF_Proto *proto, u32 fieldIndex); + +/*! creates a new field declaration in the proto. of given fieldtype and eventType +fieldName can be NULL, if so the name will be fieldN, N being the index of the created field +\param proto the target proto +\param fieldType the data type of the field to create +\param eventType the event type of the field to create +\param fieldName the name of the field to create (may be NULL) +\return the new proto field interface +*/ +GF_ProtoFieldInterface *gf_sg_proto_field_new(GF_Proto *proto, u32 fieldType, u32 eventType, char *fieldName); + +/*! assigns the node field to a field of the proto (the node field IS the proto field) +the node shall be a node of the proto scenegraph, and the fieldtype/eventType of both fields shall match +(except SF/MFString and MF/SFURL which are allowed) due to BIFS semantics + +\param proto the target proto +\param protoFieldIndex the proto field index to assign +\param node the node (shall be part of the proto node code) to link to +\param nodeFieldIndex the field index of the node to link to +\return error if any +*/ +GF_Err gf_sg_proto_field_set_ised(GF_Proto *proto, u32 protoFieldIndex, GF_Node *node, u32 nodeFieldIndex); + +/*! returns field info of the field - this is typically used to setup the default value of the field +\param field the proto field interface to query +\param info filled with the proto field interface info +\return error if any +*/ +GF_Err gf_sg_proto_field_get_field(GF_ProtoFieldInterface *field, GF_FieldInfo *info); + +/* + NOTE on proto instances: + The proto instance is handled as an GF_Node outside the scenegraph lib, and is manipulated with the same functions + as an GF_Node + The proto instance may or may not be loaded. + An unloaded instance only contains the proto instance fields + A loaded instance contains the proto instance fields plus all the proto code (Nodes, routes) and + will load any scripts present in it. This allows keeping the memory usage of proto very low, especially + when nested protos (protos used as building blocks of their parent proto) are used. +*/ + +/*! creates the proto instance without the proto code +\param sg the target scene graph of the node +\param proto the proto to instanciate +\return the new prototype instance node +*/ +GF_Node *gf_sg_proto_create_instance(GF_SceneGraph *sg, GF_Proto *proto); + +/*! loads code in this instance - all subprotos are automatically created, thus you must only instantiate +top-level protos. VRML/BIFS doesn't allow for non top-level proto instanciation in the main graph +All nodes created in this proto will be forwarded to the app for initialization +\param proto_inst the proto instance to load +\return error if any +*/ +GF_Err gf_sg_proto_load_code(GF_Node *proto_inst); + +/*! locates a prototype definition by ID or by name. when looking by name, ID is ignored +\param sg the target scene graph of the proto +\param ProtoID the ID of the proto to locate +\param name the name of the proto to locate +\return the proto node or NULL if not found +*/ +GF_Proto *gf_sg_find_proto(GF_SceneGraph *sg, u32 ProtoID, char *name); + +/*! deletes all protos in given scene - does NOT delete instances of protos, only the proto object is destroyed +\param sg the target scene graph +\return error if any +*/ +GF_Err gf_sg_delete_all_protos(GF_SceneGraph *sg); + +/*! gets proto of a prototype instance node +\param node the target prototype instance node +\return the proto node or NULL if the node is not a prototype instance or the source proto was destroyed*/ +GF_Proto *gf_node_get_proto(GF_Node *node); +/*! returns the ID of a proto +\param proto the target proto +\return the proto ID +*/ +u32 gf_sg_proto_get_id(GF_Proto *proto); +/*! returns the name of a proto +\param proto the target proto +\return the proto name +*/ +const char *gf_sg_proto_get_class_name(GF_Proto *proto); + +/*! checks if a proto instance field is an SFTime routed to a startTime/stopTime field in the proto code (MPEG-4 specific for updates) +\param node the target prototype instance node +\param field the target field info +\return GF_TRUE if this is the case +*/ +Bool gf_sg_proto_field_is_sftime_offset(GF_Node *node, GF_FieldInfo *field); + +/*! sets an ISed (route between proto instance and internal proto code) field in a proto instance (not a proto) - this is needed with dynamic node creation inside a proto instance (conditionals) +\param protoinst the target prototype instance node +\param protoFieldIndex field index in prototype instance node +\param node the target node +\param nodeFieldIndex field index in the target node +\return error if any +*/ +GF_Err gf_sg_proto_instance_set_ised(GF_Node *protoinst, u32 protoFieldIndex, GF_Node *node, u32 nodeFieldIndex); + +/*! returns root node (the one and only one being traversed) of this proto instance if any +\param node the target prototype instance node +\return the root node of the proto code - may be NULL +*/ +GF_Node *gf_node_get_proto_root(GF_Node *node); + +/*! indicates proto field has been parsed and its value is valid - this is needed for externProtos not specifying default +values +\param node the target prototype instance node +\param info the target field info +*/ +void gf_sg_proto_mark_field_loaded(GF_Node *node, GF_FieldInfo *info); + + +/*! sets proto loader callback - callback user data is the same as simulation time callback + +GetExternProtoLib is a pointer to the proto lib loader - this callback shall return the LPSCENEGRAPH +of the extern proto lib if found and loaded, NULL if not found and GF_SG_INTERNAL_PROTO for internal +hardcoded protos (extensions of MPEG-4 scene graph used for module deveopment) +\param sg the target scene graph +\param GetExternProtoLib the callback function +*/ +void gf_sg_set_proto_loader(GF_SceneGraph *sg, GF_SceneGraph *(*GetExternProtoLib)(void *SceneCallback, MFURL *lib_url)); + +/*! gets a pointer to the MF URL field for externProto info - DO NOT TOUCH THIS FIELD +\param proto the target proto field +\return the MFURL associated with an extern proto, or NULL if proto is not an extern proto +*/ +MFURL *gf_sg_proto_get_extern_url(GF_Proto *proto); + + +/* + JavaScript tools +*/ + +/*! script fields type don't have the same value as the bifs ones...*/ +enum +{ + GF_SG_SCRIPT_TYPE_FIELD = 0, + GF_SG_SCRIPT_TYPE_EVENT_IN, + GF_SG_SCRIPT_TYPE_EVENT_OUT, +}; +/*! script field object*/ +typedef struct _scriptfield GF_ScriptField; +/*! creates new sript field - script fields are dynamically added to the node, and thus can be accessed through the +same functions as other GF_Node fields +\param script the script node +\param eventType the event type of the new field +\param fieldType the data type of the new field +\param name the name of the new field +\return a new scritp field +*/ +GF_ScriptField *gf_sg_script_field_new(GF_Node *script, u32 eventType, u32 fieldType, const char *name); +/*! retrieves field info of a script field object, useful to get the field index +\param field the script field to query +\param info filled with the field info +\return error if any +*/ +GF_Err gf_sg_script_field_get_info(GF_ScriptField *field, GF_FieldInfo *info); + +/*! activates eventIn for script node - needed for BIFS field replace +\param script the target script node +\param in_field the field info of the activated event in field +*/ +void gf_sg_script_event_in(GF_Node *script, GF_FieldInfo *in_field); + + +/*! signals eventOut has been set by field index +\note Routes are automatically triggered when the event is signaled +\param n the target node emitin the event +\param FieldIndex the field index emiting the event +*/ +void gf_node_event_out(GF_Node *n, u32 FieldIndex); +/*! signals eventOut has been set by event name. +\note Routes are automatically triggered when the event is signaled +\param n the target node emitin the event +\param eventName the name of the field emiting the event +*/ +void gf_node_event_out_str(GF_Node *n, const char *eventName); + +/*! gets MPEG-4 / VRML node tag by class name +\param node_name the node name +\return the node tag*/ +u32 gf_node_mpeg4_type_by_class_name(const char *node_name); + +#ifndef GPAC_DISABLE_X3D +/*! gets X3D node tag by class name +\param node_name the node name +\return the node tag*/ +u32 gf_node_x3d_type_by_class_name(const char *node_name); +#endif + + +#endif /*GPAC_DISABLE_VRML*/ + + +/*! check if a hardcoded prototype node acts as a grouping node +\param n the target prototype instance node +\return GF_TRUE if acting as a grouping node*/ +Bool gf_node_proto_is_grouping(GF_Node *n); + +/*! tags a hardcoded proto as being a grouping node +\param n the target prototype instance node +\return error if any +*/ +GF_Err gf_node_proto_set_grouping(GF_Node *n); + +/*! assigns callback to an eventIn field of an hardcoded proto +\param n the target prototype instance node +\param fieldIndex the target field index +\param event_in_cbk the event callback function +\return error if any +*/ +GF_Err gf_node_set_proto_eventin_handler(GF_Node *n, u32 fieldIndex, void (*event_in_cbk)(GF_Node *pThis, struct _route *route) ); + +/*! @} */ + + +#ifdef __cplusplus +} +#endif + + + +#endif /*_GF_SG_VRML_H_*/ diff --git a/include/gpac/setup.h b/include/gpac/setup.h new file mode 100644 index 0000000..a7793f5 --- /dev/null +++ b/include/gpac/setup.h @@ -0,0 +1,745 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / general OS configuration file + * + * 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_SETUP_H_ +#define _GF_SETUP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file "gpac/setup.h" +\brief Base data types of GPAC. + +This file contains the base data types of GPAC, depending on the platform. + + +*/ + + +/*! +\addtogroup setup_grp +\brief Base data types of GPAC. + +This section documents the base data types of GPAC, as well as some macros wrapping platform-specific functionalities. +For better portability, only use the base data types defined here. + +@{ +*/ + +/*This is to handle cases where config.h is generated at the root of the gpac build tree (./configure) +This is only needed when building libgpac and modules when libgpac is not installed*/ +#ifdef GPAC_HAVE_CONFIG_H +# include "config.h" +#else +# include <gpac/configuration.h> +#endif + + +/*WIN32 and WinCE config*/ +#if defined(WIN32) || defined(_WIN32_WCE) + +/*common win32 parts*/ +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> + +#if defined(_WIN64) && !defined(GPAC_64_BITS) +/*! macro defined for 64-bits platforms*/ +#define GPAC_64_BITS +#endif + +/*! 64 bit unsigned integer*/ +typedef unsigned __int64 u64; +/*! 64 bit signed integer*/ +typedef __int64 s64; +/*! 32 bit unsigned integer*/ +typedef unsigned int u32; +/*! 32 bit signed integer*/ +typedef int s32; +/*! 16 bit unsigned integer*/ +typedef unsigned short u16; +/*! 16 bit signed integer*/ +typedef short s16; +/*! 8 bit unsigned integer*/ +typedef unsigned char u8; +/*! 8 bit signed integer*/ +typedef char s8; + +#if defined(__GNUC__) +/*! macro for cross-platform inlining of functions*/ +#define GFINLINE inline +#else +/*! macro for cross-platform inlining of functions*/ +#define GFINLINE __inline +#endif + +/*! default path separator of the current platform*/ +#define GF_PATH_SEPARATOR '\\' +/*! default max filesystem path size of the current platform*/ +#define GF_MAX_PATH 1024 + +/*WINCE config*/ +#if defined(_WIN32_WCE) + +/*win32 assert*/ +#ifndef assert +void CE_Assert(u32 valid, char *file, u32 line); +#ifndef NDEBUG +#define assert( t ) CE_Assert((unsigned int) (t), __FILE__, __LINE__ ) +#else +#define assert(t) +#endif + +#endif + + +/*performs wide->char and char->wide conversion on a buffer GF_MAX_PATH long*/ +void CE_WideToChar(unsigned short *w_str, char *str); +void CE_CharToWide(char *str, unsigned short *w_str); + + +#define strdup _strdup +#define stricmp _stricmp +#define strnicmp _strnicmp +#define strupr _strupr +#define mkdir _mkdir +#define snprintf _snprintf +#define memccpy _memccpy + + +#ifndef _PTRDIFF_T_DEFINED +typedef int ptrdiff_t; +#define PTRDIFF(p1, p2, type) ((p1) - (p2)) +#define _PTRDIFF_T_DEFINED +#endif + +#ifndef _SIZE_T_DEFINED +typedef unsigned int size_t; +#define _SIZE_T_DEFINED +#endif + +#ifndef offsetof +#define offsetof(s,m) ((size_t)&(((s*)0)->m)) +#endif + +#ifndef getenv +#define getenv(a) 0L +#endif + +#define strupr _strupr +#define strlwr _strlwr + +/* +#define GPAC_DISABLE_LOG +*/ +#else /*END WINCE*/ + +/*WIN32 not-WinCE*/ +#include <ctype.h> +#include <string.h> +#include <float.h> +#include <limits.h> +#include <stdarg.h> +#include <assert.h> + +#define snprintf _snprintf + +#endif /*END WIN32 non win-ce*/ +/*end WIN32 config*/ + +/*start SYMBIAN config*/ +#elif defined(__SYMBIAN32__) + +/*! macro for cross-platform inlining of functions*/ +#define GFINLINE inline +/*! default path separator of the current platform*/ +#define GF_PATH_SEPARATOR '\\' + +/*we must explicitly export our functions...*/ + +/*! macro for cross-platform signaling of exported function of libgpac*/ +#define GF_EXPORT EXPORT_C + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <limits.h> +#include <ctype.h> +#include <string.h> +#include <assert.h> + +#ifdef __SERIES60_3X__ + +/*! 64 bit unsigned integer*/ +typedef unsigned __int64 u64; +/*! 64 bit signed integer*/ +typedef __int64 s64; + +#else + +/*FIXME - we don't have 64bit support here we should get rid of all 64bits divisions*/ +/* +typedef unsigned long long u64; +typedef long long s64; +*/ + +/*! 64 bit unsigned integer*/ +typedef unsigned int u64; +/*! 64 bit signed integer*/ +typedef signed int s64; + +#endif /*symbian 8*/ + + +/*! 32 bit unsigned integer*/ +typedef unsigned int u32; +/*! 32 bit signed integer*/ +typedef int s32; +/*! 16 bit unsigned integer*/ +typedef unsigned short u16; +/*! 16 bit signed integer*/ +typedef short s16; +/*! 8 bit unsigned integer*/ +typedef unsigned char u8; +/*! 8 bit signed integer*/ +typedef signed char s8; + +#pragma mpwc_relax on + +/*! default max filesystem path size of the current platform*/ +#define GF_MAX_PATH 260 + +/*sorry this was developed under w32 :)*/ +#define stricmp strcasecmp +#define strnicmp strncasecmp + +#ifndef strupr +char * my_str_upr(char *str); +#define strupr my_str_upr +#endif + +#ifndef strlwr +char * my_str_lwr(char *str); +#define strlwr my_str_lwr +#endif + +#ifndef DBL_MAX +#include <libc/ieeefp.h> +#define DBL_MAX (__IEEE_DBL_MAXPOWTWO) +#endif + +#ifndef FLT_MAX +#include <libc/ieeefp.h> +#define FLT_MAX (__IEEE_FLT_MAXPOWTWO) +#endif + +#ifndef FLT_EPSILON +#define FLT_EPSILON 1 +#endif + +/*end SYMBIAN config*/ + +#else + +/*UNIX likes*/ + +/*! max file offset bits*/ +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif +/*! largefile*/ +#ifndef _LARGEFILE_SOURCE +#define _LARGEFILE_SOURCE +#endif +/*! largefile64*/ +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <limits.h> +#include <float.h> +#include <ctype.h> +#include <string.h> +#include <assert.h> + +#if __APPLE__ && defined GPAC_CONFIG_IOS +#include <TargetConditionals.h> +#endif + +/*! 64 bit unsigned integer*/ +typedef uint64_t u64; +/*! 64 bit signed integer*/ +typedef int64_t s64; +/*! 32 bit unsigned integer*/ +typedef uint32_t u32; +/*! 32 bit signed integer*/ +typedef int32_t s32; +/*! 16 bit unsigned integer*/ +typedef uint16_t u16; +/*! 16 bit signed integer*/ +typedef int16_t s16; +/*! 8 bit unsigned integer*/ +typedef uint8_t u8; +/*! 8 bit signed integer*/ +typedef int8_t s8; + +/*! macro for cross-platform inlining of functions*/ +#define GFINLINE inline + +/*! use stricmp */ +#define stricmp strcasecmp +/*! use strnicmp */ +#define strnicmp strncasecmp + +#ifndef strupr +/*! gets upper case +\param str input string +\return uper case to free*/ +char *my_str_upr(char *str); +/*! use strupr */ +#define strupr my_str_upr +#endif + +#ifndef strlwr +/*! gets lower case +\param str input string +\return lower case to free*/ +char * my_str_lwr(char *str); +/*! use strulwr */ +#define strlwr my_str_lwr +#endif + +/*! default path separator of the current platform*/ +#define GF_PATH_SEPARATOR '/' + +#ifdef PATH_MAX +/*! default max filesystem path size of the current platform*/ +#define GF_MAX_PATH PATH_MAX +#else +/*! default max filesystem path size of the current platform*/ +#define GF_MAX_PATH 1023 +#endif + + +#endif /* end platform specific Win32/WinCE/UNIX*/ + + +//! @cond Doxygen_Suppress + +/*define what's missing*/ + +#ifndef NULL +#define NULL 0 +#endif + +//! @endcond + + +/*! Double-precision floating point number*/ +typedef double Double; +/*! Single-precision floating point number*/ +typedef float Float; +/*! 128 bit IDs */ +typedef u8 bin128[16]; +/*! max positive possible value for Double*/ +#define GF_MAX_DOUBLE DBL_MAX +/*! max negative possible value for Double*/ +#define GF_MIN_DOUBLE -GF_MAX_DOUBLE +/*! max positive possible value for Float*/ +#define GF_MAX_FLOAT FLT_MAX +/*! max negative possible value for Float*/ +#define GF_MIN_FLOAT -GF_MAX_FLOAT +/*! smallest possible value for float*/ +#define GF_EPSILON_FLOAT FLT_EPSILON +/*! max possible value for s16*/ +#define GF_SHORT_MAX SHRT_MAX +/*! min possible value for s16*/ +#define GF_SHORT_MIN SHRT_MIN +/*! max possible value for u32*/ +#define GF_UINT_MAX UINT_MAX +/*! max possible value for s32*/ +#define GF_INT_MAX INT_MAX +/*! min possible value for s32*/ +#define GF_INT_MIN INT_MIN + +#ifndef MIN +/*! get the smallest of two numbers*/ +#define MIN(X, Y) ((X)<(Y)?(X):(Y)) +#endif +#ifndef MAX +/*! get the biggest of two numbers*/ +#define MAX(X, Y) ((X)>(Y)?(X):(Y)) +#endif + +/*! get the absolute difference betwee two numbers*/ +#define ABSDIFF(a, b) ( ( (a) > (b) ) ? ((a) - (b)) : ((b) - (a)) ) + +#ifndef ABS +/*! get the absolute value of a number*/ +#define ABS(a) ( ( (a) > 0 ) ? (a) : - (a) ) +#endif + +#ifndef Bool +/*! boolean value*/ +typedef enum { + GF_FALSE = 0, + GF_TRUE +} Bool; +#endif + +/*! 32 bit fraction*/ +typedef struct { + s32 num; + u32 den; +} GF_Fraction; + +/*! 64 bit fraction*/ +typedef struct { + s64 num; + u64 den; +} GF_Fraction64; + +#if (defined (WIN32) || defined (_WIN32_WCE)) && (defined(__MINGW32__) || !defined(__GNUC__)) + +#if defined(__MINGW32__) +/*! macro for cross-platform suffix used for formatting s64 integers in logs and printf routines*/ +#define LLD_SUF "lld" +/*! macro for cross-platform suffix used for formatting u64 integers in logs and printf routines*/ +#define LLU_SUF "llu" +/*! macro for cross-platform suffix used for formatting u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "llx" +#else +/*! macro for cross-platform suffix used for formatting s64 integers in logs and printf routines*/ +#define LLD_SUF "I64d" +/*! macro for cross-platform suffix used for formatting u64 integers in logs and printf routines*/ +#define LLU_SUF "I64u" +/*! macro for cross-platform suffix used for formatting u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "I64x" +#endif + +#ifdef _WIN64 +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u64) +#else +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u32) +#endif + +#elif defined (__SYMBIAN32__) + +/*! macro for cross-platform suffix used for formatting s64 integers in logs and printf routines*/ +#define LLD_SUF "d" +/*! macro for cross-platform suffix used for formatting u64 integers in logs and printf routines*/ +#define LLU_SUF "u" +/*! macro for cross-platform suffix used for formatting u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "x" + +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u32) + +/*seems that even though _LP64 is defined in OSX, %ll modifiers are still needed*/ +#elif defined(__DARWIN__) || defined(__APPLE__) + +/*! macro for cross-platform suffix used for formatting s64 integers in logs and printf routines*/ +#define LLD_SUF "lld" +/*! macro for cross-platform suffix used for formatting u64 integers in logs and printf routines*/ +#define LLU_SUF "llu" +/*! macro for cross-platform suffix used for formatting u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "llx" + +#ifdef __LP64__ /* Mac OS 64 bits */ +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u64) +#else +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u32) +#endif + +#elif defined(_LP64) /*Unix 64 bits*/ + +/*! macro for cross-platform suffix used for formatting s64 integers in logs and printf routines*/ +#define LLD_SUF "ld" +/*! macro for cross-platform suffix used for formatting u64 integers in logs and printf routines*/ +#define LLU_SUF "lu" +/*! macro for cross-platform suffix used for formatting u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "lx" + +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u64) + +#else /*Unix 32 bits*/ + +/*! macro for cross-platform suffix used for formatting s64 integers in logs and printf routines*/ +#define LLD_SUF "lld" +/*! macro for cross-platform suffix used for formatting u64 integers in logs and printf routines*/ +#define LLU_SUF "llu" +/*! macro for cross-platform suffix used for formatting u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "llx" + +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u32) + +#endif + +#ifndef PTR_TO_U_CAST +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST +#endif + +/*! macro for cross-platform formatting of s64 integers in logs and printf routines*/ +#define LLD "%" LLD_SUF +/*! macro for cross-platform formatting of u64 integers in logs and printf routines*/ +#define LLU "%" LLU_SUF +/*! macro for cross-platform formatting of u64 integers as hexadecimal in logs and printf routines*/ +#define LLX "%" LLX_SUF + + +#if !defined(GF_EXPORT) +#if defined(__GNUC__) && __GNUC__ >= 4 && !defined(GPAC_CONFIG_IOS) +/*! macro for cross-platform signaling of exported function of libgpac*/ +#define GF_EXPORT __attribute__((visibility("default"))) +#else +/*use def files for windows or let compiler decide*/ + +/*! macro for cross-platform signaling of exported function of libgpac*/ +#define GF_EXPORT +#endif +#endif + + +//! @cond Doxygen_Suppress + +#if defined(GPAC_CONFIG_IOS) +#define GPAC_STATIC_MODULES +#endif + +/*safety checks on macros*/ + +#ifdef GPAC_DISABLE_ZLIB +# define GPAC_DISABLE_LOADER_BT +# define GPAC_DISABLE_SWF_IMPORT +#endif + +#ifdef GPAC_DISABLE_VRML +# ifndef GPAC_DISABLE_BIFS +# define GPAC_DISABLE_BIFS +# endif +# ifndef GPAC_DISABLE_QTVR +# define GPAC_DISABLE_QTVR +# endif +# ifndef GPAC_DISABLE_X3D +# define GPAC_DISABLE_X3D +# endif +# ifndef GPAC_DISABLE_LOADER_BT +# define GPAC_DISABLE_LOADER_BT +# endif +# ifndef GPAC_DISABLE_LOADER_XMT +# define GPAC_DISABLE_LOADER_XMT +# endif +#endif + +#ifdef GPAC_DISABLE_SVG +# ifndef GPAC_DISABLE_LASER +# define GPAC_DISABLE_LASER +# endif +#endif + + +#ifdef GPAC_DISABLE_AV_PARSERS +# ifndef GPAC_DISABLE_MPEG2PS +# define GPAC_DISABLE_MPEG2PS +# endif +# ifndef GPAC_DISABLE_ISOM_HINTING +# define GPAC_DISABLE_ISOM_HINTING +# endif +#endif + +#ifdef GPAC_DISABLE_ISOM +# ifndef GPAC_DISABLE_ISOM_WRITE +# define GPAC_DISABLE_ISOM_WRITE +# endif +# ifndef GPAC_DISABLE_ISOM_HINTING +# define GPAC_DISABLE_ISOM_HINTING +# endif +# ifndef GPAC_DISABLE_ISOM_FRAGMENTS +# define GPAC_DISABLE_ISOM_FRAGMENTS +# endif +# ifndef GPAC_DISABLE_SCENE_ENCODER +# define GPAC_DISABLE_SCENE_ENCODER +# endif +# ifndef GPAC_DISABLE_ISOM_DUMP +# define GPAC_DISABLE_ISOM_DUMP +# endif +# ifndef GPAC_DISABLE_LOADER_ISOM +# define GPAC_DISABLE_LOADER_ISOM +# endif +# ifndef GPAC_DISABLE_MEDIA_EXPORT +# define GPAC_DISABLE_MEDIA_EXPORT +# endif +#endif + +#ifdef GPAC_DISABLE_ISOM_WRITE +# ifndef GPAC_DISABLE_MEDIA_IMPORT +# define GPAC_DISABLE_MEDIA_IMPORT +# endif +# ifndef GPAC_DISABLE_QTVR +# define GPAC_DISABLE_QTVR +# endif +# ifndef GPAC_DISABLE_ISOM_HINTING +# define GPAC_DISABLE_ISOM_HINTING +# endif +# ifndef GPAC_DISABLE_SCENE_ENCODER +# define GPAC_DISABLE_SCENE_ENCODER +# endif +#endif + +#ifdef GPAC_DISABLE_STREAMING +# ifndef GPAC_DISABLE_ISOM_HINTING +# define GPAC_DISABLE_ISOM_HINTING +# endif +#endif + +#ifdef GPAC_DISABLE_BIFS +# ifndef GPAC_DISABLE_BIFS_ENC +# define GPAC_DISABLE_BIFS_ENC +# endif +#endif + +#if defined(GPAC_DISABLE_BIFS_ENC) && defined(GPAC_DISABLE_LASER) +# ifndef GPAC_DISABLE_LOADER_ISOM +# define GPAC_DISABLE_LOADER_ISOM +# endif +# ifndef GPAC_DISABLE_SENG +# define GPAC_DISABLE_SENG +# endif +#endif + +//we currently disable all extra IPMP/IPMPX/OCI/extra MPEG-4 descriptors parsing +#ifndef GPAC_MINIMAL_ODF +#define GPAC_MINIMAL_ODF +#endif + +//define this to remove most of built-in doc of libgpac - for now filter description and help is removed, but argument help is not +//#define GPAC_DISABLE_DOC + + +//! @endcond + +/*! @} */ + +/*! +\addtogroup mem_grp +\brief Memory management + +GPAC can use its own memory tracker, depending on compilation option. It is recommended to use only the functions +defined in this section to allocate and free memory whenever developing within the GPAC library. + +\warning these functions shall only be used after initializing the library using \ref gf_sys_init +@{ +*/ +/*GPAC memory tracking*/ +#if defined(GPAC_MEMORY_TRACKING) + + +void *gf_mem_malloc(size_t size, const char *filename, int line); +void *gf_mem_calloc(size_t num, size_t size_of, const char *filename, int line); +void *gf_mem_realloc(void *ptr, size_t size, const char *filename, int line); +void gf_mem_free(void *ptr, const char *filename, int line); +char *gf_mem_strdup(const char *str, const char *filename, int line); +void gf_memory_print(void); /*prints the state of current allocations*/ +u64 gf_memory_size(); /*gets memory allocated in bytes*/ + +/*! free memory allocated with gpac*/ +#define gf_free(ptr) gf_mem_free(ptr, __FILE__, __LINE__) +/*! allocates memory, shall be freed using \ref gf_free*/ +#define gf_malloc(size) gf_mem_malloc(size, __FILE__, __LINE__) +/*! allocates memory array, shall be freed using \ref gf_free*/ +#define gf_calloc(num, size_of) gf_mem_calloc(num, size_of, __FILE__, __LINE__) +/*! duplicates string, shall be freed using \ref gf_free*/ +#define gf_strdup(s) gf_mem_strdup(s, __FILE__, __LINE__) +/*! reallocates memory, shall be freed using \ref gf_free*/ +#define gf_realloc(ptr1, size) gf_mem_realloc(ptr1, size, __FILE__, __LINE__) + +#else + +/*! free memory allocated with gpac +\param ptr same as free() +*/ +void gf_free(void *ptr); + +/*! allocates memory, shall be freed using \ref gf_free +\param size same as malloc() +\return address of allocated block +*/ +void* gf_malloc(size_t size); + +/*! allocates memory array, shall be freed using \ref gf_free +\param num same as calloc() +\param size_of same as calloc() +\return address of allocated block +*/ +void* gf_calloc(size_t num, size_t size_of); + +/*! duplicates string, shall be freed using \ref gf_free +\param str same as strdup() +\return duplicated string +*/ +char* gf_strdup(const char *str); + +/*! reallocates memory, shall be freed using \ref gf_free +\param ptr same as realloc() +\param size same as realloc() +\return address of reallocated block +*/ +void* gf_realloc(void *ptr, size_t size); + +#endif +/*! @} */ + + +/*end GPAC memory tracking*/ + +/*! copy source string to destination, ensuring 0-terminated string result +\param dst destination buffer +\param src source buffer +\param dsize size of destination buffer +\return same as strlcpy +*/ +size_t gf_strlcpy(char *dst, const char *src, size_t dsize); + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_SETUP_H_*/ diff --git a/include/gpac/svg_types.h b/include/gpac/svg_types.h new file mode 100644 index 0000000..a32a21f --- /dev/null +++ b/include/gpac/svg_types.h @@ -0,0 +1,1065 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2004-2019 + * All rights reserved + * + * This file is part of GPAC / Scene Graph 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_SVG_SVG_TYPES_H_ +#define _GF_SVG_SVG_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/svg_types.h> +\brief Data types used for SVG scene graph +*/ + +/*! +\ingroup ssvg +\brief Data types used for SVG scene graph. + +This section documents the data types used for SVG scene graph. + +@{ +*/ + +#include <gpac/path2d.h> +#include <gpac/events_constants.h> + + + +/* Attributes in SVG can be accessed using a GF_FieldInfo structure + like it is done in the BIFS part of the implementation: + + fieldIndex: attribute tag to identify the attribute in the element in the case of dynamic alloc (default) + or index of the attribute in the element in the case of static allocation of attributes + + fieldType: attribute data type as in the enumeration below + + name: attribute name (WARNING: this may be NULL) + + far_ptr: pointer to the actual data with one of the type given in this file + + NDTType: unused in SVG + eventType: unused in SVG + on_event_in: unused in SVG +*/ + +/*! SVG attribute types */ +enum { + SVG_Unknown_datatype = 0, + + /* keyword enum types */ + XML_Space_datatype, + XMLEV_Propagate_datatype, + XMLEV_DefaultAction_datatype, + XMLEV_Phase_datatype, + SVG_FillRule_datatype, + SVG_StrokeLineJoin_datatype, + SVG_StrokeLineCap_datatype, + SVG_FontStyle_datatype, + SVG_FontWeight_datatype, + SVG_FontVariant_datatype, + SVG_TextAnchor_datatype, + SVG_TransformType_datatype, + SVG_Display_datatype, + SVG_Visibility_datatype, + SVG_Overflow_datatype, + SVG_ZoomAndPan_datatype, + SVG_DisplayAlign_datatype, + SVG_PointerEvents_datatype, + SVG_RenderingHint_datatype, + SVG_VectorEffect_datatype, + SVG_PlaybackOrder_datatype, + SVG_TimelineBegin_datatype, + SVG_GradientUnit_datatype, + SVG_InitialVisibility_datatype, + SVG_FocusHighlight_datatype, + SVG_Overlay_datatype, + SVG_TransformBehavior_datatype, + SVG_SpreadMethod_datatype, + SVG_TextAlign_datatype, + SVG_Focusable_datatype, + SVG_Filter_TransferType_datatype, + SMIL_SyncBehavior_datatype, + SMIL_SyncTolerance_datatype, + SMIL_AttributeType_datatype, + SMIL_CalcMode_datatype, + SMIL_Additive_datatype, + SMIL_Accumulate_datatype, + SMIL_Restart_datatype, + SMIL_Fill_datatype, + SVG_ClipPath_datatype, + + SVG_LAST_U8_PROPERTY, + + DOM_String_datatype, + DOM_StringList_datatype, + + XMLEV_Event_datatype, + XMLRI_datatype, + XMLRI_List_datatype, + XML_IDREF_datatype, + + SMIL_KeyTimes_datatype, + SMIL_KeySplines_datatype, + SMIL_KeyPoints_datatype, + SMIL_Times_datatype, + + /* animated (untyped) value */ + SMIL_AnimateValue_datatype, + SMIL_AnimateValues_datatype, + SMIL_Duration_datatype, + SMIL_RepeatCount_datatype, + SMIL_AttributeName_datatype, + + /* SVG Number */ + SVG_Number_datatype, + SVG_FontSize_datatype, + SVG_Length_datatype, + SVG_Coordinate_datatype, + SVG_Rotate_datatype, + + /* List of */ + SVG_Numbers_datatype, + SVG_Points_datatype, + SVG_Coordinates_datatype, + + /*all other types*/ + SVG_Boolean_datatype, + SVG_Color_datatype, + SVG_Paint_datatype, + SVG_PathData_datatype, + SVG_FontFamily_datatype, + SVG_ID_datatype, + + SVG_StrokeDashArray_datatype, + SVG_PreserveAspectRatio_datatype, + SVG_ViewBox_datatype, + SVG_GradientOffset_datatype, + SVG_Focus_datatype, + SVG_Clock_datatype, + SVG_ContentType_datatype, + SVG_LanguageID_datatype, + + /* Matrix related types */ + SVG_Transform_datatype, + SVG_Transform_Translate_datatype, + SVG_Transform_Scale_datatype, + SVG_Transform_SkewX_datatype, + SVG_Transform_SkewY_datatype, + SVG_Transform_Rotate_datatype, + SVG_Motion_datatype, + + /*LASeR types*/ + LASeR_Choice_datatype, + LASeR_Size_datatype, + + SVG_Matrix2D_datatype, + + /*internal type for node list*/ + SVG_NodeList_datatype +}; + +//! @cond Doxygen_Suppress +/* Definition of SVG base data types */ +typedef char *DOM_String; +typedef DOM_String SVG_String; +typedef DOM_String SVG_ContentType; +typedef DOM_String SVG_LanguageID; +typedef DOM_String SVG_TextContent; + +/* types not yet handled properly, i.e. for the moment using strings */ +typedef DOM_String SVG_ID; +typedef DOM_String SVG_LinkTarget; +typedef DOM_String SVG_GradientOffset; + +typedef Double SVG_Clock; + +typedef GF_List *ListOfXXX; +typedef GF_List *SVG_Numbers; +typedef GF_List *SVG_Coordinates; +typedef GF_List *SVG_FeatureList; +typedef GF_List *SVG_ExtensionList; +typedef GF_List *SVG_FormatList; +typedef GF_List *SVG_ListOfIRI; +typedef GF_List *SVG_LanguageIDs; +typedef GF_List *SVG_FontList; +typedef GF_List *SVG_TransformList; +typedef GF_List *SVG_Points; +typedef GF_List *SMIL_Times; +typedef GF_List *SMIL_KeyTimes; +typedef GF_List *SMIL_KeyPoints; +/* Fixed between 0 and 1 */ +typedef GF_List *SMIL_KeySplines; + +typedef GF_Matrix2D SVG_Motion; + +//! @endcond + + +/*! SMIL Anim types */ +typedef struct { + /*field type*/ + u32 type; + /*field pointer*/ + void *field_ptr; + /*attribute name for textual parsing*/ + char *name; + /*attribute tag for live transcoding*/ + u32 tag; +} SMIL_AttributeName; + +/*! SMIL time types */ +enum { + /*clock time*/ + GF_SMIL_TIME_CLOCK = 0, + /*wallclock time*/ + GF_SMIL_TIME_WALLCLOCK = 1, + /*resolved time of an event, discarded when restarting animation.*/ + GF_SMIL_TIME_EVENT_RESOLVED = 2, + /*event time*/ + GF_SMIL_TIME_EVENT = 3, + /*indefinite time*/ + GF_SMIL_TIME_INDEFINITE = 4, +}; + +/*! macro to check if a SMIL time is a clock value*/ +#define GF_SMIL_TIME_IS_CLOCK(v) (v<=GF_SMIL_TIME_EVENT_RESOLVED) +/*! macro to check if a SMIL time is a resolved clock value*/ +#define GF_SMIL_TIME_IS_SPECIFIED_CLOCK(v) (v<GF_SMIL_TIME_EVENT_RESOLVED) + +/*! XML event*/ +typedef struct +{ + GF_EventType type; + /*for accessKey and mouse button, or repeatCount when the event is a SMIL repeat */ + u32 parameter; +} XMLEV_Event; + +/*! SMIL time*/ +typedef struct { + /* Type of timing value*/ + u8 type; + /* in case of syncbase, event, repeat value: this is the pointer to the source of the event */ + GF_Node *element; + /* id of the element before resolution of the pointer to the element */ + char *element_id; + /* listener associated with event */ + GF_Node *listener; + + /* event type and parameter */ + XMLEV_Event event; + /*set if event is + begin rather than beginEvent, + end rather than endEvent, + repeat rather than repeatEvent */ + Bool is_absolute_event; + /*clock offset (absolute or relative to event)*/ + Double clock; + +} SMIL_Time; + +/*! SMIL duration types */ +enum { + SMIL_DURATION_AUTO = 0, + SMIL_DURATION_INDEFINITE, + SMIL_DURATION_MEDIA, + SMIL_DURATION_NONE, + SMIL_DURATION_DEFINED, +}; +/*! SMIL duration*/ +typedef struct { + u8 type; + Double clock_value; +} SMIL_Duration; + +/*! SMIL retart types */ +enum { + SMIL_RESTART_ALWAYS = 0, + SMIL_RESTART_NEVER, + SMIL_RESTART_WHENNOTACTIVE, +}; +/*! SMIL restart*/ +typedef u8 SMIL_Restart; + +/*! SMIL fill types */ +enum { + SMIL_FILL_FREEZE=0, + SMIL_FILL_REMOVE, + +}; +/*! SMIL fill*/ +typedef u8 SMIL_Fill; + +/*! SMIL repeatcount types */ +enum { + SMIL_REPEATCOUNT_INDEFINITE = 0, + SMIL_REPEATCOUNT_DEFINED = 1, + /* used only for static allocation of SVG attributes */ + SMIL_REPEATCOUNT_UNSPECIFIED = 2 +}; +/*! SMIL repeat count*/ +typedef struct { + u8 type; + Fixed count; +} SMIL_RepeatCount; + +/*! SMIL animate value*/ +typedef struct { + u8 type; + void *value; +} SMIL_AnimateValue; + +/*! SMIL animate values*/ +typedef struct { + u8 type, laser_strings; + GF_List *values; +} SMIL_AnimateValues; + +/*! SMIL additive types */ +enum { + SMIL_ADDITIVE_REPLACE = 0, + SMIL_ADDITIVE_SUM +}; +/*! SMIL additive*/ +typedef u8 SMIL_Additive; + +/*! SMIL accumulate types */ +enum { + SMIL_ACCUMULATE_NONE = 0, + SMIL_ACCUMULATE_SUM +}; +/*! SMIL accumulate*/ +typedef u8 SMIL_Accumulate; + +/*! SMIL calcmode types */ +enum { + /*WARNING: default value is linear, order changed for LASeR coding*/ + SMIL_CALCMODE_DISCRETE = 0, + SMIL_CALCMODE_LINEAR, + SMIL_CALCMODE_PACED, + SMIL_CALCMODE_SPLINE +}; +/*! SMIL calc mode*/ +typedef u8 SMIL_CalcMode; +/* end of SMIL Anim types */ + +/*! XMLRI types */ +enum { + XMLRI_ELEMENTID = 0, + XMLRI_STRING, + XMLRI_STREAMID +}; +/*! XMLRI object*/ +typedef struct __xml_ri { + u8 type; + char *string; + void *target; + u32 lsr_stream_id; + u32 node_id; +} XMLRI; + +/*! XML IDREF object + \note the same structure is used to watch for IDREF changes (LASeR node replace) +*/ +typedef struct __xml_ri XML_IDREF; + +/*! SVG focus types */ +enum +{ + SVG_FOCUS_AUTO = 0, + SVG_FOCUS_SELF, + SVG_FOCUS_IRI, +}; + +/*! SVG focus attribute*/ +typedef struct +{ + u8 type; + XMLRI target; +} SVG_Focus; + +/*! SVG fontfamiliy types */ +enum { + SVG_FONTFAMILY_INHERIT = 0, + SVG_FONTFAMILY_VALUE +}; + +/*! SVG font attribute*/ +typedef struct { + u8 type; + SVG_String value; +} SVG_FontFamily; + +/*! SVG fontstyle types */ +enum { + SVG_FONTSTYLE_INHERIT = 0, + SVG_FONTSTYLE_ITALIC = 1, + SVG_FONTSTYLE_NORMAL = 2, + SVG_FONTSTYLE_OBLIQUE = 3 +}; +/*! SVG fontstyle attribute*/ +typedef u8 SVG_FontStyle; + +/*! SVG path commands types +\note the values are chosen to match LASeR code points +*/ +enum { + SVG_PATHCOMMAND_M = 3, + SVG_PATHCOMMAND_L = 2, + SVG_PATHCOMMAND_C = 0, + SVG_PATHCOMMAND_S = 5, + SVG_PATHCOMMAND_Q = 4, + SVG_PATHCOMMAND_T = 6, + SVG_PATHCOMMAND_A = 20, + SVG_PATHCOMMAND_Z = 8 +}; + +/*! macros to use GF_Path directly as SVG path*/ +#define USE_GF_PATH 1 + +#if USE_GF_PATH +/*! SVG path data*/ +typedef GF_Path SVG_PathData; +#else +/*! SVG path data*/ +typedef struct { + GF_List *commands; + GF_List *points; +} SVG_PathData; +#endif + +/*! SVG point*/ +typedef struct { + Fixed x, y; +} SVG_Point; + +/*! SVG point+angle*/ +typedef struct { + Fixed x, y, angle; +} SVG_Point_Angle; + +/*! SVG ViewBox*/ +typedef struct { + Bool is_set; + Fixed x, y, width, height; +} SVG_ViewBox; + +/*! SVG Boolean*/ +typedef Bool SVG_Boolean; + +/*! SVG fill rule types */ +enum { + SVG_FILLRULE_EVENODD= 0, + SVG_FILLRULE_NONZERO, + SVG_FILLRULE_INHERIT +}; +/*! SVG fill rule*/ +typedef u8 SVG_FillRule; + +/*! SVG stroke line join types */ +enum { + SVG_STROKELINEJOIN_MITER = GF_LINE_JOIN_MITER_SVG, + SVG_STROKELINEJOIN_ROUND = GF_LINE_JOIN_ROUND, + SVG_STROKELINEJOIN_BEVEL = GF_LINE_JOIN_BEVEL, + SVG_STROKELINEJOIN_INHERIT = 100 +}; +/*! SVG stroke line join*/ +typedef u8 SVG_StrokeLineJoin; + +/*! SVG stroke line cap types +\warning GPAC naming is not the same as SVG naming for line cap Flat = butt and Butt = square*/ +enum { + SVG_STROKELINECAP_BUTT = GF_LINE_CAP_FLAT, + SVG_STROKELINECAP_ROUND = GF_LINE_CAP_ROUND, + SVG_STROKELINECAP_SQUARE = GF_LINE_CAP_SQUARE, + SVG_STROKELINECAP_INHERIT = 100 +}; +/*! SVG stroke line cap*/ +typedef u8 SVG_StrokeLineCap; + +/*! SVG overflow types */ +enum { + SVG_OVERFLOW_INHERIT = 0, + SVG_OVERFLOW_VISIBLE = 1, + SVG_OVERFLOW_HIDDEN = 2, + SVG_OVERFLOW_SCROLL = 3, + SVG_OVERFLOW_AUTO = 4 +}; +/*! SVG overflow*/ +typedef u8 SVG_Overflow; + +/*! SVG color types */ +enum { + SVG_COLOR_RGBCOLOR = 0, + SVG_COLOR_INHERIT, + SVG_COLOR_CURRENTCOLOR, + SVG_COLOR_ACTIVE_BORDER, /*Active window border*/ + SVG_COLOR_ACTIVE_CAPTION, /*Active window caption. */ + SVG_COLOR_APP_WORKSPACE, /*Background color of multiple document interface. */ + SVG_COLOR_BACKGROUND, /*Desktop background. */ + SVG_COLOR_BUTTON_FACE, /* Face color for three-dimensional display elements. */ + SVG_COLOR_BUTTON_HIGHLIGHT, /* Dark shadow for three-dimensional display elements (for edges facing away from the light source). */ + SVG_COLOR_BUTTON_SHADOW, /* Shadow color for three-dimensional display elements. */ + SVG_COLOR_BUTTON_TEXT, /*Text on push buttons. */ + SVG_COLOR_CAPTION_TEXT, /* Text in caption, size box, and scrollbar arrow box. */ + SVG_COLOR_GRAY_TEXT, /* Disabled ('grayed') text. */ + SVG_COLOR_HIGHLIGHT, /* Item(s) selected in a control. */ + SVG_COLOR_HIGHLIGHT_TEXT, /*Text of item(s) selected in a control. */ + SVG_COLOR_INACTIVE_BORDER, /* Inactive window border. */ + 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_MENU, /*Menu background. */ + SVG_COLOR_MENU_TEXT, /* Text in menus. */ + SVG_COLOR_SCROLLBAR, /* Scroll bar gray area. */ + SVG_COLOR_3D_DARK_SHADOW, /* Dark shadow for three-dimensional display elements. */ + SVG_COLOR_3D_FACE, /* Face color for three-dimensional display elements. */ + SVG_COLOR_3D_HIGHLIGHT, /* Highlight color for three-dimensional display elements. */ + SVG_COLOR_3D_LIGHT_SHADOW, /* Light color for three-dimensional display elements (for edges facing the light source). */ + SVG_COLOR_3D_SHADOW, /* Dark shadow for three-dimensional display elements. */ + SVG_COLOR_WINDOW, /* Window background. */ + SVG_COLOR_WINDOW_FRAME, /* Window frame. */ + SVG_COLOR_WINDOW_TEXT /* Text in windows.*/ +}; + +/*! SVG color*/ +typedef struct { + u8 type; + Fixed red, green, blue; +} SVG_Color; + +/*! SVG paint types */ +enum { + SVG_PAINT_NONE = 0, + SVG_PAINT_COLOR = 1, + SVG_PAINT_URI = 2, + SVG_PAINT_INHERIT = 3 +}; + +/*! SVG paint attribute*/ +struct __svg_color{ + u8 type; + SVG_Color color; + XMLRI iri; +}; +/*! SVG paint attribute*/ +typedef struct __svg_color SVG_Paint; +/*! SVG color attribute*/ +typedef struct __svg_color SVG_SVGColor; + +/*! SVG number types */ +enum { + SVG_NUMBER_VALUE = 0, + SVG_NUMBER_PERCENTAGE = 1, + SVG_NUMBER_EMS = 2, + SVG_NUMBER_EXS = 3, + SVG_NUMBER_PX = 4, + SVG_NUMBER_CM = 5, + SVG_NUMBER_MM = 6, + SVG_NUMBER_IN = 7, + SVG_NUMBER_PT = 8, + SVG_NUMBER_PC = 9, + SVG_NUMBER_INHERIT = 10, + SVG_NUMBER_AUTO = 11, + SVG_NUMBER_AUTO_REVERSE = 12 +}; + +struct __svg_number { + u8 type; + Fixed value; +}; +/*! SVG number*/ +typedef struct __svg_number SVG_Number; +/*! SVG font size*/ +typedef struct __svg_number SVG_FontSize; +/*! SVG length*/ +typedef struct __svg_number SVG_Length; +/*! SVG coordinate*/ +typedef struct __svg_number SVG_Coordinate; +/*! SVG rotation*/ +typedef struct __svg_number SVG_Rotate; + +/*! SVG transform*/ +typedef struct { + u8 is_ref; + GF_Matrix2D mat; +} SVG_Transform; + +/*! SVG transform types */ +enum { + SVG_TRANSFORM_MATRIX = 0, + SVG_TRANSFORM_TRANSLATE = 1, + SVG_TRANSFORM_SCALE = 2, + SVG_TRANSFORM_ROTATE = 3, + SVG_TRANSFORM_SKEWX = 4, + SVG_TRANSFORM_SKEWY = 5 +}; + +/*! SVG transform type*/ +typedef u8 SVG_TransformType; + +/*! SVG fontweight types */ +enum { + SVG_FONTWEIGHT_100 = 0, + SVG_FONTWEIGHT_200, + SVG_FONTWEIGHT_300, + SVG_FONTWEIGHT_400, + SVG_FONTWEIGHT_500, + SVG_FONTWEIGHT_600, + SVG_FONTWEIGHT_700, + SVG_FONTWEIGHT_800, + SVG_FONTWEIGHT_900, + SVG_FONTWEIGHT_BOLD, + SVG_FONTWEIGHT_BOLDER, + SVG_FONTWEIGHT_INHERIT, + SVG_FONTWEIGHT_LIGHTER, + SVG_FONTWEIGHT_NORMAL +}; +/*! SVG font weight*/ +typedef u8 SVG_FontWeight; + +/*! SVG fontvariant types */ +enum { + SVG_FONTVARIANT_INHERIT = 0, + SVG_FONTVARIANT_NORMAL = 1, + SVG_FONTVARIANT_SMALLCAPS = 2 +}; +/*! SVG font variant*/ +typedef u8 SVG_FontVariant; + +/*! SVG visibility types */ +enum { + SVG_VISIBILITY_HIDDEN = 0, + SVG_VISIBILITY_INHERIT = 1, + SVG_VISIBILITY_VISIBLE = 2, + SVG_VISIBILITY_COLLAPSE = 3 +}; +/*! SVG visibility*/ +typedef u8 SVG_Visibility; + +/*! SVG display types */ +enum { + SVG_DISPLAY_INHERIT = 0, + SVG_DISPLAY_NONE = 1, + SVG_DISPLAY_INLINE = 2, + SVG_DISPLAY_BLOCK, + SVG_DISPLAY_LIST_ITEM, + SVG_DISPLAY_RUN_IN, + SVG_DISPLAY_COMPACT, + SVG_DISPLAY_MARKER, + SVG_DISPLAY_TABLE, + SVG_DISPLAY_INLINE_TABLE, + SVG_DISPLAY_TABLE_ROW_GROUP, + SVG_DISPLAY_TABLE_HEADER_GROUP, + SVG_DISPLAY_TABLE_FOOTER_GROUP, + SVG_DISPLAY_TABLE_ROW, + SVG_DISPLAY_TABLE_COLUMN_GROUP, + SVG_DISPLAY_TABLE_COLUMN, + SVG_DISPLAY_TABLE_CELL, + SVG_DISPLAY_TABLE_CAPTION +}; +/*! SVG display*/ +typedef u8 SVG_Display; + +/*! SVG display-align types */ +enum { + SVG_DISPLAYALIGN_INHERIT = 0, + SVG_DISPLAYALIGN_AUTO = 1, + SVG_DISPLAYALIGN_AFTER = 2, + SVG_DISPLAYALIGN_BEFORE = 3, + SVG_DISPLAYALIGN_CENTER = 4 +}; +/*! SVG display alignment*/ +typedef u8 SVG_DisplayAlign; + +/*! SVG text-align types */ +enum { + SVG_TEXTALIGN_INHERIT = 0, + SVG_TEXTALIGN_START = 1, + SVG_TEXTALIGN_CENTER = 2, + SVG_TEXTALIGN_END = 3 +}; +/*! SVG text alignment*/ +typedef u8 SVG_TextAlign; + +/*! SVG stroke dash array types */ +enum { + SVG_STROKEDASHARRAY_NONE = 0, + SVG_STROKEDASHARRAY_INHERIT = 1, + SVG_STROKEDASHARRAY_ARRAY = 2 +}; + +/*! SVG unit array*/ +typedef struct { + u32 count; + Fixed* vals; + u8 *units; +} UnitArray; + +/*! SVG stroke dash array*/ +typedef struct { + u8 type; + UnitArray array; +} SVG_StrokeDashArray; + +/*! SVG text anchor types */ +enum { + SVG_TEXTANCHOR_INHERIT = 0, + SVG_TEXTANCHOR_END = 1, + SVG_TEXTANCHOR_MIDDLE = 2, + SVG_TEXTANCHOR_START = 3 +}; +/*! SVG text anchor*/ +typedef u8 SVG_TextAnchor; + +/*! SVG angle types */ +enum { + SVG_ANGLETYPE_UNKNOWN = 0, + SVG_ANGLETYPE_UNSPECIFIED = 1, + SVG_ANGLETYPE_DEG = 2, + SVG_ANGLETYPE_RAD = 3, + SVG_ANGLETYPE_GRAD = 4 +}; + +/*! SVG unit-type types */ +enum { + SVG_UNIT_TYPE_UNKNOWN = 0, + SVG_UNIT_TYPE_USERSPACEONUSE = 1, + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = 2 +}; + +/*! SVG preserve aspect ratio types */ +enum { + // Alignment Types + SVG_PRESERVEASPECTRATIO_NONE = 1, + SVG_PRESERVEASPECTRATIO_XMINYMIN = 2, + SVG_PRESERVEASPECTRATIO_XMIDYMIN = 3, + SVG_PRESERVEASPECTRATIO_XMAXYMIN = 4, + SVG_PRESERVEASPECTRATIO_XMINYMID = 5, + SVG_PRESERVEASPECTRATIO_XMIDYMID = 0, //default + SVG_PRESERVEASPECTRATIO_XMAXYMID = 6, + SVG_PRESERVEASPECTRATIO_XMINYMAX = 7, + SVG_PRESERVEASPECTRATIO_XMIDYMAX = 8, + SVG_PRESERVEASPECTRATIO_XMAXYMAX = 9 +}; + +/*! SVG meet or slice types */ +enum { + // Meet_or_slice Types + SVG_MEETORSLICE_MEET = 0, + SVG_MEETORSLICE_SLICE = 1 +}; + +/*! SVG preserve aspect ratio*/ +typedef struct { + Bool defer; + u8 align; + u8 meetOrSlice; +} SVG_PreserveAspectRatio; + +/*! SVG zoom and pan types */ +enum { + SVG_ZOOMANDPAN_DISABLE = 0, + SVG_ZOOMANDPAN_MAGNIFY, +}; + +/*! SVG zoom and pan*/ +typedef u8 SVG_ZoomAndPan; + +/*! SVG lenght adjust types */ +enum { + LENGTHADJUST_UNKNOWN = 0, + LENGTHADJUST_SPACING = 1, + LENGTHADJUST_SPACINGANDGLYPHS = 2 +}; + +/*! SVG textPath methods types */ +enum { + TEXTPATH_METHODTYPE_UNKNOWN = 0, + TEXTPATH_METHODTYPE_ALIGN = 1, + TEXTPATH_METHODTYPE_STRETCH = 2 +}; + +/*! SVG textPath spacing types */ +enum { + TEXTPATH_SPACINGTYPE_UNKNOWN = 0, + TEXTPATH_SPACINGTYPE_AUTO = 1, + TEXTPATH_SPACINGTYPE_EXACT = 2 +}; + +/*! SVG Marker Unit types */ +enum { + SVG_MARKERUNITS_UNKNOWN = 0, + SVG_MARKERUNITS_USERSPACEONUSE = 1, + SVG_MARKERUNITS_STROKEWIDTH = 2 +}; + +/*! SVG Marker Orientation types */ +enum { + SVG_MARKER_ORIENT_UNKNOWN = 0, + SVG_MARKER_ORIENT_AUTO = 1, + SVG_MARKER_ORIENT_ANGLE = 2 +}; + +/*! SVG Spread Method types */ +enum { + SVG_SPREADMETHOD_UNKNOWN = 0, + SVG_SPREADMETHOD_PAD = 1, + SVG_SPREADMETHOD_REFLECT = 2, + SVG_SPREADMETHOD_REPEAT = 3 +}; + +/*! SVG pointer events types */ +enum { + SVG_POINTEREVENTS_INHERIT = 0, + SVG_POINTEREVENTS_ALL = 1, + SVG_POINTEREVENTS_FILL = 2, + SVG_POINTEREVENTS_NONE = 3, + SVG_POINTEREVENTS_PAINTED = 4, + SVG_POINTEREVENTS_STROKE = 5, + SVG_POINTEREVENTS_VISIBLE = 6, + SVG_POINTEREVENTS_VISIBLEFILL = 7, + SVG_POINTEREVENTS_VISIBLEPAINTED = 8, + SVG_POINTEREVENTS_VISIBLESTROKE = 9, + SVG_POINTEREVENTS_BOUNDINGBOX = 10 +}; +/*! SVG pointer events*/ +typedef u8 SVG_PointerEvents; + +/*! SVG rendering hints types */ +enum { + SVG_RENDERINGHINT_INHERIT = 0, + SVG_RENDERINGHINT_AUTO = 1, + SVG_RENDERINGHINT_OPTIMIZEQUALITY = 2, + SVG_RENDERINGHINT_OPTIMIZESPEED = 3, + SVG_RENDERINGHINT_OPTIMIZELEGIBILITY = 4, + SVG_RENDERINGHINT_CRISPEDGES = 5, + SVG_RENDERINGHINT_GEOMETRICPRECISION = 6, + +}; +/*! SVG rendering hints*/ +typedef u8 SVG_RenderingHint; + +/*! SVG vector effect types */ +enum { + SVG_VECTOREFFECT_INHERIT = 0, + SVG_VECTOREFFECT_NONE = 1, + SVG_VECTOREFFECT_NONSCALINGSTROKE = 2, +}; +/*! SVG vector effect*/ +typedef u8 SVG_VectorEffect; + +/*! XML event propagate types */ +enum { + XMLEVENT_PROPAGATE_CONTINUE = 0, + XMLEVENT_PROPAGATE_STOP = 1 +}; +/*! DOM Event propagate*/ +typedef u8 XMLEV_Propagate; + +/*! XML event default action types */ +enum { + XMLEVENT_DEFAULTACTION_CANCEL = 0, + XMLEVENT_DEFAULTACTION_PERFORM, + +}; +/*! DOM Event default action*/ +typedef u8 XMLEV_DefaultAction; + +/*! XML event phase types */ +enum { + XMLEVENT_PHASE_DEFAULT = 0, + XMLEVENT_PHASE_CAPTURE = 1 +}; +/*! DOM Event phase*/ +typedef u8 XMLEV_Phase; + +/*! SMIL sync behavior types */ +enum { + SMIL_SYNCBEHAVIOR_INHERIT = 0, + /*LASeR order*/ + SMIL_SYNCBEHAVIOR_CANSLIP, + SMIL_SYNCBEHAVIOR_DEFAULT, + SMIL_SYNCBEHAVIOR_INDEPENDENT, + SMIL_SYNCBEHAVIOR_LOCKED, +}; +/*! SMIL sync behavior*/ +typedef u8 SMIL_SyncBehavior; + +/*! SMIL sync tolerance types */ +enum { + SMIL_SYNCTOLERANCE_INHERIT = 0, + SMIL_SYNCTOLERANCE_DEFAULT = 1, + SMIL_SYNCTOLERANCE_VALUE = 2 +}; + +/*! SMIL sync tolerance*/ +typedef struct { + u8 type; + SVG_Clock value; +} SMIL_SyncTolerance; + +/*! SMIL attributeType types */ +enum { + SMIL_ATTRIBUTETYPE_CSS = 0, + SMIL_ATTRIBUTETYPE_XML, + SMIL_ATTRIBUTETYPE_AUTO, +}; +/*! SMIL attribute type*/ +typedef u8 SMIL_AttributeType; + +/*! SVG playbackorder types */ +enum { + SVG_PLAYBACKORDER_ALL = 0, + SVG_PLAYBACKORDER_FORWARDONLY = 1, +}; +/*! SVG playback order*/ +typedef u8 SVG_PlaybackOrder; + +/*! SVG timeline begin types */ +enum { + SVG_TIMELINEBEGIN_ONLOAD=0, + SVG_TIMELINEBEGIN_ONSTART, +}; +/*! SVG timeline begin*/ +typedef u8 SVG_TimelineBegin; + +/*! XML space types */ +enum { + XML_SPACE_DEFAULT = 0, + XML_SPACE_PRESERVE = 1 +}; +/*! XML space type*/ +typedef u8 XML_Space; + + +/*! SVG gradient units types */ +enum { + SVG_GRADIENTUNITS_OBJECT = 0, + SVG_GRADIENTUNITS_USER = 1 +}; +/*! SVG gradient unit*/ +typedef u8 SVG_GradientUnit; + +/*! SVG focus highlight types */ +enum { + SVG_FOCUSHIGHLIGHT_AUTO = 0, + SVG_FOCUSHIGHLIGHT_NONE = 1 +}; +/*! SVG focus highlight*/ +typedef u8 SVG_FocusHighlight; + +/*! SVG initial visibility types */ +enum { + SVG_INITIALVISIBILTY_WHENSTARTED = 0, + SVG_INITIALVISIBILTY_ALWAYS = 1 +}; +/*! SVG initial visibility*/ +typedef u8 SVG_InitialVisibility; + +/*! SVG transform behavior types */ +enum { + SVG_TRANSFORMBEHAVIOR_GEOMETRIC = 0, + SVG_TRANSFORMBEHAVIOR_PINNED, + SVG_TRANSFORMBEHAVIOR_PINNED180, + SVG_TRANSFORMBEHAVIOR_PINNED270, + SVG_TRANSFORMBEHAVIOR_PINNED90, +}; +/*! SVG transform behavior*/ +typedef u8 SVG_TransformBehavior; + +/*! SVG overlay types */ +enum { + SVG_OVERLAY_NONE = 0, + SVG_OVERLAY_TOP, +}; +/*! SVG overlay*/ +typedef u8 SVG_Overlay; + +/*! SVG focusable types */ +enum { + SVG_FOCUSABLE_AUTO = 0, + SVG_FOCUSABLE_FALSE, + SVG_FOCUSABLE_TRUE, +}; +/*! SVG focusable*/ +typedef u8 SVG_Focusable; + +/*! SVG filter transfer types */ +enum +{ + SVG_FILTER_TRANSFER_IDENTITY, + SVG_FILTER_TRANSFER_TABLE, + SVG_FILTER_TRANSFER_DISCRETE, + SVG_FILTER_TRANSFER_LINEAR, + SVG_FILTER_TRANSFER_GAMMA +}; +/*! SVG filter transfer type*/ +typedef u8 SVG_Filter_TransferType; + +/*! SVG spread types */ +enum { + SVG_SPREAD_PAD = 0, + SVG_SPREAD_REFLECT, + SVG_SPREAD_REPEAT, +}; +/*! SVG spread method*/ +typedef u8 SVG_SpreadMethod; + +/*! SVG clip-path attribute*/ +typedef struct +{ + XMLRI target; +} SVG_ClipPath; + +/*! LASeR choice types */ +enum { + LASeR_CHOICE_ALL = 0, + LASeR_CHOICE_NONE = 1, + LASeR_CHOICE_N = 2 +}; + +/*! LASeR choice*/ +typedef struct { + u32 type; + u32 choice_index; +} LASeR_Choice; + +/*! LASeR size*/ +typedef struct { + Fixed width, height; +} LASeR_Size; + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_SVG_SVG_TYPES_H_*/ + + diff --git a/include/gpac/sync_layer.h b/include/gpac/sync_layer.h new file mode 100644 index 0000000..8a2eb9a --- /dev/null +++ b/include/gpac/sync_layer.h @@ -0,0 +1,180 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / SL header file + * + * 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_SYNC_LAYER_H_ +#define _GF_SYNC_LAYER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/sync_layer.h> +\brief MPEG-4 Object Descriptor Framework Sync Layer. +*/ + +/*! +\ingroup odf_grp +\brief MPEG-4 Object Descriptor Framework Sync Layer + +This section documents the MPEG-4 OD Sync Layer used in GPAC. +@{ + */ + +/*the Sync Layer config descriptor*/ +typedef struct +{ + /*base descriptor*/ + u8 tag; + + u8 predefined; + u8 useAccessUnitStartFlag; + u8 useAccessUnitEndFlag; + u8 useRandomAccessPointFlag; + u8 hasRandomAccessUnitsOnlyFlag; + u8 usePaddingFlag; + u8 useTimestampsFlag; + u8 useIdleFlag; + u8 durationFlag; + u32 timestampResolution; + u32 OCRResolution; + u8 timestampLength; + u8 OCRLength; + u8 AULength; + u8 instantBitrateLength; + u8 degradationPriorityLength; + u8 AUSeqNumLength; + u8 packetSeqNumLength; + u32 timeScale; + u16 AUDuration; + u16 CUDuration; + u64 startDTS; + u64 startCTS; + //internal + Bool no_dts_signaling; + u32 carousel_version; +} GF_SLConfig; + +/*! SL config predefined values*/ +enum +{ + SLPredef_Null = 0x01, + SLPredef_MP4 = 0x02, +}; + +/*! sets SL predefined (assign all fields according to sl->predefined value) +\param sl the target SL config descriptor +\return error if any +*/ +GF_Err gf_odf_slc_set_pref(GF_SLConfig *sl); + +/*! MPEG-4 sync layer header information*/ +typedef struct +{ + u8 accessUnitStartFlag; + u8 accessUnitEndFlag; + u8 paddingFlag; + u8 randomAccessPointFlag; + u8 OCRflag; + u8 idleFlag; + u8 decodingTimeStampFlag; + u8 compositionTimeStampFlag; + u8 instantBitrateFlag; + u8 degradationPriorityFlag; + + u8 paddingBits; + u16 packetSequenceNumber; + u64 objectClockReference; + u16 AU_sequenceNumber; + u64 decodingTimeStamp; + u64 compositionTimeStamp; + u16 accessUnitLength; + u32 instantBitrate; + u16 degradationPriority; + + /*Everything below this comment is internal to GPAC*/ + + /*this is NOT part of standard SL, only used internally: signals duration of access unit if known + this is useful for streams with very random updates, to prevent buffering for instance a subtitle stream + which is likely to have no updates during the first minutes... expressed in media timescale*/ + u32 au_duration; + /*ISMACryp extensions*/ + u8 isma_encrypted; + u64 isma_BSO; + /*CENC extensions*/ + u8 cenc_encrypted; + u8 IV_size; + + //for CENC pattern encryption mode + u8 crypt_byte_block, skip_byte_block; + u8 constant_IV_size; + 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 + u8 m2ts_pcr; + /* HTML5 MSE Packet info */ + s64 timeStampOffset; + //ntp at sender/producer side for this packet, 0 otherwise + u64 sender_ntp; + //set for AUs which should be decodedd but not presented during seek + u8 seekFlag; + + u32 samplerate, channels; +} GF_SLHeader; + + +/*! packetizes SL-PDU. If PDU is NULL or size 0, only writes the SL header +\param slConfig the target SL config descriptor +\param Header the SL header for this data block +\param PDU the payload to packetize +\param size the payload size +\param outPacket set to an allocated buffer containing the serialized SL packet - to be destroyed by caller +\param OutSize set to the size of the serialized SL packer +*/ +void gf_sl_packetize(GF_SLConfig* slConfig, GF_SLHeader *Header, u8 *PDU, u32 size, u8 **outPacket, u32 *OutSize); +/*! gets SL header size +\param slConfig the target SL config descriptor +\param Header the SL header for this data block +\return the size of the SL header in bytes +*/ +u32 gf_sl_get_header_size(GF_SLConfig* slConfig, GF_SLHeader *Header); + +/*! depacketizes SL-PDU +\param slConfig the target SL config descriptor +\param Header filled with the decoded SL header +\param PDU the SL packet to parse +\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); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_SYNC_LAYER_H_*/ diff --git a/include/gpac/term_info.h b/include/gpac/term_info.h new file mode 100644 index 0000000..7558f98 --- /dev/null +++ b/include/gpac/term_info.h @@ -0,0 +1,298 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Stream Management 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_TERM_INFO_H_ +#define _GF_TERM_INFO_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/term_info.h> +\brief GPAC media object browsing of the player. +*/ + +/*! +\addtogroup terminfo_grp Terminal Info +\ingroup playback_grp +\brief GPAC media object browsing of the player. + +This section documents how a user can get information on running media object in the player. + +@{ + */ + + +/* + OD Browsing API - YOU MUST INCLUDE <gpac/terminal.h> before + (this has been separated from terminal.h to limit dependency of core to mpeg4_odf.h header) + ALL ITEMS ARE READ-ONLY AND SHALL NOT BE MODIFIED +*/ +#include <gpac/mpeg4_odf.h> +#include <gpac/terminal.h> + +/*! Media object manager*/ +typedef struct _od_manager GF_ObjectManager; + +/*! gets top-level OD of the presentation +\param term the target terminal +\return the root object descriptor or NULL if not connected*/ +GF_ObjectManager *gf_term_get_root_object(GF_Terminal *term); + +/*! gets number of objects in sub scene of object +\param term the target terminal +\param scene_od the OD to browse for resources. This must be an inline OD +\return the number of objects in the scene described by the OD*/ +u32 gf_term_get_object_count(GF_Terminal *term, GF_ObjectManager *scene_od); + +/*! gets an OD manager in the scene +\param term the target terminal +\param scene_od the OD to browse for resources. This must be an inline OD +\param index 0-based index of the obhect to query +\return the object manager or NULL if not found +*/ +GF_ObjectManager *gf_term_get_object(GF_Terminal *term, GF_ObjectManager *scene_od, u32 index); + +/*! gets the type of the associated subscene +\param term the target terminal +\param odm the object to query +\return 0: regular media object, not inline, 1: root scene, 2: inline scene, 3: externProto library +*/ +u32 gf_term_object_subscene_type(GF_Terminal *term, GF_ObjectManager *odm); + +/*! selects a given object when stream selection is available +\param term the target terminal +\param odm the object to select +*/ +void gf_term_select_object(GF_Terminal *term, GF_ObjectManager *odm); + +/*! selects service by given ID for multiplexed services (MPEG-2 TS) +\param term the target terminal +\param odm an object in the scene attached to the same multiplex +\param service_id the service ID to select +*/ +void gf_term_select_service(GF_Terminal *term, GF_ObjectManager *odm, u32 service_id); + +/*! sets addon on or off (only one addon possible for now). When OFF, the associated service is shut down +\param term the target terminal +\param show_addons if GF_TRUE, displays addons otheriwse hides the addons +*/ +void gf_term_toggle_addons(GF_Terminal *term, Bool show_addons); + +/*! checks for an existing service ID in a session +\param term the target terminal +\param odm an object in the scene attached to the same multiplex +\param service_id the ID of the service to find +\return GF_TRUE if given service ID is declared*/ +Bool gf_term_find_service(GF_Terminal *term, GF_ObjectManager *odm, u32 service_id); + +/*! Media Object information*/ +typedef struct +{ + u32 ODID; + u32 ServiceID; + u32 pid_id, ocr_id; + Double duration; + Double current_time; + /*0: stopped, 1: playing, 2: paused, 3: not setup, 4; setup failed.*/ + u32 status; + + Bool raw_media; + Bool generated_scene; + + /*name of module handling the service service */ + const char *service_handler; + /*name of service*/ + const char *service_url; + /*service redirect object*/ + const char *remote_url; + /*set if the service is owned by this object*/ + Bool owns_service; + + /*stream buffer: + -2: stream is not playing + -1: stream has no buffering + >=0: amount of media data present in buffer, in ms + */ + s32 buffer; + u32 min_buffer, max_buffer; + /*number of AUs in DB (cumulated on all input channels)*/ + u32 db_unit_count; + /*number of CUs in composition memory (if any) and CM capacity*/ + u32 cb_unit_count, cb_max_count; + /*inidciate that thye composition memory is bypassed for this decoder (video only) */ + Bool direct_video_memory; + /*clock delay in ms of object clock: this is the delay set by the audio renderer to keep AV in sync + and corresponds to the amount of ms to delay non-audio streams to keep sync*/ + s32 clock_drift; + /*codec name*/ + const char *codec_name; + /*object type - match streamType (cf constants.h)*/ + u32 od_type; + /*audio properties*/ + u32 sample_rate, afmt, num_channels; + /*video properties (w & h also used for scene codecs)*/ + u32 width, height, pixelFormat, par; + + /*average birate over last second and max bitrate over one second at decoder input according to media timeline - expressed in bits per sec*/ + u32 avg_bitrate, max_bitrate; + /*average birate over last second and max bitrate over one second at decoder input according to processing time - expressed in bits per sec*/ + u32 avg_process_bitrate, max_process_bitrate; + u32 nb_dec_frames, nb_dropped; + u32 first_frame_time, last_frame_time; + u64 total_dec_time, irap_total_dec_time; + u32 max_dec_time, irap_max_dec_time; + u32 au_duration; + u32 nb_iraps; + s32 ntp_diff; + //0 if mono, 2 or more otherwise + u32 nb_views; + + /*4CC of original protection scheme used*/ + u32 protection; + + u32 lang; + const char *lang_code; + + /*name of media if not defined in OD framework*/ + const char *media_url; +} GF_MediaInfo; + +/*! gets information on an object +\param term the target terminal +\param odm the object manager to query +\param info filled with object manager properties +\return error if any +*/ +GF_Err gf_term_get_object_info(GF_Terminal *term, GF_ObjectManager *odm, GF_MediaInfo *info); + +/*! gets current downloads info for the service - only use if ODM owns the service, returns 0 otherwise. +\param term the target terminal +\param odm the object manager to query +\param d_enum in/out current enum - shall start to 0, incremented at each call. fct returns 0 if no more downloads +\param url service URL +\param bytes_done bytes downloaded for this service +\param total_bytes total bytes for this service, may be 0 (eg http streaming) +\param bytes_per_sec download speed +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_term_get_download_info(GF_Terminal *term, GF_ObjectManager *odm, u32 *d_enum, const char **url, u32 *bytes_done, u32 *total_bytes, u32 *bytes_per_sec); + +/*! Net statistics gathering object*/ +typedef struct +{ + /*percentage of packet loss from network. This cannot be figured out by the app since there is no + one-to-one mapping between the protocol packets and the final SL packet (cf RTP payloads)*/ + Float pck_loss_percentage; + /*channel port, control channel port if any (eg RTCP)*/ + u16 port, ctrl_port; + /*bandwidth used by channel & its control channel if any (both up and down) - expressed in bits per second + for HTTP connections, typically only bw_down is used*/ + u32 bw_up, bw_down, ctrl_bw_down, ctrl_bw_up; + /*set to 0 if channel is not part of a multiplex. Otherwise set to the multiplex port, and + above port info shall be identifiers in the multiplex - note that multiplexing overhead is ignored + in GPAC for the current time*/ + u16 multiplex_port; +} GF_TermNetStats; + +/*! gets network statistics for the given channel in the given object +\param term the target terminal +\param odm the object manager to query +\param d_enum in/out current enum - shall start to 0, incremented at each call. fct returns 0 if no more downloads +\param chid set to the channel identifier +\param net_stats filled with the channel network statistics +\param ret_code set to the error code if error +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_term_get_channel_net_info(GF_Terminal *term, GF_ObjectManager *odm, u32 *d_enum, u32 *chid, GF_TermNetStats *net_stats, GF_Err *ret_code); + +/*! URL information for current session*/ +typedef struct +{ + u32 service_id; + u16 track_num; + u16 track_total; + u32 genre; + const char *album; + const char *artist; + const char *comment; + const char *composer; + const char *name; + const char *writer; + const char *provider; + //as in MPEG_DASH role + const char *role; + const char *accessibility; + const char *rating; +} GF_TermURLInfo; +/*! gets meta information about the service +\param term the target terminal +\param odm the object manager to query +\param info filled with service information +\return error if any +*/ +GF_Err gf_term_get_service_info(GF_Terminal *term, GF_ObjectManager *odm, GF_TermURLInfo *info); + +/*! retrieves world info of the scene of an object. +\param term the target terminal +\param scene_od the object manager to query. If this is an inline OD, the world info of the inline content is retrieved. If NULL, the world info of the main scene is retrieved +\param descriptions any textual descriptions is stored here (const strings not allocated, do NOT modify) +\return NULL if no WorldInfo available or world title if available +*/ +const char *gf_term_get_world_info(GF_Terminal *term, GF_ObjectManager *scene_od, GF_List *descriptions); + +/*! dumps scene graph in specified file, in BT or XMT format +\param term the target terminal +\param rad_name file radical (NULL for stdout) - if not NULL MUST BE GF_MAX_PATH length +\param filename sets to the complete filename (rad + ext) and shall be destroyed by caller (optional can be NULL) +\param xml_dump if GF_TRUE, duimps using XML format (XMT-A, X3D) for scene graphs having both XML and simple text representations +\param skip_proto is GF_TRUE, proto declarations are not dumped +\param odm if this is an inline OD, the inline scene is dumped; if this is NULL, the main scene is dumped; otherwise the parent scene is dumped +\return error if any +*/ +GF_Err gf_term_dump_scene(GF_Terminal *term, char *rad_name, char **filename, Bool xml_dump, Bool skip_proto, GF_ObjectManager *odm); + +/*! prints filter session statistics as log tool app log level debuf +\param term the target terminal +*/ +void gf_term_print_stats(GF_Terminal *term); + +/*! prints filter session graph as log tool app log level debuf +\param term the target terminal +*/ +void gf_term_print_graph(GF_Terminal *term); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_TERM_INFO_H_*/ diff --git a/include/gpac/terminal.h b/include/gpac/terminal.h new file mode 100644 index 0000000..366bb86 --- /dev/null +++ b/include/gpac/terminal.h @@ -0,0 +1,328 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Stream Management 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_TERMINAL_H_ +#define _GF_TERMINAL_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/terminal.h> +\brief GPAC media player API. + */ + +/*! +\addtogroup playback_grp Media Player +\brief GPAC media player (streaming engine and compositor). +*/ + +/*! +\addtogroup terminal_grp Terminal +\ingroup playback_grp +\brief GPAC media player APIs. + +This section documents the user-level API of the GPAC media player. + +@{ +*/ + + + +#include <gpac/user.h> + +/*! creates a new terminal for a userApp callback +\param user a user description +\return a new terminal*/ +GF_Terminal *gf_term_new(GF_User *user); + +/*! deletes a terminal - stop is done automatically +\param term the target terminal +\return error if any (GF_IO_ERR if client couldn't be shutdown normally)*/ +GF_Err gf_term_del(GF_Terminal *term); + +/*! connects to a URL - connection OK or error is acknowledged via the user callback +\param term the target terminal +\param URL the target URL +*/ +void gf_term_connect(GF_Terminal *term, const char *URL); +/*! disconnects the current url +\param term the target terminal +*/ +void gf_term_disconnect(GF_Terminal *term); + +/*! navigates to a given destination or shutdown/restart the current one if any. +This is the only safe way of restarting/jumping a presentation from inside the EventProc +where doing a disconnect/connect could deadlock if toURL is NULL, uses the current URL +\param term the target terminal +\param toURL the new target URL +*/ +void gf_term_navigate_to(GF_Terminal *term, const char *toURL); + +/*! restarts url from given time (in ms). +\param term the target terminal +\param from_time restart time in milliseconds +\param pause_at_first_frame if 1, pauses at the first frame. If 2, pauses at the first frame only if the terminal is in paused state. +\return 0: service is not connected yet, 1: service has no seeking capabilities, 2: service has been seeked +*/ +u32 gf_term_play_from_time(GF_Terminal *term, u64 from_time, u32 pause_at_first_frame); + +/*! connects URL and seek right away - only needed when reloading the complete player (avoids waiting +for connection and post a seek..) + +\param term the target terminal +\param URL the target URL +\param time_in_ms connect time in milliseconds +\param pause_at_first_frame if 1, pauses rendering and streaming when starting, if 2 pauses only rendering +*/ +void gf_term_connect_from_time(GF_Terminal *term, const char *URL, u64 time_in_ms, u32 pause_at_first_frame); + +/*! connects a URL but specify a parent path for this URL +\param term the target terminal +\param URL the target URL +\param parent_URL the parent URL of the connected service +*/ +void gf_term_connect_with_path(GF_Terminal *term, const char *URL, const char *parent_URL); + +/*! gets framerate +\param term the target terminal +\param absoluteFPS if GF_TRUE, the return value is the absolute framerate, eg NbFrameCount/NbTimeSpent regardless of whether a frame has been drawn or not, which means the FPS returned can be much greater than the target rendering framerate. If GF_FALSE, the return value is the FPS taking into account not drawn frames (eg, less than or equal to compositor FPS) +\return current framerate +*/ +Double gf_term_get_framerate(GF_Terminal *term, Bool absoluteFPS); +/*! gets main scene current time +\param term the target terminal +\return time in milliseconds +*/ +u32 gf_term_get_time_in_ms(GF_Terminal *term); + +/*! gets elapsed time since loading of the scene - may be different from scene time when seeking or live content +\param term the target terminal +\return time elapsed in milliseconds +*/ +u32 gf_term_get_elapsed_time_in_ms(GF_Terminal *term); + +/*! gets current URL +\param term the target terminal +\return the current URL +*/ +const char *gf_term_get_url(GF_Terminal *term); + +/*! gets viewpoints/viewports for main scene +\param term the target terminal +\param viewpoint_idx 1-based index of the viewport to query +\param outName set to the name of the viewport +\param is_bound set to GF_TRUE of that viewport is bound +\return error if any, GF_EOS if index is greater than number of viewpoints +*/ +GF_Err gf_term_get_viewpoint(GF_Terminal *term, u32 viewpoint_idx, const char **outName, Bool *is_bound); + +/*! sets active viewpoints/viewports for main scene given its name +\note if only one viewpoint is present in the scene +\param term the target terminal +\param viewpoint_idx 1-based index of the viewport to set or 0 to set by viewpoint name +\param viewpoint_name name of the viewport +\return error if any, GF_EOS if index is greater than number of viewpoints +*/ +GF_Err gf_term_set_viewpoint(GF_Terminal *term, u32 viewpoint_idx, const char *viewpoint_name); + +/*! adds an object to the scene - only possible if scene has selectable streams (cf GF_OPT_CAN_SELECT_STREAMS option) +\param term the target terminal +\param url the URL of the object to inject +\param auto_play selcts the object for playback when inserting it +\return error if any +*/ +GF_Err gf_term_add_object(GF_Terminal *term, const char *url, Bool auto_play); + +/*! sets option - most of the terminal cfg is done through options, please refer to options.h for details +\param term the target terminal +\param opt_type option to set +\param opt_value option value +\return error if any +*/ +GF_Err gf_term_set_option(GF_Terminal *term, u32 opt_type, u32 opt_value); + +/*! gets option - most of the terminal cfg is done through options, please refer to options.h for details +\param term the target terminal +\param opt_type option to set +\return option value +*/ +u32 gf_term_get_option(GF_Terminal *term, u32 opt_type); + +/*! checks if given URL is understood by client. +\param term the target terminal +\param URL the URL to check +\param use_parent_url if GF_TRUE, relative URLs are solved against the current presentation URL +\param no_mime_check if GF_TRUE, checks by file extension only +\return GF_TRUE if client should be able to handle the URL*/ +Bool gf_term_is_supported_url(GF_Terminal *term, const char *URL, Bool use_parent_url, Bool no_mime_check); + +/*! get the current service ID for MPEG-2 TS mux +\param term the target terminal +\return service ID or 0 if no service ID is associated or not loaded yet +*/ +u32 gf_term_get_current_service_id(GF_Terminal *term); + +/*! gets simulation frame rate +\param term the target terminal +\param nb_frames_drawn set to the number of frames drawn +\return simulation frames per seconds +*/ +Double gf_term_get_simulation_frame_rate(GF_Terminal *term, u32 *nb_frames_drawn); + +/*! gets visual output size (current window/display size) +\param term the target terminal +\param width set to the display width +\param height set to the display height +\return error if any +*/ +GF_Err gf_term_get_visual_output_size(GF_Terminal *term, u32 *width, u32 *height); +/*! sets playback speed +\param term the target terminal +\param speed the requested speed +\return error if any +*/ +GF_Err gf_term_set_speed(GF_Terminal *term, Fixed speed); + +/*! sends a set of scene commands (BT, XMT, X3D, LASeR+XML) to the scene +\param term the target terminal +\param type indicates the language used - accepted values are + "model/x3d+xml" or "x3d": commands is an X3D+XML scene + "model/x3d+vrml" or "xrdv": commands is an X3D+VRML scene + "model/vrml" or "vrml": commands is an VRML scene + "application/x-xmt" or "xmt": commands is an XMT-A scene or a list of XMT-A updates + "application/x-bt" or "bt": commands is a BT scene or a list of BT updates + "image/svg+xml" or "svg": commands is an SVG scene + "application/x-laser+xml" or "laser": commands is an SVG/LASeR+XML scene or a list of LASeR+XML updates + if not specified, the type will be guessed from the current root node if any +\param com the textual update +\return error if any +*/ +GF_Err gf_term_scene_update(GF_Terminal *term, char *type, char *com); + +/*! sets visual output size change: + * NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h) + * if the user app manages the output window it shall resize it before calling this + +\param term the target terminal +\param NewWidth the output width in pixels +\param NewHeight the output height in pixels +\return error if any +*/ +GF_Err gf_term_set_size(GF_Terminal *term, u32 NewWidth, u32 NewHeight); + +/*! returns current text selection. +\param term the target terminal +\param probe_only if GF_TRUE, simply returns a non-NULL string ("") if some text is selected +\return text selection if any, or NULL otherwise. */ +const char *gf_term_get_text_selection(GF_Terminal *term, Bool probe_only); + +/*! pastes text into current selection if any. +\param term the target terminal +\param txt the text to set +\param probe_only if GF_TRUE, only check if text is currently edited +\return error if any +*/ +GF_Err gf_term_paste_text(GF_Terminal *term, const char *txt, Bool probe_only); + +/*! processes pending tasks in the media session +\note If filter session regulation is not disabled, the function will sleep for until next frame should be drawn before returning. +\param term the target terminal +\return GF_TRUE if a new frame was drawn, GF_FALSE otherwise +*/ +Bool gf_term_process_step(GF_Terminal *term); + +/*! post user interaction to terminal +\param term the target terminal +\param event the event to post +\return GF_TRUE if event was directly consumed +*/ +Bool gf_term_user_event(GF_Terminal *term, GF_Event *event); + +/*! post event to terminal +\warning NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h) +\param term the target terminal +\param evt the event to post +\return GF_TRUE if event was directly consumed +*/ +Bool gf_term_send_event(GF_Terminal *term, GF_Event *evt); + + + + +/*framebuffer access*/ +#include <gpac/color.h> + +/*! gets screen buffer +\note the screen buffer is released by calling gf_term_release_screen_buffer +\param term the target terminal +\param framebuffer set to the framebuffer information +\return error if any +*/ +GF_Err gf_term_get_screen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer); + +/*! gets view buffer - this locks the scene graph too until released is called +\note the screen buffer is released by calling gf_term_release_screen_buffer +\param term the target terminal +\param framebuffer set to the framebuffer information +\param view_idx indicates the view index, and ranges from 0 to GF_OPT_NUM_STEREO_VIEWS value +\param depth_buffer_type indicates the depth buffer mode +\return error if any +*/ +GF_Err gf_term_get_offscreen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer, u32 view_idx, GF_CompositorGrabMode depth_buffer_type); + +/*! releases screen buffer and unlocks graph +\param term the target terminal +\param framebuffer the pointer passed to \ref gf_term_get_screen_buffer +\return error if any +*/ +GF_Err gf_term_release_screen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer); + +/*! switches quality up or down - can be called several time in the same direction +this will call all decoders to adjust their quality levels + +\param term the target terminal +\param up if GF_TRUE, switches quality up,otherwise down +*/ +void gf_term_switch_quality(GF_Terminal *term, Bool up); + +/*! checks if a mime type is supported +\param term the target terminal +\param mime the mime type to check +\return GF_TRUE if supported +*/ +Bool gf_term_is_type_supported(GF_Terminal *term, const char* mime); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_TERMINAL_H_*/ diff --git a/include/gpac/thread.h b/include/gpac/thread.h new file mode 100644 index 0000000..4b9dc77 --- /dev/null +++ b/include/gpac/thread.h @@ -0,0 +1,378 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2021 + * 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_THREAD_H_ +#define _GF_THREAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/thread.h> +\brief Threading and Mutual Exclusion +*/ + +/*! +\addtogroup thr_grp +\brief Threading and Mutual Exclusion + +This section documents the threading of the GPAC framework. These provide an easy way to implement +safe multithreaded tools. + +Available tools are thread, mutex and semaphore + +\defgroup thread_grp Thread +\ingroup thr_grp +\defgroup mutex_grp Mutex +\ingroup thr_grp +\defgroup sema_grp Semaphore +\ingroup thr_grp +*/ + +/*! +\addtogroup thread_grp +\brief Thread processing + +The thread object allows executing some code independently of the main process of your application. +@{ +*/ + +#include <gpac/tools.h> + + + //atomic ref_count++ / ref_count-- +#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) +#include <windows.h> +#include <winbase.h> + +/*! atomic integer increment */ +#define safe_int_inc(__v) InterlockedIncrement((int *) (__v)) +/*! atomic integer decrement */ +#define safe_int_dec(__v) InterlockedDecrement((int *) (__v)) +/*! atomic integer addition */ +#define safe_int_add(__v, inc_val) InterlockedAdd((int *) (__v), inc_val) +/*! atomic integer subtraction */ +#define safe_int_sub(__v, dec_val) InterlockedAdd((int *) (__v), -dec_val) +/*! atomic large integer addition */ +#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) + +#else + +#ifdef GPAC_NEED_LIBATOMIC + +/*! atomic integer increment */ +#define safe_int_inc(__v) __atomic_add_fetch((int *) (__v), 1, __ATOMIC_SEQ_CST) +/*! atomic integer decrement */ +#define safe_int_dec(__v) __atomic_sub_fetch((int *) (__v), 1, __ATOMIC_SEQ_CST) +/*! atomic integer addition */ +#define safe_int_add(__v, inc_val) __atomic_add_fetch((int *) (__v), inc_val, __ATOMIC_SEQ_CST) +/*! atomic integer subtraction */ +#define safe_int_sub(__v, dec_val) __atomic_sub_fetch((int *) (__v), dec_val, __ATOMIC_SEQ_CST) +/*! atomic large integer addition */ +#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) + +#else + +/*! atomic integer increment */ +#define safe_int_inc(__v) __sync_add_and_fetch((int *) (__v), 1) +/*! atomic integer decrement */ +#define safe_int_dec(__v) __sync_sub_and_fetch((int *) (__v), 1) +/*! atomic integer addition */ +#define safe_int_add(__v, inc_val) __sync_add_and_fetch((int *) (__v), inc_val) +/*! atomic integer subtraction */ +#define safe_int_sub(__v, dec_val) __sync_sub_and_fetch((int *) (__v), dec_val) +/*! atomic large integer addition */ +#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) + +#endif //GPAC_NEED_LIBATOMIC + +#endif + + +/*! +\brief Thread states + * + *Indicates the execution status of a thread + */ +enum +{ + /*! the thread has been initialized but is not started yet*/ + GF_THREAD_STATUS_STOP = 0, + /*! the thread is running*/ + GF_THREAD_STATUS_RUN = 1, + /*! the thread has exited its body function*/ + GF_THREAD_STATUS_DEAD = 2 +}; + +/*! +\brief abstracted thread object +*/ +typedef struct __tag_thread GF_Thread; + +/*! +\brief thread constructor + +Constructs a new thread object +\param name log name of the thread if any +\return new thread object + */ +GF_Thread *gf_th_new(const char *name); +/*! +\brief thread destructor + +Kills the thread if running and destroys the object +\param th the thread object + */ +void gf_th_del(GF_Thread *th); + +/*! +\brief thread run function callback + +The gf_thread_run type is the type for the callback of the \ref gf_thread_run function +\param par opaque user data +\return exit code of the thread, usually 1 for error and 0 if normal execution + */ +typedef u32 (*gf_thread_run)(void *par); + +/*! +\brief thread execution + +Executes the thread with the given function +\note A thread may be run several times but cannot be run twice in the same time. +\param th the thread object +\param run the function this thread will call +\param par the argument to the function the thread will call +\return error if any + */ +GF_Err gf_th_run(GF_Thread *th, gf_thread_run run, void *par); +/*! +\brief thread stopping + +Waits for the thread exit until return +\param th the thread object + */ +void gf_th_stop(GF_Thread *th); +/*! +\brief thread status query + +Gets the thread status +\param th the thread object +\return thread status + */ +u32 gf_th_status(GF_Thread *th); + +/*! thread priorities */ +enum +{ + /*!Idle Priority*/ + GF_THREAD_PRIORITY_IDLE=0, + /*!Less Idle Priority*/ + GF_THREAD_PRIORITY_LESS_IDLE, + /*!Lowest Priority*/ + GF_THREAD_PRIORITY_LOWEST, + /*!Low Priority*/ + GF_THREAD_PRIORITY_LOW, + /*!Normal Priority (the default one)*/ + GF_THREAD_PRIORITY_NORMAL, + /*!High Priority*/ + GF_THREAD_PRIORITY_HIGH, + /*!Highest Priority*/ + GF_THREAD_PRIORITY_HIGHEST, + /*!First real-time priority*/ + GF_THREAD_PRIORITY_REALTIME, + /*!Last real-time priority*/ + GF_THREAD_PRIORITY_REALTIME_END=255 +}; + +/*! +\brief thread priority + +Sets the thread execution priority level. +\param th the thread object +\param priority the desired priority +\note this should be used with caution, especially use of real-time priorities. + */ +void gf_th_set_priority(GF_Thread *th, s32 priority); +/*! +\brief current thread ID + +Gets the ID of the current thread the caller is in. +\return thread ID +*/ +u32 gf_th_id(); + +#ifdef GPAC_CONFIG_ANDROID +/*! Register a function that will be called before pthread_exist is called */ +GF_Err gf_register_before_exit_function(GF_Thread *t, u32 (*toRunBeforePthreadExit)(void *param)); + +/*! Get the current Thread if any. May return NULL + */ +GF_Thread * gf_th_current(); + +#endif /* GPAC_CONFIG_ANDROID */ + +/*! @} */ + +/*! +\addtogroup mutex_grp +\brief Mutual exclusion + +The mutex object allows ensuring that portions of the code (typically access to variables) cannot be executed by two threads (or a thread and the main process) at the same time. + +@{ +*/ + +/*! +\brief abstracted mutex object*/ +typedef struct __tag_mutex GF_Mutex; +/*! +\brief mutex constructor + +Contructs a new mutex object +\param name log name of the thread if any +\return new mutex +*/ +GF_Mutex *gf_mx_new(const char *name); +/*! +\brief mutex denstructor + +Destroys a mutex object. This will wait for the mutex to be released if needed. +\param mx the mutex object, may be NULL +*/ +void gf_mx_del(GF_Mutex *mx); +/*! +\brief mutex locking + +Locks the mutex object, making sure that another thread locking this mutex cannot execute until the mutex is unlocked. +\param mx the mutex object, may be NULL +\return 1 if success or mutex is NULL, 0 if error locking the mutex (which should never happen) +*/ +u32 gf_mx_p(GF_Mutex *mx); +/*! +\brief mutex unlocking + +Unlocks the mutex object, allowing other threads waiting on this mutex to continue their execution +\param mx the mutex object, may be NULL +*/ +void gf_mx_v(GF_Mutex *mx); +/*! +\brief mutex non-blocking lock + +Attemps to lock the mutex object without blocking until the object is released. +\param mx the mutex object, may be NULL +\return GF_TRUE if the mutex has been successfully locked or if the mutex is NULL, in which case it shall then be unlocked, or GF_FALSE if the mutex is locked by another thread. +*/ +Bool gf_mx_try_lock(GF_Mutex *mx); + +/*! +\brief get mutex number of locks + +Returns the number of locks on the mutex if the caller thread is holding the mutex. +\param mx the mutex object, may be NULL +\return -1 if the mutex is not hold by the calling thread, or the number of locks (possibly 0) otherwise. Returns 0 if mutex is NULL + */ +s32 gf_mx_get_num_locks(GF_Mutex *mx); + +/*! @} */ + +/*! +\addtogroup sema_grp +\brief Semaphore + + +The semaphore object allows controlling how portions of the code (typically access to variables) are + executed by two threads (or a thread and the main process) at the same time. The best image for a semaphore is a limited set + of money coins (always easy to understand hmm?). If no money is in the set, nobody can buy anything until a coin is put back in the set. + When the set is full, the money is wasted (call it "the bank"...). + +@{ +*/ + +/********************************************************************* + Semaphore Object +**********************************************************************/ +/*! +\brief abstracted semaphore object +*/ +typedef struct __tag_semaphore GF_Semaphore; +/*! +\brief semaphore constructor + +Constructs a new semaphore object +\param MaxCount the maximum notification count of this semaphore +\param InitCount the initial notification count of this semaphore upon construction +\return the semaphore object + */ +GF_Semaphore *gf_sema_new(u32 MaxCount, u32 InitCount); +/*! +\brief semaphore destructor + +Destructs the semaphore object. This will wait for the semaphore to be released if needed. +\param sm the semaphore object + */ +void gf_sema_del(GF_Semaphore *sm); +/*! +\brief semaphore notification. + +Notifies the semaphore of a certain amount of releases. +\param sm the semaphore object +\param nb_rel sm the number of release to notify +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_sema_notify(GF_Semaphore *sm, u32 nb_rel); +/*! +\brief semaphore wait + +Waits for the semaphore to be accessible (eg, may wait an infinite time). +\param sm the semaphore object +\return GF_TRUE if successfull wait, GF_FALSE if wait failed +*/ +Bool gf_sema_wait(GF_Semaphore *sm); +/*! +\brief semaphore time wait + +Waits for a certain for the semaphore to be accessible, and returns when semaphore is accessible or wait time has passed. +\param sm the semaphore object +\param time_out the amount of time to wait for the release in milliseconds +\return returns 1 if the semaphore was released before the timeout, 0 if the semaphore is still not released after the timeout. +*/ +Bool gf_sema_wait_for(GF_Semaphore *sm, u32 time_out); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_THREAD_H_*/ diff --git a/include/gpac/token.h b/include/gpac/token.h new file mode 100644 index 0000000..6f4b7b3 --- /dev/null +++ b/include/gpac/token.h @@ -0,0 +1,107 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * 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_TOKEN_H_ +#define _GF_TOKEN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/token.h> +\brief String Tokenizer +*/ + +/*! +\addtogroup tok_grp +\brief String Tokenizer + +This section documents the basic string tokenizer of the GPAC framework. + +@{ +*/ + +#include <gpac/tools.h> + +/*! +\brief get string component + +Gets the next string component comprised in a given set of characters +\param Buffer source string to scan +\param Start char offset from beginning of buffer where tokenization shall start +\param Separator separator characters to use +\param Container output buffer location +\param ContainerSize output buffer allocated size +\return position of the first char in the buffer after the last terminating separator, or -1 if token could not be found + */ +s32 gf_token_get(const char* Buffer, s32 Start, const char* Separator, char* Container, s32 ContainerSize); +/*! +\brief get string component without delimitting characters + +Gets the next string component comprised in a given set of characters, removing surrounding characters +\param Buffer source string to scan +\param Start char offset from beginning of buffer where tokenization shall start +\param Separator separator characters to use +\param strip_set surrounding characters to remove +\param Container output buffer location +\param ContainerSize output buffer allocated size +\return position of the first char in the buffer after the last terminating separator, or -1 if token could not be found + */ +s32 gf_token_get_strip(const char* Buffer, s32 Start, const char* Separator, const char* strip_set, char* Container, s32 ContainerSize); +/*! +\brief line removal + +Gets one line from buffer and remove delimiters CR, LF and CRLF +\param buffer source string to scan +\param start char offset from beginning of buffer where tokenization shall start +\param size size of the input buffer to analyze +\param line_buffer output buffer location +\param line_buffer_size output buffer allocated size +\return position of the first char in the buffer after the last line delimiter, or -1 if no line could be found + */ +s32 gf_token_get_line(const char *buffer, u32 start, u32 size, char *line_buffer, u32 line_buffer_size); +/*! +\brief pattern location + +Locates a pattern in the buffer +\param Buffer source string to scan +\param Start char offset from beginning of buffer where tokenization shall start +\param Size size of the input buffer to analyze +\param Pattern pattern to locate +\return position of the first char in the buffer after the pattern, or -1 if pattern could not be found + */ +s32 gf_token_find(const char* Buffer, u32 Start, u32 Size, const char* Pattern); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_TOKEN_H_*/ + diff --git a/include/gpac/tools.h b/include/gpac/tools.h new file mode 100644 index 0000000..ad97165 --- /dev/null +++ b/include/gpac/tools.h @@ -0,0 +1,2158 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * 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_TOOLS_H_ +#define _GF_TOOLS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/setup.h> +#include <gpac/version.h> +#include <time.h> + + +/*! +\file "gpac/tools.h" +\brief Core definitions and tools of GPAC. + +This file contains basic functions and core definitions of the GPAC framework. This file is +usually included by all GPAC header files since it contains the error definitions. +*/ + +/*! +\addtogroup utils_grp Core Tools +\brief Core definitions and tools of GPAC. + +You will find in this module the documentation of the core tools used in GPAC. +*/ + +/*! +\addtogroup setup_grp +@{ + */ + +/*! +\brief Stringizer +\hideinitializer + +Macro transforming its input name into a string +*/ +#define gf_stringizer(x) #x + +/*! +\brief 4CC Formatting +\hideinitializer + +Macro formatting a 4-character code (or 4CC) "abcd" as 0xAABBCCDD +*/ +#ifndef GF_4CC +#define GF_4CC(a,b,c,d) ((((u32)a)<<24)|(((u32)b)<<16)|(((u32)c)<<8)|((u32)d)) +#endif + +/*! minimum buffer size to hold any 4CC in string format*/ +#define GF_4CC_MSIZE 10 + +/*! converts four character code to string +\param type a four character code +\return a printable form of the code +*/ +const char *gf_4cc_to_str(u32 type); + +/*! converts a 4CC string to its 32 bits value +\param val four character string +\return code value or 0 if error +*/ +u32 gf_4cc_parse(const char *val); + +/*! @} */ + +/*! +\addtogroup errors_grp +\brief Error Types + +This section documents all error codes used in the GPAC framework. Most of the GPAC's functions will use these as + return values, and some of these errors are also used for state communication with the different modules of the framework. + +@{ + */ + +/*! GPAC Error +\hideinitializer + +Positive values are warning and info, 0 means no error and negative values are errors + */ +typedef enum +{ + /*!Message from any scripting engine used in the presentation (ECMAScript, MPEG-J, ...) (Info).*/ + GF_SCRIPT_INFO = 3, + /*!Indicates a send packet is not dispatched due to pending connections.*/ + GF_PENDING_PACKET = 2, + /*!Indicates the end of a stream or of a file (Info).*/ + GF_EOS = 1, + /*! + \n\n + */ + /*!Operation success (no error).*/ + GF_OK = 0, + /*!\n*/ + /*!One of the input parameter is not correct or cannot be used in the current operating mode of the framework.*/ + GF_BAD_PARAM = -1, + /*! Memory allocation failure.*/ + GF_OUT_OF_MEM = -2, + /*! Input/Output failure (disk access, system call failures)*/ + GF_IO_ERR = -3, + /*! The desired feature or operation is not supported by the framework*/ + GF_NOT_SUPPORTED = -4, + /*! Input data has been corrupted*/ + GF_CORRUPTED_DATA = -5, + /*! A modification was attempted on a scene node which could not be found*/ + GF_SG_UNKNOWN_NODE = -6, + /*! The PROTO node interface does not match the nodes using it*/ + GF_SG_INVALID_PROTO = -7, + /*! An error occured in the scripting engine*/ + 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*/ + GF_NON_COMPLIANT_BITSTREAM = -10, + /*! No filter could be found to handle the desired media type*/ + GF_FILTER_NOT_FOUND = -11, + /*! The URL is not properly formatted or cannot be found*/ + GF_URL_ERROR = -12, + /*! An service error has occured at the local side*/ + GF_SERVICE_ERROR = -13, + /*! A service error has occured at the remote (server) side*/ + GF_REMOTE_SERVICE_ERROR = -14, + /*! The desired stream could not be found in the service*/ + GF_STREAM_NOT_FOUND = -15, + /*! The URL no longer exists*/ + GF_URL_REMOVED = -16, + + /*! The IsoMedia file is not a valid one*/ + GF_ISOM_INVALID_FILE = -20, + /*! The IsoMedia file is not complete. Either the file is being downloaded, or it has been truncated*/ + GF_ISOM_INCOMPLETE_FILE = -21, + /*! The media in this IsoMedia track is not valid (usually due to a broken stream description)*/ + GF_ISOM_INVALID_MEDIA = -22, + /*! The requested operation cannot happen in the current opening mode of the IsoMedia file*/ + GF_ISOM_INVALID_MODE = -23, + /*! This IsoMedia track refers to media outside the file in an unknown way*/ + GF_ISOM_UNKNOWN_DATA_REF = -24, + + /*! An invalid MPEG-4 Object Descriptor was found*/ + GF_ODF_INVALID_DESCRIPTOR = -30, + /*! An MPEG-4 Object Descriptor was found or added to a forbidden descriptor*/ + GF_ODF_FORBIDDEN_DESCRIPTOR = -31, + /*! An invalid MPEG-4 BIFS command was detected*/ + GF_ODF_INVALID_COMMAND = -32, + /*! The scene has been encoded using an unknown BIFS version*/ + GF_BIFS_UNKNOWN_VERSION = -33, + + /*! The remote IP address could not be solved*/ + GF_IP_ADDRESS_NOT_FOUND = -40, + /*! The connection to the remote peer has failed*/ + GF_IP_CONNECTION_FAILURE = -41, + /*! The network operation has failed*/ + GF_IP_NETWORK_FAILURE = -42, + /*! The network connection has been closed*/ + GF_IP_CONNECTION_CLOSED = -43, + /*! The network operation has failed because no data is available*/ + GF_IP_NETWORK_EMPTY = -44, + /*! The network operation has been discarded because it would be a blocking one*/ + GF_IP_SOCK_WOULD_BLOCK = -45, + /*! UDP connection did not receive any data at all. Signaled by client services to reconfigure network if possible*/ + GF_IP_UDP_TIMEOUT = -46, + + /*! Authentication with the remote host has failed*/ + GF_AUTHENTICATION_FAILURE = -50, + /*! Script not ready for playback */ + GF_SCRIPT_NOT_READY = -51, + /*! Bad configuration for the current context */ + GF_INVALID_CONFIGURATION = -52, + /*! The element has not been found */ + GF_NOT_FOUND = -53, + /*! Unexpected format of data */ + GF_PROFILE_NOT_SUPPORTED = -54, + /*! 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_Err; + +/*! +\brief Error Printing + +Returns a printable version of a given error +\param e Error code requested +\return String representing the error +*/ +const char *gf_error_to_string(GF_Err e); + +/*! @} */ + + +/*! +\addtogroup mem_grp +@{ + */ + +/*! +\brief Memory allocation for a structure +\hideinitializer + +Macro allocating memory and zero-ing it +*/ +#define GF_SAFEALLOC(__ptr, __struct) {\ + (__ptr) = (__struct *) gf_malloc(sizeof(__struct));\ + if (__ptr) {\ + memset((void *) (__ptr), 0, sizeof(__struct));\ + }\ + } + +/*! +\brief Memory allocation for an array of n structs +\hideinitializer + +Macro allocating memory for n structures and zero-ing it +*/ +#define GF_SAFE_ALLOC_N(__ptr, __n, __struct) {\ + (__ptr) = (__struct *) gf_malloc( __n * sizeof(__struct));\ + if (__ptr) {\ + memset((void *) (__ptr), 0, __n * sizeof(__struct));\ + }\ + } + + +/*! +\brief dynamic string concatenation + +Dynamic concatenation of string with optional separator +\param str pointer to destination string pointer +\param to_append string to append +\param sep optional separator string to insert before concatenation. If set and initial string is NULL, will not be appended +\return error code + */ +GF_Err gf_dynstrcat(char **str, const char *to_append, const char *sep); + + +/*! +\brief fraction parsing + +Parse a 64 bit fraction from string +\param str string to parse +\param frac fraction to fill +\return GF_TRUE if success, GF_FALSE otherwise ( fraction being set to {0,0} ) + */ +Bool gf_parse_lfrac(const char *str, GF_Fraction64 *frac); + +/*! +\brief fraction parsing + +Parse a 32 bit fraction from string +\param str string to parse +\param frac fraction to fill +\return GF_TRUE if success, GF_FALSE otherwise ( fraction being set to {0,0} ) + */ +Bool gf_parse_frac(const char *str, GF_Fraction *frac); + +/*! +\brief search string without case + +Search a aubstring in a string witout checking for case +\param text text to search +\param subtext string to find +\param subtext_len length of string to find +\return GF_TRUE if success, GF_FALSE otherwise + */ +Bool gf_strnistr(const char *text, const char *subtext, u32 subtext_len); + + +/*! +\brief safe timestamp rescale + +Rescale a 64 bit timestamp value to new timescale, i.e. performs value * new_timescale / timescale +\param value value to rescale. A value of -1 means no timestamp defined and is returned unmodified +\param timescale timescale of value. Assumed to be less than 0xFFFFFFFF +\param new_timescale new timescale? Assumed to be less than 0xFFFFFFFF +\return new value + */ +u64 gf_timestamp_rescale(u64 value, u64 timescale, u64 new_timescale); + +/*! +\brief safe signed timestamp rescale + +Rescale a 64 bit timestamp value to new timescale, i.e. performs value * new_timescale / timescale +\param value value to rescale +\param timescale timescale of value. Assumed to be less than 0xFFFFFFFF +\param new_timescale new timescale. Assumed to be less than 0xFFFFFFFF +\return new value + */ +s64 gf_timestamp_rescale_signed(s64 value, u64 timescale, u64 new_timescale); + +/*! +\brief compare timestamps + +Compares two timestamps +\param value1 value to rescale +\param timescale1 timescale of value. Assumed to be less than 0xFFFFFFFF +\param value2 value to rescale +\param timescale2 timescale of value. Assumed to be less than 0xFFFFFFFF +\return GF_TRUE if (value1 / timescale1) is stricly less than (value2 / timescale2) + */ +Bool gf_timestamp_less(u64 value1, u64 timescale1, u64 value2, u64 timescale2); + +/*! +\brief compare timestamps + +Compares two timestamps +\param value1 value to rescale +\param timescale1 timescale of value. Assumed to be less than 0xFFFFFFFF +\param value2 value to rescale +\param timescale2 timescale of value. Assumed to be less than 0xFFFFFFFF +\return GF_TRUE if (value1 / timescale1) is stricly less than or equal to (value2 / timescale2) + */ +Bool gf_timestamp_less_or_equal(u64 value1, u64 timescale1, u64 value2, u64 timescale2); + +/*! +\brief compare timestamps + +Compares two timestamps +\param value1 value to rescale +\param timescale1 timescale of value. Assumed to be less than 0xFFFFFFFF +\param value2 value to rescale +\param timescale2 timescale of value. Assumed to be less than 0xFFFFFFFF +\return GF_TRUE if (value1 / timescale1) is stricly greater than (value2 / timescale2) + */ +Bool gf_timestamp_greater(u64 value1, u64 timescale1, u64 value2, u64 timescale2); + +/*! +\brief compare timestamps + +Compares two timestamps +\param value1 value to rescale +\param timescale1 timescale of value. Assumed to be less than 0xFFFFFFFF +\param value2 value to rescale +\param timescale2 timescale of value. Assumed to be less than 0xFFFFFFFF +\return GF_TRUE if (value1 / timescale1) is stricly greater than or equal to (value2 / timescale2) + */ +Bool gf_timestamp_greater_or_equal(u64 value1, u64 timescale1, u64 value2, u64 timescale2); + +/*! +\brief compare timestamps + +Compares two timestamps +\param value1 value to rescale +\param timescale1 timescale of value. Assumed to be less than 0xFFFFFFFF +\param value2 value to rescale +\param timescale2 timescale of value. Assumed to be less than 0xFFFFFFFF +\return GF_TRUE if (value1 / timescale1) is equal to (value2 / timescale2) + */ +Bool gf_timestamp_equal(u64 value1, u64 timescale1, u64 value2, u64 timescale2); + +/*! @} */ + +/*! +\addtogroup libsys_grp +\brief Library configuration + +These functions are used to initialize, shutdown and configure libgpac. + +The library shall be initialized using \ref gf_sys_init and terminated using gf_sys_close + +The library can usually be configured from command line if your program uses \ref gf_sys_set_args. + +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 filters configuration options, see https://wiki.gpac.io/Filters + +@{ + */ + +/*! +Selection flags for memory tracker +\hideinitializer + */ +typedef enum +{ + /*! No memory tracking*/ + GF_MemTrackerNone = 0, + /*! Memory tracking without backtrace*/ + GF_MemTrackerSimple, + /*! Memory tracking with backtrace*/ + GF_MemTrackerBackTrace, +} GF_MemTrackerType; + +/*! +\brief System setup + +Inits the system high-resolution clock if any, CPU usage manager, random number and GPAC global config. It is strongly recommended to call this function before calling any other GPAC functions, since on some systems (like winCE) it may result in a better memory usage estimation. + +The profile allows using a different global config file than the default, and may be a name (without / or \\) or point to an existing config file. +\note This can be called several times but only the first call will result in system setup. +\param mem_tracker_type memory tracking mode +\param profile name of the profile to load, NULL for default. +\return Error code if any + */ +GF_Err gf_sys_init(GF_MemTrackerType mem_tracker_type, const char *profile); +/*! +\brief System closing +Closes the system high-resolution clock and any CPU associated resources. +\note This can be called several times but the system will be closed when no more users are counted. + */ +void gf_sys_close(); + +/*! +\brief System arguments + +Sets the user app arguments (used by GUI mode) +\param argc Number of arguments +\param argv Array of arguments - the first string is ignored (considered to be the executable name) +\return error code if any, GF_OK otherwise + */ +GF_Err gf_sys_set_args(s32 argc, const char **argv); + +/*! +\brief Get number of args + +Gets the number of argument of the user application if any +\return number of argument of the user application + */ +u32 gf_sys_get_argc(); + +/*! +\brief Get program arguments + +Gets the arguments of the user application if any +\return argument of the user application + */ +const char **gf_sys_get_argv(); + +/*! +\brief Get number of args + +Gets the number of argument of the user application if any +\param arg Index of argument to retrieve +\return number of argument of the user application + */ +const char *gf_sys_get_arg(u32 arg); + +/*! +\brief Locate a global filter arg + +Looks for a filter option specified as global argument +\param arg name of option to search, without "--" or "-+" specififers +\return argument value string, empty string for booleans or NULL if not found + */ +const char *gf_sys_find_global_arg(const char *arg); + +/*! +\brief Mark arg as used + +Marks the argument at given index as used. By default all args are marked as not used when assigning args +\param arg_idx Index of argument to mark +\param used flag to set +*/ +void gf_sys_mark_arg_used(s32 arg_idx, Bool used); + +/*! +\brief Check if arg is marked as used + +Marks the argument at given index as used +\param arg_idx Index of argument to mark +\return used flag of the arg +*/ +Bool gf_sys_is_arg_used(s32 arg_idx); + +/*! +\brief checks if test mode is enabled + +Checks if test mode is enabled (no date nor GPAC version should be written). +\return GF_TRUE if test mode is enabled, GF_FALSE otherwise. + */ +Bool gf_sys_is_test_mode(); + +/*! +\brief checks if compatibility with old arch is enabled + +Checks if compatibility with old arch is enabled - this function will be removed when master will be moved to filters branch +\return GF_TRUE if old arch compat is enabled, GF_FALSE otherwise. + */ +Bool gf_sys_old_arch_compat(); + +#ifdef GPAC_ENABLE_COVERAGE +/*! +\brief checks if coverage tests are enabled + +Checks if coverage tests are enabled +\return GF_TRUE if coverage is enabled, GF_FALSE otherwise. + */ +Bool gf_sys_is_cov_mode(); +#endif + +/*! +\brief checks if running in quiet mode + +Checks if quiet mode is enabled +\return 2 if quiet mode is enabled, 1 if quiet mode not enabled but progress is disabled, 0 otherwise. + */ +u32 gf_sys_is_quiet(); + +/*! gets GPAC feature list in this GPAC build +\param disabled if GF_TRUE, gets disabled features, otherwise gets enabled features +\return the list of features. +*/ +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); + +/*! 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 +*/ +GF_Err gf_sys_profiler_set_callback(void *udta, gf_rmt_user_callback rmt_usr_cbk); + + +/*! 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 +*/ +GF_Err gf_sys_profiler_send(const char *msg); + +/*! Enables sampling times in RMT + \param enable if GF_TRUE, sampling will be enabled, otherwise disabled*/ +void gf_sys_profiler_enable_sampling(Bool enable); + +/*! 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(); + +/*! +GPAC Log tools +\hideinitializer + +Describes the color code for console print + */ +typedef enum +{ + /*!reset color*/ + GF_CONSOLE_RESET=0, + /*!set text to red*/ + GF_CONSOLE_RED, + /*!set text to green*/ + GF_CONSOLE_GREEN, + /*!set text to blue*/ + GF_CONSOLE_BLUE, + /*!set text to yellow*/ + GF_CONSOLE_YELLOW, + /*!set text to cyan*/ + GF_CONSOLE_CYAN, + /*!set text to white*/ + GF_CONSOLE_WHITE, + /*!set text to magenta*/ + GF_CONSOLE_MAGENTA, + /*!reset all console text*/ + GF_CONSOLE_CLEAR, + /*!save console state*/ + GF_CONSOLE_SAVE, + /*!restore console state*/ + GF_CONSOLE_RESTORE, + + /*!set text to bold modifier*/ + GF_CONSOLE_BOLD = 1<<16, + /*!set text to italic*/ + GF_CONSOLE_ITALIC = 1<<17, + /*!set text to underlined*/ + GF_CONSOLE_UNDERLINED = 1<<18, + /*!set text to strikethrough*/ + GF_CONSOLE_STRIKE = 1<<19 +} GF_ConsoleCodes; + +/*! sets console code +\param std the output stream (stderr or stdout) +\param code the console code to set +*/ +void gf_sys_set_console_code(FILE *std, GF_ConsoleCodes code); + + +/*! @} */ + + +/*! +\addtogroup log_grp +\brief Logging System + +@{ +*/ + +/*! +\brief GPAC Log Levels +\hideinitializer + +These levels describes messages priority used when filtering logs +*/ +typedef enum +{ + /*! Disable all Log message*/ + GF_LOG_QUIET = 0, + /*! Log message describes an error*/ + GF_LOG_ERROR, + /*! Log message describes a warning*/ + GF_LOG_WARNING, + /*! Log message is informational (state, etc..)*/ + GF_LOG_INFO, + /*! Log message is a debug info*/ + GF_LOG_DEBUG +} GF_LOG_Level; + +/*! +\brief Log exits at first error assignment + +When GF_LOG_ERROR happens, program leaves with instruction exit(1); +\param strict strict behavior when encoutering a serious error. +\return old value before the call. + */ +Bool gf_log_set_strict_error(Bool strict); + +/*! +\brief gets string-formatted log tools + +Gets the string-formatted log tools and levels. Returned string shall be freed by the caller. +\return string-formatted log tools. + */ +char *gf_log_get_tools_levels(void); + +/*! +\brief GPAC Log tools +\hideinitializer + +These flags describes which sub-part of GPAC generates the log and are used when filtering logs + */ +typedef enum +{ + /*! Log message from the core library (init, threads, network calls, etc)*/ + GF_LOG_CORE = 0, + /*! Log message from a raw media parser (BIFS, LASeR, A/V formats)*/ + GF_LOG_CODING, + /*! Log message from a bitstream parser (IsoMedia, MPEG-2 TS, OGG, ...)*/ + GF_LOG_CONTAINER, + /*! Log message from the network/service stack (messages & co)*/ + GF_LOG_NETWORK, + /*! Log message from the HTTP stack*/ + GF_LOG_HTTP, + /*! Log message from the RTP/RTCP stack (TS info) and packet structure & hinting (debug)*/ + GF_LOG_RTP, + /*! Log message from a codec*/ + GF_LOG_CODEC, + /*! Log message from any textual (XML, ...) parser (context loading, etc)*/ + GF_LOG_PARSER, + /*! Generic log message from a filter (not from filter core library)*/ + GF_LOG_MEDIA, + /*! Log message from the scene graph/scene manager (handling of nodes and attribute modif, DOM core)*/ + GF_LOG_SCENE, + /*! Log message from the scripting engine APIs - does not cover alert() in the script code itself*/ + GF_LOG_SCRIPT, + /*! Log message from event handling*/ + GF_LOG_INTERACT, + /*! Log message from compositor*/ + GF_LOG_COMPOSE, + /*! Log message from the terminal/compositor, indicating media object state*/ + GF_LOG_COMPTIME, + /*! Log for video object cache */ + GF_LOG_CACHE, + /*! Log message from multimedia I/O devices (audio/video input/output, ...)*/ + GF_LOG_MMIO, + /*! Log for runtime info (times, memory, CPU usage)*/ + GF_LOG_RTI, + /*! Log for memory tracker*/ + GF_LOG_MEMORY, + /*! Log for audio compositor*/ + GF_LOG_AUDIO, + /*! Generic Log for modules*/ + GF_LOG_MODULE, + /*! Log for threads and mutexes */ + GF_LOG_MUTEX, + /*! Log for threads and condition */ + GF_LOG_CONDITION, + /*! Log for all HTTP streaming */ + GF_LOG_DASH, + /*! Log for all messages from filter core library (not from a filter) */ + GF_LOG_FILTER, + /*! Log for filter scheduler only */ + GF_LOG_SCHEDULER, + /*! Log for all ROUTE message */ + GF_LOG_ROUTE, + /*! Log for all messages coming from GF_Terminal or script alert()*/ + GF_LOG_CONSOLE, + /*! Log for all messages coming the application, not used by libgpac or the modules*/ + GF_LOG_APP, + + /*! special value used to set a level for all tools*/ + GF_LOG_ALL, + GF_LOG_TOOL_MAX = GF_LOG_ALL, +} GF_LOG_Tool; + +/*! +\brief Log modules assignment + +Sets the tools to be checked for log filtering. By default no logging is performed. +\param log_tool the tool to be logged +\param log_level the level of logging for this tool + * + */ +void gf_log_set_tool_level(GF_LOG_Tool log_tool, GF_LOG_Level log_level); + +/*! +\brief Log Message Callback + +The gf_log_cbk type is the type for the callback of the \ref gf_log_set_callback function. By default all logs are redirected to stderr +\param cbck Opaque user data. +\param log_level level of the log. This value is not guaranteed in multi-threaded context. +\param log_tool tool emitting the log. This value is not guaranteed in multi-threaded context. +\param fmt message log format. +\param vlist message log param. + * + */ +typedef void (*gf_log_cbk)(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist); + +/*! +\brief Log overwrite + +Assigns a user-defined callback for printing log messages. By default all logs are redirected to stderr +\param usr_cbk Opaque user data +\param cbk Callback log function +\return previous callback function +*/ +gf_log_cbk gf_log_set_callback(void *usr_cbk, gf_log_cbk cbk); + + +/*! + \cond DUMMY_DOXY_SECTION +*/ +#ifndef GPAC_DISABLE_LOG +/*\note + - to turn log on, change to GPAC_ENABLE_LOG + - to turn log off, change to GPAC_DISABLE_LOG + this is needed by configure+sed to modify this file directly +*/ +#define GPAC_ENABLE_LOG +#endif + +/*! + \endcond +*/ + + +/*! \cond PRIVATE */ +void gf_log(const char *fmt, ...); +void gf_log_lt(GF_LOG_Level ll, GF_LOG_Tool lt); +void gf_log_va_list(GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vl); +/*! \endcond */ + +/*! +\brief Log level checking + +Checks if a given tool is logged for the given level +\param log_tool tool to check +\param log_level level to check +\return 1 if logged, 0 otherwise +*/ +Bool gf_log_tool_level_on(GF_LOG_Tool log_tool, GF_LOG_Level log_level); + +/*! +\brief Log level getter + +Gets log level of a given tool +\param log_tool tool to check +\return log level of tool +*/ +u32 gf_log_get_tool_level(GF_LOG_Tool log_tool); + +/*! +\brief Set log tools and levels + +Set log tools and levels according to the log_tools_levels string. +\param log_tools_levels string specifying the tools and levels. It is formatted as logToolX\@logLevelX:logToolZ\@logLevelZ:... +\param reset_all if GF_TRUE, all previous log settings are discarded. +\return GF_OK or GF_BAD_PARAM +*/ +GF_Err gf_log_set_tools_levels(const char *log_tools_levels, Bool reset_all); + +/*! +\brief Modify log tools and levels + +Modify log tools and levels according to the log_tools_levels string. Previous log settings are kept. +\param val string specifying the tools and levels. It is formatted as logToolX\@logLevelX:logToolZ\@logLevelZ:... +\return GF_OK or GF_BAD_PARAM +*/ +GF_Err gf_log_modify_tools_levels(const char *val); + +/*! +\brief Checks if color logs is enabled + +Checks if color logs is enabled +\return GF_TRUE if color logs are used +*/ +Bool gf_log_use_color(); + +/*! +\brief Checks if logs are stored to file + +Checks if logs are stored to file +\return GF_TRUE if logs are stored to file +*/ +Bool gf_log_use_file(); + +#ifdef GPAC_DISABLE_LOG +#define GF_LOG(_ll, _lm, __args) +#else +/*! +\brief Message logging +\hideinitializer + +Macro for logging messages. Usage is GF_LOG(log_lev, log_module, (fmt, ...)). The log function is only called if log filtering allows it. This avoids fetching logged parameters when the tool is not being logged. +*/ +#define GF_LOG(_log_level, _log_tools, __args) if (gf_log_tool_level_on(_log_tools, _log_level) ) { gf_log_lt(_log_level, _log_tools); gf_log __args ;} +#endif + +/*! +\brief Resets log file +Resets log file if any log file name was specified, by closing and reopening a new file. +*/ +void gf_log_reset_file(); + + + +/*! @} */ + +/*! +\addtogroup miscsys_grp +\brief System time CPU + +This section documents time functionalities and CPU management in GPAC. + +@{ + */ + + +/*! +\brief PseudoRandom Integer Generation Initialization + +Sets the starting point for generating a series of pseudorandom integers. +\param Reset Re-initializes the random number generator +*/ +void gf_rand_init(Bool Reset); +/*! PseudoRandom integer generation +\return a pseudorandom integer +*/ +u32 gf_rand(); + +/*! gets user name +\param buf buffer set to current user (login) name if available. +*/ +void gf_get_user_name(char buf[1024]); + +/*! +\brief Progress formatting + +Signals progress in GPAC's operations. Note that progress signaling with this function is not thread-safe, the main purpose is to use it for authoring tools only. +\param title title string of the progress, or NULL for no progress +\param done Current amount performed of the action. +\param total Total amount of the action. + */ +void gf_set_progress(const char *title, u64 done, u64 total); + +/*! +\brief Progress Callback + +The gf_on_progress_cbk type is the type for the callback of the \ref gf_set_progress_callback function +\param cbck Opaque user data. +\param title preogress title. +\param done Current amount performed of the action +\param total Total amount of the action. + * + */ +typedef void (*gf_on_progress_cbk)(const void *cbck, const char *title, u64 done, u64 total); + +/*! +\brief Progress overwriting +Overwrites the progress signaling function by a user-defined one. +\param user_cbk Opaque user data +\param prog_cbk new callback function to use. Passing NULL restore default GPAC stderr notification. + */ +void gf_set_progress_callback(void *user_cbk, gf_on_progress_cbk prog_cbk); + + +/*! +\brief Prompt checking + +Checks if a character is pending in the prompt buffer. +\return 1 if a character is ready to be fetched, 0 otherwise. +\note Function not available under WindowsCE nor SymbianOS +*/ +Bool gf_prompt_has_input(); + +/*! +\brief Prompt character flush + +Gets the current character entered at prompt if any. +\return value of the character. +\note Function not available under WindowsCE nor SymbianOS +*/ +char gf_prompt_get_char(); + +/*! +\brief Get prompt terminal size + +Gets the stdin prompt size (columns and rows) +\param width set to number of rows in the terminal +\param height set to number of columns in the terminal +\return error if any +*/ +GF_Err gf_prompt_get_size(u32 *width, u32 *height); + +/*! +\brief turns prompt echo on/off + +Turns the prompt character echo on/off - this is useful when entering passwords. +\param echo_off indicates whether echo should be turned on or off. +\note Function not available under WindowsCE nor SymbianOS +*/ +void gf_prompt_set_echo_off(Bool echo_off); + +/*! @} */ + + +/*! gets battery state +\param onBattery set to GF_TRUE if running on battery +\param onCharge set to GF_TRUE if battery is charging +\param level set to battery charge percent +\param batteryLifeTime set to battery lifetime +\param batteryFullLifeTime set to battery full lifetime +\return GF_TRUE if success +*/ +Bool gf_sys_get_battery_state(Bool *onBattery, u32 *onCharge, u32 *level, u32 *batteryLifeTime, u32 *batteryFullLifeTime); + + +/*! +\brief parses 128 bit from string + +Parses 128 bit from string +\param string the string containing the value in hexa. Non alphanum characters are skipped +\param value the value parsed +\return error code if any + */ +GF_Err gf_bin128_parse(const char *string, bin128 value); + + +enum +{ + GF_BLOB_IN_TRANSFER = 1, + GF_BLOB_CORRUPTED = 1<<1, +}; + +/*! + * Blob structure used to pass data pointer around + */ +typedef struct +{ + /*! data block of blob */ + u8 *data; + /*! size of blob */ + u32 size; + /*! blob flags */ + u32 flags; + /*! blob mutex for multi-thread access */ + struct __tag_mutex *mx; +} GF_Blob; + +/*! + * Retrieves data associated with a blob url. If success, \ref gf_blob_release must be called after this +\param blob_url URL of blob object (ie gmem://%p) +\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(const char *blob_url, u8 **out_data, u32 *out_size, u32 *blob_flags); + +/*! + * 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); + +/*! +\addtogroup time_grp +\brief Time manipulation tools +@{ +*/ + +/*! +\brief System clock query + +Gets the system clock time. +\return System clock value since GPAC initialization in milliseconds. + */ +u32 gf_sys_clock(); + +/*! +\brief High precision system clock query + +Gets the hight precision system clock time. +\return System clock value since GPAC initialization in microseconds. + */ +u64 gf_sys_clock_high_res(); + +/*! +\brief Sleeps thread/process + +Locks calling thread/process execution for a given time. +\param ms Amount of time to sleep in milliseconds. + */ +void gf_sleep(u32 ms); + +#ifdef WIN32 +/*! +\brief WINCE time constant +\hideinitializer + +time between jan 1, 1601 and jan 1, 1970 in units of 100 nanoseconds +*/ +#define TIMESPEC_TO_FILETIME_OFFSET (((LONGLONG)27111902 << 32) + (LONGLONG)3577643008) + +#endif + +/*! +\brief gets UTC time in milliseconds + +Gets UTC clock in milliseconds +\return UTC time in milliseconds + */ +u64 gf_net_get_utc(); + +/*! +\brief converts an ntp timestamp into UTC time in milliseconds + +Converts NTP 64-bit timestamp to UTC clock in milliseconds +\param ntp NTP timestamp +\return UTC time in milliseconds + */ +u64 gf_net_ntp_to_utc(u64 ntp); + +/*! +\brief parses date + +Parses date and gets UTC value for this date. Date format is an XSD dateTime format or any of the supported formats from HTTP 1.1: + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() formatgets UTC time in milliseconds + +\param date string containing the date to parse +\return UTC time in milliseconds + */ +u64 gf_net_parse_date(const char *date); + +/*! +\brief returns 64-bit UTC timestamp from year, month, day, hour, min and sec +\param year the year +\param month the month +\param day the day +\param hour the hour +\param min the min +\param sec the sec +\return UTC time in milliseconds + */ +u64 gf_net_get_utc_ts(u32 year, u32 month, u32 day, u32 hour, u32 min, u32 sec); + +/*! +\brief gets timezone adjustment in seconds + +Gets timezone adjustment in seconds, with localtime - timezone = UTC time +\return timezone shift in seconds + */ +s32 gf_net_get_timezone(); + +/*! +\brief gets timezone daylight saving time status + +Gets timezone daylight saving time +\return GF_TRUE if DST is active + */ +Bool gf_net_time_is_dst(); + +/*! +\brief gets time from UTC timestamp + +Gets time from UTC timestamp +\param time timestamp value - see gmtime +\return time description structure - see gmtime + */ +struct tm *gf_gmtime(const time_t *time); + +/*! @} */ + +/*! +\addtogroup thr_grp +\brief Time manipulation tools +@{ +*/ + +/*! +\brief Gets process ID + +Gets ID of the process running this gpac instance. +\return the ID of the main process +*/ +u32 gf_sys_get_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. +All time values are expressed in milliseconds (accuracy is not guaranteed). +*/ +typedef struct +{ + /*!start of the sampling period*/ + u32 sampling_instant; + /*!duration of the sampling period*/ + u32 sampling_period_duration; + /*!total amount of time (User+kernel) spent in CPU for all processes as evaluated at the end of the sampling period*/ + u32 total_cpu_time; + /*!total amount of time (User+kernel) spent in CPU for the calling process as evaluated at the end of the sampling period*/ + u32 process_cpu_time; + /*!amount of time (User+kernel) spent in CPU for all processes during the sampling period*/ + u32 total_cpu_time_diff; + /*!total amount of time (User+kernel) spent in CPU for the calling process during the sampling period*/ + u32 process_cpu_time_diff; + /*!total amount of idle time during the sampling period.*/ + u32 cpu_idle_time; + /*!percentage (from 0 to 100) of CPU usage during the sampling period.*/ + u32 total_cpu_usage; + /*!percentage (from 0 to 100) of the CPU usage by the calling process during the sampling period.*/ + u32 process_cpu_usage; + /*!calling process ID*/ + u32 pid; + /*!calling process thread count if known*/ + u32 thread_count; + /*!size of calling process allocated heaps*/ + u64 process_memory; + /*!total physical memory in system*/ + u64 physical_memory; + /*!available physical memory in system*/ + u64 physical_memory_avail; + /*!total memory currently allocated by gpac*/ + u64 gpac_memory; + /*!total number of cores on the system*/ + u32 nb_cores; +} GF_SystemRTInfo; + +/*! +Selection flags for run-time info retrieval +\hideinitializer + */ +enum +{ + /*!Indicates all processes' times must be fetched. If not set, only the current process times will be retrieved, and the + thread count and total times won't be available*/ + GF_RTI_ALL_PROCESSES_TIMES = 1, + /*!Indicates the process allocated heap size must be fetch. If not set, only the system physical memory is fetched. + Fetching the entire ocess allocated memory can have a large impact on performances*/ + GF_RTI_PROCESS_MEMORY = 1<<1, + /*!Indicates that only system memory should be fetched. When set, all refreshing info is ignored*/ + GF_RTI_SYSTEM_MEMORY_ONLY = 1<<2 +}; + +/*! +\brief Gets Run-Time info + +Gets CPU and memory usage info for the calling process and the system. Information gathering is controled through timeout values. +\param refresh_time_ms refresh time period in milliseconds. If the last sampling was done less than this period ago, the run-time info is not refreshed. +\param rti holder to the run-time info structure to update. +\param flags specify which info is to be retrieved. +\return 1 if info has been updated, 0 otherwise. +\note You should not try to use a too small refresh time. Typical values are 500 ms or one second. + */ +Bool gf_sys_get_rti(u32 refresh_time_ms, GF_SystemRTInfo *rti, u32 flags); + +/*! @} */ + +/*! +\addtogroup osfile_grp +\brief File System tools + +This section documents file system tools used in GPAC. + +FILE objects are wrapped in GPAC for direct memory or callback operations. All file functions not using stderr/stdout must use the gf_ prefixed versions, eg: +\code +//bad design, will fail when using wrapped memory IOs +FILE *t = fopen(url, "rb"); +fputs(t, mystring); +fclose(t); + +//good design, will work fine when using wrapped memory IOs +FILE *t = gf_fopen(url, "rb"); +gf_fputs(t, mystring); +gf_fclose(t); +\endcode + +@{ + */ + +/*! +\brief reads a file into memory + +Reads a local file into memory, in binary open mode. +\param file_name path on disk of the file to read +\param out_data pointer to allocted address, to be freed by caller +\param out_size pointer to allocted size +\return error code if any + */ +GF_Err gf_file_load_data(const char *file_name, u8 **out_data, u32 *out_size); + +/*! +\brief reads a file into memory + +Reads a local file into memory, in binary open mode. +\param file stream object to read (no seek is performed) +\param out_data pointer to allocted address, to be freed by caller +\param out_size pointer to allocted size +\return error code if any + */ +GF_Err gf_file_load_data_filep(FILE *file, u8 **out_data, u32 *out_size); + +/*! +\brief Delete Directory + +Delete a dir within the full path. +\param DirPathName the file path name. +\return error if any + */ +GF_Err gf_rmdir(const char *DirPathName); + +/*! +\brief Create Directory + +Create a directory within the full path. +\param DirPathName the dir path name. +\return error if any + */ +GF_Err gf_mkdir(const char* DirPathName); + +/*! +\brief Check Directory Exists + +Create a directory within the full path. +\param DirPathName the dir path name. +\return GF_TRUE if directory exists + */ +Bool gf_dir_exists(const char *DirPathName); + +/*! +\brief Create Directory + +Cleanup a directory within the full path, removing all the files and the directories. +\param DirPathName the dir path name. +\return error if any + */ +GF_Err gf_dir_cleanup(const char* DirPathName); + + +/** +Gets a newly allocated string containing the default cache directory. +It is the responsibility of the caller to free the string. +\return a fully qualified path to the default cache directory + */ +const char * gf_get_default_cache_directory(); + +/** +Gets the number of open file handles (gf_fopen/gf_fclose only). +\return number of open file handles + */ +u32 gf_file_handles_count(); + +/*! +\brief file writing helper + +Wrapper to properly handle calls to fwrite(), ensuring proper error handling is invoked when it fails. +\param ptr data buffer to write +\param nb_bytes number of bytes to write +\param stream stream object +\return number of bytes to written +*/ +size_t gf_fwrite(const void *ptr, size_t nb_bytes, FILE *stream); + +/*! +\brief file reading helper + +Wrapper to properly handle calls to fread() +\param ptr data buffer to read +\param nbytes number of bytes to read +\param stream stream object +\return number of bytes read +*/ +size_t gf_fread(void *ptr, size_t nbytes, FILE *stream); + +/*! +\brief file reading helper + +Wrapper to properly handle calls to fgets() +\param buf same as fgets +\param size same as fgets +\param stream same as fgets +\return same as fgets +*/ +char *gf_fgets(char *buf, size_t size, FILE *stream); +/*! +\brief file reading helper + +Wrapper to properly handle calls to fgetc() +\param stream same as fgetc +\return same as fgetc +*/ +int gf_fgetc(FILE *stream); +/*! +\brief file writing helper + +Wrapper to properly handle calls to fputc() +\param val same as fputc +\param stream same as fputc +\return same as fputc +*/ +int gf_fputc(int val, FILE *stream); +/*! +\brief file writing helper + +Wrapper to properly handle calls to fputs() +\param buf same as fputs +\param stream same as fputs +\return same as fputs +*/ +int gf_fputs(const char *buf, FILE *stream); +/*! +\brief file writing helper + +Wrapper to properly handle calls to fprintf() +\param stream same as fprintf +\param format same as fprintf +\return same as fprintf +*/ +int gf_fprintf(FILE *stream, const char *format, ...); +/*! +\brief file flush helper + +Wrapper to properly handle calls to fflush() +\param stream same as fflush +\return same as fflush +*/ +int gf_fflush(FILE *stream); +/*! +\brief end of file helper + +Wrapper to properly handle calls to feof() +\param stream same as feof +\return same as feof +*/ +int gf_feof(FILE *stream); +/*! +\brief file error helper + +Wrapper to properly handle calls to ferror() +\param stream same as ferror +\return same as ferror +*/ +int gf_ferror(FILE *stream); + +/*! +\brief file size helper + +Gets the file size given a FILE object. The FILE object position will be reset to 0 after this call +\param fp FILE object to check +\return file size in bytes +*/ +u64 gf_fsize(FILE *fp); + +/*! +\brief file IO helper + +Checks if the given FILE object is a native FILE or a GF_FileIO wrapper. +\param fp FILE object to check +\return GF_TRUE if the FILE object is a wrapper to GF_FileIO +*/ +Bool gf_fileio_check(FILE *fp); + +/*! +\brief file opening + +Opens a file, potentially bigger than 4GB. if filename identifies a blob (gmem://), the blob will be opened +\param file_name same as fopen +\param mode same as fopen +\return stream handle of the file object +*/ +FILE *gf_fopen(const char *file_name, const char *mode); + +/*! +\brief file opening + +Opens a file, potentially using file IO if the parent URL is a File IO wrapper +\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 +\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); + +/*! +\brief file closing + +Closes a file +\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. +\param file file to close +\return same as flcose +*/ +s32 gf_fclose(FILE *file); + +/*! +\brief large file position query + +Queries the current read/write position in a large file +\param f Same semantics as gf_ftell +\return position in the file +\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. +*/ +u64 gf_ftell(FILE *f); +/*! +\brief large file seeking + +Seeks the current read/write position in a large file +\param f Same semantics as fseek +\param pos Same semantics as fseek +\param whence Same semantics as fseek +\return 0 if success, -1 if error +\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. +*/ +s32 gf_fseek(FILE *f, s64 pos, s32 whence); + + +/*! gets basename from filename/path +\param filename Path of the file, can be an absolute path +\return a pointer to the start of a filepath basename or null +*/ +char* gf_file_basename(const char* filename); + +/*! gets extension from filename +\param filename Path of the file, can be an absolute path +\return a pointer to the start of a filepath extension or null +*/ +char* gf_file_ext_start(const char* filename); + +/*!\brief FileEnum info object + +The FileEnumInfo object is used to get file attributes upon enumeration of a directory. +*/ +typedef struct +{ + /*!File is marked as hidden*/ + Bool hidden; + /*!File is a directory*/ + Bool directory; + /*!File is a drive mountpoint*/ + Bool drive; + /*!File is a system file*/ + Bool system; + /*!File size in bytes*/ + u64 size; + /*!File last modif time in UTC seconds*/ + u64 last_modified; +} GF_FileEnumInfo; + +/*! +\brief Directory Enumeration Callback + +The gf_enum_dir_item type is the type for the callback of the \ref gf_enum_directory function +\param cbck Opaque user data. +\param item_name File or directory name. +\param item_path File or directory full path and name from filesystem root. +\param file_info information for the file or directory. +\return 1 to abort enumeration, 0 to continue enumeration. + * + */ +typedef Bool (*gf_enum_dir_item)(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info); +/*! +\brief Directory enumeration + +Enumerates a directory content. Feedback is provided by the enum_dir_item function +\param dir Directory to enumerate +\param enum_directory If set, only directories will be enumerated, otherwise only files are. +\param enum_dir gf_enum_dir_item callback function for enumeration. +\param cbck Opaque user data passed to callback function. +\param filter optional filter for file extensions. If a file extension without the dot '.' character is not found in the + * filter the file will be skipped. +\return error if any + */ +GF_Err gf_enum_directory(const char *dir, Bool enum_directory, gf_enum_dir_item enum_dir, void *cbck, const char *filter); + + +/*! +\brief File Deletion + +Deletes a file from the disk. +\param fileName absolute name of the file or name relative to the current working directory. +\return error if any +*/ +GF_Err gf_file_delete(const char *fileName); + +/*! +\brief File Move + +Moves or renames a file or directory. +\param fileName absolute path of the file / directory to move or rename +\param newFileName absolute new path/name of the file / directory +\return error if any +*/ +GF_Err gf_file_move(const char *fileName, const char *newFileName); + +/*! +\brief Temporary File Creation + +Creates a new temporary file in binary mode +\param fileName if not NULL, strdup() of the temporary filename when created by GPAC (NULL otherwise as the system automatically removes its own tmp files) +\return stream handle to the new file ressoucre + */ +FILE *gf_file_temp(char ** const fileName); + + +/*! +\brief File Modification Time + +Gets the UTC modification time of the given file in microseconds +\param filename file to check +\return modification time of the file + */ +u64 gf_file_modification_time(const char *filename); + +/*! +\brief File existence check + +Checks if file with given name exists +\param fileName path of the file to check +\return GF_TRUE if file exists */ +Bool gf_file_exists(const char *fileName); + +/*! +\brief File existence check + +Checks if file with given name exists, for regular files or File IO wrapper +\param file_name path of the file to check +\param par_name name of the parent file +\return GF_TRUE if file exists */ +Bool gf_file_exists_ex(const char *file_name, const char *par_name); + +/*! File IO wrapper object*/ +typedef struct __gf_file_io GF_FileIO; + + +/*! open proc for memory file IO +\param fileio_ref reference file_io. A file can be opened multiple times for the same reference, your code must handle this +\param url target file name. +\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 + - "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) +\return the opened GF_FileIO if success, or NULL otherwise + */ +typedef GF_FileIO *(*gfio_open_proc)(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_error); + +/*! seek proc for memory file IO +\param fileio target file IO object +\param offset offset in file +\param whence position from offset, same as fseek +\return error if any + */ +typedef GF_Err (*gfio_seek_proc)(GF_FileIO *fileio, u64 offset, s32 whence); + +/*! read proc for memory file IO +\param fileio target file IO object +\param buffer buffer to read. +\param bytes number of bytes to read. +\return number of bytes read, 0 if error. + */ +typedef u32 (*gfio_read_proc)(GF_FileIO *fileio, u8 *buffer, u32 bytes); + +/*! write proc for memory file IO +\param fileio target file IO object +\param buffer buffer to write. +\param bytes number of bytes to write. If 0, acts as fflush +\return number of bytes write, 0 if error + */ +typedef u32 (*gfio_write_proc)(GF_FileIO *fileio, u8 *buffer, u32 bytes); + +/*! positon tell proc for memory file IO +\param fileio target file IO object +\return position in bytes from file start + */ +typedef s64 (*gfio_tell_proc)(GF_FileIO *fileio); +/*! end of file proc for memory file IO +\param fileio target file IO object +\return GF_TRUE if end of file, GF_FALSE otherwise + */ +typedef Bool (*gfio_eof_proc)(GF_FileIO *fileio); +/*! printf proc for memory file IO +\param fileio target file IO object +\param format format string to use +\param args variable argument list for printf, already initialized (va_start called) +\return same as vfprint + */ +typedef int (*gfio_printf_proc)(GF_FileIO *fileio, const char *format, va_list args); + +/*! Creates a new file IO object + +There is no guarantee that the corresponding resource will be opened by the framework, it is therefore the caller responsability to track objects created by +gf_fileio_new or as a response to open with mode "url". + +\param url the original URL this file IO object wraps +\param udta opaque data for caller +\param open open proc for IO, must not be NULL +\param seek seek proc for IO, must not be NULL +\param read read proc for IO - if NULL the file is considered write only +\param write write proc for IO - if NULL the file is considered read only +\param tell tell proc for IO, must not be NULL +\param eof eof proc for IO, must not be NULL +\param printf printf proc for IO, may be NULL +\return the newly created file IO wrapper + */ +GF_FileIO *gf_fileio_new(char *url, void *udta, + gfio_open_proc open, + gfio_seek_proc seek, + gfio_read_proc read, + gfio_write_proc write, + gfio_tell_proc tell, + gfio_eof_proc eof, + gfio_printf_proc printf); + +/*! Deletes a new fileIO object +\param fileio the File IO object to delete +*/ +void gf_fileio_del(GF_FileIO *fileio); + +/*! Gets associated user data of a fileIO object +\param fileio target file IO object +\return the associated user data +*/ +void *gf_fileio_get_udta(GF_FileIO *fileio); + +/*! Gets URL of a fileIO object. + The url uses the protocol scheme "gfio://" +\param fileio target file IO object +\return the file IO url to use +*/ +const char * gf_fileio_url(GF_FileIO *fileio); + +/*! Sets statistics on a fileIO object. +\param fileio target file IO object +\param bytes_done number of bytes fetched for this file +\param file_size total size of this file, 0 if unknown +\param cache_complete if GF_TRUE, means the file is completely available +\param bytes_per_sec reception bytes per second, 0 if unknown +*/ +void gf_fileio_set_stats(GF_FileIO *fileio, u64 bytes_done, u64 file_size, Bool cache_complete, u32 bytes_per_sec); + +/*! Gets statistics on a fileIO object. +\param fileio target file IO object +\param bytes_done number of bytes fetched for this file (may be NULL) +\param file_size total size of this file, 0 if unknown (may be NULL) +\param cache_complete if GF_TRUE, means the file is completely available (may be NULL) +\param bytes_per_sec reception bytes per second, 0 if unknown (may be NULL) +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_fileio_get_stats(GF_FileIO *fileio, u64 *bytes_done, u64 *file_size, Bool *cache_complete, u32 *bytes_per_sec); + +/*! Checks if a FileIO object can write +\param fileio target file IO object +\param url the original resource URL to open +\param mode the desired open mode +\param out_err set to error code if any, must not be NULL +\return file IO object for this resource +*/ +GF_FileIO *gf_fileio_open_url(GF_FileIO *fileio, const char *url, const char *mode, GF_Err *out_err); + + +/*! Tags a FileIO object to be accessed from main thread only +\param fileio target file IO object +\return error if any +*/ +GF_Err gf_fileio_tag_main_thread(GF_FileIO *fileio); + +/*! Check if a FileIO object is to be accessed from main thread only + + \note Filters accessing FileIO objects by other means that a filter option must manually tag themselves as main thread, potentially rescheduling a configure call to next process. Not doing so can result in binding crashes in multi-threaded mode. + +\param url target url +\return GF_TRUE if object is tagged for main thread, GF_FALSE otherwise +*/ +Bool gf_fileio_is_main_thread(const char *url); + +/*! Gets GF_FileIO object from its URL + The url uses the protocol scheme "gfio://" +\param url the URL of the File IO object +\return the file IO object +*/ +GF_FileIO *gf_fileio_from_url(const char *url); + +/*! Constructs a new GF_FileIO object from a URL + The url can be absolute or relative to the parent GF_FileIO. This is typcically needed by filters (dash input, dasher, NHML/NHNT writers...) generating or consuming additional files associated with the main file IO object but being written or read by other filters. + The function will not open the associated resource, only create the file IO wrapper for later usage + If you need to create a new fileIO to be opened immediately, use \ref gf_fopen_ex. + +\param fileio parent file IO object +\param new_res_url the original URL of the new object to create +\return the url (gfio://) of the created file IO object, NULL otherwise +*/ +const char *gf_fileio_factory(GF_FileIO *fileio, const char *new_res_url); + +/*! Translates a FileIO object URL into the original resource URL +\param url the URL of the File IO object +\return the original resource URL associated with the file IO object +*/ +const char * gf_fileio_translate_url(const char *url); +/*! Gets a FileIO original resource URL +\param fileio target file IO object +\return the original resource URL associated with the file IO object +*/ +const char * gf_fileio_resource_url(GF_FileIO *fileio); + +/*! Checks if a FileIO object can read +\param fileio target file IO object +\return GF_TRUE if read is enabled on this object +*/ +Bool gf_fileio_read_mode(GF_FileIO *fileio); +/*! Checks if a FileIO object can write +\param fileio target file IO object +\return GF_TRUE if write is enabled on this object +*/ +Bool gf_fileio_write_mode(GF_FileIO *fileio); + +/*! @} */ + +/*! +\addtogroup hash_grp +\brief Data hashing, integrity and generic compression + +This section documents misc data functions such as integrity and parsing such as SHA-1 hashing CRC checksum, 128 bit ID parsing... + +@{ + */ + + +/*! +\brief CRC32 compute + +Computes the CRC32 value of a buffer. +\param data buffer +\param size buffer size +\return computed CRC32 + */ +u32 gf_crc_32(const u8 *data, u32 size); + + +/** +Compresses a data buffer in place using zlib/deflate. Buffer may be reallocated in the process. +\param data pointer to the data buffer to be compressed +\param data_len length of the data buffer to be compressed +\param out_size pointer for output buffer size +\return error if any + */ +GF_Err gf_gz_compress_payload(u8 **data, u32 data_len, u32 *out_size); + +/** +Compresses a data buffer in place using zlib/deflate. Buffer may be reallocated in the process. +\param data pointer to the data buffer to be compressed +\param data_len length of the data buffer to be compressed +\param out_size pointer for output buffer size +\param data_offset offset in source buffer - the input payload size is data_len - data_offset +\param skip_if_larger if GF_TRUE, will not override source buffer if compressed version is larger than input data +\param out_comp_data if not NULL, the compressed result is set in this pointer rather than doing inplace compression +\return error if any + */ +GF_Err gf_gz_compress_payload_ex(u8 **data, u32 data_len, u32 *out_size, u8 data_offset, Bool skip_if_larger, u8 **out_comp_data); + +/** +Decompresses a data buffer using zlib/deflate. +\param data data buffer to be decompressed +\param data_len length of the data buffer to be decompressed +\param uncompressed_data pointer to the uncompressed data buffer. It is the responsibility of the caller to free this buffer. +\param out_size size of the uncompressed buffer +\return error if any + */ +GF_Err gf_gz_decompress_payload(u8 *data, u32 data_len, u8 **uncompressed_data, u32 *out_size); + +/** +Compresses a data buffer in place using LZMA. Buffer may be reallocated in the process. +\param data pointer to the data buffer to be compressed +\param data_len length of the data buffer to be compressed +\param out_size pointer for output buffer size +\return error if any + */ +GF_Err gf_lz_compress_payload(u8 **data, u32 data_len, u32 *out_size); + +/** +Decompresses a data buffer using LZMA. +\param data data buffer to be decompressed +\param data_len length of the data buffer to be decompressed +\param uncompressed_data pointer to the uncompressed data buffer. It is the responsibility of the caller to free this buffer. +\param out_size size of the uncompressed buffer +\return error if any + */ +GF_Err gf_lz_decompress_payload(u8 *data, u32 data_len, u8 **uncompressed_data, u32 *out_size); + + +#ifndef GPAC_DISABLE_ZLIB +/*! Wrapper around gzseek, same parameters +\param file target gzfile +\param offset offset in file +\param whence same as gzseek +\return same as gzseek +*/ +u64 gf_gzseek(void *file, u64 offset, int whence); +/*! Wrapper around gf_gztell, same parameters + \param file target gzfile + \return postion in file + */ +u64 gf_gztell(void *file); +/*! Wrapper around gzrewind, same parameters + \param file target gzfile + \return same as gzrewind + */ +s64 gf_gzrewind(void *file); +/*! Wrapper around gzeof, same parameters + \param file target gzfile + \return same as gzeof + */ +int gf_gzeof(void *file); +/*! Wrapper around gzclose, same parameters +\param file target gzfile +\return same as gzclose +*/ +int gf_gzclose(void *file); +/*! Wrapper around gzerror, same parameters +\param file target gzfile +\param errnum same as gzerror +\return same as gzerror + */ +const char * gf_gzerror (void *file, int *errnum); +/*! Wrapper around gzclearerr, same parameters + \param file target gzfile + */ +void gf_gzclearerr(void *file); +/*! Wrapper around gzopen, same parameters +\param path the file name to open +\param mode the file open mode +\return open file + */ +void *gf_gzopen(const char *path, const char *mode); +/*! Wrapper around gzread, same parameters + \param file target gzfile + \param buf same as gzread + \param len same as gzread + \return same as gzread + */ +int gf_gzread(void *file, void *buf, unsigned len); +/*! Wrapper around gzdirect, same parameters + \param file target gzfile + \return same as gzdirect + */ +int gf_gzdirect(void *file); +/*! Wrapper around gzgetc, same parameters + \param file target gzfile + \return same as gzgetc + */ +int gf_gzgetc(void *file); +/*! Wrapper around gzgets, same parameters + \param file target gzfile + \param buf same as gzread + \param len same as gzread + \return same as gzgets +*/ +char * gf_gzgets(void *file, char *buf, int len); +#endif + + +/*! SHA1 context*/ +typedef struct __sha1_context GF_SHA1Context; + +/*! SHA1 message size */ +#define GF_SHA1_DIGEST_SIZE 20 + +/*! create SHA-1 context +\return the SHA1 context*/ +GF_SHA1Context *gf_sha1_starts(); +/*! adds byte to the SHA-1 context +\param ctx the target SHA1 context +\param input data to hash +\param length size of data in bytes +*/ +void gf_sha1_update(GF_SHA1Context *ctx, u8 *input, u32 length); +/*! generates SHA-1 of all bytes ingested +\param ctx the target SHA1 context +\param digest buffer to store message digest +*/ +void gf_sha1_finish(GF_SHA1Context *ctx, u8 digest[GF_SHA1_DIGEST_SIZE] ); + +/*! gets SHA1 message digest of a file +\param filename name of file to hash +\param digest buffer to store message digest +\return error if any +*/ +GF_Err gf_sha1_file(const char *filename, u8 digest[GF_SHA1_DIGEST_SIZE]); + +/*! gets SHA1 message digest of a opened file +\param file handle to open file +\param digest buffer to store message digest +\return error if any +*/ +GF_Err gf_sha1_file_ptr(FILE *file, u8 digest[GF_SHA1_DIGEST_SIZE] ); + +/*! gets SHA-1 of input buffer +\param buf input buffer to hash +\param buflen sizeo of input buffer in bytes +\param digest buffer to store message digest + */ +void gf_sha1_csum(u8 *buf, u32 buflen, u8 digest[GF_SHA1_DIGEST_SIZE]); +/*! @} */ + + +/*! +\addtogroup libsys_grp +@{ +*/ + +/*! gets a global config key value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, NULL otherwise. +*/ +const char *gf_opts_get_key(const char *secName, const char *keyName); + +/*! sets a global config key value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\param keyValue the desired key value +\note this will also create both section and key if they are not found in the configuration file +\return error if any +*/ +GF_Err gf_opts_set_key(const char *secName, const char *keyName, const char *keyValue); + +/*! removes all entries in the given section of the global config +\param secName the target section +*/ +void gf_opts_del_section(const char *secName); +/*! gets the number of sections in the global config +\return the number of sections +*/ +u32 gf_opts_get_section_count(); +/*! gets a section name based on its index in the global config +\param secIndex 0-based index of the section to query +\return the section name if found, NULL otherwise +*/ +const char *gf_opts_get_section_name(u32 secIndex); + +/*! gets the number of keys in a section of the global config +\param secName the target section +\return the number of keys in the section +*/ +u32 gf_opts_get_key_count(const char *secName); +/*! gets the number of keys in a section of the global config +\param secName the target section +\param keyIndex 0-based index of the key in the section +\return the key name if found, NULL otherwise +*/ +const char *gf_opts_get_key_name(const char *secName, u32 keyIndex); + +/*! gets a global config boolean value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, GF_FALSE otherwise. +*/ +Bool gf_opts_get_bool(const char *secName, const char *keyName); + +/*! gets a global config integer value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, 0 otherwise. +*/ +u32 gf_opts_get_int(const char *secName, const char *keyName); + +/*! gets a global config key value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found and if the key is not restricted, NULL otherwise. +*/ +const char *gf_opts_get_key_restricted(const char *secName, const char *keyName); + +/*! + * Do not save modification to global options +\return error code + */ +GF_Err gf_opts_discard_changes(); + +/*! + * Returns file name of global config +\return file name of global config or NULL if libgpac is not initialized + */ +const char *gf_opts_get_filename(); + +/*! + * Gets GPAC shared directory (gui, shaders, etc ..) +\param path_buffer GF_MAX_PATH buffer to store output +\return GF_TRUE if success, GF_FALSE otherwise + */ +Bool gf_opts_default_shared_directory(char *path_buffer); + +/*! @} */ + + +//! @cond Doxygen_Suppress + +#ifdef GPAC_DISABLE_3D +#define GPAC_DISABLE_REMOTERY 1 +#endif + +#ifdef GPAC_DISABLE_REMOTERY +#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 + +//! @endcond + +/* \cond dummy */ +#ifdef GPAC_CONFIG_ANDROID +typedef void (*fm_callback_func)(void *cbk_obj, u32 type, u32 param, int *value); +extern void gf_fm_request_set_callback(void *cbk_obj, fm_callback_func cbk_func); +void gf_fm_request_call(u32 type, u32 param, int *value); +#endif //GPAC_CONFIG_ANDROID + +/*to call whenever the OpenGL library is opened - this function is needed to bind OpenGL and remotery, and 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(); + +typedef enum +{ + //pbo not enabled + GF_GL_PBO_NONE=0, + //pbo enabled, both push and glTexImage textures are done in gf_gl_txw_upload + GF_GL_PBO_BOTH, + //push only is done in gf_gl_txw_upload + GF_GL_PBO_PUSH, + // glTexImage textures only is done in gf_gl_txw_upload + GF_GL_PBO_TEXIMG, +} GF_GLPBOState; + +typedef struct _gl_texture_wrap +{ + u32 textures[4]; + u32 PBOs[4]; + + u32 nb_textures; + u32 width, height, pix_fmt, stride, uv_stride; + Bool is_yuv; + u32 bit_depth, uv_w, uv_h; + u32 scale_10bit; + u32 init_active_texture; + + u32 gl_format; + u32 bytes_per_pix; + Bool has_alpha; + Bool internal_textures; + Bool uniform_setup; + u32 memory_format; + struct _gf_filter_frame_interface *frame_ifce; + Bool first_tx_load; + + //PBO state - must be managed by caller, especially if using separated push and texImg steps through gf_gl_txw_setup calls + GF_GLPBOState pbo_state; + Bool flip; + //YUV is full video range + Bool fullrange; + s32 mx_cicp; + + u32 last_program; +} GF_GLTextureWrapper; + +Bool gf_gl_txw_insert_fragment_shader(u32 pix_fmt, const char *tx_name, char **f_source, Bool y_flip); +Bool gf_gl_txw_setup(GF_GLTextureWrapper *tx, u32 pix_fmt, u32 width, u32 height, u32 stride, u32 uv_stride, Bool linear_interp, struct _gf_filter_frame_interface *frame_ifce, Bool full_range, s32 matrix_coef_or_neg); +Bool gf_gl_txw_upload(GF_GLTextureWrapper *tx, const u8 *data, struct _gf_filter_frame_interface *frame_ifce); +Bool gf_gl_txw_bind(GF_GLTextureWrapper *tx, const char *tx_name, u32 gl_program, u32 texture_unit); +void gf_gl_txw_reset(GF_GLTextureWrapper *tx); + +/* \endcond */ + + +/*! macros to get the size of an array of struct*/ +#define GF_ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0])) + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_CORE_H_*/ + diff --git a/include/gpac/user.h b/include/gpac/user.h new file mode 100644 index 0000000..d6c55f3 --- /dev/null +++ b/include/gpac/user.h @@ -0,0 +1,122 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Stream Management 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_USER_H_ +#define _GF_USER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! +\file <gpac/user.h> +\brief GPAC terminal <-> user API. +*/ + +/*! +\addtogroup termuser_grp Terminal User +\ingroup playback_grp +\brief GPAC terminal <-> user API. + +This section documents the user-level API of the GPAC media player. + +@{ +*/ + + + + +#include <gpac/events.h> +#include <gpac/module.h> + +/*! GPAC client terminal*/ +typedef struct _tag_terminal GF_Terminal; +/*! GPAC user structure*/ +typedef struct _tag_user GF_User; + +/*! terminal creation flags*/ +enum +{ + /*display should be hidden upon initialization*/ + GF_TERM_INIT_HIDE = 1, + /*no audio renderer will be created*/ + GF_TERM_NO_AUDIO = 1<<1, + /*initializes client without a default audio out - used for dump modes where audio playback is not needed*/ + GF_TERM_NO_DEF_AUDIO_OUT = 1<<2, + /*disables video output module - used for bench mode without video*/ + GF_TERM_NO_VIDEO = 1<<3, + /*works without window thread*/ + GF_TERM_WINDOW_NO_THREAD = 1<<4, + /*lets the main user handle window events (needed for browser plugins)*/ + GF_TERM_NO_WINDOWPROC_OVERRIDE = 1<<5, + /*works without title bar*/ + GF_TERM_WINDOW_NO_DECORATION = 1<<6, + + /*framebuffer works in 32 bit alpha mode - experimental, only supported on Win32*/ + GF_TERM_WINDOW_TRANSPARENT = 1<<7, + /*works in windowless mode - experimental, only supported on Win32*/ + GF_TERM_WINDOWLESS = 1<<8 +}; + +/*user object for all callbacks*/ +struct _tag_user +{ + /*! user defined callback for all functions - cannot be NULL*/ + void *opaque; + /*! the event proc. Return value depend on the event type, usually 0 + cannot be NULL if os_window_handler is specified and dont_override_window_proc is set + may be NULL otherwise*/ + Bool (*EventProc)(void *opaque, GF_Event *event); + + /*! optional os window handler (HWND on win32/winCE, XWindow for X11) + if not set the video outut will create and manage the display window.*/ + void *os_window_handler; + /*! for now, only used by X11 (indicates display the window is on)*/ + void *os_display; + + /*! init flags bypassing GPAC config file */ + u32 init_flags; +}; + +/*! compositor screen buffer grab mode*/ +typedef enum +{ + GF_SC_GRAB_DEPTH_NONE = 0, + GF_SC_GRAB_DEPTH_ONLY = 1, + GF_SC_GRAB_DEPTH_RGBD = 2, + GF_SC_GRAB_DEPTH_RGBDS = 3 +} GF_CompositorGrabMode; + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_USER_H_*/ + diff --git a/include/gpac/utf.h b/include/gpac/utf.h new file mode 100644 index 0000000..b44de3a --- /dev/null +++ b/include/gpac/utf.h @@ -0,0 +1,150 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * 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_UTF_H_ +#define _GF_UTF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/utf.h> +\brief UTF functions. + */ + +/*! +\addtogroup utf_grp +\brief UTF and Unicode-related functions + +This section documents the UTF functions of the GPAC framework.\n +The wide characters in GPAC are unsignad shorts, in other words GPAC only supports UTF8 and UTF16 coding styles. + +\note these functions are just ports of libutf8 library tools into GPAC. + +@{ + */ + +#include <gpac/tools.h> + +/*! error code for UTF-8 conversion errors*/ +#define GF_UTF8_FAIL 0xFFFFFFFF +/*! +\brief wide-char to multibyte conversion + +Converts a wide-char string to a multibyte string +\param dst multibyte destination buffer +\param dst_len multibyte destination buffer size +\param srcp address of the wide-char string. This will be set to the next char to be converted in the input buffer if not enough space in the destination, or NULL if conversion was completed. +\return length (in byte) of the multibyte string or GF_UTF8_FAIL if error. + */ +u32 gf_utf8_wcstombs(char* dst, size_t dst_len, const unsigned short** srcp); + +/*! +\brief multibyte to wide-char conversion + +Converts a multibyte string to a wide-char string +\param dst wide-char destination buffer +\param dst_len wide-char destination buffer size +\param srcp address of the multibyte character buffer. This will be set to the next char to be converted in the input buffer if not enough space in the destination, or NULL if conversion was completed. +\return length (in unsigned short) of the wide-char string or GF_UTF8_FAIL if error. + */ +u32 gf_utf8_mbstowcs(unsigned short* dst, size_t dst_len, const char** srcp); + +/*! +\brief wide-char string length + +Gets the length in character of a wide-char string +\param s the wide-char string +\return the wide-char string length + */ +u32 gf_utf8_wcslen(const unsigned short *s); + +/*! +\brief returns a UTF8 string from a string started with BOM + +Returns UTF8 from data +\param data the string or wide-char string +\param size of the data buffer + size of the data buffer +\param out_ptr set to an allocated buffer if needed for conversion, shall be destroyed by caller. Must not be NULL +\param result set to resulting UTF8 string. Must not be NULL +\return error if any: GF_IO_ERR if UTF decode error or GF_BAD_PARAM + */ +GF_Err gf_utf_get_utf8_string_from_bom(const u8 *data, u32 size, char **out_ptr, char **result); + +/*! +\brief Checks validity of a UTF8 string + +Checks if a given byte sequence is a valid UTF-8 encoding +\param data the byte equence buffer +\param size the length of the byte sequence +\return GF_TRUE if valid UTF8, GF_FALSE otherwise + */ +Bool gf_utf8_is_legal(const u8 *data, u32 size); + + +/*! +\brief string bidi reordering + +Performs a simple reordering of words in the string based on each word direction, so that glyphs are sorted in display order. +\param utf_string the wide-char string +\param len the len of the wide-char string +\return 1 if the main direction is right-to-left, 0 otherwise + */ +Bool gf_utf8_reorder_bidi(u16 *utf_string, u32 len); + +/*! maximum character size in bytes*/ +static const u32 UTF8_MAX_BYTES_PER_CHAR = 4; + + +/*! +\brief Unicode conversion from UTF-8 to UCS-4 +\param ucs4_buf The UCS-4 buffer to fill +\param utf8_len The length of the UTF-8 buffer +\param utf8_buf The buffer containing the UTF-8 data +\return the length of the ucs4_buf. Note that the ucs4_buf should be allocated by parent and should be at least utf8_len * 4 + */ +u32 utf8_to_ucs4 (u32 *ucs4_buf, u32 utf8_len, unsigned char *utf8_buf); + + + + +#if defined(WIN32) + +wchar_t* gf_utf8_to_wcs(const char* str); +char* gf_wcs_to_utf8(const wchar_t* str); + +#endif + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_UTF_H_*/ + diff --git a/include/gpac/version.h b/include/gpac/version.h new file mode 100644 index 0000000..2a58be3 --- /dev/null +++ b/include/gpac/version.h @@ -0,0 +1,84 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2012-2022 + * All rights reserved + * + * This file is part of GPAC + * + * 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_VERSION_H + +/*! +\file "gpac/version.h" +\brief GPAC version. +\addtogroup cst_grp + +@{ +*/ + +/*! +\brief GPAC Version +\hideinitializer +*/ +/* KEEP SPACE SEPARATORS FOR MAKE / GREP (SEE MAIN MAKEFILE & CONFIGURE & CO) + * NO SPACE in GPAC_VERSION / GPAC_FULL_VERSION for proper install + * SONAME versions must be digits (not strings) + */ +/*! Macro giving GPAC version name expressed as a printable string*/ +#define GPAC_VERSION "2.0" + +// WARNING: when bumping, reflect the changes in share/python/libgpac.py !! +/*! ABI Major number of libgpac */ +#define GPAC_VERSION_MAJOR 11 +/*! ABI Minor number of libgpac */ +#define GPAC_VERSION_MINOR 0 +/*! ABI Micro number of libgpac */ +#define GPAC_VERSION_MICRO 0 + +/*! gets GPAC full version including GIT revision +\return GPAC full version +*/ +const char *gf_gpac_version(); + +/*! gets GPAC copyright +\return GPAC copyright +*/ +const char *gf_gpac_copyright(); + +/*! gets GPAC copyright + citations DOI +\return GPAC copyright +*/ +const char *gf_gpac_copyright_cite(); + +/*!gets libgpac ABI major version +\return major number of libgpac ABI version*/ +u32 gf_gpac_abi_major(); + +/*!gets libgpac ABI minor version +\return minor number of libgpac ABI version*/ +u32 gf_gpac_abi_minor(); + +/*!gets libgpac ABI major version +\return micro number of libgpac ABI version*/ +u32 gf_gpac_abi_micro(); + +/*! @} */ + +#endif //_GF_VERSION_H diff --git a/include/gpac/webvtt.h b/include/gpac/webvtt.h new file mode 100644 index 0000000..e4da19c --- /dev/null +++ b/include/gpac/webvtt.h @@ -0,0 +1,126 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato, Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2013-2019 + * All rights reserved + * + * This file is part of GPAC / WebVTT header + * + * 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_WEBVTT_H_ +#define _GF_WEBVTT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/webvtt.h> +\brief Helper functions for WebVTT parsing. + */ + +/*! +\addtogroup wvtt_grp WebVTT parsing +\ingroup media_grp +\brief Helper functions for WebVTT parsing + +This section documents the audio and video parsing functions of the GPAC framework. + +@{ + */ + +/*! WebVTT types */ +typedef enum { + WEBVTT_ID, + WEBVTT_SETTINGS, + WEBVTT_PAYLOAD, + WEBVTT_POSTCUE_TEXT, + WEBVTT_PRECUE_TEXT, +} GF_WebVTTCuePropertyType; + +/*! WebVTT timestamp information*/ +typedef struct _webvtt_timestamp { + u32 hour, min, sec, ms; +} GF_WebVTTTimestamp; +/*! gets webvtt timestamp +\param wvtt_ts the target WebVTT timestamp +\return timestamp in millisecond*/ +u64 gf_webvtt_timestamp_get(GF_WebVTTTimestamp *wvtt_ts); +/*! sets webvtt timestamp +\param wvtt_ts the target WebVTT timestamp +\param value timestamp in millisecond +*/ +void gf_webvtt_timestamp_set(GF_WebVTTTimestamp *wvtt_ts, u64 value); +/*! dumps webvtt timestamp +\param wvtt_ts the target WebVTT timestamp +\param dump the output file to write to +\param dump_hour if GF_TRUE, dumps hours +*/ +void gf_webvtt_timestamp_dump(GF_WebVTTTimestamp *wvtt_ts, FILE *dump, Bool dump_hour); + +/*! WebVTT cue structure*/ +typedef struct _webvtt_cue +{ + GF_WebVTTTimestamp start; + GF_WebVTTTimestamp end; + char *id; + char *settings; + char *text; + char *pre_text; + char *post_text; + + Bool split; + /* original times before split, if applicable */ + GF_WebVTTTimestamp orig_start; + GF_WebVTTTimestamp orig_end; +} GF_WebVTTCue; +/*! destroys a WebVTT cue +\param cue the target WebVTT cue +*/ +void gf_webvtt_cue_del(GF_WebVTTCue * cue); + +#ifndef GPAC_DISABLE_VTT +/*! dumps webvtt sample boxes +\param dump the output file to write to +\param data the WebVTT ISOBMFF sample payload +\param dataLength the size of the payload in bytes +\param printLength set to the printed size in bytes +\return error if any +*/ +GF_Err gf_webvtt_dump_header_boxed(FILE *dump, const u8 *data, u32 dataLength, u32 *printLength); +#endif + +/*! dumps webvtt sample boxes +\param data the WebVTT ISOBMFF sample payload +\param dataLength the size of the payload in bytes +\param start the start time in milliseconds of the WebVTT cue +\param end the end time in milliseconds of the WebVTT cue +\return new list of cues, to destroy by caller +*/ +GF_List *gf_webvtt_parse_cues_from_data(const u8 *data, u32 dataLength, u64 start, u64 end); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_THREAD_H_*/ + diff --git a/include/gpac/xml.h b/include/gpac/xml.h new file mode 100644 index 0000000..902d1d2 --- /dev/null +++ b/include/gpac/xml.h @@ -0,0 +1,429 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * 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 _XML_PARSER_H_ +#define _XML_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <gpac/tools.h> +#include <gpac/list.h> +#include <gpac/bitstream.h> + +#ifndef GPAC_DISABLE_CORE_TOOLS + +/*! +\file <gpac/xml.h> +\brief XML functions. + */ + +/*! +\addtogroup xml_grp +\brief XML Parsing functions + +This section documents the XML functions (full doc parsing and SAX parsing) of the GPAC framework. + +\defgroup sax_grp SAX Parsing +\ingroup xml_grp +\defgroup dom_grp DOM Parsing +\ingroup xml_grp +\defgroup xmlb_grp XML Binary Formatting +\ingroup xml_grp +*/ + +/*! +\addtogroup xml_grp +@{ +*/ + + +/*! Structure containing a parsed attribute*/ +typedef struct +{ + /*! name or namespace:name*/ + char *name; + /*! value*/ + char *value; +} GF_XMLAttribute; + +/*! XML node types*/ +enum +{ + + /*! XML node*/ + GF_XML_NODE_TYPE = 0, + /*! text node (including carriage return between XML nodes)*/ + GF_XML_TEXT_TYPE, + /*! CDATA node*/ + GF_XML_CDATA_TYPE, +}; + +/*! Structure containing a parsed XML node*/ +typedef struct _xml_node +{ + /*! Type of the node*/ + u32 type; + /*! + For DOM nodes: name + For other (text, css, cdata), element content + */ + char *name; + + /*! namespace of the node, for XML node type only*/ + char *ns; + /*! list of attributes of the node, for XML node type only*/ + GF_List *attributes; + /*! list of children nodes of the node, for XML node type only*/ + GF_List *content; + /*! original pos in parent (used for DASH MPD)*/ + u32 orig_pos; +} GF_XMLNode; + +/*! @} */ + + +/*! +\addtogroup sax_grp + + ! SAX XML Parser API + GPAC can do progressive loading of XML document using this SAX api. +@{ +*/ + +/*! SAX XML Parser object*/ +typedef struct _tag_sax_parser GF_SAXParser; +/*! SAX XML node start callback + \param sax_cbck user data passed durinc creation of SAX parser + \param node_name name of the XML node starting + \param name_space namespace of the XML node starting + \param attributes array of attributes declared for that XML node + \param nb_attributes number of items in array +*/ +typedef void (*gf_xml_sax_node_start)(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes); +/*! SAX XML node end callback + \param sax_cbck user data passed durinc creation of SAX parser + \param node_name name of the XML node starting + \param name_space namespace of the XML node starting +*/ +typedef void (*gf_xml_sax_node_end)(void *sax_cbck, const char *node_name, const char *name_space); +/*! SAX text content callback + \param sax_cbck user data passed durinc creation of SAX parser + \param content text content of the node + \param is_cdata if TRUE the content was ancapsulated in CDATA; otherwise this isthe content of a text node +*/ +typedef void (*gf_xml_sax_text_content)(void *sax_cbck, const char *content, Bool is_cdata); +/*! SAX progress callback + \param cbck user data passed durinc creation of SAX parser + \param done amount of bytes parsed from the file + \param total total number of bytes in the file +*/ +typedef void (*gf_xml_sax_progress)(void *cbck, u64 done, u64 total); + +/*! creates new sax parser - all callbacks are optionals +\param on_node_start callback for XML node start +\param on_node_end callback for XML node end +\param on_text_content callback for text content +\param cbck user data passed to callback functions +\return a SAX parser object +*/ +GF_SAXParser *gf_xml_sax_new(gf_xml_sax_node_start on_node_start, + gf_xml_sax_node_end on_node_end, + gf_xml_sax_text_content on_text_content, + void *cbck); + +/*! destroys sax parser +\param parser the SAX parser to destroy +*/ +void gf_xml_sax_del(GF_SAXParser *parser); +/*! Inits the parser with string containing BOM, if any. BOM must be 4 char string with 0 terminaison. If BOM is NULL, parsing will assume UTF-8 compatible coding +\param parser the SAX parser to init +\param BOM the 4 character with 0 terminaison BOM or NULL +\return error code if any +*/ +GF_Err gf_xml_sax_init(GF_SAXParser *parser, unsigned char *BOM); +/*! Parses input string data. string data MUST be terminated by the 0 character (eg 2 0s for UTF-16) +\param parser the SAX parser to use +\param string_bytes the string to parse +\return error code if any +*/ +GF_Err gf_xml_sax_parse(GF_SAXParser *parser, const void *string_bytes); +/*! Suspends or resumes SAX parsing. + When resuming on file, the function will run until suspended/end of file/error + When resuming on steram, the function will simply return + +\param parser the SAX parser to use +\param do_suspend if GF_TRUE, SAX parsing is suspended, otherwise SAX parsing is resumed +\return error code if any +*/ +GF_Err gf_xml_sax_suspend(GF_SAXParser *parser, Bool do_suspend); +/*! parses file (potentially gzipped). OnProgress is optional, used to get progress callback +\param parser the SAX parser to use +\param fileName the file to parse +\param OnProgress the progress function to use. The callback for the progress function is the one assigned at SAX parser creation \ref gf_xml_sax_new +\return error code if any +*/ +GF_Err gf_xml_sax_parse_file(GF_SAXParser *parser, const char *fileName, gf_xml_sax_progress OnProgress); +/*! Gets current line number, useful for inspecting errors +\param parser the SAX parser to use +\return current line number of SAX parser +*/ +u32 gf_xml_sax_get_line(GF_SAXParser *parser); + +/*! Peeks a node forward in the file. This may be used to pick the attribute of the first node found matching a given (attributeName, attributeValue) couple +\param parser SAX parser to use +\param att_name attribute name to look for +\param att_value value for this attribute +\param substitute gives the name of an additional XML node type to inspect to match the node. May be NULL. +\param get_attr gives the name of the attribute in the substitute node that matches the condition. If substitue node with name atribute is found, the content of the name attribute is returned. May be NULL. +\param end_pattern gives a string indicating where to stop looking in the document. May be NULL. +\param is_substitute is set to GF_TRUE if the return value corresponds to the content of the name attribute of the substitute element +\return name of the XML node found, or NULL if no match. This string has to be freed by the caller using gf_free + +*/ +char *gf_xml_sax_peek_node(GF_SAXParser *parser, char *att_name, char *att_value, char *substitute, char *get_attr, char *end_pattern, Bool *is_substitute); + +/*! For file mode only, indicates if a file is compressed or not +\param parser SAX parser to use +\return 1 if file is compressed, 0 otherwise +*/ +Bool gf_xml_sax_binary_file(GF_SAXParser *parser); + +/*! Returns the last error found during parsing +\param parser SAX parser +\return the last SAX error encountered +*/ +const char *gf_xml_sax_get_error(GF_SAXParser *parser); + +/*! Returns the name of the root XML element +\param file the XML file to inspect +\param ret_code return error code if any +\return the name of the root XML element. This string has to be freed by the caller using gf_free +*/ +char *gf_xml_get_root_type(const char *file, GF_Err *ret_code); + +/*! Returns the position in bytes of the start of the current node being parsed. The byte offset points to the first < character in the opening tag. +\param parser SAX parser +\return the 0-based position of the current XML node +*/ +u32 gf_xml_sax_get_node_start_pos(GF_SAXParser *parser); +/*! Returns the position in bytes of the end of the current node being parsed. The byte offset points to the last > character in the closing tag. +\param parser SAX parser +\return the 0-based position of the current XML node +*/ +u32 gf_xml_sax_get_node_end_pos(GF_SAXParser *parser); + +/*! @} */ + +/*! +\addtogroup dom_grp + + ! DOM Full XML document Parsing API + GPAC can do one-pass full document parsing of XML document using this DOM API. +@{ +*/ + +/*! the DOM loader. GPAC can also load complete XML document in memory, using a DOM-like approach. This is a simpler +approach for document parsing not requiring progressive loading*/ +typedef struct _tag_dom_parser GF_DOMParser; + +/*! the DOM loader constructor +\return the created DOM loader, NULL if memory error +*/ +GF_DOMParser *gf_xml_dom_new(); + +/*! the DOM loader constructor +\param parser the DOM parser to destroy +*/ +void gf_xml_dom_del(GF_DOMParser *parser); + +/*! Parses an XML document or fragment contained in a file +\param parser the DOM parser to use +\param file the file to parse +\param OnProgress an optional callback for the parser +\param cbk an optional user data for the progress callback +\return error code if any +*/ +GF_Err gf_xml_dom_parse(GF_DOMParser *parser, const char *file, gf_xml_sax_progress OnProgress, void *cbk); +/*! Parses an XML document or fragment contained in memory +\param parser the DOM parser to use +\param string the string to parse +\return error code if any +*/ +GF_Err gf_xml_dom_parse_string(GF_DOMParser *parser, char *string); +/*! Gets the last error that happened during the parsing. The parser aborts at the first error found within a SAX callback +\param parser the DOM parser to use +\return last error code if any +*/ +const char *gf_xml_dom_get_error(GF_DOMParser *parser); +/*! Gets the current line of the parser. Used for error logging +\param parser the DOM parser to use +\return last loaded line number +*/ +u32 gf_xml_dom_get_line(GF_DOMParser *parser); + +/*! 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 +*/ +u32 gf_xml_dom_get_root_nodes_count(GF_DOMParser *parser); +/*! Gets the root node at the given index. +\param parser the DOM parser to use +\param idx index of the root node to get (0 being the first node) +\return the root element at the given index, or NULL if error +*/ +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. +\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 +*/ +GF_XMLNode *gf_xml_dom_detach_root(GF_DOMParser *parser); + +/*! Serialize a node to a string +\param node the node to flush +\param content_only Whether to include or not the parent node +\param no_escape if set, disable escape of XML reserved chars (<,>,",') in text nodes +\return The resulting serialization. The string has to be freed with gf_free + */ +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 +\param no_escape if set, disable escape of XML reserved chars (<,>,",') in text nodes +\return The resulting serialization. The string has to be freed with gf_free + */ +char *gf_xml_dom_serialize_root(GF_XMLNode *node, Bool content_only, Bool no_escape); + +/*! Get the root element -- the only top level element -- of the document. +\param parser the DOM structure +\return The corresponding node if exists, otherwise NULL; + */ +GF_XMLNode *gf_xml_dom_get_root(GF_DOMParser *parser); + +/*! +Creates an attribute with the given name and value. +\param name the attribute name +\param value the value +\return The created attribute ; +*/ +GF_XMLAttribute *gf_xml_dom_create_attribute(const char* name, const char* value); + +/*! Adds the node to the end of the list of children of this node. +\param node the GF_XMLNode node +\param child the GF_XMLNode child to append +\return Error code if any, otherwise GF_OK + */ +GF_Err gf_xml_dom_append_child(GF_XMLNode *node, GF_XMLNode *child); + + +/*! Destroys a node, its attributes and its children + +\param node the node to free + */ +void gf_xml_dom_node_del(GF_XMLNode *node); + +/*! Reset a node + +\param node the node to reset +\param reset_attribs if GF_TRUE, reset all node attributes +\param reset_children if GF_TRUE, reset all node children + */ +void gf_xml_dom_node_reset(GF_XMLNode *node, Bool reset_attribs, Bool reset_children); + + +/*! Gets the element and check that the namespace is known ('xmlns'-only supported for now) +\param n the node to process +\param expected_node_name optional expected name for node n +\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); + +/*! Writes a string to an xml file and replaces forbidden chars with xml entities + *\param file the xml output file + *\param before optional string prefix (assumed xml-valid, pass NULL if not needed) + *\param str the string to dump and escape + *\param after optional string suffix (assumed xml-valid, pass NULL if not needed) + */ +void gf_xml_dump_string(FILE* file, const char* before, const char* str, const char* after); + +/*! @} */ + + +/*! +\addtogroup xmlb_grp + +Binarization using XML in GPAC + +GPAC uses a special node name BS (for BitSequence) in XML documents to transform text data into sequences of bits. + This function inspects all child elements of the node and converts children node names BS into bits. BS take the following attributes: + + bits: value gives the number of bits used to code a value or a length + + value: value is a 32 bit signed value + + dataOffset: value gives an offset into a file + + dataLength: value gives the number of bits bytes to copy in a file + + dataFile: value gives the name of the source file + + textmode: indicates whether the file shall be opened in text or binary mode before reading + + text: or string: value gives a string (length is first coded on number of bits in bits attribute) + + fcc: value gives a four character code, coded on 32 bits + + ID128: value gives a 128 bit vlue in hexadecimal + + data64: value gives data coded as base64 + + data: value gives data coded in hexa + +@{ +*/ + +/*!\brief bit-sequence XML parser +\param bsroot the root node of XML document describing the bitstream to create +\param parent_url URL of the parent document +\param out_data pointer to output buffer allocated by the function to store the result +\param out_data_size pointer to output buffer size allocated by the function to store the result +\return error code if any or GF_OK + */ +GF_Err gf_xml_parse_bit_sequence(GF_XMLNode *bsroot, const char *parent_url, u8 **out_data, u32 *out_data_size); + +/*!\brief bit-sequence XML parser + +Parses XML bit sequence in an existing bitstream object. The syntax for the XML is the same as in \ref gf_xml_parse_bit_sequence +\param bsroot the root node of XML document describing the bitstream to create +\param parent_url URL of the parent document +\param bs target bitstream to write the result into. The bitstream must be a dynamic write bitstream object +\return error code or GF_OK + */ +GF_Err gf_xml_parse_bit_sequence_bs(GF_XMLNode *bsroot, const char *parent_url, GF_BitStream *bs); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*GPAC_DISABLE_CORE_TOOLS*/ + +#endif /*_XML_PARSER_H_*/ diff --git a/include/win32/inttypes.h b/include/win32/inttypes.h new file mode 100644 index 0000000..e90a118 --- /dev/null +++ b/include/win32/inttypes.h @@ -0,0 +1,21 @@ +#ifndef _INTTYPES_H_ +#define _INTTYPES_H_ + +#if defined(_WIN32) && !defined(PRId64) +#define PRId64 "I64d" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#endif + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +typedef int64_t ssize_t; + +#endif /*_INTTYPES_H_*/ diff --git a/include/win32/stdint.h b/include/win32/stdint.h new file mode 100644 index 0000000..5202006 --- /dev/null +++ b/include/win32/stdint.h @@ -0,0 +1,17 @@ + +#ifndef _STDINT_H +#define _STDINT_H + + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + + +#endif /* _STDINT_H */ diff --git a/include/wince/errno.h b/include/wince/errno.h new file mode 100644 index 0000000..f021af7 --- /dev/null +++ b/include/wince/errno.h @@ -0,0 +1,104 @@ +#ifdef _WIN32_WCE + +/*** +*errno.h - system wide error numbers (set by system calls) +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* +*Purpose: +* This file defines the system-wide error numbers (set by +* system calls). Conforms to the XENIX standard. Extended +* for compatibility with Uniforum standard. +* [System V] +* +* [Public] +* +****/ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#ifndef _INC_ERRNO +#define _INC_ERRNO + +#include <crtdefs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* declare reference to errno */ + +#ifndef _CRT_ERRNO_DEFINED +#define _CRT_ERRNO_DEFINED +_CRTIMP extern int * __cdecl _errno(void); +#define errno (*_errno()) + +errno_t __cdecl _set_errno(__in int _Value); +errno_t __cdecl _get_errno(__out int * _Value); +#endif + +/* Error Codes */ + +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define EDOM 33 +#define EDEADLK 36 +#define ENAMETOOLONG 38 +#define ENOLCK 39 +#define ENOSYS 40 +#define ENOTEMPTY 41 + +/* Error codes used in the Secure CRT functions */ + +#ifndef RC_INVOKED +#if !defined(_SECURECRT_ERRCODE_VALUES_DEFINED) +#define _SECURECRT_ERRCODE_VALUES_DEFINED +#define EINVAL 22 +#define ERANGE 34 +#define EILSEQ 42 +#define STRUNCATE 80 +#endif +#endif + + +/* + * Support EDEADLOCK for compatibiity with older MS-C versions. + */ +#define EDEADLOCK EDEADLK + +#ifdef __cplusplus +} +#endif + +#endif /* _INC_ERRNO */ + +#endif //_WIN32_WCE \ No newline at end of file diff --git a/manifest.rc b/manifest.rc new file mode 100644 index 0000000..5d68078 --- /dev/null +++ b/manifest.rc @@ -0,0 +1 @@ +1 24 "build/msvc14/gpac_manifest.xml" \ No newline at end of file diff --git a/mkdmg.sh b/mkdmg.sh new file mode 100755 index 0000000..a85b7fd --- /dev/null +++ b/mkdmg.sh @@ -0,0 +1,128 @@ +#!/bin/sh -e + +source_path=. + +function rewrite_deps { +# echo rewriting deps for $1 +for ref in `otool -L $1 | grep '/local' | awk '{print $1}'` +do +# echo changing $ref to @executable_path/lib/`basename $ref` $1 + install_name_tool -change $ref @executable_path/lib/`basename $ref` $1 || { echo "Failed, permissions issue for $1 ? Try with sudo..." ; exit 1 ;} + copy_lib $ref +done + +} + +function copy_lib { +# echo testing $1 for bundle copy +basefile=`basename $1` +if [ ! $basefile == 'libgpac.dylib' ] && [ ! -e lib/$basefile ]; +then +# echo copying $1 to bundle + cp $1 lib/ + chmod +w lib/$basefile + rewrite_deps lib/$basefile +fi +} + +#copy all libs +echo Copying binaries +if [ -d tmpdmg ] +then +rm -fr tmpdmg +fi +mkdir -p tmpdmg/GPAC.app +rsync -r --exclude=.git $source_path/packagers/osx/GPAC.app/ ./tmpdmg/GPAC.app/ +ln -s /Applications ./tmpdmg/Applications +cp $source_path/README.md ./tmpdmg +cp $source_path/COPYING ./tmpdmg + +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/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 +fi +cp bin/gcc/MP4Client tmpdmg/GPAC.app/Contents/MacOS/Osmo4 +cp bin/gcc/MP4Box tmpdmg/GPAC.app/Contents/MacOS/MP4Box +cp bin/gcc/gpac tmpdmg/GPAC.app/Contents/MacOS/gpac + +cd tmpdmg/GPAC.app/Contents/MacOS/ + +#check all external deps, and copy them +echo rewriting DYLIB dependencies +for dylib in lib/*.dylib modules/*.dylib +do + rewrite_deps $dylib +done + +echo rewriting APPS dependencies +install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib Osmo4 +install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib MP4Box +install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib gpac +install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib Osmo4 +install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib MP4Box +install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib gpac + +cd ../../../.. + +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/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/ +cp $source_path/share/default.cfg ./tmpdmg/GPAC.app/Contents/MacOS/share/ + +echo Building DMG +version=`grep '#define GPAC_VERSION ' $source_path/include/gpac/version.h | cut -d '"' -f 2` + +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-//") +BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "UNKNOWN") +rev="$REVISION-$BRANCH" +cd $cur_dir + +full_version=$version +if [ "$rev" != "" ] +then + full_version="$full_version-rev$rev" +else + #if no revision can be extracted, use date + rev = $(date +%Y%m%d) +fi + +sed 's/<string>.*<\/string><!-- VERSION_REV_REPLACE -->/<string>'"$version"'<\/string>/' tmpdmg/GPAC.app/Contents/Info.plist > tmpdmg/GPAC.app/Contents/Info.plist.new && sed 's/<string>.*<\/string><!-- BUILD_REV_REPLACE -->/<string>'"$rev"'<\/string>/' tmpdmg/GPAC.app/Contents/Info.plist.new > tmpdmg/GPAC.app/Contents/Info.plist && rm tmpdmg/GPAC.app/Contents/Info.plist.new + +#GPAC.app now ready, build pkg +echo "Building PKG" +find ./tmpdmg/ -name '*.DS_Store' -type f -delete +chmod -R 755 ./tmpdmg/ +xattr -rc ./tmpdmg/ + +pkgbuild --install-location /Applications --component ./tmpdmg/GPAC.app --scripts ./packagers/osx/scripts --ownership preserve ./tmppkg.pkg +cat ./packagers/osx/res/preamble.txt > ./packagers/osx/res/full_license.txt +cat ./COPYING >> ./packagers/osx/res/full_license.txt + +productbuild --distribution packagers/osx/distribution.xml --resources ./packagers/osx/res --package-path ./tmppkg.pkg gpac.pkg + +rm ./packagers/osx/res/full_license.txt + +pck_name="gpac-$full_version.pkg" +if [ "$1" = "snow-leopard" ]; then +pck_name="gpac-$full_version-$1.pkg" +fi +mv gpac.pkg $pck_name +chmod 755 $pck_name + +echo "$pck_name ready" +rm -rf tmpdmg +rm -rf tmppkg.pkg + + diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..5ea6a4f --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,104 @@ +include ../config.mak + +#all OS and lib independent +PLUGDIRS=validator + +#disabled for now +#netctrl + +ifneq ($(CONFIG_ZLIB),no) +#disabled for now +#PLUGDIRS+=widgetman +ifeq ($(DISABLE_VRML),no) +ifeq ($(DISABLE_LOADER_BT),no) +#disabled for now +#PLUGDIRS+=osd +endif +endif + +endif + + +ifeq ($(CONFIG_FT),no) +else +PLUGDIRS+=ft_font +endif + +ifeq ($(CONFIG_FREENECT),no) +else +#PLUGDIRS+=freenect +endif + +ifeq ($(CONFIG_ALSA),yes) +PLUGDIRS+=alsa +endif + +ifeq ($(CONFIG_JACK),yes) +PLUGDIRS+=jack +endif + +ifeq ($(CONFIG_SDL),yes) +PLUGDIRS+=sdl_out +endif +ifeq ($(CONFIG_PULSEAUDIO),yes) +PLUGDIRS+=pulseaudio +endif + +ifeq ($(CONFIG_X11),yes) +PLUGDIRS+=x11_out +endif + +ifeq ($(CONFIG_DIRECTFB),yes) +PLUGDIRS+=directfb_out +endif + +ifeq ($(CONFIG_HID),yes) +#PLUGDIRS+=psvr +endif + +ifeq ($(CONFIG_OPENHEVC),yes) +PLUGDIRS+=dec_openhevc +endif + +#w32 plugins +ifeq ($(CONFIG_WIN32),yes) +PLUGDIRS+=wav_out +ifeq ($(CONFIG_DIRECTX),yes) +PLUGDIRS+=dx_hw +endif +endif + +#other linux +ifeq ($(CONFIG_LINUX),yes) +endif + + +#if static modules are used, disable all previous modules (built-in in libgpac) +ifeq ($(STATIC_MODULES),yes) +PLUGDIRS= +endif + +#all modules below are not (yet) included in static build + +#ifeq ($(CONFIG_PLATINUM),yes) +#PLUGDIRS+=platinum +#endif + +ifeq ($(DISABLE_PLAYER),yes) +PLUGDIRS= +endif + + +all: plugs + +plugs: + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i all; done + +dep: + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i dep; done + +clean: + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i clean; done + +distclean: + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i distclean; done diff --git a/modules/alsa/Makefile b/modules/alsa/Makefile new file mode 100644 index 0000000..e91aace --- /dev/null +++ b/modules/alsa/Makefile @@ -0,0 +1,44 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/alsa + +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 + +#common obj +OBJS= alsa.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_alsa$(DYN_LIB_SUFFIX) + + +all: $(LIB) + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) $(LDFLAGS) -lasound -L../../bin/gcc -lgpac + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/alsa/alsa.c b/modules/alsa/alsa.c new file mode 100644 index 0000000..9f8a1eb --- /dev/null +++ b/modules/alsa/alsa.c @@ -0,0 +1,370 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / alsa audio output module + * + * 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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <poll.h> +#include <alsa/asoundlib.h> +#include <gpac/modules/audio_out.h> + + +typedef struct +{ + snd_pcm_t *playback_handle; + u32 nb_ch, buf_size, delay, num_buffers, total_duration, block_align; + u32 force_sr; + const char *dev_name; + char *wav_buf; +} ALSAContext; + + +static GF_Err ALSA_Setup(GF_AudioOutput*dr, void *os_handle, u32 num_buffers, u32 total_duration) +{ + int err; + ALSAContext *ctx = (ALSAContext*)dr->opaque; + + + ctx->force_sr = gf_opts_get_int("core", "force-alsarate"); + ctx->dev_name = gf_opts_get_key("core", "alsa-devname"); + if (!ctx->dev_name) { + ctx->dev_name = "hw:0,0"; + gf_opts_set_key("core", "alsa-devname", ctx->dev_name); + } + + /*test device*/ + err = snd_pcm_open(&ctx->playback_handle, ctx->dev_name, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot open audio device %s: %s\n", ctx->dev_name, snd_strerror (err)) ); + return GF_IO_ERR; + } + ctx->num_buffers = num_buffers ? num_buffers : 2; + ctx->total_duration = total_duration ? total_duration : 100; + return GF_OK; +} + +static void ALSA_Shutdown(GF_AudioOutput*dr) +{ + ALSAContext *ctx = (ALSAContext*)dr->opaque; + if (ctx->playback_handle) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] Closing alsa output\n") ); + snd_pcm_close(ctx->playback_handle); + ctx->playback_handle = NULL; + } + if (ctx->wav_buf) gf_free(ctx->wav_buf); + ctx->wav_buf = NULL; +} + +static GF_Err ALSA_Configure(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) +{ + snd_pcm_hw_params_t *hw_params = NULL; + int err; + int nb_bufs, sr, period_time; + ALSAContext *ctx = (ALSAContext*)dr->opaque; + + if (!ctx) return GF_BAD_PARAM; + + /*close device*/ + if (ctx->playback_handle) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] Closing audio device %s\n", ctx->dev_name )); + snd_pcm_close(ctx->playback_handle); + ctx->playback_handle = NULL; + } + if (ctx->wav_buf) gf_free(ctx->wav_buf); + ctx->wav_buf = NULL; + + err = snd_pcm_open(&ctx->playback_handle, ctx->dev_name, SND_PCM_STREAM_PLAYBACK, 0/*SND_PCM_NONBLOCK*/); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot open audio device %s: %s\n", ctx->dev_name, snd_strerror (err)) ); + return GF_IO_ERR; + } + + err = snd_pcm_hw_params_malloc(&hw_params); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot allocate hardware params: %s\n", snd_strerror (err)) ); + goto err_exit; + } + err = snd_pcm_hw_params_any(ctx->playback_handle, hw_params); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot initialize hardware params: %s\n", snd_strerror (err)) ); + goto err_exit; + } + err = snd_pcm_hw_params_set_access(ctx->playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set access type: %s\n", snd_strerror (err)) ); + goto err_exit; + } + /*set output format*/ + ctx->nb_ch = (int) (*NbChannels); + ctx->block_align = ctx->nb_ch; + + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_U8); + break; + default: + *audioFormat = GF_AUDIO_FMT_S16; + case GF_AUDIO_FMT_S16: + ctx->block_align *= 2; + err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_S16_LE); + break; + } + + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set sample format: %s\n", snd_strerror (err)) ); + goto err_exit; + } + + /*set output sample rate*/ + if (ctx->force_sr) *SampleRate = ctx->force_sr; + sr = *SampleRate; + err = snd_pcm_hw_params_set_rate_near(ctx->playback_handle, hw_params, SampleRate, 0); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set sample rate: %s\n", snd_strerror (err)) ); + goto err_exit; + } + if (sr != *SampleRate) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[ALSA] Sample rate %d not supported, using %d instead\n", sr, *SampleRate ) ); + sr = *SampleRate; + } + /*set output channels*/ + err = snd_pcm_hw_params_set_channels_near(ctx->playback_handle, hw_params, NbChannels); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set channel count: %s\n", snd_strerror (err)) ); + goto err_exit; + } + if (ctx->nb_ch != *NbChannels) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[ALSA] %d channels not supported - using %d instead\n", ctx->nb_ch, *NbChannels ) ); + ctx->block_align /= ctx->nb_ch; + ctx->nb_ch = *NbChannels; + ctx->block_align *= ctx->nb_ch; + } + /* Set number of buffers*/ + nb_bufs = ctx->num_buffers; + err = snd_pcm_hw_params_set_periods_near(ctx->playback_handle, hw_params, &nb_bufs, 0); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set number of HW buffers (%d): %s\n", nb_bufs, snd_strerror(err) )); + goto err_exit; + } + /* Set total buffer size*/ + ctx->buf_size = (sr * ctx->total_duration)/1000 / nb_bufs; + err = snd_pcm_hw_params_set_period_size_near(ctx->playback_handle, hw_params, (snd_pcm_uframes_t *)&ctx->buf_size, 0); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set HW buffer size (%d): %s\n", ctx->buf_size, snd_strerror(err) )); + goto err_exit; + } + + err = snd_pcm_hw_params_get_buffer_size(hw_params, (snd_pcm_uframes_t *)&ctx->buf_size); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot get HW buffer size (%d): %s\n", ctx->buf_size, snd_strerror(err) )); + goto err_exit; + } + ctx->buf_size *= ctx->block_align; + /*get period time*/ + snd_pcm_hw_params_get_period_time(hw_params, &period_time, 0); + + err = snd_pcm_hw_params(ctx->playback_handle, hw_params); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set parameters: %s\n", snd_strerror (err)) ); + goto err_exit; + } + snd_pcm_hw_params_free (hw_params); + hw_params = NULL; + + ctx->delay = (ctx->buf_size*1000) / (sr*ctx->block_align); + + /*allocate a single buffer*/ + ctx->wav_buf = gf_malloc(ctx->buf_size*sizeof(char)); + if(!ctx->wav_buf) return GF_OUT_OF_MEM; + memset(ctx->wav_buf, 0, ctx->buf_size*sizeof(char)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] Setup %d ch @ %d hz - %d periods of %d us - total buffer size %d - overall delay %d ms\n", ctx->nb_ch, sr, nb_bufs, period_time, ctx->buf_size, ctx->delay)); + + return GF_OK; + +err_exit: + if (hw_params) snd_pcm_hw_params_free(hw_params); + snd_pcm_close(ctx->playback_handle); + ctx->playback_handle = NULL; + return GF_IO_ERR; +} + +static void ALSA_WriteAudio(GF_AudioOutput*dr) +{ + u32 written; + snd_pcm_sframes_t nb_frames; + int err; + ALSAContext *ctx = (ALSAContext*)dr->opaque; + + /*wait ctx delay for device interrupt*/ + err = snd_pcm_wait(ctx->playback_handle, 1); + if (err<0) { + if (err == -EPIPE) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[ALSA] Broken connection to sound card - restoring!\n")); + snd_pcm_prepare(ctx->playback_handle); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] error %s while waiting!\n", snd_strerror(err) )); + return; + } + } + + nb_frames = snd_pcm_avail_update(ctx->playback_handle); + if (nb_frames < 0) { + if (nb_frames == -EPIPE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] an xrun occured!\n")); + snd_pcm_prepare(ctx->playback_handle); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] unknown ALSA avail update return value (%d)\n", nb_frames)); + } + return; + } + if (!nb_frames) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[ALSA] no frame to write\n" )); + return; + } + + //assert(nb_frames*ctx->block_align<=ctx->buf_size); + written = dr->FillBuffer(dr->audio_renderer, ctx->wav_buf, (u32) (ctx->block_align*nb_frames) ); + if (!written) return; + written /= ctx->block_align; + + err = snd_pcm_writei(ctx->playback_handle, ctx->wav_buf, written); + if (err == -EPIPE ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] an xrun occured!\n")); + snd_pcm_prepare(ctx->playback_handle); + err = snd_pcm_writei(ctx->playback_handle, ctx->wav_buf, nb_frames); + } + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Write failure: %s\n", snd_strerror(err))); + } +} + +static u32 ALSA_GetAudioDelay(GF_AudioOutput*dr) +{ + ALSAContext *ctx = (ALSAContext*)dr->opaque; + return ctx->delay; +} + +static GF_Err ALSA_QueryOutputSampleRate(GF_AudioOutput*dr, u32 *desired_sr, u32 *NbChannels, u32 *nbBitsPerSample) +{ + ALSAContext *ctx = (ALSAContext*)dr->opaque; + int err; + snd_pcm_hw_params_t *hw_params = NULL; + + err = snd_pcm_hw_params_malloc(&hw_params); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot allocate hardware params: %s\n", snd_strerror (err)) ); + goto err_exit; + } + err = snd_pcm_hw_params_any(ctx->playback_handle, hw_params); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot initialize hardware params: %s\n", snd_strerror (err)) ); + goto err_exit; + } + + err = snd_pcm_hw_params_set_rate_near(ctx->playback_handle, hw_params, desired_sr, 0); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot check available sample rates: %s\n", snd_strerror (err)) ); + goto err_exit; + } + + err = snd_pcm_hw_params_set_channels_near(ctx->playback_handle, hw_params, NbChannels); + if (err < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot check available channels: %s\n", snd_strerror (err)) ); + goto err_exit; + } + snd_pcm_hw_params_free (hw_params); + hw_params = NULL; + return GF_OK; +err_exit: + snd_pcm_hw_params_free (hw_params); + hw_params = NULL; + return GF_IO_ERR; +} + +void *NewALSAOutput() +{ + ALSAContext *ctx; + GF_AudioOutput*driv; + GF_SAFEALLOC(ctx, ALSAContext); + if(!ctx) return NULL; + + GF_SAFEALLOC(driv, GF_AudioOutput); + if(!driv) { + gf_free(ctx); + return NULL; + } + driv->opaque = ctx; + driv->SelfThreaded = 0; + driv->Setup = ALSA_Setup; + driv->Shutdown = ALSA_Shutdown; + driv->Configure = ALSA_Configure; + driv->GetAudioDelay = ALSA_GetAudioDelay; + driv->QueryOutputSampleRate = ALSA_QueryOutputSampleRate; + driv->WriteAudio = ALSA_WriteAudio; + GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "ALSA Audio Output", "gpac distribution"); + return driv; +} + +void DeleteALSAOutput(void *ifce) +{ + GF_AudioOutput*dr = (GF_AudioOutput*) ifce; + ALSAContext *ctx = (ALSAContext *)dr->opaque; + gf_free(ctx); + gf_free(dr); +} + + +/* + * ******************************************************************** + * interface + */ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) + return NewALSAOutput(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + if (ifce->InterfaceType==GF_AUDIO_OUTPUT_INTERFACE) + DeleteALSAOutput((GF_AudioOutput*)ifce); +} + +GPAC_MODULE_STATIC_DECLARATION( alsa ) diff --git a/modules/dec_openhevc/Makefile b/modules/dec_openhevc/Makefile new file mode 100644 index 0000000..03b732b --- /dev/null +++ b/modules/dec_openhevc/Makefile @@ -0,0 +1,59 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/dec_openhevc + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(OHEVC_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 $(OHEVC_LDFLAGS) +else +LINKFLAGS+=-L../../bin/gcc $(OHEVC_LDFLAGS) -Wl,-Bsymbolic +endif + +#common obj +OBJS=dec_openhevc.o +SRCS=$(SRC_PATH)/src/filters/dec_openhevc.c + + +ifeq ($(CONFIG_WIN32),yes) +else +EXTRALIBS+=-ldl +endif + +LINKFLAGS+=-lgpac + +LIB=gf_openhevc_dec$(DYN_LIB_SUFFIX) + +all: $(LIB) + +dec_openhevc.o : $(SRC_PATH)/src/filters/dec_openhevc.c + @echo " CC $<" + $(CC) $(CFLAGS) -c -o $@ $< + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/dektec_out/Makefile b/modules/dektec_out/Makefile new file mode 100644 index 0000000..0dcfe02 --- /dev/null +++ b/modules/dektec_out/Makefile @@ -0,0 +1,58 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/dektec_out + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(ENABLE_JOYSTICK),yes) +CFLAGS+=-DENABLE_JOYSTICK +endif + +ifeq ($(ENABLE_JOYSTICK_NO_CURSOR),yes) +CFLAGS+=-DENABLE_JOYSTICK_NO_CURSOR +endif + +#common obj +OBJS= dektec_video.o + +SRCS := $(OBJS:.o=.cpp) + +LIB=gm_dektec_out$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +#LDFLAGS+=-export-symbols dektec_out.def +endif + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CXX) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) +ifeq ($(STATICBUILD),yes) + $(CXX) $(SHFLAGS) -o ../../bin/gcc/gm_dektec_out-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) +endif + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/dektec_out/dektec_video.cpp b/modules/dektec_out/dektec_video.cpp new file mode 100644 index 0000000..f6f5367 --- /dev/null +++ b/modules/dektec_out/dektec_video.cpp @@ -0,0 +1,803 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Romain Bouqueau, Jean Le Feuvre +* Copyright (c) 2014-2016 GPAC Licensing +* Copyright (c) 2016-2020 Telecom Paris +* All rights reserved +* +* This file is part of GPAC / Dektec SDI video output 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 "dektec_video.h" + +#ifdef GPAC_HAS_DTAPI + +#ifndef FAKE_DT_API + +static void OnNewFrameAudio(DtMxData *pData, const DtCbkCtx *ctx) +{ + // Must have a valid frame + DtMxRowData &OurRow = pData->m_Rows[0]; + if (OurRow.m_CurFrame->m_Status != DT_FRMSTATUS_OK) { + return; // Frame is not valid so we have to assume the frame buffers are unusable + } + + // Get frame audio data + DtMxAudioData& AudioData = OurRow.m_CurFrame->m_Audio; + + // Init channel status word for all (valid) audio channels + AudioData.InitChannelStatus(); +} + +static void OnNewFrameVideo(DtMxData *pData, const DtCbkCtx *cbck) +{ + u64 now = gf_sys_clock_high_res(); + const DtMxRowData& OurRow = pData->m_Rows[0]; + GF_DTOutCtx *ctx = cbck->ctx; + GF_FilterPacket *pck; + + if (!ctx->is_configured || ctx->is_eos) return; + if (pData->m_NumSkippedFrames > 0) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] [%lld] #skipped frames=%d\n", pData->m_Frame, pData->m_NumSkippedFrames)); + } + + if (!ctx->init_clock) { + ctx->init_clock = ctx->last_frame_time = now; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] output frame %d at %d ms - %d since last\n", ctx->frameNum, (u32)(now - ctx->init_clock) / 1000, now - ctx->last_frame_time)); + + ctx->last_frame_time = now; + + if (!cbck->video) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] no connected pid !\n")); + return; + } + + // Must have a valid frame + if (OurRow.m_CurFrame->m_Status != DT_FRMSTATUS_OK) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Not valid frame 4k\n")); + return; // Frame is not valid so we have to assume the frame buffers are unusable + } + if (pData->m_NumSkippedFrames > 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] [%lld] #skipped frames=%d\n", pData->m_Frame, pData->m_NumSkippedFrames)); + } + + pck = gf_filter_pid_get_packet(cbck->video); + if (!pck) { + if (gf_filter_pid_is_eos(cbck->video)) { + ctx->is_eos = GF_TRUE; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] no packet ready !\n")); + } + return; + } + ctx->is_eos = GF_FALSE; + + u8 *pY, *pU, *pV; + u32 pck_size = 0; + u32 stride = ctx->stride, stride_uv= ctx->stride_uv; + pU = pV = NULL; + pY = (u8 *) gf_filter_pck_get_data(pck, &pck_size); + if (!pY) { + GF_FilterFrameInterface *hwframe = gf_filter_pck_get_frame_interface(pck); + if (hwframe) { + hwframe->get_plane(hwframe, 0, (const u8 **) &pY, &stride); + if (ctx->nb_planes>1) + hwframe->get_plane(hwframe, 1, (const u8 **) &pU, &stride_uv); + if (ctx->nb_planes>2) + hwframe->get_plane(hwframe, 2, (const u8 **) &pV, &stride_uv); + } + } + if (!pY) { + gf_filter_pid_drop_packet(cbck->video); + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] No data associated with packet\n")); + return; + } + if (!pU && !pV) { + if (ctx->nb_planes>1) + pU = pY + stride * ctx->height; + if (ctx->nb_planes>2) + pV = pU + stride_uv * ctx->uv_height; + } + + //TODO - move all this into color convertion code in libgpac ! + + const DtMxVideoBuf& VideoBuf = OurRow.m_CurFrame->m_Video[0]; + u8* pDstY = VideoBuf.m_Planes[0].m_pBuf; + u8* pDstU = VideoBuf.m_Planes[1].m_pBuf; + u8* pDstV = VideoBuf.m_Planes[2].m_pBuf; + + if (ctx->is_10b) { + u32 nb_pix = 2 * ctx->width*ctx->height; + int lineC = 2 * ctx->width / 2; + unsigned char *pSrcY = pY; + unsigned char *pSrcU = pU; + unsigned char *pSrcV = pV; + + if (ctx->clip) { + const u16 nYRangeMin = 4; + const u16 nYRangeMax = 1019; + const __m128i mMin = _mm_set1_epi16(nYRangeMin); + const __m128i mMax = _mm_set1_epi16(nYRangeMax); + + for (u32 i = 0; i < nb_pix; i += 16) { + _mm_storeu_si128((__m128i *)&pDstY[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcY[i])))); + } + + for (u32 h = 0; h < ctx->height / 2; h++) { + for (u32 i = 0; i < ctx->width; i += 16) { + _mm_storeu_si128((__m128i *)&pDstU[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcU[i])))); + } + //420->422: copy over each U and V line + memcpy(pDstU + lineC, pDstU, lineC); + pSrcU += lineC; + pDstU += 2 * lineC; + } + + //NV12 formats + if (!pSrcV) pSrcV = pU + 1; + + for (u32 h = 0; h < ctx->height / 2; h++) { + for (u32 i = 0; i < ctx->width; i += 16) { + _mm_storeu_si128((__m128i *)&pDstV[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcV[i])))); + } + memcpy(pDstV + lineC, pDstV, lineC); + pSrcV += lineC; + pDstV += 2 * lineC; + } + } + else { + memcpy(pDstY, pY, stride * ctx->height); + + //420->422: copy over each U and V line + for (u32 h = 0; h < ctx->height / 2; h++) { + memcpy(pDstU, pSrcU, lineC); + memcpy(pDstU + lineC, pSrcU, lineC); + pSrcU += lineC; + pDstU += 2 * lineC; + } + for (u32 h = 0; h < ctx->height / 2; h++) { + memcpy(pDstV, pSrcV, lineC); + memcpy(pDstV + lineC, pSrcV, lineC); + pSrcV += lineC; + pDstV += 2 * lineC; + } + } + } + else { + const int lineC = ctx->width / 2; + u32 nb_pix = ctx->width*ctx->height; + unsigned char *pSrcY = pY; + unsigned char *pSrcU = pU; + unsigned char *pSrcV = pV; + + const u16 nYRangeMin = 4; + const u16 nYRangeMax = 1019; + + for (u32 i = 0; i < nb_pix; i++) { + u16 srcy = ((u16)(*pSrcY)) << 2; + if (ctx->clip) { + if (srcy < nYRangeMin) srcy = nYRangeMin; + else if (srcy > nYRangeMax) srcy = nYRangeMax; + } + *(short *)pDstY = srcy; + + pSrcY++; + pDstY += sizeof(short); //char type but short buffer + } + + u32 hw = ctx->width / 2; + u32 hh = ctx->height / 2; + + for (u32 i = 0; i < hh; i++) { + //420->422: copy over each U and V line + u8 *p_dst_u = pDstU + sizeof(short) * lineC * i * 2; + u8 *p_dst_v = pDstV + sizeof(short) * lineC * i * 2; + u8 *p_src_u = NULL; + u8 *p_src_v = NULL; + u8 *op_dst_u = p_dst_u; + u8 *op_dst_v = p_dst_v; + + //NV12 formats + if (!pV) { + p_src_u = pSrcU + 2 * ctx->stride_uv * i; + p_src_v = p_src_u + 1; + } + else { + p_src_u = pSrcU + ctx->stride_uv * i; + p_src_v = pSrcV + ctx->stride_uv * i; + } + + for (u32 j = 0; j < hw; j++) { + u16 srcu = ((u16)(*p_src_u)) << 2; + u16 srcv = ((u16)(*p_src_v)) << 2; + + if (ctx->clip) { + if (srcu < nYRangeMin) srcu = nYRangeMin; + else if (srcu > nYRangeMax) srcu = nYRangeMax; + + if (srcv < nYRangeMin) srcv = nYRangeMin; + else if (srcv > nYRangeMax) srcv = nYRangeMax; + } + + *(short *)p_dst_u = srcu; + p_src_u += 2; + p_dst_u += sizeof(short); //char type but short buffer + + *(short *)p_dst_v = srcv; + p_src_v += 2; + p_dst_v += sizeof(short); //char type but short buffer + } + memcpy(op_dst_u + lineC * sizeof(short), op_dst_u, lineC * sizeof(short)); + memcpy(op_dst_v + lineC * sizeof(short), op_dst_v, lineC * sizeof(short)); + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] wrote YUV data in "LLU" us\n", gf_sys_clock_high_res() - now)); + gf_filter_pid_drop_packet(cbck->video); + ctx->frameNum++; +} + +static void OnNewFrame(DtMxData *pData, void *pOpaque) { + const DtCbkCtx *ctx = ((const DtCbkCtx*)pOpaque); + if (ctx->video) + OnNewFrameVideo(pData, ctx); + else if (ctx->audio) + OnNewFrameAudio(pData, ctx); +} + +static void dtout_disconnect(GF_DTOutCtx *ctx) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] Disconnecting\n")); + DtMxProcess *Matrix = ctx->dt_matrix; + ctx->is_configured = GF_FALSE; + + DTAPI_RESULT res = Matrix->Stop(); + if (res != DTAPI_OK) + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't stop transmission: %s\n", DtapiResult2Str(res))); + + Matrix->Reset(); + ctx->dt_device->Detach(); + ctx->is_sending = GF_FALSE; +} + + +static GF_Err dtout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + DTAPI_RESULT res; + int port = 1; + Bool send_play = GF_FALSE; + Bool is4K = GF_FALSE; + Bool is_audio = GF_FALSE; + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + const GF_PropertyValue *p; + DtDevice *dt_device = ctx->dt_device; + + if (is_remove) { + if (ctx->audio_cbk.audio == pid) { + ctx->audio_cbk.audio = NULL; + } + else if (ctx->video_cbk.video == pid) { + ctx->video_cbk.video = NULL; + } + if (ctx->is_sending && !ctx->audio_cbk.audio && !ctx->video_cbk.video) { + dtout_disconnect(ctx); + } + return GF_OK; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!p) return GF_NOT_SUPPORTED; + if (p->value.uint == GF_STREAM_VISUAL) { + if (!ctx->video_cbk.video) { + ctx->video_cbk.video = pid; + send_play = GF_TRUE; + } + else if (ctx->video_cbk.video != pid) return GF_REQUIRES_NEW_INSTANCE; + } + else if (p->value.uint == GF_STREAM_AUDIO) { + if (!ctx->audio_cbk.audio) { + ctx->audio_cbk.audio = pid; + send_play = GF_TRUE; + } + else if (ctx->audio_cbk.audio != pid) return GF_REQUIRES_NEW_INSTANCE; + is_audio=GF_TRUE; + } + else { + return GF_NOT_SUPPORTED; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p || (p->value.uint != GF_CODECID_RAW)) return GF_NOT_SUPPORTED; + + + if (send_play) { + GF_FilterEvent fevt; + //set a minimum buffer (although we don't buffer) + GF_FEVT_INIT(fevt, GF_FEVT_BUFFER_REQ, pid); + fevt.buffer_req.max_buffer_us = 200000; + gf_filter_pid_send_event(pid, &fevt); + + gf_filter_pid_init_play_event(pid, &fevt, ctx->start, 1.0, "DekTecOut"); + gf_filter_pid_send_event(pid, &fevt); + } + + if (is_audio) { + //todo + return GF_NOT_SUPPORTED; + } + + u32 width = 0; + u32 height = 0; + u32 pix_fmt = 0; + u32 stride = 0; + u32 stride_uv = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) height = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) pix_fmt = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV); + if (p) stride_uv = p->value.uint; + + //not configured yet + if (!width || !height || !pix_fmt) return GF_OK; + + Bool is_10_bits = GF_FALSE; + switch (pix_fmt) { + case GF_PIXEL_YUV: + break; + case GF_PIXEL_YUV_10: + is_10_bits = GF_TRUE; + break; + case GF_PIXEL_YUV422: + break; + case GF_PIXEL_YUV422_10: + is_10_bits = GF_TRUE; + break; + case GF_PIXEL_YUV444: + break; + case GF_PIXEL_YUV444_10: + is_10_bits = GF_TRUE; + break; + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + break; + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + is_10_bits = GF_TRUE; + break; + default: + GF_PropertyValue val; + val.type = GF_PROP_UINT; + val.value.uint = GF_PIXEL_YUV; + gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &val); + return GF_OK; + } + if (!stride) stride = width * (is_10_bits ? 2 : 1); + if (!stride_uv) stride_uv = stride / 2; + + GF_Fraction fps = { 30, 1 }; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) fps = p->value.frac; + + if ((ctx->width == width) && (ctx->height == height) && (ctx->pix_fmt == pix_fmt) && (ctx->framerate.num * fps.den == ctx->framerate.den * fps.num) && (ctx->stride==stride) && ctx->is_configured) + return GF_OK; + + ctx->width = width; + ctx->height = height; + ctx->pix_fmt = pix_fmt; + ctx->framerate = fps; + ctx->is_10b = is_10_bits; + ctx->stride = stride; + ctx->stride_uv = stride_uv; + + gf_pixel_get_size_info((GF_PixelFormat) pix_fmt, width, height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height); + + int dFormat = -1, linkStd = -1; + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Reconfigure to %dx%d @ %g FPS %d/%d bits src - SDI clipping: %s\n", width, height, fps.num, fps.den, is_10_bits ? "10" : "8", ctx->clip ? "yes" : "no")); + if (ctx->is_configured) { + DtMxProcess *Matrix = ctx->dt_matrix; + if (ctx->is_sending) { + res = Matrix->Stop(); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't stop transmission: %s\n", DtapiResult2Str(res))); + } + } + Matrix->Reset(); + ctx->is_configured = GF_FALSE; + ctx->is_sending = GF_FALSE; + } + + if (fps.num == 30 * fps.den) { + ctx->frame_scale = 30; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P30; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P30; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P30; + } + } + else if (fps.num == 50 * fps.den) { + ctx->frame_scale = 50; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P50; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P50; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P50; + } + } + else if (fps.num == 60 * fps.den) { + ctx->frame_scale = 60; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P60; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P60; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P60; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] Dealing with 60 fps video is not stable, use with caution.\n")); + } + else if (fps.num*100 == 5994 * fps.den) { + ctx->frame_scale = 3000; + ctx->frame_dur = 1001; + + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P59_94; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P59_94; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P59_94; + } + } + else if (fps.num == 24 * fps.den) { + ctx->frame_scale = 24; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P24; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P24; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P24; + } + } + else if (fps.num == 25 * fps.den) { + ctx->frame_scale = 25; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P25; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P25; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P25; + } + } + + if (dFormat == -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unsupported frame rate %d/%d / resolution %dx%d\n", fps.num, fps.den, ctx->width, ctx->height)); + return GF_FILTER_NOT_SUPPORTED; + } + + if ((ctx->width == 3840) && (ctx->height == 2160)) { + is4K = GF_TRUE; + linkStd = DTAPI_VIDLNK_4K_SMPTE425B; + } + + port = ctx->port ? ctx->port : 1; + + DtMxPort mxPort; + if (!is4K) + { + res = dt_device->SetIoConfig(port, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + int IoStdValue = -1, IoStdSubValue = -1; + res = DtapiVidStd2IoStd(dFormat, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unknown VidStd: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dt_device->SetIoConfig(port, DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + res = mxPort.AddPhysicalPort(dt_device, port); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to add port %d to logical port", port)); + return GF_BAD_PARAM; + } + } + else + { + DtMxPort mxPort4k(dFormat, linkStd); + for (int i = 0; i < 4; i++) + { + res = dt_device->SetIoConfig(i + 1, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + } + + int IoStdValue = -1, IoStdSubValue = -1; + res = DtapiVidStd2IoStd(dFormat, linkStd, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unknown VidStd: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + for (int i = 0; i < 4; i++) + { + res = dt_device->SetIoConfig(i + 1, DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + } + for (int i = 0; i < 4; i++) + { + res = mxPort4k.AddPhysicalPort(dt_device, i + 1); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to add port %d to logical 4k port", (i + 1))); + return GF_BAD_PARAM; + } + } + + DtIoConfig Cfg; + std::vector<DtIoConfig> IoConfigs; + DtHwFuncDesc* pHwf = dt_device->m_pHwf; + for (int i = 0; i < dt_device->m_pHwf->m_DvcDesc.m_NumPorts; i++, pHwf++) + { + // Check if the port has GENNREF capability + if ((pHwf->m_Flags & DTAPI_CAP_GENREF) == 0) + continue; // No GENREF cap + + Cfg.m_Port = pHwf->m_Port; + Cfg.m_Group = DTAPI_IOCONFIG_GENREF; + + // Check if this is the internal GENREF (has DTAPI_CAP_VIRTUAL)? + if ((pHwf->m_Flags & DTAPI_CAP_VIRTUAL) != 0) + Cfg.m_Value = DTAPI_IOCONFIG_TRUE; // The internal => enable as GENREF + else + Cfg.m_Value = DTAPI_IOCONFIG_FALSE; // Not the internal => disable as GENREF + Cfg.m_SubValue = -1; + Cfg.m_ParXtra[0] = Cfg.m_ParXtra[1] = -1; + // Add to list + IoConfigs.push_back(Cfg); + + // Set the IO-standard for 'THE' GENREF port + if (Cfg.m_Value != DTAPI_IOCONFIG_TRUE) + continue; + Cfg.m_Group = DTAPI_IOCONFIG_IOSTD; + // Set a standard with matching frame rate + DtVidStdInfo VidInfo; + res = ::DtapiGetVidStdInfo(dFormat, linkStd, VidInfo); + Cfg.m_Value = DTAPI_IOCONFIG_HDSDI; + switch ((int)VidInfo.m_Fps) + { + case 23: Cfg.m_SubValue = DTAPI_IOCONFIG_720P23_98; break; + case 24: Cfg.m_SubValue = DTAPI_IOCONFIG_720P24; break; + case 25: Cfg.m_SubValue = DTAPI_IOCONFIG_720P25; break; + case 29: Cfg.m_SubValue = DTAPI_IOCONFIG_720P29_97; break; + case 30: Cfg.m_SubValue = DTAPI_IOCONFIG_720P30; break; + case 50: Cfg.m_SubValue = DTAPI_IOCONFIG_720P50; break; + case 59: Cfg.m_SubValue = DTAPI_IOCONFIG_720P59_94; break; + case 60: Cfg.m_SubValue = DTAPI_IOCONFIG_720P60; break; + } + Cfg.m_ParXtra[0] = Cfg.m_ParXtra[1] = -1; + // Add to list + IoConfigs.push_back(Cfg); + } + res = dt_device->SetIoConfig(IoConfigs.data(), (int)IoConfigs.size()); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to apply genref\n")); + return GF_BAD_PARAM; + } + mxPort = mxPort4k; + } + + res = ctx->dt_matrix->AttachRowToOutput(0, mxPort); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't attach to port %d: %s\n", port, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + /* --- Set row configuration --- */ + DtMxRowConfig RowConfig; + RowConfig.m_Enable = true; + RowConfig.m_RowSize = 1; // Keep a history of one frame + + // Enable the video + RowConfig.m_VideoEnable = true; + //todo, make this configurable ! + RowConfig.m_Video.m_PixelFormat = DT_PXFMT_YUV422P_16B; // Use a planar format (16-bit) + RowConfig.m_Video.m_StartLine1 = RowConfig.m_Video.m_StartLine2 = 1; + RowConfig.m_Video.m_NumLines1 = RowConfig.m_Video.m_NumLines2 = -1; + RowConfig.m_Video.m_Scaling = DTAPI_SCALING_OFF; // No scaling for this row + RowConfig.m_Video.m_LineAlignment = 8; // Want an 8-byte alignment + + // Enable audio + RowConfig.m_AudioEnable = true; + // We will add four channels (2x stereo) + for (int i = 0; i < 4; i++) + { + DtMxAudioConfig AudioConfig; + AudioConfig.m_Index = i; + AudioConfig.m_DeEmbed = false; // Nothing to de-embed for an output only row + AudioConfig.m_OutputMode = DT_OUTPUT_MODE_ADD; // We want to add audio samples + AudioConfig.m_Format = DT_AUDIO_SAMPLE_PCM; // We will add PCM samples + + // Add to config list + RowConfig.m_Audio.push_back(AudioConfig); + } + + // Apply the row configuration + res = ctx->dt_matrix->SetRowConfig(0, RowConfig); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to set row 0 config")); + return GF_BAD_PARAM; + } + + /* --- Final Configuration --- */ + // Determine the default recommended end-to-end delay for this configuration + + int DefEnd2EndDelay = 0; + double CbFrames = 0.0; // Time available for the call-back method + ctx->dt_matrix->GetDefEndToEndDelay(DefEnd2EndDelay, CbFrames); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Default end-to-end delay = %d Frames\n", DefEnd2EndDelay)); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Time-for-Callback = %.1f Frame Period\n", CbFrames)); + // Apply the default end-to-end delay + res = ctx->dt_matrix->SetEndToEndDelay(DefEnd2EndDelay); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to set the end-to-end delay (delay=%d)", DefEnd2EndDelay)); + return GF_BAD_PARAM; + } + + res = ctx->dt_matrix->AddMatrixCbFunc(OnNewFrame, &ctx->audio_cbk); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed register audio callback function")); + return GF_BAD_PARAM; + } + res = ctx->dt_matrix->AddMatrixCbFunc(OnNewFrame, &ctx->video_cbk); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed register video callback function")); + return GF_BAD_PARAM; + } + + ctx->frameNum = 1; + ctx->is_configured = GF_TRUE; + // Start transmission right now - this function is called during the blit(), and the object clock is initialized + //after the blit, starting now will reduce the number of skipped frames at the dektec startup + res = ctx->dt_matrix->Start(); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] Can't start transmission: %s.\n", DtapiResult2Str(res))); + } + else { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Transmission started.\n")); + ctx->is_sending = GF_TRUE; + } + return GF_OK; +} + +static GF_Err dtout_initialize(GF_Filter *filter) +{ + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + DTAPI_RESULT res; + + ctx->dt_device = new DtDevice; + ctx->dt_matrix = new DtMxProcess; + if (!ctx->dt_matrix || !ctx->dt_device) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] DTA API couldn't be initialized.\n")); + if (ctx->dt_device) delete ctx->dt_device; + if (ctx->dt_matrix) delete ctx->dt_matrix; + ctx->dt_device = NULL; + ctx->dt_matrix = NULL; + return GF_IO_ERR; + } + + ctx->audio_cbk.ctx = ctx; + ctx->video_cbk.ctx = ctx; + + if ((ctx->bus >= 0) && (ctx->slot >= 0)) { + res = ctx->dt_device->AttachToSlot(ctx->bus, ctx->slot); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] No DTA found on PIC bus %d slot %d: %s - trying enumerating cards\n", ctx->bus, ctx->slot, DtapiResult2Str(res))); + } + else { + return GF_OK; + } + } + + res = ctx->dt_device->AttachToType(2174); + if (res != DTAPI_OK) res = ctx->dt_device->AttachToType(2154); + + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] No DTA 2174 or 2154 in system: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + return GF_OK; +} + +static void dtout_finalize(GF_Filter *filter) +{ + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + + if (ctx->is_sending) { + dtout_disconnect(ctx); + } + delete(ctx->dt_matrix); + delete(ctx->dt_device); +} + +#endif //FAKE_DT_API + +static GF_Err dtout_process(GF_Filter *filter) +{ + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + if (!ctx->is_configured && !ctx->is_sending) { +#ifndef FAKE_DT_API + if (ctx->audio_cbk.audio) gf_filter_pid_get_packet(ctx->audio_cbk.audio); + if (ctx->video_cbk.video) gf_filter_pid_get_packet(ctx->video_cbk.video); +#endif + } + if (ctx->is_eos) + return GF_EOS; + gf_filter_ask_rt_reschedule(filter, 200000); + return GF_OK; +} + +#endif //GPAC_HAS_DTAPI + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dektec_video_decl.c" + + +#ifdef __cplusplus +} +#endif diff --git a/modules/dektec_out/dektec_video.h b/modules/dektec_out/dektec_video.h new file mode 100644 index 0000000..825ba14 --- /dev/null +++ b/modules/dektec_out/dektec_video.h @@ -0,0 +1,98 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Romain Bouqueau, Jean Le Feuvre +* Copyright (c) 2014-2016 GPAC Licensing +* Copyright (c) 2016-2020 Telecom Paris +* All rights reserved +* +* This file is part of GPAC / Dektec SDI video output 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. +* +*/ + +#ifndef _GF_DEKTECVID_H_ +#define _GF_DEKTECVID_H_ + +#include <gpac/filters.h> + + + +//#define FAKE_DT_API + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) +#include <DTAPI.h> +#endif + +#include <gpac/constants.h> +#include <gpac/color.h> +#include <gpac/thread.h> + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) + +#if defined(WIN32) && !defined(__GNUC__) +# include <intrin.h> +#else +# include <emmintrin.h> +#endif + +#endif + + +typedef struct { + struct _dtout_ctx *ctx; + GF_FilterPid *video; //null if audio callback + GF_FilterPid *audio; //null if video callback +} DtCbkCtx; + +typedef struct _dtout_ctx +{ + //opts + s32 bus, slot; + GF_Fraction fps; + Bool clip; + u32 port; + Double start; + + u32 width, height, pix_fmt, stride, stride_uv, uv_height, nb_planes; + GF_Fraction framerate; + Bool is_sending, is_configured, is_10b, is_eos; + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) + DtMxProcess *dt_matrix; + DtDevice *dt_device; + DtCbkCtx audio_cbk, video_cbk; +#endif + + s64 frameNum; + u64 init_clock, last_frame_time; + + u32 frame_dur, frame_scale; + Bool needs_reconfigure; +} GF_DTOutCtx; + + +#if defined(GPAC_HAS_DTAPI) +GF_Err dtout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); +GF_Err dtout_initialize(GF_Filter *filter); +void dtout_finalize(GF_Filter *filter); +GF_Err dtout_process(GF_Filter *filter); +#endif + + +#endif //_GF_DEKTECVID_H_ + + diff --git a/modules/dektec_out/dektec_video_decl.c b/modules/dektec_out/dektec_video_decl.c new file mode 100644 index 0000000..b81c4fc --- /dev/null +++ b/modules/dektec_out/dektec_video_decl.c @@ -0,0 +1,117 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Romain Bouqueau, Jean Le Feuvre +* Copyright (c) 2014-2022 GPAC Licensing +* Copyright (c) 2016-2020 Telecom Paris +* All rights reserved +* +* This file is part of GPAC / Dektec SDI video output 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 "dektec_video.h" + + +#ifdef GPAC_HAS_DTAPI +#define OFFS(_n) #_n, offsetof(GF_DTOutCtx, _n) +#else +#define OFFS(_n) #_n, -1 + +static GF_Err dtout_config_dummy(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + return GF_OK; +} +static GF_Err dtout_process_dummy(GF_Filter *filter) +{ + return GF_OK; +} +#endif + +static const GF_FilterArgs DTOutArgs[] = +{ + { OFFS(bus), "PCI bus number. If not set, device discovery is used", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(slot), "PCI bus number. If not set, device discovery is used", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT }, + { OFFS(fps), "default FPS to use if input stream fps cannot be detected", GF_PROP_FRACTION, "30/1", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(clip), "clip YUV data to valid SDI range, slower", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(port), "set sdi output port of card", GF_PROP_UINT, "1", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(start), "set playback start offset, [-1, 0] means percent of media dur, e.g. -1 == dur", GF_PROP_DOUBLE, "0.0", NULL, GF_FS_ARG_HINT_NORMAL }, + { 0 } +}; + +static GF_FilterCapability DTOutCaps[3]; + +GF_FilterRegister DTOutRegister; + +#ifdef GPAC_HAS_DTAPI +GPAC_MODULE_EXPORT +GF_FilterRegister *RegisterFilter(GF_FilterSession *session) +#else +GF_FilterRegister *dtout_register(GF_FilterSession *session) +#endif +{ + memset(DTOutCaps, 0, sizeof(DTOutCaps)); + memset(&DTOutRegister, 0, sizeof(GF_FilterRegister)); + +#ifndef GPAC_HAS_DTAPI + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + DTOutRegister.version = "! Warning: DekTek SDK NOT AVAILABLE IN THIS BUILD !"; +#endif + + DTOutCaps[0].code = GF_PROP_PID_STREAM_TYPE; + DTOutCaps[0].flags = GF_CAPS_INPUT; + DTOutCaps[0].val.type = GF_PROP_UINT; + DTOutCaps[0].val.value.uint = GF_STREAM_VISUAL; + + DTOutCaps[1].code = GF_PROP_PID_STREAM_TYPE; + DTOutCaps[1].flags = GF_CAPS_INPUT; + DTOutCaps[1].val.type = GF_PROP_UINT; + DTOutCaps[1].val.value.uint = GF_STREAM_AUDIO; + + DTOutCaps[2].code = GF_PROP_PID_CODECID; + DTOutCaps[2].flags = GF_CAPS_INPUT; + DTOutCaps[2].val.type = GF_PROP_UINT; + DTOutCaps[2].val.value.uint = GF_CODECID_RAW; + + DTOutRegister.name = "dtout"; +#ifndef GPAC_DISABLE_DOC + DTOutRegister.description = "DekTec SDIOut"; + 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; + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) + DTOutRegister.initialize = dtout_initialize; + DTOutRegister.finalize = dtout_finalize; + DTOutRegister.configure_pid = dtout_configure_pid; + DTOutRegister.process = dtout_process; +#else + DTOutRegister.configure_pid = dtout_config_dummy; + DTOutRegister.process = dtout_process_dummy; + +#ifdef GPAC_ENABLE_COVERAGE + dtout_config_dummy(NULL, NULL, GF_FALSE); + dtout_process_dummy(NULL); +#endif + +#endif + return &DTOutRegister; +} diff --git a/modules/dektec_out/dektec_video_old.cpp b/modules/dektec_out/dektec_video_old.cpp new file mode 100644 index 0000000..2358bad --- /dev/null +++ b/modules/dektec_out/dektec_video_old.cpp @@ -0,0 +1,367 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Romain Bouqueau + * Copyright (c) Romain Bouqueau @ GPAC Licensing + * All rights reserved + * + * This file is part of GPAC / Dektec SDI video render module + * + * 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. + * + */ + +#define _DTAPI_DISABLE_AUTO_LINK +#include <DTAPI.h> + +extern "C" { + + /*driver interfaces*/ +#include <gpac/modules/video_out.h> +#include <gpac/modules/audio_out.h> +#include <gpac/constants.h> +#include <gpac/setup.h> + + +//#define DEKTEC_DUMP_UYVY +#ifdef NDEBUG +#define tru 1 +#else +#define tru 1 +#endif + +//TODO: use gf_stretch_bits and get values from config file +#if 1 +#define DWIDTH 1280 +#define DHEIGHT 720 +#define DFORMAT DTAPI_IOCONFIG_720P59_94 +#define DNUMFIELDS 1 +#else +#define DWIDTH 1920 +#define DHEIGHT 1080 +#define DFORMAT DTAPI_IOCONFIG_1080I50 +#define DNUMFIELDS 2 +#endif + + typedef struct + { + char *pixels; + unsigned char *pixels_UYVY; + u32 width, height, pixel_format, bpp; + + DtDevice *dvc; + DtFrameBuffer *dtf; + bool isSending; + s64 frameNum; + } DtContext; + + + void dx_copy_pixels(GF_VideoSurface *dst_s, const GF_VideoSurface *src_s, const GF_Window *src_wnd); //FIXME: referenced from dx + static GF_Err Dektec_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) + { + DtContext *dtc = (DtContext*)dr->opaque; + GF_VideoSurface temp_surf; + memset(&temp_surf, 0, sizeof(GF_VideoSurface)); + temp_surf.pixel_format = GF_PIXEL_UYVY; + temp_surf.video_buffer = (char*)dtc->pixels_UYVY; + temp_surf.pitch_y = dtc->width*2; + dx_copy_pixels(&temp_surf, video_src, src_wnd); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Blit\n")); + return GF_OK; + } + + static GF_Err Dektec_Flush(GF_VideoOutput *dr, GF_Window *dest) + { + DtContext *dtc = (DtContext*)dr->opaque; + DtFrameBuffer *dtf = dtc->dtf; + DTAPI_RESULT res; + if (dest->w != DWIDTH || dest->h != DHEIGHT) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Discard data with wrong dimensions\n")); + if (tru) return GF_OK; + } + if (!dtc->isSending) { + // Start transmission + res = dtf->Start(); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[Dektec Out] Can't start transmission: %s.\n", DtapiResult2Str(res))); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Transmission started.\n")); + dtc->isSending = true; + } + } + s64 firstSafeFrame=-1, lastSafeFrame=-1; + res = dtf->WaitFrame(firstSafeFrame, lastSafeFrame); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't get the next frame: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + if (dtc->frameNum == -1) + dtc->frameNum = firstSafeFrame; + + int numLines=-1, numWritten=DWIDTH*DHEIGHT*2/DNUMFIELDS; + res = dtf->WriteVideo(dtc->frameNum, dtc->pixels_UYVY, numWritten, DTAPI_SDI_FIELD1, DTAPI_SDI_8B, 1, numLines); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + if (DNUMFIELDS > 1) { + assert(DNUMFIELDS == 2); + numLines=-1, numWritten=DWIDTH*DHEIGHT*2/DNUMFIELDS; + res = dtf->WriteVideo(dtc->frameNum, dtc->pixels_UYVY+numWritten, numWritten, DTAPI_SDI_FIELD2, DTAPI_SDI_8B, 1, numLines); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Written frame "LLD" [(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)]\n", dtc->frameNum, DWIDTH, DHEIGHT, + dtc->pixels_UYVY[0], dtc->pixels_UYVY[1], dtc->pixels_UYVY[2], dtc->pixels_UYVY[3], dtc->pixels_UYVY[4], dtc->pixels_UYVY[5], dtc->pixels_UYVY[6], dtc->pixels_UYVY[7], + dtc->pixels_UYVY[8], dtc->pixels_UYVY[9], dtc->pixels_UYVY[10], dtc->pixels_UYVY[11], dtc->pixels_UYVY[12], dtc->pixels_UYVY[13], dtc->pixels_UYVY[14], dtc->pixels_UYVY[15])); + + // commit HANC/VANC - mandatory to form a valid frame + res = dtf->AncCommit(dtc->frameNum); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write VANC for frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + dtc->frameNum++; + +#ifdef DEKTEC_DUMP_UYVY + char szName[1024]; + sprintf(szName, "test.uyvy"); + FILE *f = fopen(szName, "ab"); + fwrite(dtc->pixels_UYVY, dtc->width*dtc->height*2, 1, f); + fclose(f); +#endif + return GF_OK; + } + + static GF_Err Dektec_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) + { + DtContext *dtc = (DtContext*)dr->opaque; + + if (do_lock) { + if (!vi) return GF_BAD_PARAM; + memset(vi, 0, sizeof(GF_VideoSurface)); + vi->height = dtc->height; + vi->width = dtc->width; + vi->video_buffer = dtc->pixels; + vi->pitch_x = dtc->bpp; + vi->pitch_y = dtc->bpp * vi->width;/*the correct value here is dtc->pixel_format, but the soft raster only supports RGB*/ + vi->pixel_format = GF_PIXEL_RGB_24; + } + return GF_OK; + } + + static void Dektec_Shutdown(GF_VideoOutput *dr) + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Dektec_Shutdown\n")); + DtContext *dtc = (DtContext*)dr->opaque; + DtFrameBuffer *dtf = dtc->dtf; + DTAPI_RESULT res = dtf->Start(false); + if (tru && res != DTAPI_OK) + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't stop transmission: %s\n", DtapiResult2Str(res))); + dtf->Detach(); + dtc->dvc->Detach(); + dtc->isSending = false; + } + + static GF_Err Dektec_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) + { + //DtFrameBuffer *dtf = (DtFrameBuffer*)(((DtContext*)dr->opaque)->dtf); + + if (evt) { + switch (evt->type) { + case GF_EVENT_VIDEO_SETUP: + //FIXME: we do not know how to resize - Dektec_Shutdown(dr); Dektec_attach_start(dr); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] resize (%ux%u) received.\n", evt->size.width, evt->size.height)); + if (evt->size.width != DWIDTH || evt->size.height != DHEIGHT) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Bad resize (%ux%u) received. Expected %dx%d.\n", evt->size.width, evt->size.height, DWIDTH, DHEIGHT)); + if (evt->size.width*evt->size.height > DWIDTH*DHEIGHT) //FIXME + return GF_BAD_PARAM; + } + return GF_OK; + } + } + return GF_OK; + } + + static GF_Err Dektec_resize(GF_VideoOutput *dr, u32 w, u32 h) + { + DtContext *dtc = (DtContext*)dr->opaque; + if (dtc->pixels) gf_free(dtc->pixels); + if (dtc->pixels_UYVY) gf_free(dtc->pixels_UYVY); + dtc->width = w; + dtc->height = h; + dtc->pixels = (char*)gf_malloc(dtc->bpp * w * h); + if (!dtc->pixels) return GF_OUT_OF_MEM; + memset(dtc->pixels, 0, dtc->bpp * w * h); + dtc->pixels_UYVY = (unsigned char*)gf_malloc(2 * w * h); + if (!dtc->pixels_UYVY) return GF_OUT_OF_MEM; + memset(dtc->pixels_UYVY, 0, 2 * w * h); + return GF_OK; + } + + GF_Err Dektec_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Dektec_Setup\n")); + DtContext *dtc = (DtContext*)dr->opaque; + dtc->bpp = 3; //lock_buffer expects RGB + dtc->pixel_format = GF_PIXEL_YV12; + dtc->isSending = false; + Dektec_resize(dr, DWIDTH, DHEIGHT); + + int port = 1; + const char *opt; + opt = gf_opts_get_key("DektecVideo", "SDIOutput"); + if (opt) { + port = atoi(opt); + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Using port %d (%s)\n", port, opt)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] No port specified, using default port: %d\n", port)); + } + + DtDevice *dvc = dtc->dvc; + DtFrameBuffer *dtf = dtc->dtf; + DTAPI_RESULT res; + + res = dvc->AttachToType(2174); + if (res != DTAPI_OK) res = dvc->AttachToType(2154); + if (res == DTAPI_OK) res = dvc->AttachToType(-1); + if (res == DTAPI_OK) res = dvc->AttachToSlot(10, 33); + if (res == DTAPI_OK) res = dvc->AttachToSerial(2174000447); + + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] No DTA 2174 or 2154 in system: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dvc->SetIoConfig(port, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dtf->AttachToOutput(dvc, port, 0); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't attach to port %d: %s\n", port, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + int IoStdValue=-1, IoStdSubValue=-1; + res = DtapiVidStd2IoStd(DFORMAT, IoStdValue, IoStdSubValue); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Unknown VidStd: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dtf->SetIoConfig(DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't set I/O config: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + dtc->frameNum = -1; + + return GF_OK; + } + + GF_VideoOutput *NewDektecVideoOutput() + { + GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "Dektec Video Output", "gpac distribution") + + DtDevice *dvc = new DtDevice; + DtFrameBuffer *dtf = new DtFrameBuffer; + if (!dtf || !dvc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] DTA API couldn't be initialized.\n")); + delete dvc; + delete dtf; + gf_free(driv); + return NULL; + } + + DtContext *dtc = new DtContext; + memset(dtc, 0, sizeof(DtContext)); + dtc->dvc = dvc; + dtc->dtf = dtf; + driv->opaque = (void*)dtc; + + driv->Flush = Dektec_Flush; + driv->LockBackBuffer = Dektec_LockBackBuffer; + driv->Setup = Dektec_Setup; + driv->Shutdown = Dektec_Shutdown; + driv->ProcessEvent = Dektec_ProcessEvent; + driv->Blit = Dektec_Blit; + + driv->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY | GF_VIDEO_HW_HAS_YUV; + + return driv; + } + + void DeleteDektecVideoOutput(void *ifce) + { + GF_VideoOutput *driv = (GF_VideoOutput *) ifce; + DtContext *dtc = (DtContext*)driv->opaque; + gf_free(dtc->pixels); + gf_free(dtc->pixels_UYVY); + delete(dtc->dtf); + delete(dtc->dvc); + delete(dtc); + gf_free(driv); + } + + +#ifndef GPAC_STANDALONE_RENDER_2D + + /*interface query*/ + GPAC_MODULE_EXPORT + const u32 *QueryInterfaces() + { + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + 0 + }; + return si; + } + + /*interface create*/ + GPAC_MODULE_EXPORT + GF_BaseInterface *LoadInterface(u32 InterfaceType) + { + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewDektecVideoOutput(); + return NULL; + } + + /*interface destroy*/ + GPAC_MODULE_EXPORT + void ShutdownInterface(GF_BaseInterface *ifce) + { + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + DeleteDektecVideoOutput((GF_VideoOutput *)ifce); + break; + } + } + + + GPAC_MODULE_STATIC_DECLARATION( dektec_out ) + +#endif + +} /* extern "C" */ diff --git a/modules/dektec_out/out_dektec.vcxproj b/modules/dektec_out/out_dektec.vcxproj new file mode 100644 index 0000000..fc6f56d --- /dev/null +++ b/modules/dektec_out/out_dektec.vcxproj @@ -0,0 +1,294 @@ +<?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|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{042E3638-67F3-4C6C-8C00-CD9BFA416974}</ProjectGuid> + <RootNamespace>out_dektec</RootNamespace> + <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> + <ProjectName>out_dektec</ProjectName> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental> + <GenerateManifest Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</GenerateManifest> + <GenerateManifest Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</GenerateManifest> + <EmbedManifest Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</EmbedManifest> + <EmbedManifest Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</EmbedManifest> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">gf_$(ProjectName)</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Midl> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TargetEnvironment>Win32</TargetEnvironment> + <TypeLibraryName>.\obj/out_dektec_deb/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <Manifest> + <OutputManifestFile>$(IntDir)$(TargetFileName).embed.manifest</OutputManifestFile> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Midl> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TypeLibraryName>.\obj/out_dektec_deb/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + </Link> + <Manifest> + <OutputManifestFile>$(IntDir)$(TargetFileName).embed.manifest</OutputManifestFile> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Midl> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TargetEnvironment>Win32</TargetEnvironment> + <TypeLibraryName>.\obj/out_dektec_rel/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + <OutputFile>$(IntDir)$(ProjectName).bsc</OutputFile> + </Bscmake> + <Manifest> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Midl> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TypeLibraryName>.\obj/out_dektec_rel/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Full</Optimization> + <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + </Link> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + <OutputFile>$(IntDir)$(ProjectName).bsc</OutputFile> + </Bscmake> + <Manifest> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\modules\dektec_out\dektec_video.cpp" /> + <ClCompile Include="..\..\modules\filter_export.cpp" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\build\msvc14\libgpac_dll.vcxproj"> + <Project>{d3540754-e0cf-4604-ac11-82de9bd4d814}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/modules/demo_is/Makefile b/modules/demo_is/Makefile new file mode 100644 index 0000000..09b25c2 --- /dev/null +++ b/modules/demo_is/Makefile @@ -0,0 +1,46 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/demo_is + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= demo_is.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_demo_is$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +#LDFLAGS+=-export-symbols demo_is.def +endif + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/demo_is/demo-sensor.bt b/modules/demo_is/demo-sensor.bt new file mode 100644 index 0000000..73f5683 --- /dev/null +++ b/modules/demo_is/demo-sensor.bt @@ -0,0 +1,86 @@ +InitialObjectDescriptor { + objectDescriptorID 1 + audioProfileLevelIndication 255 + visualProfileLevelIndication 254 + sceneProfileLevelIndication 1 + graphicsProfileLevelIndication 1 + ODProfileLevelIndication 1 + esDescr [ + ES_Descriptor { + ES_ID 1 + decConfigDescr DecoderConfigDescriptor { + streamType 3 + decSpecificInfo BIFSConfig { + isCommandStream true + pixelMetric true + pixelWidth 400 + pixelHeight 300 + } + } + } + ES_Descriptor { + ES_ID 2 + decConfigDescr DecoderConfigDescriptor { + streamType 1 + } + } + ] +} + +OrderedGroup { + children [ + Background2D { + backColor 1 1 1 + } + WorldInfo { + info ["This shows usage of a demo InputSensor" "" "GPAC Regression Tests" "$Date: 2009-05-20 15:59:18 $ - $Revision: 1.1 $" "(C) 2009 ENST"] + title "InputSensor Test - Demo device" + } + Transform2D { + translation 0 90 + children [ + Shape { + appearance DEF APP Appearance { + material Material2D { + emissiveColor 0 0 0 + filled TRUE + } + } + geometry DEF TEXT Text { + string ["DemoSensor", ""] + fontStyle FontStyle { + justify ["MIDDLE" "MIDDLE"] + size 30 + } + } + } + ] + } + InputSensor { + url [od:10] + buffer { + REPLACE TEXT.string[1] BY "" + } + } + ] +} + +AT 0 { + UPDATE OD [ + ObjectDescriptor { + objectDescriptorID 10 + esDescr [ + ES_Descriptor { + ES_ID 5 + decConfigDescr DecoderConfigDescriptor { + streamType 10 + decSpecificInfo UIConfig { + deviceName "DemoSensor" + } + } + } + ] + } + ] +} + diff --git a/modules/demo_is/demo_is.c b/modules/demo_is/demo_is.c new file mode 100644 index 0000000..c083eee --- /dev/null +++ b/modules/demo_is/demo_is.c @@ -0,0 +1,104 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2009-2012 + * All rights reserved + * + * This file is part of GPAC / Dummy input module + * + * 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/modules/codec.h> +#include <gpac/scenegraph_vrml.h> + +static Bool DEV_RegisterDevice(struct __input_device *ifce, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) +{ + if (strcmp(urn, "DemoSensor")) return 0; + + AddField(ifce, GF_SG_VRML_SFSTRING, "content"); + + return 1; +} + +static void DEV_Start(struct __input_device *ifce) +{ + GF_BitStream *bs; + char *buf, *szWord; + u32 len, val, i, buf_size; + + szWord = "Hello InputSensor!"; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + /*HTK sensor buffer format: SFString - SFInt32 - SFFloat*/ + gf_bs_write_int(bs, 1, 1); + len = strlen(szWord); + val = gf_get_bit_size(len); + gf_bs_write_int(bs, val, 5); + gf_bs_write_int(bs, len, val); + for (i=0; i<len; i++) gf_bs_write_int(bs, szWord[i], 8); + + gf_bs_align(bs); + gf_bs_get_content(bs, &buf, &buf_size); + gf_bs_del(bs); + + ifce->DispatchFrame(ifce, buf, buf_size); + gf_free(buf); +} + +static void DEV_Stop(struct __input_device *ifce) +{ +} + + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_INPUT_DEVICE_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + GF_InputSensorDevice *plug; + if (InterfaceType != GF_INPUT_DEVICE_INTERFACE) return NULL; + + GF_SAFEALLOC(plug, GF_InputSensorDevice); + GF_REGISTER_MODULE_INTERFACE(plug, GF_INPUT_DEVICE_INTERFACE, "GPAC Demo InputSensor", "gpac distribution") + + plug->RegisterDevice = DEV_RegisterDevice; + plug->Start = DEV_Start; + plug->Stop = DEV_Stop; + + return (GF_BaseInterface *)plug; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *bi) +{ + GF_InputSensorDevice *ifcn = (GF_InputSensorDevice*)bi; + if (ifcn->InterfaceType==GF_INPUT_DEVICE_INTERFACE) { + gf_free(bi); + } +} + +GPAC_MODULE_STATIC_DECLARATION( demo_is ) diff --git a/modules/directfb_out/Makefile b/modules/directfb_out/Makefile new file mode 100644 index 0000000..2d70d08 --- /dev/null +++ b/modules/directfb_out/Makefile @@ -0,0 +1,47 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/directfb_out + +CFLAGS=-I"$(SRC_PATH)/include" -I$(DIRECTFB_INC_PATH) $(OPTFLAGS) +LDFLAGS+=$(DIRECTFB_LIB) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + + + + +#common obj +OBJS=directfb_out.o directfb_wrapper.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_directfb_out$(DYN_LIB_SUFFIX) + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/directfb_out/directfb_out.c b/modules/directfb_out/directfb_out.c new file mode 100755 index 0000000..faaf441 --- /dev/null +++ b/modules/directfb_out/directfb_out.c @@ -0,0 +1,343 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Romain Bouqueau - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2020 + * All rights reserved + * + * This file is part of GPAC / DirectFB video output module + * + * 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.0 + * + * 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/modules/video_out.h> +#include <gpac/scenegraph_svg.h> + +#include "directfb_out.h" + + +#define DirectFBVID() DirectFBVidCtx *ctx = (DirectFBVidCtx *)driv->opaque + +/** + * function DirectFBVid_Setup + * - DirectFB setup + **/ +GF_Err DirectFBVid_Setup(GF_VideoOutput *driv, void *os_handle, void *os_display, u32 init_flags) +{ + const char* opt; + + DirectFBVID(); + DirectFBVid_CtxSetIsInit(ctx, 0); + + // initialisation and surface creation + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] Initialization\n")); + // check window mode used - SDL or X11 + { + WINDOW_MODE window_mode = 0; + opt = gf_opts_get_key("DirectFB", "WindowMode"); + if (!opt) gf_opts_set_key("DirectFB", "WindowMode", "X11"); + if (!opt || !strcmp(opt, "X11")) window_mode = WINDOW_X11; + else if (!strcmp(opt, "SDL")) window_mode = WINDOW_SDL; + DirectFBVid_InitAndCreateSurface(ctx, window_mode); + } + + // check hardware accelerator configuration + DirectFBVid_CtxSetDisableAcceleration(ctx, 0); + opt = gf_opts_get_key("DirectFB", "DisableAcceleration"); + if (!opt) gf_opts_set_key("DirectFB", "DisableAcceleration", "no"); + if (opt && !strcmp(opt, "yes")) DirectFBVid_CtxSetDisableAcceleration(ctx, 1); + + // check for display configuration + DirectFBVid_CtxSetDisableDisplay(ctx, 0); + opt = gf_opts_get_key("DirectFB", "DisableDisplay"); + if (!opt) gf_opts_set_key("DirectFB", "DisableDisplay", "no"); + if (opt && !strcmp(opt, "yes")) DirectFBVid_CtxSetDisableDisplay(ctx, 1); + + // set flip mode + { + FLIP_MODE flip_mode = 0; + opt = gf_opts_get_key("DirectFB", "FlipSyncMode"); + if (!opt) gf_opts_set_key("DirectFB", "FlipSyncMode", "waitsync"); + if (!opt || !strcmp(opt, "waitsync")) flip_mode |= FLIP_WAITFORSYNC; + else if (!strcmp(opt, "wait")) flip_mode |= FLIP_WAIT; + else if (!strcmp(opt, "sync")) flip_mode |= FLIP_ONSYNC; + else if (!strcmp(opt, "swap")) flip_mode |= FLIP_SWAP; + + DirectFBVid_CtxSetFlipMode(ctx, flip_mode); + } + + // enable/disable blit + opt = gf_opts_get_key("DirectFB", "DisableBlit"); + if (!opt) gf_opts_set_key("DirectFB", "DisableBlit", "no"); + if (opt && !strcmp(opt, "all")) { + driv->hw_caps &= ~(GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_YUV); + } + else if (opt && !strcmp(opt, "yuv")) driv->hw_caps &= ~GF_VIDEO_HW_HAS_YUV; + else if (opt && !strcmp(opt, "rgb")) driv->hw_caps &= ~GF_VIDEO_HW_HAS_RGB; + else if (opt && !strcmp(opt, "rgba")) driv->hw_caps &= ~GF_VIDEO_HW_HAS_RGBA; + + // end of initialization + DirectFBVid_CtxSetIsInit(ctx, 1); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] Initialization success - HW caps %08x\n", driv->hw_caps)); + +// GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] Pixel format %s\n", gf_4cc_to_str(ctx->pixel_format))); + return GF_OK; +} + + +/** + * function DirectFBVid_Shutdown + * - shutdown DirectFB module + **/ +static void DirectFBVid_Shutdown(GF_VideoOutput *driv) +{ + u32 ret; + DirectFBVID(); + ret = DirectFBVid_ShutdownWrapper(ctx); + if (ret) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] Failed to shutdown properly\n")); + } +} + + +/** + * function DirectFBVid_Flush + * - flushing buffer + **/ +static GF_Err DirectFBVid_Flush(GF_VideoOutput *driv, GF_Window *dest) +{ + DirectFBVID(); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] Flipping backbuffer\n")); + // if display is disabled, nothing to be done + if (DirectFBVid_CtxGetDisableDisplay(ctx)) return GF_OK; + + return DirectFBVid_CtxPrimaryFlip(ctx); +} + + +/** + * function DirectFBVid_SetFullScreen + * - set fullscreen mode + **/ +GF_Err DirectFBVid_SetFullScreen(GF_VideoOutput *driv, u32 bFullScreenOn, u32 *screen_width, u32 *screen_height) +{ + DirectFBVID(); + + *screen_width = DirectFBVid_CtxGetWidth(ctx); + *screen_height = DirectFBVid_CtxGetHeight(ctx); + + return GF_OK; +} + + +/** + * function DirectFBVid_ProcessMessageQueue + * - handle DirectFB events + **/ +Bool DirectFBVid_ProcessMessageQueue(DirectFBVidCtx *ctx, GF_VideoOutput *driv) +{ + GF_Event gpac_evt; + memset(&gpac_evt, 0, sizeof(gpac_evt)); + while (DirectFBVid_ProcessMessageQueueWrapper(ctx, &gpac_evt.type, &gpac_evt.key.flags, &gpac_evt.key.key_code, &gpac_evt.mouse.x, &gpac_evt.mouse.y, &gpac_evt.mouse.button) == GF_OK) + { + driv->on_event(driv->evt_cbk_hdl, &gpac_evt); + gpac_evt.key.flags = 0; + } + + return GF_OK; +} + + +/** + * function DirectFBVid_ProcessEvent + * - process events + **/ +static GF_Err DirectFBVid_ProcessEvent(GF_VideoOutput *driv, GF_Event *evt) +{ + DirectFBVID(); + + if (!evt) { + DirectFBVid_ProcessMessageQueue(ctx, driv); + return GF_OK; + } + + switch (evt->type) { + case GF_EVENT_SIZE: + if ((DirectFBVid_CtxGetWidth(ctx) !=evt->size.width) || (DirectFBVid_CtxGetHeight(ctx) != evt->size.height)) { + GF_Event gpac_evt; + gpac_evt.type = GF_EVENT_SIZE; + gpac_evt.size.width = DirectFBVid_CtxGetWidth(ctx); + gpac_evt.size.height = DirectFBVid_CtxGetHeight(ctx); + driv->on_event(driv->evt_cbk_hdl, &gpac_evt); + } + return GF_OK; + + case GF_EVENT_VIDEO_SETUP: + if (evt->setup.use_opengl) return GF_NOT_SUPPORTED; + + if ((DirectFBVid_CtxGetWidth(ctx) !=evt->setup.width) || (DirectFBVid_CtxGetHeight(ctx) != evt->setup.height)) { + GF_Event gpac_evt; + gpac_evt.type = GF_EVENT_SIZE; + gpac_evt.size.width = DirectFBVid_CtxGetWidth(ctx); + gpac_evt.size.height = DirectFBVid_CtxGetHeight(ctx); + driv->on_event(driv->evt_cbk_hdl, &gpac_evt); + } + return GF_OK; + default: + return GF_OK; + } +} + + +/** + * function DirectFBVid_LockBackBuffer + * - lock the surface to access certain data + **/ +static GF_Err DirectFBVid_LockBackBuffer(GF_VideoOutput *driv, GF_VideoSurface *video_info, u32 do_lock) +{ + void *buf; + u32 pitch, ret; + + DirectFBVID(); + if (!DirectFBVid_CtxGetPrimary(ctx)) return GF_BAD_PARAM; + if (do_lock) + { + if (!video_info) return GF_BAD_PARAM; + // lock surface first in order to access data below + ret = DirectFBVid_CtxPrimaryLock(ctx, &buf, &pitch); + if (ret != 0) return GF_IO_ERR; + + // fetch data + memset(video_info, 0, sizeof(GF_VideoSurface)); + video_info->width = DirectFBVid_CtxGetWidth(ctx); + video_info->height = DirectFBVid_CtxGetHeight(ctx); + video_info->pitch_x = 0; + video_info->pitch_y = pitch; + video_info->video_buffer = buf; + video_info->pixel_format = DirectFBVid_CtxGetPixelFormat(ctx); + video_info->is_hardware_memory = !DirectFBVid_CtxIsHwMemory(ctx); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] backbuffer locked\n")); + } else { + // unlock the surface after direct access + DirectFBVid_CtxPrimaryUnlock(ctx); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] backbuffer unlocked\n")); + } + + return GF_OK; +} + + +/** + * function DirectFBVid_Blit + * - blit a surface + **/ +static GF_Err DirectFBVid_Blit(GF_VideoOutput *driv, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) +{ + u32 ret; + DirectFBVID(); + + ret = DirectFBVid_BlitWrapper(ctx, video_src->width, video_src->height, video_src->pixel_format, video_src->video_buffer, video_src->pitch_y, src_wnd->x, src_wnd->y, src_wnd->w, src_wnd->h, dst_wnd->x, dst_wnd->y, dst_wnd->w, dst_wnd->h, overlay_type); + if (ret) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectFB] cannot create blit source surface for pixel format %s\n", gf_4cc_to_str(video_src->pixel_format))); + return GF_NOT_SUPPORTED; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] blit successful\n")); + + return GF_OK; +} + + +/** + * function DirectFBNewVideo + * - creates a DirectFb module + **/ +void *DirectFBNewVideo() +{ + DirectFBVidCtx *ctx; + GF_VideoOutput *driv; + + driv = gf_malloc(sizeof(GF_VideoOutput)); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "DirectFB Video Output", "gpac distribution"); + + ctx = gf_malloc(DirectFBVid_GetCtxSizeOf()); + memset(ctx, 0, DirectFBVid_GetCtxSizeOf()); + + /* GF_VideoOutput */ + driv->opaque = ctx; + driv->Setup = DirectFBVid_Setup; + driv->Shutdown = DirectFBVid_Shutdown; + driv->Flush = DirectFBVid_Flush; + driv->SetFullScreen = DirectFBVid_SetFullScreen; + driv->ProcessEvent = DirectFBVid_ProcessEvent; + driv->LockBackBuffer = DirectFBVid_LockBackBuffer; + driv->LockOSContext = NULL; + driv->Blit = DirectFBVid_Blit; + driv->hw_caps |= GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_STRETCH; + + return driv; +} + + +/** + * function DirectFBDeleteVideo + * - delete video + **/ +void DirectFBDeleteVideo(void *ifce) +{ + GF_VideoOutput *driv = (GF_VideoOutput *)ifce; + DirectFBVID(); + gf_free(ctx); + gf_free(driv); +} + + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + + +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return DirectFBNewVideo(); + return NULL; +} + + +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + DirectFBDeleteVideo(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( directfb_out ) diff --git a/modules/directfb_out/directfb_out.h b/modules/directfb_out/directfb_out.h new file mode 100755 index 0000000..f7b948d --- /dev/null +++ b/modules/directfb_out/directfb_out.h @@ -0,0 +1,74 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Romain Bouqueau - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2012 + * All rights reserved + * + * This file is part of GPAC / DirectFB video output module + * + * 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 _DIRECTFB_OUT_H_ +#define _DIRECTFB_OUT_H_ + +typedef struct __DirectFBVidCtx DirectFBVidCtx; + +typedef enum { + WINDOW_X11 = 1, + WINDOW_SDL = 1 << 1, +} WINDOW_MODE; + +typedef enum { + FLIP_SWAP = 1, + FLIP_WAITFORSYNC = 1 << 1, + FLIP_WAIT = 1 << 2, + FLIP_ONSYNC = 1 << 3, +} FLIP_MODE; + +#ifndef Bool +#define Bool u32 +#endif + +u32 DirectFBVid_TranslatePixelFormatToGPAC(u32 dfbpf); +u32 DirectFBVid_TranslatePixelFormatFromGPAC(u32 gpacpf); +size_t DirectFBVid_GetCtxSizeOf(void); +void DirectFBVid_InitAndCreateSurface(DirectFBVidCtx *ctx, WINDOW_MODE window_mode); +void DirectFBVid_CtxSetFlipMode(DirectFBVidCtx *ctx, FLIP_MODE flip_mode); +void DirectFBVid_CtxPrimaryProcessGetAccelerationMask(DirectFBVidCtx *ctx); +u32 DirectFBVid_ProcessMessageQueueWrapper(DirectFBVidCtx *ctx, u8 *type, u32 *flags, u32 *key_code, s32 *x, s32 *y, u32 *button); +void DirectFBVid_DrawHLineWrapper(DirectFBVidCtx *ctx, u32 x, u32 y, u32 length, u8 r, u8 g, u8 b); +void DirectFBVid_DrawHLineAlphaWrapper(DirectFBVidCtx *ctx, u32 x, u32 y, u32 length, u8 r, u8 g, u8 b, u8 alpha); +void DirectFBVid_DrawRectangleWrapper(DirectFBVidCtx *ctx, u32 x, u32 y, u32 width, u32 height, u8 r, u8 g, u8 b, u8 a); +u32 DirectFBVid_CtxPrimaryLock(DirectFBVidCtx *ctx, void **buf, u32 *pitch); +void DirectFBVid_CtxPrimaryUnlock(DirectFBVidCtx *ctx); +u32 DirectFBVid_CtxGetWidth(DirectFBVidCtx *ctx); +u32 DirectFBVid_CtxGetHeight(DirectFBVidCtx *ctx); +void *DirectFBVid_CtxGetPrimary(DirectFBVidCtx *ctx); +u32 DirectFBVid_CtxGetPixelFormat(DirectFBVidCtx *ctx); +Bool DirectFBVid_CtxIsHwMemory(DirectFBVidCtx *ctx); +u32 DirectFBVid_CtxPrimaryFlip(DirectFBVidCtx *ctx); +void DirectFBVid_CtxSetDisableDisplay(DirectFBVidCtx *ctx, Bool val); +Bool DirectFBVid_CtxGetDisableDisplay(DirectFBVidCtx *ctx); +void DirectFBVid_CtxSetDisableAcceleration(DirectFBVidCtx *ctx, Bool val); +Bool DirectFBVid_CtxGetDisableAcceleration(DirectFBVidCtx *ctx); +void DirectFBVid_CtxSetIsInit(DirectFBVidCtx *ctx, Bool val); +u32 DirectFBVid_ShutdownWrapper(DirectFBVidCtx *ctx); +u32 DirectFBVid_BlitWrapper(DirectFBVidCtx *ctx, u32 video_src_width, u32 video_src_height, u32 video_src_pixel_format, char *video_src_buffer, s32 video_src_pitch_y, u32 src_wnd_x, u32 src_wnd_y, u32 src_wnd_w, u32 src_wnd_h, u32 dst_wnd_x, u32 dst_wnd_y, u32 dst_wnd_w, u32 dst_wnd_h, u32 overlay_type); + + +#endif diff --git a/modules/directfb_out/directfb_wrapper.c b/modules/directfb_out/directfb_wrapper.c new file mode 100644 index 0000000..ddf3f37 --- /dev/null +++ b/modules/directfb_out/directfb_wrapper.c @@ -0,0 +1,924 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Romain Bouqueau - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2012 + * All rights reserved + * + * This file is part of GPAC / DirectFB video output module + * + * 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. + * + */ + + +/* DirectFB */ +#include <directfb.h> +#include <directfb_strings.h> +#include <directfb_util.h> +#include <direct/util.h> + +#include <gpac/constants.h> +#include <gpac/events_constants.h> +#include <gpac/events.h> + +#include "directfb_out.h" + +static int do_xor = 0; + +/* macro for a safe call to DirectFB functions */ +#define DFBCHECK(x...) \ + do { \ + err = x; \ + if (err != DFB_OK) { \ + fprintf( stderr, "%s <%d>:\n\t", __FILE__, __LINE__ ); \ + DirectFBErrorFatal( #x, err ); \ + } \ + } while (0) + +#define SET_DRAWING_FLAGS( flags ) \ + ctx->primary->SetDrawingFlags( ctx->primary, (flags) | (do_xor ? DSDRAW_XOR : 0) ) + + +/* Structs */ + +struct __DirectFBVidCtx +{ + /* the super interface */ + IDirectFB *dfb; + /* the primary surface */ + IDirectFBSurface *primary; + + /* for keyboard input */ + //~ IDirectFBInputDevice *keyboard; + + /* screen width, height */ + u32 width, height, pixel_format; + Bool use_systems_memory, disable_acceleration, disable_aa, is_init, disable_display; + + /* acceleration */ + int accel_drawline, accel_fillrect; + DFBSurfaceFlipFlags flip_mode; + + /*===== for key events =====*/ + + /* Input interfaces: devices and its buffer */ + IDirectFBEventBuffer *events; + + /* mouse events */ + IDirectFBInputDevice *mouse; + + /*============================= + if using window + ============================= */ + + /* DirectFB window */ + IDirectFBWindow *window; + + /* display layer */ + IDirectFBDisplayLayer *layer; + +}; + + +size_t DirectFBVid_GetCtxSizeOf(void) +{ + return sizeof(DirectFBVidCtx); +} + + +typedef struct _DeviceInfo DeviceInfo; +struct _DeviceInfo { + DFBInputDeviceID device_id; + DFBInputDeviceDescription desc; + DeviceInfo *next; +}; + + + +/* Wrapper to DirectFB members */ + +/** + * function DirectFBVid_DrawHLineWrapper + * - using hardware accelerator to draw horizontal line + **/ +void DirectFBVid_DrawHLineWrapper(DirectFBVidCtx *ctx, u32 x, u32 y, u32 length, u8 r, u8 g, u8 b) +{ + //GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] in DirectFBVid_DrawHLine(). Drawing line x %d y %d length %d color %08X\n", x, y, length, color)); + + // DSDRAW_NOFX: flag controlling drawing command, drawing without using any effects + SET_DRAWING_FLAGS( DSDRAW_NOFX ); + + // set the color used without alpha + ctx->primary->SetColor(ctx->primary, r, g, b, 0xFF); + + // to draw a line using hardware accelerators, we can use either DrawLine (in our STB, DrawLine() function is not accelerated) or FillRectangle function with height equals to 1 + //ctx->primary->DrawLine(ctx->primary, x, y, x+length, y); // no acceleration + ctx->primary->FillRectangle(ctx->primary, x, y,length, 1); +} + + +/** + * function DirectFBVid_DrawHLineWrapper + * - using hardware accelerator to draw horizontal line with alpha + **/ +void DirectFBVid_DrawHLineAlphaWrapper(DirectFBVidCtx *ctx, u32 x, u32 y, u32 length, u8 r, u8 g, u8 b, u8 alpha) +{ + //GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] in DirectFBVid_DrawHLineAlpha(). Alpha line drawing x %d y %d length %d color %08X alpha %d\n", x, y, length, color, alpha)); + + // DSDRAW_BLEND: using alpha from color + SET_DRAWING_FLAGS( DSDRAW_BLEND ); + + ctx->primary->SetColor(ctx->primary, r, g, b, alpha); + //ctx->primary->DrawLine(ctx->primary, x, y, x+length, y); + ctx->primary->FillRectangle(ctx->primary, x, y, length, 1); // with acceleration +} + + +/** + * function DirectFBVid_DrawRectangleWrapper + * - using hardware accelerator to fill a rectangle with the given color + **/ +void DirectFBVid_DrawRectangleWrapper(DirectFBVidCtx *ctx, u32 x, u32 y, u32 width, u32 height, u8 r, u8 g, u8 b, u8 a) +{ + //GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] in DirectFBVid_DrawRectangle(). Drawing rectangle x %d y %d width %d height %d color %08x\n", x, y, width, height, color)); + + SET_DRAWING_FLAGS( DSDRAW_NOFX ); + + ctx->primary->SetColor(ctx->primary, r, g, b, a); + ctx->primary->FillRectangle(ctx->primary, x, y, width, height); + //ctx->primary->Blit( ctx->primary, ctx->primary, NULL, x, y ); +} + + +/** + * function DirectFBVid_CtxPrimaryLock + * - lock the surface (in order to access certain data) + **/ +u32 DirectFBVid_CtxPrimaryLock(DirectFBVidCtx *ctx, void **buf, u32 *pitch) +{ + DFBResult ret = ctx->primary->Lock(ctx->primary, DSLF_READ | DSLF_WRITE, buf, pitch); + if (ret != DFB_OK) return 1; + return 0; +} + + +static DFBEnumerationResult enum_input_device(DFBInputDeviceID device_id, DFBInputDeviceDescription desc, void *data ) +{ + DeviceInfo **devices = data; + + DeviceInfo *device = malloc(sizeof(DeviceInfo) ); + + device->device_id = device_id; + device->desc = desc; + device->next = *devices; + + *devices = device; + + return 0; +} + + +/** + * function DirectFBVid_InitAndCreateSurface + * - initialize and create DirectFB surface + **/ +u32 DirectFBVid_TranslatePixelFormatToGPAC(u32 dfbpf); +void DirectFBVid_InitAndCreateSurface(DirectFBVidCtx *ctx, u32 window_mode) +{ + DFBResult err; + DFBSurfaceDescription dsc; + DFBSurfacePixelFormat dfbpf; + DeviceInfo *devices = NULL; + + //fake arguments and DirectFBInit() + { + int i, argc=2, argc_ro=2; + char **argv = malloc(argc*sizeof(char*)); + char *argv_ro[2]; + //http://directfb.org/wiki/index.php/Configuring_DirectFB + argv_ro[0]=argv[0]=strdup("gpac"); + // graphis system used is X11 + if (window_mode == WINDOW_SDL) { + argv_ro[1]=argv[1]=strdup("--dfb:system=sdl"); + } else { + argv_ro[1]=argv[1]=strdup("--dfb:system=x11"); + } + + // screen resolution 640x480 + //~ argv_ro[2]=argv[2]=strdup("--dfb:mode=640x480"); + //~ argv_ro[2]=argv[2]=strdup(""); + + /* create the super interface */ + DFBCHECK(DirectFBInit(&argc, &argv)); + + for (i=0; i<argc_ro; i++) + free(argv_ro[i]); + free(argv); + } + + // disable background handling + DFBCHECK(DirectFBSetOption ("bg-none", NULL)); + // disable layers + DFBCHECK(DirectFBSetOption ("no-init-layer", NULL)); + + /* create the surface */ + DFBCHECK(DirectFBCreate( &(ctx->dfb) )); + + /* create a list of input devices */ + ctx->dfb->EnumInputDevices(ctx->dfb, enum_input_device, &devices ); + if (devices->desc.type & DIDTF_JOYSTICK) { + // for mouse + DFBCHECK(ctx->dfb->GetInputDevice(ctx->dfb, devices->device_id, &(ctx->mouse))); + } + + /* create an event buffer for all devices */ + DFBCHECK(ctx->dfb->CreateInputEventBuffer(ctx->dfb, DICAPS_KEYS, DFB_FALSE, &(ctx->events) )); + + /* Set the cooperative level */ + DFBCHECK(ctx->dfb->SetCooperativeLevel( ctx->dfb, DFSCL_FULLSCREEN )); + + /* Get the primary surface, i.e. the surface of the primary layer. */ + // capabilities field is valid + dsc.flags = DSDESC_CAPS; + // primary, double-buffered surface + dsc.caps = DSCAPS_PRIMARY | DSCAPS_DOUBLE; + + // if using system memory, data is permanently stored in this memory (no video memory allocation) + if (ctx->use_systems_memory) dsc.caps |= DSCAPS_SYSTEMONLY; + + DFBCHECK(ctx->dfb->CreateSurface( ctx->dfb, &dsc, &(ctx->primary) )); + + // fetch pixel format + ctx->primary->GetPixelFormat( ctx->primary, &dfbpf ); + // translate DirectFB pixel format to GPAC + ctx->pixel_format = DirectFBVid_TranslatePixelFormatToGPAC(dfbpf); + // surface width and height in pixel + ctx->primary->GetSize( ctx->primary, &(ctx->width), &(ctx->height) ); + ctx->primary->Clear( ctx->primary, 0, 0, 0, 0xFF); +} + + +/** + * function DirectFBVid_CtxPrimaryUnlock + * - unlock a surface after direct access (in order to fetch data) + **/ +void DirectFBVid_CtxPrimaryUnlock(DirectFBVidCtx *ctx) +{ + ctx->primary->Unlock(ctx->primary); +} + + +/** + * function DirectFBVid_CtxGetWidth + * - returns screen width + **/ +u32 DirectFBVid_CtxGetWidth(DirectFBVidCtx *ctx) +{ + return ctx->width; +} + + +/** + * function DirectFBVid_CtxGetHeight + * - returns screen height + **/ +u32 DirectFBVid_CtxGetHeight(DirectFBVidCtx *ctx) +{ + return ctx->height; +} + + +/** + * function DirectFBVid_CtxGetPrimary + * - return the primary surface + **/ +void *DirectFBVid_CtxGetPrimary(DirectFBVidCtx *ctx) +{ + return ctx->primary; +} + + +/** + * function DirectFBVid_CtxGetPixelFormat + * - get pixel format + **/ +u32 DirectFBVid_CtxGetPixelFormat(DirectFBVidCtx *ctx) +{ + return ctx->pixel_format; +} + + +/** + * function DirectFBVid_CtxIsHwMemory + * - return value whether system memory is used or not + **/ +Bool DirectFBVid_CtxIsHwMemory(DirectFBVidCtx *ctx) +{ + return ctx->use_systems_memory; +} + + +/** + * function DirectFBVid_CtxPrimaryFlip + * - flipping buffers + **/ +u32 DirectFBVid_CtxPrimaryFlip(DirectFBVidCtx *ctx) +{ + return ctx->primary->Flip(ctx->primary, NULL, ctx->flip_mode); +} + + +/** + * function DirectFBVid_CtxSetDisableDisplay + * - set disable display value + **/ +void DirectFBVid_CtxSetDisableDisplay(DirectFBVidCtx *ctx, Bool val) +{ + ctx->disable_display = val; +} + + +/** + * function DirectFBVid_CtxGetDisableDisplay + * - boolean showing whether display is enabled/disabled + **/ +Bool DirectFBVid_CtxGetDisableDisplay(DirectFBVidCtx *ctx) +{ + return ctx->disable_display; +} + + +/** + * function DirectFBVid_CtxSetDisableAcceleration + * - boolean showing whether hardware accelerator is enabled/disabled + **/ +void DirectFBVid_CtxSetDisableAcceleration(DirectFBVidCtx *ctx, Bool val) +{ + ctx->disable_acceleration = val; +} + + +/** + * function DirectFBVid_CtxGetDisableAcceleration + * - return disable_acceleration value + **/ +Bool DirectFBVid_CtxGetDisableAcceleration(DirectFBVidCtx *ctx) +{ + return ctx->disable_acceleration; +} + + +/** + * function DirectFBVid_CtxSetIsInit + * - boolean showing whether DirectFB is initialized + **/ +void DirectFBVid_CtxSetIsInit(DirectFBVidCtx *ctx, Bool val) +{ + ctx->is_init = val; +} + + +/** + * function DirectFBVid_CtxSetFlipMode + * - set flip mode + **/ +void DirectFBVid_CtxSetFlipMode(DirectFBVidCtx *ctx, u32 flip_mode) +{ + ctx->flip_mode = DSFLIP_BLIT; + switch(flip_mode) { + case FLIP_WAITFORSYNC: + ctx->flip_mode |= DSFLIP_WAITFORSYNC; + break; + case FLIP_WAIT: + ctx->flip_mode |= DSFLIP_WAIT; + break; + case FLIP_ONSYNC: + ctx->flip_mode |= DSFLIP_ONSYNC; + break; + case FLIP_SWAP: + ctx->flip_mode &= ~DSFLIP_BLIT; + break; + } +} + + +/** + * function DirectFBVid_CtxPrimaryProcessGetAccelerationMask + * - Get a mask of drawing functions that are hardware accelerated with the current settings. + **/ +void DirectFBVid_CtxPrimaryProcessGetAccelerationMask(DirectFBVidCtx *ctx) +{ + DFBAccelerationMask mask; + ctx->primary->GetAccelerationMask( ctx->primary, NULL, &mask ); + + if (mask & DFXL_DRAWLINE ) // DrawLine() is accelerated. DFXL_DRAWLINE + ctx->accel_drawline = 1; + if (mask & DFXL_FILLRECTANGLE) // FillRectangle() is accelerated. + ctx->accel_fillrect = 1; +} + + +/** + * function DirectFBVid_ShutdownWrapper + * - shutdown DirectFB module + **/ +u32 DirectFBVid_ShutdownWrapper(DirectFBVidCtx *ctx) +{ + // case where initialization is not done + if (!ctx->is_init) return 1; + + //~ ctx->keyboard->Release( ctx->keyboard ); + ctx->primary->Release( ctx->primary ); + ctx->events->Release( ctx->events ); + ctx->dfb->Release( ctx->dfb ); + ctx->is_init = 0; + //we use x11 thus it should be useless: + //system("stgfb_control /dev/fb0 a 0") + return 0; +} + + +/** + * Blit a surface + **/ +u32 DirectFBVid_TranslatePixelFormatFromGPAC(u32 gpacpf); +u32 DirectFBVid_BlitWrapper(DirectFBVidCtx *ctx, u32 video_src_width, u32 video_src_height, u32 video_src_pixel_format, char *video_src_buffer, s32 video_src_pitch_y, u32 src_wnd_x, u32 src_wnd_y, u32 src_wnd_w, u32 src_wnd_h, u32 dst_wnd_x, u32 dst_wnd_y, u32 dst_wnd_w, u32 dst_wnd_h, u32 overlay_type) +{ + DFBResult res; + DFBSurfaceDescription srcdesc; + IDirectFBSurface *src; + DFBRectangle dfbsrc, dfbdst; + + if (overlay_type != 0) return 1; + if (ctx->disable_display) return 0; + + memset(&srcdesc, 0, sizeof(srcdesc)); + + srcdesc.flags = DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT | DSDESC_PREALLOCATED; + srcdesc.width = video_src_width; + srcdesc.height = video_src_height; + srcdesc.pixelformat = DirectFBVid_TranslatePixelFormatFromGPAC(video_src_pixel_format); + srcdesc.preallocated[0].data = video_src_buffer; + srcdesc.preallocated[0].pitch = video_src_pitch_y; + + + switch (video_src_pixel_format) { + case GF_PIXEL_ARGB: //return DSPF_ARGB; + case GF_PIXEL_RGBA: //return DSPF_ARGB; + ctx->primary->SetBlittingFlags(ctx->primary, DSBLIT_BLEND_ALPHACHANNEL); + break; + default: + ctx->primary->SetBlittingFlags(ctx->primary, DSBLIT_NOFX); + } + + // create a surface with the new surface description + res = ctx->dfb->CreateSurface(ctx->dfb, &srcdesc, &src); + if (res != DFB_OK) { + //GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectFB] cannot create blit source surface for pixel format %s: %s (%d)\n", gf_4cc_to_str(video_src->pixel_format), DirectFBErrorString(res), res)); + return 1; + } + + + dfbsrc.x = src_wnd_x; + dfbsrc.y = src_wnd_y; + dfbsrc.w = src_wnd_w; + dfbsrc.h = src_wnd_h; + + // blit on the surface + if (!src_wnd_x && !src_wnd_y && (dst_wnd_w==src_wnd_w) && (dst_wnd_h==src_wnd_h)) { + ctx->primary->Blit(ctx->primary, src, &dfbsrc, dst_wnd_x, dst_wnd_y); + // blit an area scaled from the source to the destination rectangle + } else { + dfbdst.x = dst_wnd_x; + dfbdst.y = dst_wnd_y; + dfbdst.w = dst_wnd_w; + dfbdst.h = dst_wnd_h; + ctx->primary->StretchBlit(ctx->primary, src, &dfbsrc, &dfbdst); + } + + src->Release(src); + + return 0; +} + + +/** + * function DirectFBVid_ProcessMessageQueueWrapper + * - handle DirectFB events + * - key events + **/ +void directfb_translate_key(DFBInputDeviceKeyIdentifier DirectFBkey, u32 *flags, u32 *key_code); +u32 DirectFBVid_ProcessMessageQueueWrapper(DirectFBVidCtx *ctx, u8 *type, u32 *flags, u32 *key_code, s32 *x, s32 *y, u32 *button) +{ + DFBInputEvent directfb_evt; + + if (ctx->events->GetEvent( ctx->events, DFB_EVENT(&directfb_evt) ) == DFB_OK) + { + switch (directfb_evt.type) { + case DIET_KEYPRESS: + case DIET_KEYRELEASE: + directfb_translate_key(directfb_evt.key_id, flags, key_code); + *type = (directfb_evt.type == DIET_KEYPRESS) ? GF_EVENT_KEYDOWN : GF_EVENT_KEYUP; + break; + case DIET_BUTTONPRESS: + case DIET_BUTTONRELEASE: + *type = (directfb_evt.type == DIET_BUTTONPRESS) ? GF_EVENT_MOUSEUP : GF_EVENT_MOUSEDOWN; + switch(directfb_evt.button) { + case DIBI_LEFT: + *button = GF_MOUSE_LEFT; + break; + case DIBI_RIGHT: + *button = GF_MOUSE_RIGHT; + break; + case DIBI_MIDDLE: + *button = GF_MOUSE_MIDDLE; + break; + default: + //fprintf(stderr, "in here for others\n"); + break; + } + break; + case DIET_AXISMOTION: + *type = GF_EVENT_MOUSEMOVE; + ctx->mouse->GetXY(ctx->mouse, x, y); + default: + break; + } + + return 0; + } + + return 1; +} + + +/* Events translation */ +/** + * function DirectFBVid_TranslatePixelFormatToGPAC + * - translate pixel from DirectFb to GPAC + **/ +u32 DirectFBVid_TranslatePixelFormatToGPAC(u32 dfbpf) +{ + switch (dfbpf) { + case DSPF_RGB16: + return GF_PIXEL_RGB_565; + case DSPF_RGB555: + return GF_PIXEL_RGB_555; + case DSPF_RGB24: + return GF_PIXEL_RGB; + case DSPF_RGB32: + return GF_PIXEL_RGBX; + case DSPF_ARGB: + return GF_PIXEL_ARGB; + default: + return 0; + } +} + +/** + * function DirectFBVid_TranslatePixelFormatToGPAC + * - translate pixel from GPAC to DirectFB + **/ +u32 DirectFBVid_TranslatePixelFormatFromGPAC(u32 gpacpf) +{ + switch (gpacpf) { + case GF_PIXEL_RGB_565: + return DSPF_RGB16; + case GF_PIXEL_RGB_555 : + return DSPF_RGB555; + case GF_PIXEL_BGR: + return DSPF_RGB24; + case GF_PIXEL_RGB: + return DSPF_RGB24; + case GF_PIXEL_RGBX: + return DSPF_RGB32; + case GF_PIXEL_ARGB: + return DSPF_ARGB; + case GF_PIXEL_RGBA: + return DSPF_ARGB; + case GF_PIXEL_UYVY : + return DSPF_YUY2; + case GF_PIXEL_YUV: + return DSPF_YV12; + default: + ;//GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectFB] pixel format %s not supported\n", gf_4cc_to_str(gpacpf))); + } + + return 0; +} + + +/** + * function directfb_translate_key + * - translate key from DirectFB to GPAC + **/ +void directfb_translate_key(DFBInputDeviceKeyIdentifier DirectFBkey, u32 *flags, u32 *key_code) +{ + //~ fprintf(stderr, "DirectFBkey=%d\n", DirectFBkey); + + switch (DirectFBkey) { + case DIKI_BACKSPACE: + *key_code = GF_KEY_BACKSPACE; + //~ fprintf(stderr, "DIKI_BACKSPACE\n"); + break; + case DIKI_TAB: + *key_code = GF_KEY_TAB; + //~ fprintf(stderr, "DIKI_TAB\n"); + break; + case DIKI_ENTER: + *key_code = GF_KEY_ENTER; + //~ fprintf(stderr, "DIKI_ENTER\n"); + break; + case DIKI_ESCAPE: + *key_code = GF_KEY_ESCAPE; + //~ fprintf(stderr, "DIKI_ESCAPE\n"); + break; + case DIKI_SPACE: + *key_code = GF_KEY_SPACE; + //~ fprintf(stderr, "DIKI_SPACE\n"); + break; + case DIKI_SHIFT_L: + case DIKI_SHIFT_R: + *key_code = GF_KEY_SHIFT; + //~ fprintf(stderr, "DIKI_SHIFT_R/DIKI_SHIFT_L\n"); + break; + case DIKI_CONTROL_L: + case DIKI_CONTROL_R: + *key_code = GF_KEY_CONTROL; + //~ fprintf(stderr, "DIKI_CONTROL_L/DIKI_CONTROL_R\n"); + break; + case DIKI_ALT_L: + case DIKI_ALT_R: + *key_code = GF_KEY_ALT; + //~ fprintf(stderr, "DIKI_ALT_L/DIKI_ALT_R\n"); + break; + case DIKI_CAPS_LOCK: + *key_code = GF_KEY_CAPSLOCK; + //~ fprintf(stderr, "DIKI_CAPS_LOCK\n"); + break; + case DIKI_META_L: + case DIKI_META_R: + *key_code = GF_KEY_META; + break; + case DIKI_KP_EQUAL: + *key_code = GF_KEY_EQUALS; + break; + case DIKI_SUPER_L: + case DIKI_SUPER_R: + *key_code = GF_KEY_WIN; + break; + + /* alphabets */ + case DIKI_A: + *key_code = GF_KEY_A; + //~ fprintf(stderr, "DIKI_A\n"); + break; + case DIKI_B: + *key_code = GF_KEY_B; + //~ fprintf(stderr, "DIKI_B\n"); + break; + case DIKI_C: + *key_code = GF_KEY_C; + //~ fprintf(stderr, "DIKI_C\n"); + break; + case DIKI_D: + *key_code = GF_KEY_D; + //~ fprintf(stderr, "DIKI_D\n"); + break; + case DIKI_E: + *key_code = GF_KEY_E; + //~ fprintf(stderr, "DIKI_E\n"); + break; + case DIKI_F: + *key_code = GF_KEY_F; + break; + case DIKI_G: + *key_code = GF_KEY_G; + break; + case DIKI_H: + *key_code = GF_KEY_H; + break; + case DIKI_I: + *key_code = GF_KEY_I; + break; + case DIKI_J: + *key_code = GF_KEY_J; + break; + case DIKI_K: + *key_code = GF_KEY_K; + break; + case DIKI_L: + *key_code = GF_KEY_L; + break; + case DIKI_M: + *key_code = GF_KEY_M; + break; + case DIKI_N: + *key_code = GF_KEY_N; + break; + case DIKI_O: + *key_code = GF_KEY_O; + break; + case DIKI_P: + *key_code = GF_KEY_P; + break; + case DIKI_Q: + *key_code = GF_KEY_Q; + break; + case DIKI_R: + *key_code = GF_KEY_R; + break; + case DIKI_S: + *key_code = GF_KEY_S; + break; + case DIKI_T: + *key_code = GF_KEY_T; + break; + case DIKI_U: + *key_code = GF_KEY_U; + break; + case DIKI_V: + *key_code = GF_KEY_V; + break; + case DIKI_W: + *key_code = GF_KEY_W; + break; + case DIKI_X: + *key_code = GF_KEY_X; + break; + case DIKI_Y: + *key_code = GF_KEY_Y; + break; + case DIKI_Z: + *key_code = GF_KEY_Z; + break; + + case DIKI_PRINT: + *key_code = GF_KEY_PRINTSCREEN; + break; + case DIKI_SCROLL_LOCK: + *key_code = GF_KEY_SCROLL; + break; + case DIKI_PAUSE: + *key_code = GF_KEY_PAUSE; + break; + case DIKI_INSERT: + *key_code = GF_KEY_INSERT; + break; + case DIKI_DELETE: + *key_code = GF_KEY_DEL; + break; + case DIKI_HOME: + *key_code = GF_KEY_HOME; + break; + case DIKI_END: + *key_code = GF_KEY_END; + break; + case DIKI_PAGE_UP: + *key_code = GF_KEY_PAGEUP; + break; + case DIKI_PAGE_DOWN: + *key_code = GF_KEY_PAGEDOWN; + break; + + /* arrows */ + case DIKI_UP: + *key_code = GF_KEY_UP; + break; + case DIKI_DOWN: + *key_code = GF_KEY_DOWN; + break; + case DIKI_RIGHT: + *key_code = GF_KEY_RIGHT; + break; + case DIKI_LEFT: + *key_code = GF_KEY_LEFT; + break; + + /* extended numerical pad */ + case DIKI_NUM_LOCK: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_NUMLOCK; + break; + case DIKI_KP_DIV: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_SLASH; + break; + case DIKI_KP_MULT: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_STAR; + break; + case DIKI_KP_MINUS: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_HYPHEN; + break; + case DIKI_KP_PLUS: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_PLUS; + break; + case DIKI_KP_ENTER: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_ENTER; + break; + case DIKI_KP_DECIMAL: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_FULLSTOP; + break; + case DIKI_KP_0: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_0; + break; + case DIKI_KP_1: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_1; + break; + case DIKI_KP_2: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_2; + break; + case DIKI_KP_3: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_3; + break; + case DIKI_KP_4: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_4; + break; + case DIKI_KP_5: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_5; + break; + case DIKI_KP_6: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_6; + break; + case DIKI_KP_7: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_7; + break; + case DIKI_KP_8: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_8; + break; + case DIKI_KP_9: + *flags = GF_KEY_EXT_NUMPAD; + *key_code = GF_KEY_9; + break; + + /* Fn functions */ + case DIKI_F1: + *key_code = GF_KEY_F1; + break; + case DIKI_F2: + *key_code = GF_KEY_F2; + break; + case DIKI_F3: + *key_code = GF_KEY_F3; + break; + case DIKI_F4: + *key_code = GF_KEY_F4; + break; + case DIKI_F5: + *key_code = GF_KEY_F5; + break; + case DIKI_F6: + *key_code = GF_KEY_F6; + break; + case DIKI_F7: + *key_code = GF_KEY_F7; + break; + case DIKI_F8: + *key_code = GF_KEY_F8; + break; + case DIKI_F9: + *key_code = GF_KEY_F9; + break; + case DIKI_F10: + *key_code = GF_KEY_F10; + break; + case DIKI_F11: + *key_code = GF_KEY_F11; + break; + case DIKI_F12: + *key_code = GF_KEY_F12; + break; + + default: + *key_code = GF_KEY_UNIDENTIFIED; + break; + } +} + diff --git a/modules/droid_audio/droidaudio.c b/modules/droid_audio/droidaudio.c new file mode 100644 index 0000000..3ec1b9c --- /dev/null +++ b/modules/droid_audio/droidaudio.c @@ -0,0 +1,510 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / Wrapper + * + * 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 "javaenv.h" + +#include <gpac/modules/audio_out.h> +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#define STREAM_MUSIC 3 +#define CHANNEL_CONFIGURATION_MONO 2 +#define CHANNEL_CONFIGURATION_STEREO 3 +#define ENCODING_PCM_8BIT 3 +#define ENCODING_PCM_16BIT 2 +#define MODE_STREAM 1 +#define CHANNEL_OUT_MONO 4 +#define CHANNEL_IN_STEREO 12 +#define CHANNEL_IN_MONO 16 + + +/*for channel codes*/ +#include <gpac/constants.h> + +#ifdef GPAC_STATIC_MODULES + +JavaVM* GetJavaVM(); +JNIEnv* GetEnv(); + +#endif + + + +static const char android_device[] = "Android Default"; + +static jclass cAudioTrack = NULL; +static jobject mtrack = NULL; + +static jmethodID mAudioTrack; +static jmethodID setStereoVolume; +static jmethodID mGetMinBufferSize; +static jmethodID mPlay; +static jmethodID mStop; +static jmethodID mRelease; +static jmethodID mWriteB; +static jmethodID mWriteS; +static jmethodID mFlush; + +#include <android/log.h> +#define TAG "GPAC Android Audio" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) + + +/* Uncomment the next line if you want to debug */ +/* #define DROID_EXTREME_LOGS */ + +typedef struct +{ + JNIEnv* env; + + jobject mtrack; + + u32 num_buffers; + + u32 delay, total_length_ms; + + Bool force_config; + u32 cfg_num_buffers, cfg_duration; + + u32 sampleRateInHz; + u32 channelConfig; //AudioFormat.CHANNEL_OUT_MONO + u32 audioFormat; //AudioFormat.ENCODING_PCM_16BIT + s32 mbufferSizeInBytes; + u32 volume; + u32 pan; + jarray buff; +} DroidContext; + +//---------------------------------------------------------------------- +//---------------------------------------------------------------------- +// Called by the main thread +static GF_Err WAV_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + JNIEnv* env = GetEnv(); + LOGV("[Android Audio] Setup for %d buffers", num_buffers); + + ctx->force_config = (num_buffers && total_duration) ? 1 : 0; + ctx->cfg_num_buffers = num_buffers; + if (ctx->cfg_num_buffers <= 1) ctx->cfg_num_buffers = 2; + ctx->cfg_duration = total_duration; + if (!ctx->force_config) ctx->num_buffers = 1; + ctx->volume = 100; + ctx->pan = 50; + + if (!cAudioTrack) { + cAudioTrack = (*env)->FindClass(env, "android/media/AudioTrack"); + if (!cAudioTrack) { + return GF_NOT_SUPPORTED; + } + + cAudioTrack = (*env)->NewGlobalRef(env, cAudioTrack); + + mAudioTrack = (*env)->GetMethodID(env, cAudioTrack, "<init>", "(IIIIII)V"); + mGetMinBufferSize = (*env)->GetStaticMethodID(env, cAudioTrack, "getMinBufferSize", "(III)I"); + mPlay = (*env)->GetMethodID(env, cAudioTrack, "play", "()V"); + mStop = (*env)->GetMethodID(env, cAudioTrack, "stop", "()V"); + mRelease = (*env)->GetMethodID(env, cAudioTrack, "release", "()V"); + mWriteB = (*env)->GetMethodID(env, cAudioTrack, "write", "([BII)I"); + mWriteS = (*env)->GetMethodID(env, cAudioTrack, "write", "([SII)I"); + mFlush = (*env)->GetMethodID(env, cAudioTrack, "flush", "()V"); + setStereoVolume = (*env)->GetMethodID(env, cAudioTrack, "setStereoVolume", "(FF)I"); + } + + return GF_OK; +} +//---------------------------------------------------------------------- +// Called by the audio thread +static void WAV_Shutdown(GF_AudioOutput *dr) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + JNIEnv* env = NULL; + jint res = 0; + + LOGV("[Android Audio] Shutdown START."); + + res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + } + + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mStop); + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mRelease); + + (*env)->PopLocalFrame(env, NULL); + + (*env)->DeleteGlobalRef(env, ctx->buff); + (*env)->DeleteGlobalRef(env, mtrack); + (*env)->DeleteGlobalRef(env, cAudioTrack); + + //if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->DetachCurrentThread(GetJavaVM()); + //} + + LOGV("[Android Audio] Shutdown DONE."); +} + + +/*we assume what was asked is what we got*/ +/* Called by the audio thread */ +static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u64 channel_cfg) +{ + JNIEnv* env = NULL; + u32 i; + DroidContext *ctx = (DroidContext *)dr->opaque; + + LOGI("[Android Audio] Configure Output for %u channels...", *NbChannels); + + if (!ctx) return GF_BAD_PARAM; + + ctx->sampleRateInHz = *SampleRate; + ctx->channelConfig = (*NbChannels == 1) ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO; //AudioFormat.CHANNEL_CONFIGURATION_MONO + ctx->audioFormat = (*nbBitsPerSample == 8)? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT; //AudioFormat.ENCODING_PCM_16BIT + + // Get the java environment in the new thread + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + ctx->env = env; + LOGV("[Android Audio] SampleRate : %d",ctx->sampleRateInHz); + LOGV("[Android Audio] BitPerSample : %d", *nbBitsPerSample); + + (*env)->PushLocalFrame(env, 2); + + ctx->num_buffers = 1; + ctx->mbufferSizeInBytes = (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + ctx->sampleRateInHz, ctx->channelConfig, ctx->audioFormat); + + //ctx->mbufferSizeInBytes *= 3; + + LOGV("[Android Audio] Buffer Size : %d", ctx->mbufferSizeInBytes); + + i = 1; + if ( ctx->channelConfig == CHANNEL_CONFIGURATION_STEREO ) + i *= 2; + if ( ctx->audioFormat == ENCODING_PCM_16BIT ) + i *= 2; + + ctx->total_length_ms = 1000 * ctx->num_buffers * ctx->mbufferSizeInBytes / i / ctx->sampleRateInHz; + + LOGV("[Android Audio] Buffer Length ms : %d", ctx->total_length_ms); + + /*initial delay is full buffer size*/ + ctx->delay = ctx->total_length_ms; + + mtrack = (*env)->NewObject(env, cAudioTrack, mAudioTrack, STREAM_MUSIC, ctx->sampleRateInHz, + ctx->channelConfig, ctx->audioFormat, ctx->mbufferSizeInBytes, MODE_STREAM); //AudioTrack.MODE_STREAM + if (mtrack) { + mtrack = (*env)->NewGlobalRef(env, mtrack); + ctx->mtrack = mtrack; + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mPlay); +// (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mStop); + } else { + LOGV("[Android Audio] mtrack = %p", mtrack); + return GF_NOT_SUPPORTED; + } + + if ( ctx->audioFormat == ENCODING_PCM_8BIT ) + ctx->buff = (*env)->NewByteArray(env, ctx->mbufferSizeInBytes); + else + ctx->buff = (*env)->NewShortArray(env, ctx->mbufferSizeInBytes/2); + if ( ctx->buff ) { + ctx->buff = (*env)->NewGlobalRef(env, ctx->buff); + } else { + LOGV("[Android Audio] ctx->buff = %p", ctx->buff ); + return GF_NOT_SUPPORTED; + } + + LOGV("[Android Audio] ConfigureOutput DONE."); + return GF_OK; +} + +/* Called by the audio thread */ +static void WAV_WriteAudio(GF_AudioOutput *dr) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + if (!ctx) + return; + JNIEnv* env = ctx->env; + u32 written; + void* pBuffer; + if (!env) + return; +#ifdef DROID_EXTREME_LOGS + LOGV("[Android Audio] WAV_WriteAudio() : entering",ctx->sampleRateInHz); +#endif /* DROID_EXTREME_LOGS */ + + if ( ctx->audioFormat == ENCODING_PCM_8BIT ) + pBuffer = (*env)->GetByteArrayElements(env, ctx->buff, NULL); + else + pBuffer = (*env)->GetShortArrayElements(env, ctx->buff, NULL); + + if (pBuffer) + { + written = dr->FillBuffer(dr->audio_renderer, pBuffer, ctx->mbufferSizeInBytes); + + if ( ctx->audioFormat == ENCODING_PCM_8BIT ) + (*env)->ReleaseByteArrayElements(env, ctx->buff, pBuffer, 0); + else + (*env)->ReleaseShortArrayElements(env, ctx->buff, pBuffer, 0); + + if (written) + { + if ( ctx->audioFormat == ENCODING_PCM_8BIT ) + (*env)->CallNonvirtualIntMethod(env, mtrack, cAudioTrack, mWriteB, ctx->buff, 0, ctx->mbufferSizeInBytes); + else + (*env)->CallNonvirtualIntMethod(env, mtrack, cAudioTrack, mWriteS, ctx->buff, 0, ctx->mbufferSizeInBytes/2); + } + } + else + { + LOGV("[Android Audio] Failed to get pointer to array bytes = %p", pBuffer); + } +#ifdef DROID_EXTREME_LOGS + LOGV("[Android Audio] WAV_WriteAudio() : done",ctx->sampleRateInHz); +#endif /* DROID_EXTREME_LOGS */ +} + +/* Called by the main thread */ +static void WAV_Play(GF_AudioOutput *dr, u32 PlayType) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + JNIEnv* env = GetEnv(); + + LOGV("[Android Audio] Play: %d\n", PlayType); + + switch ( PlayType ) + { + case 0: + // Stop playing + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mStop); + // Clear the internal buffers + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mFlush); + break; + case 2: + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mFlush); + case 1: + (*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mPlay); + break; + default: + LOGW("[Android Audio] Unknown Play method=%d.\n", PlayType); + } + LOGV("[Android Audio] Play DONE (%d).\n", PlayType); +} + +static void WAV_UpdateVolume(DroidContext *ctx) { + float lV, rV; + JNIEnv* env = GetEnv(); + if (!ctx) + return; + if (ctx->pan > 100) + ctx->pan = 100; + lV =rV = ctx->volume / 100.0; + if (ctx->pan > 50) { + float m = (100 - ctx->pan) / 50.0; + lV*=m; + } else if (ctx->pan < 50) { + float m = ctx->pan / 50.0; + rV*=m; + } + if (env && setStereoVolume && mtrack && cAudioTrack) { + int success; + if (0!= (success=((*env)->CallNonvirtualIntMethod(env, mtrack, cAudioTrack, setStereoVolume, lV, rV)))) + LOGE("SetVolume(%f,%f) returned Error code %d", lV, rV, success ); + } else { + LOGD("SetVolume(%f,%f)", lV, rV ); + } +} + +static void WAV_SetVolume(GF_AudioOutput *dr, u32 Volume) { + DroidContext *ctx = (DroidContext *)dr->opaque; + ctx->volume = Volume; + WAV_UpdateVolume(ctx); +} + +static void WAV_SetPan(GF_AudioOutput *dr, u32 Pan) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + WAV_UpdateVolume(ctx); +} + +/* Called by the audio thread */ +static GF_Err WAV_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample) +{ +#ifdef TEST_QUERY_SAMPLE + DroidContext *ctx = (DroidContext *)dr->opaque; + JNIEnv* env = ctx->env; + u32 sampleRateInHz, channelConfig, audioFormat; +#endif + + LOGV("Query sample=%d", *desired_samplerate ); + +#ifdef TEST_QUERY_SAMPLE + sampleRateInHz = *desired_samplerate; + channelConfig = (*NbChannels == 1) ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO; + audioFormat = (*nbBitsPerSample == 8)? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT; + + LOGV3("[Android Audio] Query: SampleRate ChannelConfig AudioFormat: %d %d %d \n", + sampleRateInHz, + (channelConfig == CHANNEL_CONFIGURATION_MONO)? 1 : 2, + (ctx->audioFormat == ENCODING_PCM_8BIT)? 8 : 16); + + switch (*desired_samplerate) { + case 11025: + *desired_samplerate = 11025; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + case 22050: + *desired_samplerate = 22050; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + break; + case 8000: + *desired_samplerate = 8000; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + case 16000: + *desired_samplerate = 16000; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + case 32000: + *desired_samplerate = 32000; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + break; + case 24000: + *desired_samplerate = 24000; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + case 48000: + *desired_samplerate = 48000; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + break; + case 44100: + *desired_samplerate = 44100; + if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize, + *desired_samplerate, channelConfig, audioFormat) > 0 ) + return GF_OK; + break; + default: + break; + } +#endif + + return GF_OK; +} + +static u32 WAV_GetAudioDelay(GF_AudioOutput *dr) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + + return ctx->delay; +} + +static u32 WAV_GetTotalBufferTime(GF_AudioOutput *dr) +{ + DroidContext *ctx = (DroidContext *)dr->opaque; + + return ctx->total_length_ms; +} + +//---------------------------------------------------------------------- +void *NewWAVRender() +{ + DroidContext *ctx; + GF_AudioOutput *driv; + ctx = gf_malloc(sizeof(DroidContext)); + memset(ctx, 0, sizeof(DroidContext)); + ctx->num_buffers = 1; + ctx->pan = 50; + ctx->volume = 100; + driv = gf_malloc(sizeof(GF_AudioOutput)); + memset(driv, 0, sizeof(GF_AudioOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "Android Audio Output", "gpac distribution") + + driv->opaque = ctx; + + driv->SelfThreaded = 0; + driv->Setup = WAV_Setup; + driv->Shutdown = WAV_Shutdown; + driv->Configure = WAV_ConfigureOutput; + driv->GetAudioDelay = WAV_GetAudioDelay; + driv->GetTotalBufferTime = WAV_GetTotalBufferTime; + driv->SetVolume = WAV_SetVolume; + driv->SetPan = WAV_SetPan; + driv->Play = WAV_Play; + driv->QueryOutputSampleRate = WAV_QueryOutputSampleRate; + driv->WriteAudio = WAV_WriteAudio; + return driv; +} +//---------------------------------------------------------------------- +void DeleteWAVRender(void *ifce) +{ + GF_AudioOutput *dr = (GF_AudioOutput *) ifce; + if (!ifce) + return; + gf_free(dr); +} +//---------------------------------------------------------------------- + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) return NewWAVRender(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_AUDIO_OUTPUT_INTERFACE: + DeleteWAVRender((GF_AudioOutput *) ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( droid_audio ) diff --git a/modules/droid_audio/javaenv.c b/modules/droid_audio/javaenv.c new file mode 100644 index 0000000..ab81805 --- /dev/null +++ b/modules/droid_audio/javaenv.c @@ -0,0 +1,49 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / Wrapper + * + * 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 "javaenv.h" + +static JavaVM* javaVM = 0; + +//---------------------------------------------------------------------- +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + javaVM = vm; + return JNI_VERSION_1_2; +} +//---------------------------------------------------------------------- +JNIEnv* GetEnv() +{ + JNIEnv* env = 0; + //if (javaVM) javaVM->GetEnv((void**)&env, JNI_VERSION_1_2); + if (javaVM) (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2); + return env; +} +//---------------------------------------------------------------------- +JavaVM* GetJavaVM() +{ + return javaVM; +} +//---------------------------------------------------------------------- diff --git a/modules/droid_audio/javaenv.h b/modules/droid_audio/javaenv.h new file mode 100644 index 0000000..4091018 --- /dev/null +++ b/modules/droid_audio/javaenv.h @@ -0,0 +1,34 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / Wrapper + * + * 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 JAVA_ENV +#define JAVA_ENV + +#include <jni.h> + +JavaVM* GetJavaVM(); +JNIEnv* GetEnv(); + +#endif //JAVA_ENV diff --git a/modules/droid_cam/droid_cam.c b/modules/droid_cam/droid_cam.c new file mode 100644 index 0000000..0c1f221 --- /dev/null +++ b/modules/droid_cam/droid_cam.c @@ -0,0 +1,725 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / Android camera module + * + * 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/terminal.h> +#include <gpac/internal/terminal_dev.h> +#include <gpac/internal/compositor_dev.h> +#include <gpac/modules/codec.h> +#include <gpac/constants.h> +#include <gpac/modules/service.h> +#include <gpac/thread.h> +#include <gpac/media_tools.h> + +#include <jni.h> +#include <android/log.h> + +#define LOG_TAG "ANDROID_CAMERA" +#ifdef ANDROID +# define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +# define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#else +# define QUOTEME_(x) #x +# define QUOTEME(x) QUOTEME_(x) +# define LOGI(...) fprintf(stderr, "I/" LOG_TAG " (" __FILE__ ":" QUOTEME(__LINE__) "): " __VA_ARGS__) +# define LOGE(...) fprintf(stderr, "E/" LOG_TAG "(" ")" __VA_ARGS__) +#endif + +static JavaVM* javaVM = 0; +static jclass camCtrlClass; +static jmethodID cid; +static jmethodID startCamera; +static jmethodID stopCamera; +static jmethodID startProcessing; +static jmethodID stopProcessing; +static jmethodID getImageFormat; +static jmethodID getImageHeight; +static jmethodID getImageWidth; +static jmethodID getBitsPerPixel; + +#ifndef GPAC_STATIC_MODULES +jint JNI_OnLoad(JavaVM* vm, void* reserved) +#else +jint static_JNI_OnLoad(JavaVM* vm, void* reserved) +#endif +{ + JNIEnv* env = 0; + javaVM = vm; + + if ( (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2) != JNI_OK ) + return -1; + + // Get the class and its methods in the main env + + // Get the CameraController class + // This is just a local refenrece. Cannot be used in the other JNI calls. + camCtrlClass = (*env)->FindClass(env, "com/gpac/Osmo4/Preview"); + if (camCtrlClass == 0) + { + LOGE("CameraController class = null [myCamera.c, startCameraNative()]"); + return -1; + } + + // Get Global Reference to be able to use the class + camCtrlClass = (*env)->NewGlobalRef(env, camCtrlClass); + if ( camCtrlClass == 0 ) + { + LOGE("[MPEG-V_IN] Cannot create Global Reference\n"); + return -1; + } + + // Get the method ID for the CameraController constructor. + cid = (*env)->GetMethodID(env, camCtrlClass, "<init>", "()V"); + if (cid == 0) + { + LOGE("Method ID for CameraController constructor is null [myCamera.c, startCameraNative()]"); + return -1; + } + + // Get startCamera() method from class CameraController + startCamera = (*env)->GetMethodID(env, camCtrlClass, "initializeCamera", "(Z)Z"); + if (startCamera == 0) + { + LOGE("[ANDROID_CAMERA] Function startCamera not found"); + return -1; + } + + stopCamera = (*env)->GetMethodID(env, camCtrlClass, "stopCamera", "()V"); + if (stopCamera == 0) + { + LOGE("[ANDROID_CAMERA] Function stopCamera not found"); + return -1; + } + + startProcessing = (*env)->GetMethodID(env, camCtrlClass, "resumePreview", "()V"); + if (startProcessing == 0) + { + LOGE("[ANDROID_CAMERA] Function startProcessing not found"); + return -1; + } + + stopProcessing = (*env)->GetMethodID(env, camCtrlClass, "pausePreview", "()V"); + if (stopProcessing == 0) + { + LOGE("[ANDROID_CAMERA] Function stopProcessing not found"); + return -1; + } + + getImageFormat = (*env)->GetMethodID(env, camCtrlClass, "getPreviewFormat", "()I"); + if (getImageFormat == 0) + { + LOGE("[ANDROID_CAMERA] Function getImageFormat not found"); + return -1; + } + + getImageHeight = (*env)->GetMethodID(env, camCtrlClass, "getImageHeight", "()I"); + if (getImageHeight == 0) + { + LOGE("[ANDROID_CAMERA] Function getImageHeight not found"); + return -1; + } + + getImageWidth = (*env)->GetMethodID(env, camCtrlClass, "getImageWidth", "()I"); + if (getImageWidth == 0) + { + LOGE("[ANDROID_CAMERA] Function getImageWidth not found"); + return -1; + } + + getBitsPerPixel = (*env)->GetMethodID(env, camCtrlClass, "getBitsPerPixel", "()I"); + if (getBitsPerPixel == 0) + { + LOGE("[ANDROID_CAMERA] Function getBitsPerPixel not found"); + return -1; + } + + return JNI_VERSION_1_2; +} + +#ifndef GPAC_STATIC_MODULES +//---------------------------------------------------------------------- +JavaVM* GetJavaVM() +{ + return javaVM; +} + +JNIEnv* GetEnv() +{ + JNIEnv* env; + if ( (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2) != JNI_OK ) + return NULL; + + return env; +} +#else +JavaVM* GetJavaVM(); +JNIEnv* GetEnv(); + +#endif + +void JNI_OnUnload(JavaVM *vm, void *reserved) +{ + JNIEnv* env = 0; + + if ( (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_2) != JNI_OK ) + return; + + (*env)->DeleteGlobalRef(env, camCtrlClass); +} +//---------------------------------------------------------------------- + + +//#define CAM_PIXEL_FORMAT GF_PIXEL_NV21 +//GF_PIXEL_RGB_32 +//#define CAM_PIXEL_SIZE 1.5f +//4 +#define CAM_WIDTH 640 +#define CAM_HEIGHT 480 + +//GF_PIXEL_RGB_24; + +typedef struct +{ + GF_InputService *input; + /*the service we're responsible for*/ + GF_ClientService *service; + LPNETCHANNEL channel; + + /*input file*/ + u32 time_scale; + + u32 base_track_id; + + struct _tag_terminal *term; + + u32 cntr; + + u32 width; + u32 height; + + Bool started; + + JNIEnv* env; + u8 isAttached; + jclass camCtrlClass; + jmethodID cid; + jobject camCtrlObj; + jmethodID startCamera; + jmethodID stopCamera; + jmethodID startProcessing; + jmethodID stopProcessing; + jmethodID getImageFormat; + jmethodID getImageHeight; + jmethodID getImageWidth; + jmethodID getBitsPerPixel; + +} ISOMReader; + +ISOMReader* globReader; + +void loadCameraControler(ISOMReader *read); +void camStartCamera(ISOMReader *read); +void camStopCamera(ISOMReader *read); + +Bool CAM_CanHandleURL(GF_InputService *plug, const char *url) +{ + if (!strnicmp(url, "hw://camera", 11)) return 1; + + return 0; +} + +void unloadCameraControler(ISOMReader *read) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] unloadCameraControler: %d\n", gf_th_id())); + if ( read->isAttached ) + { + //(*rc->env)->PopLocalFrame(rc->env, NULL); + (*GetJavaVM())->DetachCurrentThread(GetJavaVM()); + read->isAttached = 0; + } + + read->env = NULL; +} + +u32 unregisterFunc(void* data) +{ + unloadCameraControler(globReader); + return 0; +} + +void loadCameraControler(ISOMReader *read) +{ + JNIEnv* env = NULL; + jint res = 0; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] loadCameraControler: %d\n", gf_th_id())); + + // Get the JNI interface pointer + res = (*GetJavaVM())->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] The current thread is not attached to the VM, assuming native thread\n")); + res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + if ( res ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] Attach current thread failed: %d\n", res)); + return; + } + gf_register_before_exit_function(gf_th_current(), unregisterFunc); + read->isAttached = 1; + //(*rc->env)->PushLocalFrame(rc->env, 2); + } + else if ( res == JNI_EVERSION ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] The specified version is not supported\n")); + return; + } + + read->env = env; + read->camCtrlClass = camCtrlClass; + read->cid = cid; + read->startCamera = startCamera; + read->stopCamera = stopCamera; + read->startProcessing = startProcessing; + read->stopProcessing = stopProcessing; + read->getImageFormat = getImageFormat; + read->getImageHeight = getImageHeight; + read->getImageWidth = getImageWidth; + read->getBitsPerPixel = getBitsPerPixel; + + // Create the object. + read->camCtrlObj = (*env)->NewObject(env, read->camCtrlClass, read->cid); + if (read->camCtrlObj == 0) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("CameraController object creation failed. [myCamera.c, startCameraNative()]")); + return; + } +} + +GF_Err CAM_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) +{ + ISOMReader *read; + if (!plug || !plug->priv || !serv) return GF_SERVICE_ERROR; + read = (ISOMReader *) plug->priv; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_ConnectService: %d\n", gf_th_id())); + + read->input = plug; + read->service = serv; + read->base_track_id = 1; + read->time_scale = 1000; + + read->term = serv->term; + + loadCameraControler(read); + + camStartCamera(read); + + /*reply to user*/ + gf_service_connect_ack(serv, NULL, GF_OK); + //if (read->no_service_desc) isor_declare_objects(read); + + return GF_OK; +} + +GF_Err CAM_CloseService(GF_InputService *plug) +{ + GF_Err reply; + ISOMReader *read; + if (!plug || !plug->priv) return GF_SERVICE_ERROR; + read = (ISOMReader *) plug->priv; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_CloseService: %d\n", gf_th_id())); + + reply = GF_OK; + + (*GetEnv())->DeleteLocalRef( GetEnv(), read->camCtrlObj ); + read->camCtrlObj = NULL; + + //unloadCameraControler(read); + + gf_service_disconnect_ack(read->service, NULL, reply); + return GF_OK; +} + +u32 getWidth(ISOMReader *read); +u32 getHeight(ISOMReader *read); +u32 getFormat(ISOMReader *read); +u32 getBitsPerPix(ISOMReader *read); + +static GF_Descriptor *CAM_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) +{ + u32 trackID; + ISOMReader *read; + char *buf; + u32 buf_size; + if (!plug || !plug->priv) return NULL; + read = (ISOMReader *) plug->priv; + + trackID = read->base_track_id; + read->base_track_id = 0; + + if (trackID && (expect_type==GF_MEDIA_OBJECT_VIDEO) ) { + GF_BitStream *bs; + GF_ESD *esd; + GF_ObjectDescriptor *od; + od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); + od->objectDescriptorID = 1; + + esd = gf_odf_desc_esd_new(0); + esd->slConfig->timestampResolution = 1000; + esd->decoderConfig->streamType = GF_STREAM_VISUAL; + esd->ESID = 1; + esd->decoderConfig->objectTypeIndication = GPAC_OTI_RAW_MEDIA_STREAM; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + read->width = getWidth(read); + read->height = getHeight(read); + + gf_bs_write_u32(bs, getFormat(read)); // fourcc + gf_bs_write_u16(bs, read->width); // width + gf_bs_write_u16(bs, read->height); // height + gf_bs_write_u32(bs, read->width * read->height * getBitsPerPix(read) / 8); // framesize + gf_bs_write_u32(bs, read->width); // stride + + gf_bs_align(bs); + gf_bs_get_content(bs, &buf, &buf_size); + gf_bs_del(bs); + + esd->decoderConfig->decoderSpecificInfo->data = buf; + esd->decoderConfig->decoderSpecificInfo->dataLength = buf_size; + + gf_list_add(od->ESDescriptors, esd); + return (GF_Descriptor *) od; + } + + return NULL; +} + + + + +GF_Err CAM_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) +{ + GF_Err e; + ISOMReader *read; + if (!plug || !plug->priv) return GF_SERVICE_ERROR; + read = (ISOMReader *) plug->priv; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_ConnectChannel: %d\n", gf_th_id())); + + e = GF_OK; + if (upstream) { + e = GF_ISOM_INVALID_FILE; + } + + read->channel = channel; + + gf_service_connect_ack(read->service, channel, e); + return e; +} + +GF_Err CAM_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) +{ + GF_Err e; + ISOMReader *read; + if (!plug || !plug->priv) return GF_SERVICE_ERROR; + read = (ISOMReader *) plug->priv; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_DisconnectChannel: %d\n", gf_th_id())); + + e = GF_OK; + + camStopCamera(read); + + gf_service_disconnect_ack(read->service, channel, e); + return e; +} + +void Java_com_gpac_Osmo4_Preview_processFrameBuf( JNIEnv* env, jobject thiz, jbyteArray arr) +{ + ISOMReader* ctx = globReader; + if ( ctx + && ctx->started + && ctx->term + && ctx->term->compositor + && ctx->term->compositor->audio_renderer + ) { + + u8* data; + u32 datasize; + GF_SLHeader hdr; + u32 cts = 0; + //u32 convTime = 0; + //u32 j = 0; + jbyte *jdata; + jsize len; + + len = (*env)->GetArrayLength(env, arr); + if ( len <= 0 ) return; + jdata = (*env)->GetByteArrayElements(env,arr,0); + + //convTime = gf_term_get_time(ctx->term); + + data = (u8*)jdata;//(u8*)decodeYUV420SP((char*)jdata, ctx->width, ctx->height); // + datasize = len;//ctx->width * ctx->height * CAM_PIXEL_SIZE;// + + cts = gf_term_get_time(ctx->term); + + //convTime = cts - convTime; + + memset(&hdr, 0, sizeof(hdr)); + hdr.compositionTimeStampFlag = 1; + hdr.compositionTimeStamp = cts; + gf_service_send_packet(ctx->service, ctx->channel, (void*)data, datasize, &hdr, GF_OK); + + //gf_free(data); + + (*env)->ReleaseByteArrayElements(env,arr,jdata,JNI_ABORT); + + //GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Camera Frame Sent %d\n", gf_sys_clock())); + } +} + +void CallCamMethod(ISOMReader *read, jmethodID methodID) +{ + JNIEnv* env = NULL; + jint res = 0; + u8 isAttached = 0; + + // Get the JNI interface pointer + res = (*GetJavaVM())->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] The current thread is not attached to the VM, assuming native thread\n")); + res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + if ( res ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] Attach current thread failed: %d\n", res)); + return; + } + if ( env != read->env ) + isAttached = 1; + } + + (*env)->CallNonvirtualVoidMethod(env, read->camCtrlObj, read->camCtrlClass, methodID); + + if (isAttached) + { + (*GetJavaVM())->DetachCurrentThread(GetJavaVM()); + } +} + +void camStartCamera(ISOMReader *read) +{ + JNIEnv* env; + jboolean isPortrait = JNI_FALSE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] startCamera: %d\n", gf_th_id())); + + // Get the JNI interface pointer + env = read->env; + + (*env)->CallNonvirtualBooleanMethod(env, read->camCtrlObj, read->camCtrlClass, read->startCamera, isPortrait); +} + +void camStopCamera(ISOMReader *read) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] stopCamera: %d\n", gf_th_id())); + + CallCamMethod(read, read->stopCamera); +} + +void pauseCamera(ISOMReader *read) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] pauseCamera: %d\n", gf_th_id())); + + read->started = 0; + CallCamMethod(read, read->stopProcessing); +} + +void resumeCamera(ISOMReader *read) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] resumeCamera: %d\n", gf_th_id())); + + read->started = 1; + CallCamMethod(read, read->startProcessing); +} + +u32 getWidth(ISOMReader *read) +{ + JNIEnv* env; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getWidth: %d\n", gf_th_id())); + + // Get the JNI interface pointer + env = read->env; + + return (*env)->CallNonvirtualIntMethod(env, read->camCtrlObj, read->camCtrlClass, read->getImageWidth); +} + +u32 getHeight(ISOMReader *read) +{ + JNIEnv* env; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getHeight: %d\n", gf_th_id())); + + // Get the JNI interface pointer + env = read->env; + + return (*env)->CallNonvirtualIntMethod(env, read->camCtrlObj, read->camCtrlClass, read->getImageHeight); +} + +u32 getFormat(ISOMReader *read) +{ + JNIEnv* env; + u32 pixel_format; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getFormat: %d\n", gf_th_id())); + + // Get the JNI interface pointer + env = read->env; + + pixel_format = (*env)->CallNonvirtualIntMethod(env, read->camCtrlObj, read->camCtrlClass, read->getImageFormat); + switch (pixel_format) { + case 17: //NV21 + return GF_PIXEL_NV21; + case 842094169: //YV12 + return GF_PIXEL_YV12; + default: + return 0; //should never be here + } +} + +u32 getBitsPerPix(ISOMReader *read) +{ + JNIEnv* env; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getBitsPerPix: %d\n", gf_th_id())); + + // Get the JNI interface pointer + env = read->env; + + return (*env)->CallNonvirtualIntMethod(env, read->camCtrlObj, read->camCtrlClass, read->getBitsPerPixel); +} + +GF_Err CAM_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) +{ + ISOMReader *read; + if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR; + read = (ISOMReader *) plug->priv; + + if (com->command_type==GF_NET_SERVICE_INFO) { + return GF_OK; + } + if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) { + return GF_NOT_SUPPORTED; + } + if (!com->base.on_channel) return GF_NOT_SUPPORTED; + + switch (com->command_type) { + case GF_NET_CHAN_INTERACTIVE: + return GF_OK; + case GF_NET_CHAN_BUFFER: + com->buffer.max = com->buffer.min = 0; + return GF_OK; + case GF_NET_CHAN_PLAY: + resumeCamera(read); + return GF_OK; + case GF_NET_CHAN_STOP: + pauseCamera(read); + return GF_OK; + /*nothing to do on MP4 for channel config*/ + case GF_NET_CHAN_CONFIG: + return GF_OK; + default: + break; + } + return GF_NOT_SUPPORTED; +} + +GF_InputService *CAM_client_load() +{ + ISOMReader *reader; + GF_InputService *plug; + GF_SAFEALLOC(plug, GF_InputService); + GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC Camera Plugin", "gpac distribution") + plug->CanHandleURL = CAM_CanHandleURL; + plug->ConnectService = CAM_ConnectService; + plug->CloseService = CAM_CloseService; + plug->GetServiceDescriptor = CAM_GetServiceDesc; + plug->ConnectChannel = CAM_ConnectChannel; + plug->DisconnectChannel = CAM_DisconnectChannel; + plug->ServiceCommand = CAM_ServiceCommand; + + GF_SAFEALLOC(reader, ISOMReader); + plug->priv = reader; + globReader = reader; + + +#ifdef GPAC_STATIC_MODULES + static_JNI_OnLoad(GetJavaVM(), NULL); +#endif + + return plug; +} + +void CAM_client_del(GF_BaseInterface *bi) +{ + GF_InputService *plug = (GF_InputService *) bi; + ISOMReader *read = (ISOMReader *)plug->priv; + + gf_free(read); + gf_free(bi); +} + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_NET_CLIENT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_NET_CLIENT_INTERFACE) + return (GF_BaseInterface *)CAM_client_load(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_NET_CLIENT_INTERFACE: + CAM_client_del(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( droid_cam ) diff --git a/modules/droid_mpegv/droid_mpegv.c b/modules/droid_mpegv/droid_mpegv.c new file mode 100644 index 0000000..dc096c3 --- /dev/null +++ b/modules/droid_mpegv/droid_mpegv.c @@ -0,0 +1,495 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / MPEG-V Input sensor for android + * + * 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 <jni.h> +#include <android/log.h> + +/*driver interfaces*/ + +#include <gpac/list.h> +#include <gpac/constants.h> + +#include <gpac/setup.h> + +#include <gpac/modules/codec.h> +#include <gpac/scenegraph_vrml.h> + +#include <gpac/thread.h> + +#define LOG_TAG "MPEG-V_IN" +#ifdef ANDROID +# define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +# define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#else +# define QUOTEME_(x) #x +# define QUOTEME(x) QUOTEME_(x) +# define LOGI(...) fprintf(stderr, "I/" LOG_TAG " (" __FILE__ ":" QUOTEME(__LINE__) "): " __VA_ARGS__) +# define LOGE(...) fprintf(stderr, "E/" LOG_TAG "(" ")" __VA_ARGS__) +#endif + +JNIEXPORT void JNICALL Java_com_gpac_Osmo4_MPEGVSensor_sendData( JNIEnv* env, jobject thiz, jint ptr, jstring data); + +static JavaVM* javaVM = 0; +static jclass sensCtrlClass; +static jmethodID cid; +static jmethodID startSensor; +static jmethodID stopSensor; + +#ifndef GPAC_STATIC_MODULES + +//---------------------------------------------------------------------- +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = 0; + javaVM = vm; + + if ( (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2) != JNI_OK ) + return -1; + + // Get the class and its methods in the main env + + // Get the CameraController class + // This is just a local refenrece. Cannot be used in the other JNI calls. + sensCtrlClass = (*env)->FindClass(env, "com/gpac/Osmo4/MPEGVSensor"); + if (sensCtrlClass == 0) + { + LOGE("[MPEG-V_IN] Class MPEGVSensor not found\n"); + return -1; + } + + // Get Global Reference to be able to use the class + sensCtrlClass = (*env)->NewGlobalRef(env, sensCtrlClass); + if ( sensCtrlClass == 0 ) + { + LOGE("[MPEG-V_IN] Caanot create Global Reference\n"); + return -1; + } + + // Get the method ID for the CameraController constructor. + cid = (*env)->GetMethodID(env, sensCtrlClass, "<init>", "()V"); + if (cid == 0) + { + LOGE("[MPEG-V_IN] MPEGVSensor Constructor not found\n"); + return -1; + } + + // Get startCamera() method from class CameraController + startSensor = (*env)->GetMethodID(env, sensCtrlClass, "startSensor", "(II)V"); + if (startSensor == 0) + { + LOGE("[MPEG-V_IN] Function startSensor not found\n"); + return -1; + } + + stopSensor = (*env)->GetMethodID(env, sensCtrlClass, "stopSensor", "()V"); + if (stopSensor == 0) + { + LOGE("[MPEG-V_IN] Function stopSensor not found\n"); + return -1; + } + + return JNI_VERSION_1_2; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) +{ + JNIEnv* env = 0; + + if ( (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_2) != JNI_OK ) + return; + + (*env)->DeleteGlobalRef(env, sensCtrlClass); +} + +//---------------------------------------------------------------------- +JavaVM* GetJavaVM() +{ + return javaVM; +} +//---------------------------------------------------------------------- +#else +JavaVM* GetJavaVM(); +JNIEnv* GetEnv(); + +#endif + +typedef struct +{ + char sensor[50]; + u16 sensorAndroidType; + u8 isAttached; + + GF_Thread *trd; + u8 stop; + + JNIEnv* env; + jclass sensCtrlClass; + jmethodID cid; + jobject sensCtrlObj; + jmethodID startSensor; + jmethodID stopSensor; +} MPEGVSensorContext; + +#define MPEGVSCTX MPEGVSensorContext *rc = (MPEGVSensorContext *)dr->udta + +void unloadSensorController(MPEGVSensorContext *rc) +{ + if ( rc->isAttached ) + { + (*rc->env)->PopLocalFrame(rc->env, NULL); + (*GetJavaVM())->DetachCurrentThread(GetJavaVM()); + rc->isAttached = 0; + } + + rc->env = NULL; +} + +void loadSensorControler(MPEGVSensorContext *rc) +{ + JNIEnv* env = NULL; + jint res = 0; + + // Here we can be in a thread + + res = (*GetJavaVM())->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] The current thread is not attached to the VM, assuming native thread\n")); + res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + if ( res ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Attach current thread failed: %d\n", res)); + return; + } + rc->isAttached = 1; + (*env)->PushLocalFrame(env, 2); + } + else if ( res == JNI_EVERSION ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] The specified version is not supported\n")); + return; + } + + rc->env = env; + rc->sensCtrlClass = sensCtrlClass; + rc->cid = cid; + rc->startSensor = startSensor; + rc->stopSensor = stopSensor; + + // Create the sensor object in the thread + rc->sensCtrlObj = (*rc->env)->NewObject(rc->env, rc->sensCtrlClass, rc->cid); + if (rc->sensCtrlObj == 0) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Cannot create MPEGVSensor object\n")); + return; + } +} + +Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) +{ + MPEGVSCTX; + + //"MPEG-V:siv:OrientationSensorType" + + if ( strnicmp(urn, "MPEG-V", 6) ) + return 0; + + if ( strlen(urn) <= 6 ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[MPEG-V] No sensor type specified\n")); + return 0; + } + + if ( strnicmp(urn+6, ":siv:", 5) ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[MPEG-V] Not valid sensor type specified\n")); + return 0; + } + + strcpy(rc->sensor, urn+11); + + if ( !strcmp(rc->sensor, "OrientationSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "Orientation"); + + rc->sensorAndroidType = 3; + + return 1; + } + else if ( !strcmp(rc->sensor, "AccelerationSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "Acceleration"); + + rc->sensorAndroidType = 1; + + return 1; + } + else if ( !strcmp(rc->sensor, "AngularVelocitySensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "AngularVelocity"); + + rc->sensorAndroidType = 4; + + return 1; + } + else if ( !strcmp(rc->sensor, "MagneticFieldSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "MagneticField"); + + rc->sensorAndroidType = 2; + + return 1; + } + else if ( !strcmp(rc->sensor, "LightSensorType") ) + { + AddField(dr, GF_SG_VRML_SFFLOAT, "Light"); + + rc->sensorAndroidType = 5; + + return 1; + } + else if ( !strcmp(rc->sensor, "AtmosphericPressureSensorType") ) + { + AddField(dr, GF_SG_VRML_SFFLOAT, "AtmosphericPressure"); + + rc->sensorAndroidType = 6; + + return 1; + } + else if ( !strcmp(rc->sensor, "RotationVectorSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "RotationVector"); + + rc->sensorAndroidType = 11; + + return 1; + } + else if ( !strcmp(rc->sensor, "GlobalPositionSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "Position"); + AddField(dr, GF_SG_VRML_SFFLOAT, "Accuracy"); + AddField(dr, GF_SG_VRML_SFFLOAT, "Bearing"); + + rc->sensorAndroidType = 100; + + return 1; + } + else + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[MPEG-V_IN] Unsupported sensor type: %s\n", rc->sensor)); + return 0; + } +} + +u32 MPEGVS_OnData(struct __input_device * dr, const char* data) +{ + GF_BitStream *bs; + u8 *buf; + u32 buf_size; + float x, y, z, /*q,*/ a, b; + MPEGVSCTX; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + if ( rc->sensorAndroidType == 1 + || rc->sensorAndroidType == 2 + || rc->sensorAndroidType == 3 + || rc->sensorAndroidType == 4 ) + { + sscanf(data, "%f;%f;%f;", &x, &y, &z); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + gf_bs_write_float(bs, y); + gf_bs_write_float(bs, z); + } + else if ( rc->sensorAndroidType == 5 + || rc->sensorAndroidType == 6 ) + { + sscanf(data, "%f;", &x); + + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + } + else if ( rc->sensorAndroidType == 11 ) + { + sscanf(data, "%f;%f;%f;", &x, &y, &z); + + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + gf_bs_write_float(bs, y); + gf_bs_write_float(bs, z); + /*gf_bs_write_float(bs, q);*/ + } + else if ( rc->sensorAndroidType == 100 ) + { + sscanf(data, "%f;%f;%f;%f;%f;", &x, &y, &z, &a, &b); + + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + gf_bs_write_float(bs, y); + gf_bs_write_float(bs, z); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, a); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, b); + /*gf_bs_write_float(bs, q);*/ + } + + gf_bs_align(bs); + gf_bs_get_content(bs, &buf, &buf_size); + gf_bs_del(bs); + + dr->DispatchFrame(dr, (u8*)buf, buf_size); + gf_free(buf); + + return GF_OK; +} + +JNIEXPORT void Java_com_gpac_Osmo4_MPEGVSensor_sendData( JNIEnv* env, jobject thiz, jint ptr, jstring data) +{ + jboolean isCopy; + const char * cData = (*env)->GetStringUTFChars(env, data, &isCopy); + + MPEGVS_OnData((struct __input_device *)ptr, cData); + + (*env)->ReleaseStringUTFChars(env, data, cData); +} + +u32 ThreadRun(void* param) +{ + struct __input_device * dr = (struct __input_device *)param; + MPEGVSCTX; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Start: %d\n", gf_th_id())); + + loadSensorControler(rc); + + if (!rc->env || !rc->sensCtrlObj) + return GF_OK; + + (*rc->env)->CallNonvirtualVoidMethod(rc->env, rc->sensCtrlObj, rc->sensCtrlClass, rc->startSensor, (s32)dr, rc->sensorAndroidType); + + while (!rc->stop) + gf_sleep(10); + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Stop: %d\n", gf_th_id())); + + if (!rc->env) + return GF_OK; + + if ( rc->sensCtrlObj ) + { + (*rc->env)->CallNonvirtualVoidMethod(rc->env, rc->sensCtrlObj, rc->sensCtrlClass, rc->stopSensor); + + (*rc->env)->DeleteLocalRef( rc->env, rc->sensCtrlObj ); + } + + unloadSensorController(rc); + return GF_OK; +} + +void MPEGVS_Start(struct __input_device * dr) +{ + MPEGVSCTX; + + rc->trd = gf_th_new("MPEG-V_IN"); + gf_th_run(rc->trd, ThreadRun, dr); +} + +void MPEGVS_Stop(struct __input_device * dr) +{ + MPEGVSCTX; + + if ( rc->trd ) + { + rc->stop = 1; + while ( gf_th_status(rc->trd) == GF_THREAD_STATUS_RUN ) + gf_sleep(5); + + gf_th_del(rc->trd); + rc->trd = NULL; + rc->stop = 0; + } +} + +GF_InputSensorDevice* NewMPEGVSInputSesor() +{ + MPEGVSensorContext* ctx; + GF_InputSensorDevice* driv; + + driv = (GF_InputSensorDevice *) gf_malloc(sizeof(GF_InputSensorDevice)); + memset(driv, 0, sizeof(GF_InputSensorDevice)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_INPUT_DEVICE_INTERFACE, "MPEG-V Sensors Input Module", "gpac distribution"); + + driv->RegisterDevice = MPEGVS_RegisterDevice; + driv->Start = MPEGVS_Start; + driv->Stop = MPEGVS_Stop; + + ctx = (MPEGVSensorContext*) gf_malloc (sizeof(MPEGVSensorContext)); + memset(ctx, 0, sizeof(MPEGVSensorContext)); + + driv->udta = (void*)ctx; + + return driv; +} + +void DeleteMPEGVSInputSensor(GF_InputSensorDevice* dev) +{ + MPEGVS_Stop(dev); + gf_free(dev->udta); + gf_free(dev); +} + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_INPUT_DEVICE_INTERFACE, + 0 + }; + return si; +} + +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_INPUT_DEVICE_INTERFACE) return (GF_BaseInterface *) NewMPEGVSInputSesor(); + return NULL; +} + +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_INPUT_DEVICE_INTERFACE: + DeleteMPEGVSInputSensor((GF_InputSensorDevice *)ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( droid_mpegv ) diff --git a/modules/droid_out/droid_vout-bitmap.c b/modules/droid_out/droid_vout-bitmap.c new file mode 100644 index 0000000..b33d884 --- /dev/null +++ b/modules/droid_out/droid_vout-bitmap.c @@ -0,0 +1,217 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / Wrapper + * + * 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. + * + */ + + +/*driver interfaces*/ +#include <gpac/modules/video_out.h> +#include <gpac/list.h> +#include <gpac/constants.h> + +#include <gpac/setup.h> + +#include <android/bitmap.h> + +typedef struct +{ + JNIEnv * env; + jobject * bitmap; + u32 width, height; + void * locked_data; +} AndroidContext; + + +#define RAW_OUT_PIXEL_FORMAT GF_PIXEL_RGB_32 +#define NBPP 4 + +#define RAWCTX AndroidContext *rc = (AndroidContext *)dr->opaque + +static GF_Err raw_resize(GF_VideoOutput *dr, u32 w, u32 h) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout raw_resize\n")); + return GF_OK; +} + +GF_Err RAW_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) +{ + AndroidBitmapInfo info; + RAWCTX; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_Setup\n")); + + if (!rc->width) + { + rc->env = (JNIEnv *)os_handle; + rc->bitmap = (jobject *)os_display; + + AndroidBitmap_getInfo(rc->env, *(rc->bitmap), &info); + rc->width = info.width; + rc->height = info.height; + rc->locked_data = NULL; + } + else + { + rc->env = (JNIEnv *)os_handle; + rc->bitmap = (jobject *)os_display; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout rc dims: %d:%d\n", rc->height, rc->width)); + + return GF_OK; +} + + +static void RAW_Shutdown(GF_VideoOutput *dr) +{ + RAWCTX; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_Shutdown\n")); + rc->bitmap = NULL; +} + + +static GF_Err RAW_Flush(GF_VideoOutput *dr, GF_Window *dest) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_Flush\n")); + return GF_OK; +} + +static GF_Err RAW_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) +{ + RAWCTX; + void * pixels; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_LockBackBuffer: %d\n", do_lock)); + if (do_lock) { + if (!vi) return GF_BAD_PARAM; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout locked_data: %d\n", rc->locked_data)); + if (!rc->locked_data) + { + int ret; + if ((ret = AndroidBitmap_lockPixels(rc->env, *(rc->bitmap), &pixels)) < 0) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout lock failed (%d)\n", ret)); + } + rc->locked_data = pixels; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout rc dims: %d:%d\n", rc->height, rc->width)); + memset(vi, 0, sizeof(GF_VideoSurface)); + vi->height = rc->height; + vi->width = rc->width; + vi->video_buffer = rc->locked_data; + vi->is_hardware_memory = 0; + vi->pitch_x = NBPP; + vi->pitch_y = NBPP * vi->width; + vi->pixel_format = RAW_OUT_PIXEL_FORMAT; + } + else + { + if (rc->locked_data) + { + AndroidBitmap_unlockPixels(rc->env, *(rc->bitmap)); + rc->locked_data = NULL; + } + } + return GF_OK; +} + +static GF_Err RAW_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_ProcessEvent\n")); + if (evt) { + switch (evt->type) { + case GF_EVENT_SIZE: + //if (evt->setup.opengl_mode) return GF_OK; + return raw_resize(dr, evt->setup.width, evt->setup.height); + case GF_EVENT_VIDEO_SETUP: + return GF_OK; + } + } + return GF_OK; +} + +GF_VideoOutput *NewRawVideoOutput() +{ + AndroidContext *pCtx; + GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "Android Video Output", "gpac distribution") + + pCtx = gf_malloc(sizeof(AndroidContext)); + memset(pCtx, 0, sizeof(AndroidContext)); + + driv->opaque = pCtx; + + driv->Flush = RAW_Flush; + driv->LockBackBuffer = RAW_LockBackBuffer; + driv->Setup = RAW_Setup; + driv->Shutdown = RAW_Shutdown; + driv->ProcessEvent = RAW_ProcessEvent; + + driv->hw_caps = GF_VIDEO_HW_OPENGL; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout init\n")); + return (void *)driv; +} + +void DeleteVideoOutput(void *ifce) +{ + AndroidContext *rc; + GF_VideoOutput *driv = (GF_VideoOutput *) ifce; + + RAW_Shutdown(driv); + rc = (AndroidContext *)driv->opaque; + gf_free(rc); + gf_free(driv); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout deinit\n")); +} + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + 0 + }; + return si; +} +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewRawVideoOutput(); + return NULL; +} +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + DeleteVideoOutput((GF_VideoOutput *)ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( droid_vidbmp ) diff --git a/modules/droid_out/droid_vout.c b/modules/droid_out/droid_vout.c new file mode 100644 index 0000000..5ae1113 --- /dev/null +++ b/modules/droid_out/droid_vout.c @@ -0,0 +1,898 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / Wrapper + * + * 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. + * + */ + +/*driver interfaces*/ +#include <gpac/modules/video_out.h> +#include <gpac/list.h> +#include <gpac/constants.h> + +#include <gpac/setup.h> + +#ifdef GPAC_USE_GLES2 +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#else +#include <GLES/gl.h> +#include <GLES/glext.h> +#endif + +#ifdef PI +#undef PI +#endif + +#define PI 3.1415926535897932f + +/* Uncomment the next line if you want to debug */ +/* #define DROID_EXTREME_LOGS */ + +typedef struct +{ + u32 width, height; + void * locked_data; + u8 out_3d_type; + + u32 tex_width, tex_height; + + GLint texID; + + GLubyte* texData; + + u8 draw_texture; + u8 non_power_two; + + Bool fullscreen; + + //Functions specific to OpenGL ES2 +#ifdef GPAC_USE_GLES2 + GLuint base_vertex, base_fragment, base_program; + GF_Matrix identity, ortho; +#endif +} AndroidContext; + + +#define RAW_OUT_PIXEL_FORMAT GF_PIXEL_RGBA +#define NBPP 4 + +#define RAWCTX AndroidContext *rc = (AndroidContext *)dr->opaque + + + +//Functions specific to OpenGL ES2 +#ifdef GPAC_USE_GLES2 + +#define GF_TRUE 1 +#define GF_FALSE 0 + + +//we custom-define these instead of importing gl_inc.h +#define GL_COMPILE_STATUS 0x8B81 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_LINK_STATUS 0x8B82 +#define GL_VERTEX_SHADER 0x8B31 + + + +static char *glsl_vertex = "precision mediump float;\ + attribute vec4 gfVertex;\ + attribute vec4 gfTexCoord;\ + varying vec2 TexCoord;\ + uniform mat4 gfModelViewMatrix;\ + uniform mat4 gfProjectionMatrix;\ + void main(void){\ + vec4 gfEye;\ + gfEye = gfModelViewMatrix * gfVertex;\ + TexCoord = vec2(gfTexCoord);\ + gl_Position = gfProjectionMatrix * gfEye;\ + }"; + +static char *glsl_fragment = "precision mediump float;\ + varying vec2 TexCoord;\ + uniform sampler2D img;\ + void main(void){\ + gl_FragColor = texture2D(img, TexCoord);\ + }"; + +static inline void gl_check_error() +{ + s32 res = glGetError(); + if (res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, + ("GL Error %d file %s line %d\n", res, + __FILE__, __LINE__)); + } +} + + +static GLint gf_glGetUniformLocation(u32 glsl_program, const char *uniform_name) +{ + GLint loc = glGetUniformLocation(glsl_program, uniform_name); + if (loc<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[V3D:GLSL] Cannot find uniform \"%s\" in GLSL program\n", uniform_name)); + } + return loc; +} + +static GLint gf_glGetAttribLocation(u32 glsl_program, const char *attrib_name) +{ + GLint loc = glGetAttribLocation(glsl_program, attrib_name); + if (loc<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[V3D:GLSL] Cannot find attrib \"%s\" in GLSL program\n", attrib_name)); + } + return loc; +} + +//modified version of visual_3d_compile_shader function +Bool compile_shader(u32 shader_id, const char *name, const char *source) { + GLint blen = 0; + GLsizei slen = 0; + u32 len; + GLint is_compiled = 0; + + + if(!source || !shader_id) return 0; + len = (u32) strlen(source); + glShaderSource(shader_id, 1, &source, &len); + glCompileShader(shader_id); + + glGetShaderiv(shader_id, GL_COMPILE_STATUS, &is_compiled); + if (is_compiled == 1) return GF_TRUE; + + glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH , &blen); + if (blen > 1) { + char* compiler_log = (char*) gf_malloc(blen); + glGetShaderInfoLog(shader_id, blen, &slen, compiler_log); + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[GLSL] Failed to compile %s shader: %s\n", name, compiler_log)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[GLSL] ***** faulty shader code ****\n%s\n**********************\n", source)); + gf_free (compiler_log); + return GF_FALSE; + } + + return GF_TRUE; +} + + +static Bool initGLES2(AndroidContext *rc) { + + +//PRINT OpengGL INFO + char* ext; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android InitGLES2")); + + ext = (char*)glGetString(GL_VENDOR); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Vendor: %s", ext)); + + ext = (char*)glGetString(GL_RENDERER); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Renderer: %s", ext)); + + ext = (char*)glGetString(GL_VERSION); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Version: %s", ext)); + + ext = (char*)glGetString(GL_EXTENSIONS); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Extensions: %s", ext)); + + + +//Generic GL setup + /* Set the background black */ + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + /* Depth buffer setup */ + glClearDepthf(1.0f); + + /* Enables Depth Testing */ + glEnable(GL_DEPTH_TEST); + + /* The Type Of Depth Test To Do */ + glDepthFunc(GL_LEQUAL); + + +//Shaders setup + Bool res = GF_FALSE; + GLint linked; + + gl_check_error(); + gf_mx_init(rc->identity); + rc->base_program = glCreateProgram(); + rc->base_vertex = glCreateShader(GL_VERTEX_SHADER); + rc->base_fragment = glCreateShader(GL_FRAGMENT_SHADER); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Compiling shaders")); + res = compile_shader(rc->base_vertex, "vertex", glsl_vertex); + if(!res) return GF_FALSE; + res = compile_shader(rc->base_fragment, "fragment", glsl_fragment); + if(!res) return GF_FALSE; + + glAttachShader(rc->base_program, rc->base_vertex); + glAttachShader(rc->base_program, rc->base_fragment); + glLinkProgram(rc->base_program); + + glGetProgramiv(rc->base_program, GL_LINK_STATUS, &linked); + if (!linked) { + int i32CharsWritten, i32InfoLogLength; + char pszInfoLog[2048]; + glGetProgramiv(rc->base_program, GL_INFO_LOG_LENGTH, &i32InfoLogLength); + glGetProgramInfoLog(rc->base_program, i32InfoLogLength, &i32CharsWritten, pszInfoLog); + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, (pszInfoLog)); + return GF_FALSE; + } + glUseProgram(rc->base_program); + gl_check_error(); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Shaders compiled")) + + return GF_TRUE; +} + +static void load_matrix_shaders(GLuint program, Fixed *mat, const char *name) +{ + GLint loc; +#ifdef GPAC_FIXED_POINT + Float _mat[16]; + u32 i; +#endif + gl_check_error(); + loc = glGetUniformLocation(program, name); + if(loc<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("GL Error (file %s line %d): Invalid matrix name", __FILE__, __LINE__)); + return; + } + gl_check_error(); + +#ifdef GPAC_FIXED_POINT + for (i=0; i<16; i++) _mat[i] = FIX2FLT(mat[i]); + glUniformMatrix4fv(loc, 1, GL_FALSE, (GLfloat *) _mat); +#else + glUniformMatrix4fv(loc, 1, GL_FALSE, mat); +#endif + gl_check_error(); +} + + +//ES2 version of glOrthox() - resulting matrix is stored in rc->ortho +//more info on Orthographic projection matrix at http://www.songho.ca/opengl/gl_projectionmatrix.html#ortho +static void calculate_ortho(Fixed left, Fixed right, Fixed bottom, Fixed top, Fixed near, Fixed far, AndroidContext *rc) { + + + if((left==right)|(bottom==top)|(near==far)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("GL Error (file %s line %d): Invalid Orthogonal projection values", __FILE__, __LINE__)); + return; + } + + gf_mx_init(rc->ortho); + +//For Orthographic Projection + rc->ortho.m[0] = gf_divfix(2, (right-left)); + rc->ortho.m[5] = gf_divfix(2, (top-bottom)); + rc->ortho.m[10] = gf_divfix(-2, far-near); + rc->ortho.m[12] = -gf_divfix(right+left, right-left); + rc->ortho.m[13] = -gf_divfix(top+bottom, top-bottom); + rc->ortho.m[14] = -gf_divfix(far+near, far-near); + rc->ortho.m[15] = FIX_ONE; +} + + + +#endif //Endof specifix for GLES2 (ifdef GPAC_USE_GLES2) + + +#ifndef GPAC_USE_GLES2 +void initGL(AndroidContext *rc) +{ + char* ext; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android InitGL")); + + ext = (char*)glGetString(GL_VENDOR); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Vendor: %s", ext)); + + ext = (char*)glGetString(GL_RENDERER); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Renderer: %s", ext)); + + ext = (char*)glGetString(GL_VERSION); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Version: %s", ext)); + + ext = (char*)glGetString(GL_EXTENSIONS); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("OpenGL ES Extensions: %s", ext)); + + if ( strstr(ext, "GL_OES_draw_texture") ) + { + rc->draw_texture = 1; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("Using GL_OES_draw_texture")); + } + if ( strstr(ext, "GL_ARB_texture_non_power_of_two") ) + { + rc->non_power_two = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("Using GL_ARB_texture_non_power_of_two")); + } + + /* Enable smooth shading */ + glShadeModel(GL_SMOOTH); + + /* Set the background black */ + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + /* Depth buffer setup */ + glClearDepthf(1.0f); + + /* Enables Depth Testing */ + glEnable(GL_DEPTH_TEST); + + /* The Type Of Depth Test To Do */ + glDepthFunc(GL_LEQUAL); + + /* Really Nice Perspective Calculations */ + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +} +#endif + +void gluPerspective(GLfloat fovy, GLfloat aspect, + GLfloat zNear, GLfloat zFar) +{ +#ifndef GPAC_USE_GLES2 + GLfloat xmin, xmax, ymin, ymax; + + ymax = zNear * (GLfloat)tan(fovy * PI / 360); + ymin = -ymax; + xmin = ymin * aspect; + xmax = ymax * aspect; + glFrustumx((GLfixed)(xmin * 65536), (GLfixed)(xmax * 65536), + (GLfixed)(ymin * 65536), (GLfixed)(ymax * 65536), + (GLfixed)(zNear * 65536), (GLfixed)(zFar * 65536)); +#endif +} + +void resizeWindow(AndroidContext *rc) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("resizeWindow : start")); + + /* Protect against a divide by zero */ + if (rc->height==0) + rc->height = 1; + + /* Setup our viewport. */ + glViewport(0, 0, (GLsizei)rc->width, (GLsizei)rc->height); +#ifndef GPAC_USE_GLES2 + /* change to the projection matrix and set our viewing volume. */ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + /* Set our perspective */ + glOrthox(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1)); + + /* Make sure we're chaning the model view and not the projection */ + glMatrixMode(GL_MODELVIEW); + + /* Reset The View */ + glLoadIdentity(); +#else + gl_check_error(); + glUseProgram(rc->base_program); + calculate_ortho(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1), rc); + load_matrix_shaders(rc->base_program, (Fixed *) rc->ortho.m, "gfProjectionMatrix"); + load_matrix_shaders(rc->base_program, (Fixed *) rc->identity.m, "gfModelViewMatrix"); + gl_check_error(); +#endif + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("resizeWindow : end")); +} + +void drawGLScene(AndroidContext *rc) +{ +#ifdef DROID_EXTREME_LOGS + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("drawGLScene : start")); +#endif /* DROID_EXTREME_LOGS */ +#ifdef GPAC_USE_GLES2 + GLint loc_vertex_array, loc_texcoord_array; +#endif + + GLfloat vertices[4][3]; + GLfloat texcoord[4][2]; +// int i, j; + +#ifndef GPAC_USE_GLES2 + float rgba[4]; +#endif + gl_check_error(); + + // Reset states +#ifndef GPAC_USE_GLES2 + rgba[0] = rgba[1] = rgba[2] = 0.f; + rgba[0] = 1.f; + glColor4f(1.f, 1.f, 1.f, 1.f); + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, rgba); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, rgba); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, rgba); +#endif + /* Clear The Screen And The Depth Buffer */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + gl_check_error(); + //glEnable(GL_BLEND); + //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +#ifndef GPAC_USE_GLES2 + glEnable(GL_TEXTURE_2D); +#endif + glUseProgram(rc->base_program); + glActiveTexture(GL_TEXTURE0); + glBindTexture( GL_TEXTURE_2D, rc->texID); + gl_check_error(); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); +#ifndef GPAC_USE_GLES2 + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); +#endif + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + +// for ( i = 0; i < rc->height/2; i++ ) +// for ( j = 0; j < rc->width; j++ ) +// rc->texData[ i*rc->width*NBPP + j*NBPP + 3] = 200; + +// memset(rc->texData, 255, 4 * rc->width * rc->height ); + + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, rc->tex_width, rc->tex_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, rc->texData ); + + gl_check_error(); + if ( rc->draw_texture ) + { +#ifndef GPAC_USE_GLES2 + gl_check_error(); + int cropRect[4] = {0,rc->height,rc->width,-rc->height}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); + glDrawTexsOES(0, 0, 0, rc->width, rc->height); +#endif + } + else + { + gl_check_error(); + +#ifndef GPAC_USE_GLES2 + /* Enable VERTEX array */ + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + /* Setup pointer to VERTEX array */ + glVertexPointer(3, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texcoord); + + /* Move Left 1.5 Units And Into The Screen 6.0 */ + glLoadIdentity(); +#else + loc_vertex_array = glGetAttribLocation(rc->base_program, "gfVertex"); + if(loc_vertex_array<0) + return; + glEnableVertexAttribArray(loc_vertex_array); + glVertexAttribPointer(loc_vertex_array, 3, GL_FLOAT, GL_FALSE, 0, vertices); + + loc_texcoord_array = glGetAttribLocation(rc->base_program, "gfTexCoord"); + if (loc_texcoord_array>=0) { + glVertexAttribPointer(loc_texcoord_array, 2, GL_FLOAT, GL_FALSE, 0, texcoord); + glEnableVertexAttribArray(loc_texcoord_array); + } + + +#endif + //glTranslatef(0.0f, 0.0f, -3.3f); + //glTranslatef(0.0f, 0.0f, -2.3f); + + /* Top Right Of The Quad */ + vertices[0][0]=rc->tex_width; + vertices[0][1]=rc->tex_height; + vertices[0][2]=0.0f; + texcoord[0][0]=1.f; + texcoord[0][1]=0.f; + /* Top Left Of The Quad */ + vertices[1][0]=0.f; + vertices[1][1]=rc->tex_height; + vertices[1][2]=0.0f; + texcoord[1][0]=0.f; + texcoord[1][1]=0.f; + /* Bottom Left Of The Quad */ + vertices[2][0]=rc->tex_width; + vertices[2][1]=0.f; + vertices[2][2]=0.0f; + texcoord[2][0]=1.f; + texcoord[2][1]=1.f; + /* Bottom Right Of The Quad */ + vertices[3][0]=0.f; + vertices[3][1]=0.f; + vertices[3][2]=0.0f; + texcoord[3][0]=0.f; + texcoord[3][1]=1.f; + + /* Drawing using triangle strips, draw triangles using 4 vertices */ + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +#ifndef GPAC_USE_GLES2 + /* Disable vertex array */ + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +#endif + } +#ifndef GPAC_USE_GLES2 + glDisable(GL_TEXTURE_2D); +#endif + gl_check_error(); + + /* Flush all drawings */ + glFinish(); +#ifdef DROID_EXTREME_LOGS + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("drawGLScene : end")); +#endif /* DROID_EXTREME_LOGS */ +} + +int releaseTexture(AndroidContext *rc) +{ + gl_check_error(); + if (!rc) + return 0; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Delete Texture")); + + if ( rc->texID >= 0) + { + glDeleteTextures(1, &(rc->texID)); + rc->texID = -1; + } + if (rc->texData) + { + gf_free(rc->texData); + rc->texData = NULL; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Delete Texture DONE")) + gl_check_error(); + return 0; +} + +int createTexture(AndroidContext *rc) +{ + if (!rc) + return 0; + if ( rc->texID >= 0 ) + releaseTexture(rc); + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("Android Create Texture Size: WxH: %dx%d", rc->tex_width, rc->tex_height)); + + glGenTextures( 1, &(rc->texID) ); + + rc->texData = (GLubyte*)gf_malloc( 4 * rc->tex_width * rc->tex_height ); + memset(rc->texData, 255, 4 * rc->tex_width * rc->tex_height ); + //memset(data, 0, 4 * width * height/2 ); + + glBindTexture( GL_TEXTURE_2D, rc->texID); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); +#ifndef GPAC_USE_GLES2 + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); +#endif + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, rc->tex_width, rc->tex_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL/*rc->texData*/ ); + + glBindTexture( GL_TEXTURE_2D, 0); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Create Texture DONE")); + return 0; +} + + +u32 find_pow_2(u32 num) +{ + u32 res = 1; + while (res < num) + res *= 2; + return res; +} + +static GF_Err droid_Resize(GF_VideoOutput *dr, u32 w, u32 h) +{ + RAWCTX; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Resize: %dx%d", w, h)); + + rc->width = w; + rc->height = h; + + if ((dr->max_screen_width < w) || (dr->max_screen_height < h)) { + dr->max_screen_width = w; + dr->max_screen_height = h; + } + //npot textures are supported in ES2 +#ifdef GPAC_USE_GLES2 + rc->tex_width = rc->width; + rc->tex_height = rc->height; +#else + if ( rc->non_power_two ) + { + rc->tex_width = rc->width; + rc->tex_height = rc->height; + } + else + { + rc->tex_width = find_pow_2(rc->width); + rc->tex_height = find_pow_2(rc->height); + } +#endif + gl_check_error(); + resizeWindow(rc); + + if ( rc->out_3d_type == 0 ) + { + createTexture(rc); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Resize DONE", w, h)); + gl_check_error(); + return GF_OK; +} + +GF_Err droid_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) +{ + RAWCTX; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Setup: %d", init_flags)); + + +#ifdef GPAC_USE_GLES2 + + if ( rc->out_3d_type == 0 ) { + Bool res = GF_FALSE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("We are in OpenGL: disable mode")); + res = initGLES2(rc); + if(res==GF_FALSE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("ERROR Compiling ES2 Shaders")); + } else { //set texture + glUseProgram(rc->base_program); + GLint loc = gf_glGetUniformLocation(rc->base_program, "img"); + glUniform1i(loc,0); + } + } + +#else + + if ( rc->out_3d_type == 0 ) + + initGL(rc); +#endif //GPAC_USE_GLES2 + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Setup DONE")); + return GF_OK; +} + + +static void droid_Shutdown(GF_VideoOutput *dr) +{ + RAWCTX; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Shutdown\n")); + + releaseTexture(rc); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Shutdown DONE")); +} + + +static GF_Err droid_Flush(GF_VideoOutput *dr, GF_Window *dest) +{ + RAWCTX; +#ifdef DROID_EXTREME_LOGS + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Flush\n")); +#endif /* DROID_EXTREME_LOGS */ + + if ( rc->out_3d_type == 0 ) + drawGLScene(rc); +#ifdef DROID_EXTREME_LOGS + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Flush DONE")); +#endif /* DROID_EXTREME_LOGS */ + return GF_OK; +} + +static GF_Err droid_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) +{ + RAWCTX; +#ifdef DROID_EXTREME_LOGS + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android LockBackBuffer: %d", do_lock)); +#endif /* DROID_EXTREME_LOGS */ + if (do_lock) { + if (!vi) return GF_BAD_PARAM; + + if ( rc->out_3d_type != 0 ) + return GF_NOT_SUPPORTED; + + memset(vi, 0, sizeof(GF_VideoSurface)); + vi->height = rc->height; + vi->width = rc->width; + vi->video_buffer = rc->texData; + vi->is_hardware_memory = 0; + vi->pitch_x = NBPP; + vi->pitch_y = NBPP * rc->tex_width; + vi->pixel_format = RAW_OUT_PIXEL_FORMAT; + } + else + { + if (rc->locked_data) + { + //glBindTexture( GL_TEXTURE_2D, rc->texID); + //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + //glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + + //glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, rc->width, rc->height, 0, + // GL_RGBA, GL_UNSIGNED_BYTE, rc->locked_data ); + } + } +#ifdef DROID_EXTREME_LOGS + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android LockBackBuffer DONE")); +#endif /* DROID_EXTREME_LOGS */ + return GF_OK; +} + +static GF_Err droid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) +{ + RAWCTX; + + if (!evt) return GF_OK; + + switch (evt->type) { + case GF_EVENT_SIZE: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SIZE( %d x %d)", evt->setup.width, evt->setup.height)); + //in fullscreen mode: do not change viewport; just update perspective + if (rc->fullscreen) { +#ifdef GPAC_USE_GLES2 + gl_check_error(); + glUseProgram(rc->base_program); + calculate_ortho(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1), rc); + load_matrix_shaders(rc->base_program, (Fixed *) rc->ortho.m, "gfProjectionMatrix"); + load_matrix_shaders(rc->base_program, (Fixed *) rc->identity.m, "gfModelViewMatrix"); + gl_check_error(); +#else + /* change to the projection matrix and set our viewing volume. */ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + /* Set our perspective */ + glOrthox(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1)); + + /* Make sure we're chaning the model view and not the projection */ + glMatrixMode(GL_MODELVIEW); + + /* Reset The View */ + glLoadIdentity(); +#endif + return GF_OK; + } else + return droid_Resize(dr, evt->setup.width, evt->setup.height); + + case GF_EVENT_VIDEO_SETUP: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android OpenGL mode: %d", evt->setup.use_opengl)); + if (!evt->setup.use_opengl) { + rc->out_3d_type = 0; +// initGL(rc); + droid_Resize(dr, evt->setup.width, evt->setup.height); + } else { + rc->out_3d_type = 1; + droid_Resize(dr, evt->setup.width, evt->setup.height); + } + return GF_OK; + + case GF_EVENT_SET_CURSOR: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SET_CURSOR")); + return GF_OK; + case GF_EVENT_SET_CAPTION: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SET_CAPTION")); + return GF_OK; + case GF_EVENT_SHOWHIDE: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SHOWHIDE")); + return GF_OK; + default: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Process Unknown Event: %d", evt->type)); + return GF_OK; + + case GF_EVENT_TEXT_EDITING_START: + case GF_EVENT_TEXT_EDITING_END: + return GF_NOT_SUPPORTED; + } + return GF_OK; +} + +static GF_Err droid_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 *outHeight) +{ + RAWCTX; + + *outWidth = dr->max_screen_width; + *outHeight = dr->max_screen_height; + rc->fullscreen = bOn; + return droid_Resize(dr, dr->max_screen_width, dr->max_screen_height); +} + +GF_VideoOutput *NewAndroidVideoOutput() +{ + AndroidContext *pCtx; + GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("Android Video Initialization in progress...")); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "Android Video Output", "gpac distribution") + + pCtx = gf_malloc(sizeof(AndroidContext)); + memset(pCtx, 0, sizeof(AndroidContext)); + + pCtx->texID = -1; + driv->opaque = pCtx; + + driv->Flush = droid_Flush; + driv->LockBackBuffer = droid_LockBackBuffer; + driv->Setup = droid_Setup; + driv->Shutdown = droid_Shutdown; + driv->ProcessEvent = droid_ProcessEvent; + driv->SetFullScreen = droid_SetFullScreen; + + driv->max_screen_width = 1024; + driv->max_screen_height = 1024; + + driv->hw_caps = GF_VIDEO_HW_OPENGL;// | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA;//GF_VIDEO_HW_DIRECT_ONLY;// + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("Android Video Init Done.\n")); + return (void *)driv; +} + +void DeleteAndroidVideoOutput(void *ifce) +{ + AndroidContext *rc; + GF_VideoOutput *driv = (GF_VideoOutput *) ifce; + if (!ifce) + return; + droid_Shutdown(driv); + rc = (AndroidContext *)driv->opaque; + if (rc) + gf_free(rc); + driv->opaque = NULL; + gf_free(driv); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android vout deinit\n")); +} + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + 0 + }; + return si; +} +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewAndroidVideoOutput(); + return NULL; +} +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + DeleteAndroidVideoOutput((GF_VideoOutput *)ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( droid_vidgl ) diff --git a/modules/dx_hw/Makefile b/modules/dx_hw/Makefile new file mode 100644 index 0000000..dc8f588 --- /dev/null +++ b/modules/dx_hw/Makefile @@ -0,0 +1,59 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/dx_hw + +#DIRECTSOUND_VERSION is needed for GCC compil.. +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -w -DDIRECTSOUND_VERSION=0x0500 + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(DX_PATH),system) +LDFLAGS_DX=-ldsound -ldxguid -lddraw -lole32 -lgdi32 -lopengl32 +else +CFLAGS+= -I$(DX_PATH)/include +LDFLAGS_DX=-L$(DX_PATH)/lib -ldsound -ldxguid -lddraw -lole32 -lgdi32 -lopengl32 +endif + + +#common obj +OBJS=dx_audio.o dx_video.o dx_window.o dx_2d.o + + +SRCS := $(OBJS:.o=.c) + +LIB=gm_dx_hw$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +#LDFLAGS+=-export-symbols dx_hw.def +endif + +all: $(LIB) + + +$(LIB): $(OBJS) + $(WINDRES) "$(SRC_PATH)/modules/dx_hw/dx_hw.rc" dw_hw.o + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) dw_hw.o $(LDFLAGS_DX) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) +ifeq ($(CONFIG_WIN32),yes) + rm -f dx_hw.o +endif + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/dx_hw/collide.cur b/modules/dx_hw/collide.cur new file mode 100644 index 0000000000000000000000000000000000000000..388b9f0e1384c42ea2108ac7ae67023f70ed5155 GIT binary patch literal 2238 zcmeH{A&(kS5Xa{ll9Gm`!JtlDP8B^0*Wi(8+<b+zVD2N3j5~3nnoj_!>4ixxricc2 z3LHtr^!H{{S6A6vnD_Sm-`n~BcHZuayptQbyOTP;^hHLTeIo9N&%})&ax8n*{iRtl ziM(8P9WAG_-EL+3V=L!x&(iDlq~Gt$U@(y3a44hENN#U$<^KL&#^bTf=X06OW-^^l zWipw_YPBLEQQfNRXiFAl&Il($YN%+6rfAx<?3Nh1p&PoP8+yCU(H-3xIJ%?LNuURM zpa(*rC*x$C(OJ<d{z@X{B9)Y~r%@Ob28BUkC?psZ28BUkP#6jm28BUkP#6@3LWe<N zP#6>jg+UVy4uwM-M`l$3g}(iq2m-y`GAxF<VPqsVSgH(68Yc#e!BS;`!{9JD3=V^% zN(G0(U@#aAt#k&5!C){1UP)Ue#Haf)uJX3(K!vxns!ENw!{nGc1`d<s?MymJ9B;?F zwxhh!@pt?k|F%Vrzf&8B!C`Rf(6)`L)ZL@d)3$A-27zC|7H|Z9wY80d8U!5CIJmLe zZY~N~0+s+GFsw%amf*r*Q6K~?{0Z`~Cjm>edK6208rm+P!H_T{3<*QRkTBHa1Vg}( zFeD5C18E4>!;ml}3<(3N33M0|hJ+yjNc@9j$%V-xBZo0III?oLH72{=P8N%WJUl$e z<Kv^O*K67D_p;e+<Zw91@pzQe=_IeOuX4Fu<a)iz`B%#G(-ZIUlIpg1pmpS{QOnzg yDIeaBw+g&f;J>W^-++HUt{*ec?=w%QE?!>vn(<-(KIx*J|NPp`wRLU%pW`=WcNq`> literal 0 HcmV?d00001 diff --git a/modules/dx_hw/dx_2d.c b/modules/dx_hw/dx_2d.c new file mode 100644 index 0000000..1e54ab3 --- /dev/null +++ b/modules/dx_hw/dx_2d.c @@ -0,0 +1,883 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / DirectX audio and video render module + * + * 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 "dx_hw.h" + +#define DDCONTEXT DDContext *dd = (DDContext *)dr->opaque; +#define DDBACK DDSurface *pBack = (DDSurface *) gf_list_get(dd->surfaces, 0); + +enum +{ + GF_PIXEL_IYUV = GF_4CC('I', 'Y', 'U', 'V'), + GF_PIXEL_I420 = GF_4CC('I', '4', '2', '0'), + /*!YUV packed format*/ + GF_PIXEL_UYNV = GF_4CC('U', 'Y', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_YUNV = GF_4CC('Y', 'U', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_V422 = GF_4CC('V', '4', '2', '2'), + + GF_PIXEL_YV12 = GF_4CC('Y', 'V', '1', '2'), + GF_PIXEL_Y422 = GF_4CC('Y', '4', '2', '2'), + GF_PIXEL_YUY2 = GF_4CC('Y', 'U', 'Y', '2'), +}; + + + +static Bool pixelformat_yuv(u32 pixel_format) +{ + switch (pixel_format) { + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + case GF_PIXEL_UYNV: + case GF_PIXEL_YUNV: + case GF_PIXEL_V422: + case GF_PIXEL_YUV: + case GF_PIXEL_IYUV: + case GF_PIXEL_I420: + case GF_PIXEL_YUV_10: + case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444_10: + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: + return GF_TRUE; + default: + return GF_FALSE; + } +} + + +static u32 get_win_4CC(u32 pixel_format) +{ + switch (pixel_format) { + case GF_PIXEL_YUYV: + return mmioFOURCC('Y', 'U', 'Y', '2'); + case GF_PIXEL_YVYU: + return mmioFOURCC('Y', 'V', 'Y', 'U'); + case GF_PIXEL_UYVY: + return mmioFOURCC('U', 'Y', 'V', 'Y'); + case GF_PIXEL_VYUY: + return mmioFOURCC('V', 'Y', 'U', 'Y'); + case GF_PIXEL_YUV422: + return mmioFOURCC('Y', '4', '2', '2'); + case GF_PIXEL_UYNV: + return mmioFOURCC('U', 'Y', 'N', 'V'); + case GF_PIXEL_YUNV: + return mmioFOURCC('Y', 'U', 'N', 'V'); + case GF_PIXEL_V422: + return mmioFOURCC('V', '4', '2', '2'); + case GF_PIXEL_YUV: + return mmioFOURCC('Y', 'V', '1', '2'); + case GF_PIXEL_IYUV: + return mmioFOURCC('I', 'Y', 'U', 'V'); + case GF_PIXEL_I420: + return mmioFOURCC('I', '4', '2', '0'); + case GF_PIXEL_YUV_10: + return mmioFOURCC('P', '0', '1', '0'); + case GF_PIXEL_YUV444: + return mmioFOURCC('Y', '4', '4', '4'); + case GF_PIXEL_YUV444_10: + return mmioFOURCC('Y', '4', '1', '0'); + case GF_PIXEL_YUV422_10: + return mmioFOURCC('Y', '2', '1', '0'); + default: + return 0; + } +} + +/*!\hideinitializer transfoms a 32-bits color into a 16-bits one.\note alpha component is lost*/ +#define GF_COL_TO_565(c) (((GF_COL_R(c) & 248)<<8) + ((GF_COL_G(c) & 252)<<3) + (GF_COL_B(c)>>3)) +/*!\hideinitializer transfoms a 32-bits color into a 15-bits one.\note alpha component is lost*/ +#define GF_COL_TO_555(c) (((GF_COL_R(c) & 248)<<7) + ((GF_COL_G(c) & 248)<<2) + (GF_COL_B(c)>>3)) + +static GF_Err DD_ClearBackBuffer(GF_VideoOutput *dr, u32 color) +{ + HRESULT hr; + RECT rc; + DDBLTFX ddbltfx; + DDCONTEXT; + + // Erase the background + ZeroMemory( &ddbltfx, sizeof(ddbltfx) ); + ddbltfx.dwSize = sizeof(ddbltfx); + switch (dd->pixelFormat) { + case GF_PIXEL_RGB_565: + ddbltfx.dwFillColor = GF_COL_TO_565(color); + break; + case GF_PIXEL_RGB_555: + ddbltfx.dwFillColor = GF_COL_TO_555(color); + break; + default: + ddbltfx.dwFillColor = color; + break; + } + rc.left = rc.top = 0; + rc.right = dd->width; + rc.bottom = dd->height; + + hr = dd->pBack->lpVtbl->Blt(dd->pBack, &rc, NULL, NULL, DDBLT_COLORFILL, &ddbltfx); + + return FAILED(hr) ? GF_IO_ERR : GF_OK; +} + +GF_Err CreateBackBuffer(GF_VideoOutput *dr, u32 Width, u32 Height, Bool use_system_memory) +{ + Bool force_reinit; + HRESULT hr; + const char *opt; + DDSURFDESC ddsd; + + DDCONTEXT; + + opt = gf_opts_get_key("core", "hwvmem"); + if (opt) { + if (use_system_memory) { + if (!strcmp(opt, "always")) use_system_memory = GF_FALSE; + } else { + if (!strcmp(opt, "never")) use_system_memory = GF_TRUE; + else if (!strcmp(opt, "auto")) dd->force_video_mem_for_yuv = GF_TRUE; + } + } + + force_reinit = GF_FALSE; + if (use_system_memory && !dd->systems_memory) force_reinit = GF_TRUE; + else if (!use_system_memory && dd->systems_memory) force_reinit = GF_TRUE; + if (dd->pBack && !force_reinit&& !dd->fullscreen && (dd->width == Width) && (dd->height == Height) ) { + return GF_OK; + } + + SAFE_DD_RELEASE(dd->pBack); + + /*create backbuffer*/ + ZeroMemory(&ddsd, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; + + if (dd->systems_memory==2) { + ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; + } else { + if (!use_system_memory) { + dd->systems_memory = GF_FALSE; + ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY; + } else { + ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; + dd->systems_memory = GF_TRUE; + } + } + if (dd->systems_memory) dr->hw_caps &= ~GF_VIDEO_HW_HAS_RGB; + else dr->hw_caps |= GF_VIDEO_HW_HAS_RGB; + + + ddsd.dwWidth = Width; + ddsd.dwHeight = Height; + + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &dd->pBack, NULL ); + + if (FAILED(hr)) { + if (!dd->systems_memory) { + ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY; + ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; + dd->systems_memory = 2; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] Hardware Video not available for backbuffer)\n")); + + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &dd->pBack, NULL ); + } + if (FAILED(hr)) return GF_IO_ERR; + } + + /*store size*/ + if (!dd->fullscreen) { + dd->width = Width; + dd->height = Height; + } + DD_ClearBackBuffer(dr, 0xFF000000); + + if (!dd->yuv_init) DD_InitYUV(dr); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] BackBuffer %d x %d created on %s memory\n", Width, Height, dd->systems_memory ? "System" : "Video")); + return GF_OK; +} + + +GF_Err InitDirectDraw(GF_VideoOutput *dr, u32 Width, u32 Height) +{ + HRESULT hr; + DWORD cooplev; + LPDIRECTDRAW ddraw; + DDSURFDESC ddsd; + DDPIXELFORMAT pixelFmt; + LPDIRECTDRAWCLIPPER pcClipper; + DDCONTEXT; + if (!dd->cur_hwnd || !Width || !Height || !dd->DirectDrawCreate) return GF_BAD_PARAM; + DestroyObjects(dd); + + if( FAILED( hr = dd->DirectDrawCreate(NULL, &ddraw, NULL ) ) ) + return GF_IO_ERR; + + hr = ddraw->lpVtbl->QueryInterface(ddraw, &IID_IDirectDraw7, (LPVOID *)&dd->pDD); + ddraw->lpVtbl->Release(ddraw); + if (FAILED(hr)) return GF_IO_ERR; + + + dd->switch_res = GF_FALSE; + cooplev = DDSCL_NORMAL; + /*Setup FS*/ + if (dd->fullscreen) { + + /*change display mode*/ + if (dd->switch_res) { +#ifdef USE_DX_3 + hr = dd->pDD->lpVtbl->SetDisplayMode(dd->pDD, dd->fs_width, dd->fs_height, dd->video_bpp); +#else + hr = dd->pDD->lpVtbl->SetDisplayMode(dd->pDD, dd->fs_width, dd->fs_height, dd->video_bpp, 0, 0 ); +#endif + if( FAILED(hr)) return GF_IO_ERR; + } + dd->NeedRestore = 1; +// cooplev = DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN; + } + + hr = dd->pDD->lpVtbl->SetCooperativeLevel(dd->pDD, dd->cur_hwnd, cooplev); + if( FAILED(hr) ) return GF_IO_ERR; + + /*create primary*/ + ZeroMemory( &ddsd, sizeof( ddsd ) ); + ddsd.dwSize = sizeof( ddsd ); + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &dd->pPrimary, NULL); + if( FAILED(hr) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DX Out] Failed creating primary surface: error %08x\n", hr)); + return GF_IO_ERR; + } + + /*get pixel format of video board*/ + memset (&pixelFmt, 0, sizeof(pixelFmt)); + pixelFmt.dwSize = sizeof(pixelFmt); + + hr = dd->pPrimary->lpVtbl->GetPixelFormat(dd->pPrimary, &pixelFmt); + if( FAILED(hr) ) return GF_IO_ERR; + + switch(pixelFmt.dwRGBBitCount) { + case 16: + if ((pixelFmt.dwRBitMask == 0xf800) && (pixelFmt.dwGBitMask == 0x07e0) && (pixelFmt.dwBBitMask == 0x001f)) + dd->pixelFormat = GF_PIXEL_RGB_565; + else if ((pixelFmt.dwRBitMask == 0x7c00) && (pixelFmt.dwGBitMask == 0x03e0) && (pixelFmt.dwBBitMask == 0x001f)) + dd->pixelFormat = GF_PIXEL_RGB_555; + else + return GF_NOT_SUPPORTED; + dd->video_bpp = 16; + break; + case 24: + if ((pixelFmt.dwRBitMask == 0x0000FF) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0xFF0000)) + dd->pixelFormat = GF_PIXEL_BGR; + else if ((pixelFmt.dwRBitMask == 0xFF0000) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0x0000FF)) + dd->pixelFormat = GF_PIXEL_RGB; + dd->video_bpp = 24; + break; + case 32: + if ((pixelFmt.dwRBitMask == 0x0000FF) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0xFF0000)) { + dd->pixelFormat = dd->force_alpha ? GF_PIXEL_RGBA : GF_PIXEL_BGRX; + } else if ((pixelFmt.dwRBitMask == 0xFF0000) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0x0000FF)) { + dd->pixelFormat = dd->force_alpha ? GF_PIXEL_ARGB : GF_PIXEL_RGBX; + } + dd->video_bpp = 32; + break; + default: + return GF_IO_ERR; + } + + hr = dd->pDD->lpVtbl->CreateClipper(dd->pDD, 0, &pcClipper, NULL); + if( FAILED(hr)) + return GF_IO_ERR; + + hr = pcClipper->lpVtbl->SetHWnd(pcClipper, 0, dd->cur_hwnd); + if( FAILED(hr) ) { + pcClipper->lpVtbl->Release(pcClipper); + return GF_IO_ERR; + } + hr = dd->pPrimary->lpVtbl->SetClipper(dd->pPrimary, pcClipper); + if( FAILED(hr)) { + pcClipper->lpVtbl->Release(pcClipper); + return GF_IO_ERR; + } + pcClipper->lpVtbl->Release(pcClipper); + + + dd->ddraw_init = GF_TRUE; + /*if YUV not initialize, init using HW video memory to setup HW caps*/ + return GF_OK; +} + +static GF_Err DD_LockSurface(DDContext *dd, GF_VideoSurface *vi, LPDDRAWSURFACE surface) +{ + HRESULT hr; + DDSURFDESC desc; + + if (!dd || !vi || !surface) return GF_BAD_PARAM; + + ZeroMemory(&desc, sizeof(desc)); + desc.dwSize = sizeof(DDSURFACEDESC); + + hr = surface->lpVtbl->Lock(surface, NULL, &desc, DDLOCK_SURFACEMEMORYPTR | /*DDLOCK_WRITEONLY | */ DDLOCK_WAIT, NULL); + if (FAILED(hr)) + return GF_IO_ERR; + + vi->video_buffer = (char*)desc.lpSurface; + vi->width = desc.dwWidth; + vi->height = desc.dwHeight; + vi->pitch_x = 0; + vi->pitch_y = desc.lPitch; + vi->is_hardware_memory = dd->systems_memory ? GF_FALSE : GF_TRUE; + return GF_OK; +} + +static GF_Err DD_UnlockSurface(DDContext *dd, LPDDRAWSURFACE surface) +{ + HRESULT hr; + if (!dd || !dd->ddraw_init) return GF_IO_ERR; + hr = surface->lpVtbl->Unlock(surface, NULL); + return FAILED(hr) ? GF_IO_ERR : GF_OK; +} + + +static GF_Err DD_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) +{ + DDCONTEXT; + + if (do_lock) { + memset(vi, 0, sizeof(GF_VideoSurface)); + vi->pixel_format = dd->pixelFormat; + return DD_LockSurface(dd, vi, dd->pBack); + } + else return DD_UnlockSurface(dd, dd->pBack); +} + +static void *LockOSContext(GF_VideoOutput *dr, Bool do_lock) +{ + DDCONTEXT; + + if (!dd->pBack) return NULL; + + if (do_lock) { + if (!dd->lock_hdc && ! dd->pBack->lpVtbl->IsLost(dd->pBack)) { + if (FAILED(dd->pBack->lpVtbl->GetDC(dd->pBack, &dd->lock_hdc)) ) + dd->lock_hdc = NULL; + } + } else if (dd->lock_hdc) { + dd->pBack->lpVtbl->ReleaseDC(dd->pBack, dd->lock_hdc); + dd->lock_hdc = NULL; + } + return (void *)dd->lock_hdc ; +} + + +static GF_Err DD_BlitSurface(DDContext *dd, DDSurface *src, GF_Window *src_wnd, GF_Window *dst_wnd, GF_ColorKey *key) +{ + HRESULT hr; + u32 dst_w, dst_h, src_w, src_h, flags; + RECT r_dst, r_src; + u32 left, top; + src_w = src_wnd ? src_wnd->w : src->width; + src_h = src_wnd ? src_wnd->h : src->height; + dst_w = dst_wnd ? dst_wnd->w : dd->width; + dst_h = dst_wnd ? dst_wnd->h : dd->height; + + if (src_wnd != NULL) MAKERECT(r_src, src_wnd); + if (dst_wnd != NULL) MAKERECT(r_dst, dst_wnd); + + if (key) { + u32 col; + DDCOLORKEY ck; + col = GF_COL_ARGB(0xFF, key->r, key->g, key->b); + ck.dwColorSpaceHighValue = ck.dwColorSpaceLowValue = col; + hr = src->pSurface->lpVtbl->SetColorKey(src->pSurface, DDCKEY_SRCBLT, &ck); + if (FAILED(hr)) + return GF_IO_ERR; + } + + if ((dst_w==src_w) && (dst_h==src_h)) { + flags = DDBLTFAST_WAIT; + if (key) flags |= DDBLTFAST_SRCCOLORKEY; + if (dst_wnd) { + left = r_dst.left; + top = r_dst.top; + } + else left = top = 0; + hr = dd->pBack->lpVtbl->BltFast(dd->pBack, left, top, src->pSurface, src_wnd ? &r_src : NULL, flags); + } else { + flags = DDBLT_WAIT; + if (key) flags |= DDBLT_KEYSRC; + hr = dd->pBack->lpVtbl->Blt(dd->pBack, dst_wnd ? &r_dst : NULL, src->pSurface, src_wnd ? &r_src : NULL, flags, NULL); + } + + if (src->is_yuv && dd->force_video_mem_for_yuv && dd->systems_memory) return GF_IO_ERR; + +#ifndef GPAC_DISABLE_LOG + if (hr) { + GF_LOG(dd->systems_memory ? GF_LOG_INFO : GF_LOG_ERROR, GF_LOG_MMIO, ("[DX Out] Failed blitting %s %s memory: error %08x\n", src->is_yuv ? "YUV" : "RGB", dd->systems_memory ? "systems" : "hardware", hr)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX Out] %s blit %s memory %dx%d -> %dx%d\n", src->is_yuv ? "YUV" : "RGB", dd->systems_memory ? "systems" : "hardware", src_w, src_h, dst_w, dst_h)); + } +#endif + return FAILED(hr) ? GF_IO_ERR : GF_OK; +} + +static DDSurface *DD_GetSurface(GF_VideoOutput *dr, u32 width, u32 height, u32 pixel_format, Bool check_caps) +{ + DDSURFDESC ddsd; + HRESULT hr; + DDCONTEXT; + + if (!dd->pDD) return NULL; + + /*yuv format*/ + if (pixelformat_yuv(pixel_format)) { + /*some drivers give broken result if YUV surface dimensions are not even*/ + while (width%2) width++; + while (height%2) height++; + + if (dr->yuv_pixel_format) { + DDSurface *yuvp = &dd->yuv_pool; + + /*don't recreate a surface if not needed*/ + if (yuvp->pSurface && (yuvp->width >= width) && (yuvp->height >= height) ) return yuvp; + + width = MAX(yuvp->width, width); + height = MAX(yuvp->height, height); + + SAFE_DD_RELEASE(yuvp->pSurface); + memset(yuvp, 0, sizeof(DDSurface)); + yuvp->is_yuv = GF_TRUE; + + memset (&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddsd.ddpfPixelFormat.dwSize = sizeof(ddsd.ddpfPixelFormat); + ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + + ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY; + + if (!dd->offscreen_yuv_to_rgb && (dr->hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY)) + ddsd.ddsCaps.dwCaps |= DDSCAPS_OVERLAY; + + ddsd.ddpfPixelFormat.dwFlags = DDPF_FOURCC; + ddsd.dwWidth = width; + ddsd.dwHeight = height; + ddsd.ddpfPixelFormat.dwFourCC = get_win_4CC(dr->yuv_pixel_format); + + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &yuvp->pSurface, NULL); + if (FAILED(hr) ) { + if (!check_caps) return NULL; + + /*try without offscreen yuv->rgbcap*/ + if (dd->offscreen_yuv_to_rgb) { + dd->offscreen_yuv_to_rgb = GF_FALSE; + if (dr->hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) { + ddsd.ddsCaps.dwCaps |= DDSCAPS_OVERLAY; + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &yuvp->pSurface, NULL); + if( FAILED(hr) ) { + return NULL; + } + } else { + return NULL; + } + } + + /*try without overlay cap*/ + if (dr->hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) { + dr->hw_caps &= ~GF_VIDEO_HW_HAS_YUV_OVERLAY; + ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY; + + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &yuvp->pSurface, NULL); + if( FAILED(hr) ) { + return NULL; + } + } + /*YUV not supported*/ + else { + return NULL; + } + + } + yuvp->format = dr->yuv_pixel_format; + yuvp->width = width; + yuvp->height = height; + + + return yuvp; + } + } + + /*don't recreate a surface if not needed*/ + if ((dd->rgb_pool.width >= width) && (dd->rgb_pool.height >= height) ) return &dd->rgb_pool; + width = MAX(dd->rgb_pool.width, width); + height = MAX(dd->rgb_pool.height, height); + SAFE_DD_RELEASE(dd->rgb_pool.pSurface); + memset(&dd->rgb_pool, 0, sizeof(DDSurface)); + + memset (&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; + ddsd.dwWidth = width; + ddsd.dwHeight = height; + + hr = dd->pDD->lpVtbl->CreateSurface(dd->pDD, &ddsd, &dd->rgb_pool.pSurface, NULL); + if (FAILED(hr)) + return NULL; + dd->rgb_pool.width = width; + dd->rgb_pool.height = height; + dd->rgb_pool.format = dd->pixelFormat; + return &dd->rgb_pool; +} + +static GF_Err DD_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) +{ + GF_VideoSurface temp_surf; + GF_Err e; + GF_Window src_wnd_2; + u32 w, h; + DDSurface *pool; + DDCONTEXT; + + if (!video_src) { + if (overlay_type && dd->yuv_pool.pSurface) + dd->yuv_pool.pSurface->lpVtbl->UpdateOverlay(dd->yuv_pool.pSurface, NULL, dd->pPrimary, NULL, DDOVER_HIDE, NULL); + return GF_OK; + } + if (src_wnd) { + w = src_wnd->w; + h = src_wnd->h; + } else { + w = video_src->width; + h = video_src->height; + } + /*get RGB or YUV pool surface*/ + //if (video_src->pixel_format==GF_PIXEL_YUVD) return GF_NOT_SUPPORTED; + if (video_src->pixel_format==GF_PIXEL_YUVD) video_src->pixel_format = GF_PIXEL_YUV; + pool = DD_GetSurface(dr, w, h, video_src->pixel_format, GF_FALSE); + if (!pool) + return GF_IO_ERR; + + memset(&temp_surf, 0, sizeof(GF_VideoSurface)); + temp_surf.pixel_format = pool->format; + e = DD_LockSurface(dd, &temp_surf, pool->pSurface); + if (e) return e; + + /*copy pixels to pool*/ + e = gf_stretch_bits(&temp_surf, video_src, NULL, src_wnd, 0xFF, GF_FALSE, NULL, NULL); + + DD_UnlockSurface(dd, pool->pSurface); + if (e) return e; + + if (overlay_type) { + HRESULT hr; + RECT dst_rc, src_rc; + POINT pt; + GF_Window dst = *dst_wnd; + GF_Window src = *src_wnd; + + src.x = src.y = 0; + MAKERECT(src_rc, (&src)); + pt.x = dst.x; + pt.y = dst.y; + ClientToScreen(dd->cur_hwnd, &pt); + dst.x = pt.x; + dst.y = pt.y; + MAKERECT(dst_rc, (&dst)); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX] Blit surface to dest %d x %d - overlay type %s\n", dst.w, dst.h, (overlay_type==1) ? "Top-Level" : "ColorKey" )); + +#if 1 + if (overlay_type==1) { + hr = pool->pSurface->lpVtbl->UpdateOverlay(pool->pSurface, &src_rc, dd->pPrimary, &dst_rc, DDOVER_SHOW | DDOVER_AUTOFLIP, NULL); + } else { + DDOVERLAYFX ddofx; + memset(&ddofx, 0, sizeof(DDOVERLAYFX)); + ddofx.dwSize = sizeof(DDOVERLAYFX); + ddofx.dckDestColorkey.dwColorSpaceLowValue = dr->overlay_color_key; + ddofx.dckDestColorkey.dwColorSpaceHighValue = dr->overlay_color_key; + hr = pool->pSurface->lpVtbl->UpdateOverlay(pool->pSurface, &src_rc, dd->pPrimary, &dst_rc, DDOVER_SHOW | DDOVER_KEYDESTOVERRIDE | DDOVER_AUTOFLIP, &ddofx); + } + if (FAILED(hr)) { + pool->pSurface->lpVtbl->UpdateOverlay(pool->pSurface, NULL, dd->pPrimary, NULL, DDOVER_HIDE, NULL); + } +#else + if (overlay_type==1) { + hr = dd->pPrimary->lpVtbl->Blt(dd->pPrimary, &dst_rc, pool->pSurface, &src_rc, 0, NULL); + } else { + DDBLTFX ddfx; + memset(&ddfx, 0, sizeof(DDBLTFX)); + ddfx.dwSize = sizeof(DDBLTFX); + ddfx.ddckDestColorkey.dwColorSpaceLowValue = dr->overlay_color_key; + ddfx.ddckDestColorkey.dwColorSpaceHighValue = dr->overlay_color_key; + + hr = dd->pPrimary->lpVtbl->Blt(dd->pPrimary, &dst_rc, pool->pSurface, &src_rc, DDBLT_WAIT | DDBLT_KEYDESTOVERRIDE, &ddfx); + } +#endif + return FAILED(hr) ? GF_IO_ERR : GF_OK; + } + + src_wnd_2.x = src_wnd_2.y = 0; + if (src_wnd) { + src_wnd_2.w = src_wnd->w; + src_wnd_2.h = src_wnd->h; + } else { + src_wnd_2.w = video_src->width; + src_wnd_2.h = video_src->height; + } + /*and blit surface*/ + return DD_BlitSurface(dd, pool, &src_wnd_2, dst_wnd, NULL); +} + + +static GFINLINE u32 is_yuv_supported(u32 win_4cc) +{ + if (win_4cc==get_win_4CC(GF_PIXEL_YV12)) return GF_PIXEL_YUV; + else if (win_4cc==get_win_4CC(GF_PIXEL_I420)) return GF_PIXEL_I420; + else if (win_4cc==get_win_4CC(GF_PIXEL_IYUV)) return GF_PIXEL_IYUV; + else if (win_4cc==get_win_4CC(GF_PIXEL_UYVY)) return GF_PIXEL_UYVY; + else if (win_4cc==get_win_4CC(GF_PIXEL_Y422)) return GF_PIXEL_YUV422; + else if (win_4cc==get_win_4CC(GF_PIXEL_UYNV)) return GF_PIXEL_UYNV; + else if (win_4cc==get_win_4CC(GF_PIXEL_YUY2)) return GF_PIXEL_YUYV; + else if (win_4cc==get_win_4CC(GF_PIXEL_YUNV)) return GF_PIXEL_YUNV; + else if (win_4cc==get_win_4CC(GF_PIXEL_V422)) return GF_PIXEL_V422; + else if (win_4cc==get_win_4CC(GF_PIXEL_YVYU)) return GF_PIXEL_YVYU; + else return 0; +} + +static GFINLINE Bool is_yuv_planar(u32 format) +{ + switch (format) { + case GF_PIXEL_YUV: + case GF_PIXEL_YUV_10: + case GF_PIXEL_I420: + case GF_PIXEL_IYUV: + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: + case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444_10: + return GF_TRUE; + default: + return GF_FALSE; + } +} + +#define YUV_NUM_TEST 20 + +/*gets fastest YUV format for YUV to RGB blit from YUV overlay (support is quite random on most cards)*/ +void DD_InitYUV(GF_VideoOutput *dr) +{ + u32 w, h, j, i, num_yuv; + Bool force_yv12 = GF_FALSE; + DWORD numCodes; + DWORD formats[30]; + DWORD *codes; + u32 now, min_packed = 0xFFFFFFFF, min_planar = 0xFFFFFFFF; + u32 best_packed = 0, best_planar = 0; + Bool checkPacked = TRUE; + const char *opt; + + DDCONTEXT; + + w = 720; + h = 576; + + + opt = gf_opts_get_key("core", "pref-yuv4cc"); + if (opt) { + char a, b, c, d; + if (sscanf(opt, "%c%c%c%c", &a, &b, &c, &d)==4) { + dr->yuv_pixel_format = GF_4CC(a, b, c, d); + dr->hw_caps |= GF_VIDEO_HW_HAS_YUV; + opt = gf_opts_get_key("core", "yuv-overlay"); + if (opt && !strcmp(opt, "yes")) + dr->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY; + dd->yuv_init = GF_TRUE; + num_yuv = 1; + } + } + + /*first run on this machine, to some benchmark*/ + if (! dd->yuv_init) { + char szOpt[100]; + dd->yuv_init = GF_TRUE; + + dr->hw_caps |= GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_YUV_OVERLAY; + + dd->pDD->lpVtbl->GetFourCCCodes(dd->pDD, &numCodes, NULL); + if (!numCodes) return; + codes = (DWORD *)gf_malloc(numCodes*sizeof(DWORD)); + dd->pDD->lpVtbl->GetFourCCCodes(dd->pDD, &numCodes, codes); + + num_yuv = 0; + for (i=0; i<numCodes; i++) { + formats[num_yuv] = is_yuv_supported(codes[i]); + if (formats[num_yuv]) num_yuv++; + } + gf_free(codes); + + /*too bad*/ + if (!num_yuv) { + dr->hw_caps &= ~(GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_YUV_OVERLAY); + dr->yuv_pixel_format = 0; + return; + } + + for (i=0; i<num_yuv; i++) { + /*check planar first*/ + if (!checkPacked && !is_yuv_planar(formats[i])) goto go_on; + /*then check packed */ + if (checkPacked && is_yuv_planar(formats[i])) goto go_on; + + SAFE_DD_RELEASE(dd->yuv_pool.pSurface); + memset(&dd->yuv_pool, 0, sizeof(DDSurface)); + dd->yuv_pool.is_yuv = GF_TRUE; + + dr->yuv_pixel_format = formats[i]; + if (DD_GetSurface(dr, w, h, dr->yuv_pixel_format, GF_TRUE) == NULL) + goto rem_fmt; + + now = gf_sys_clock(); + /*perform blank blit*/ + for (j=0; j<YUV_NUM_TEST; j++) { + if (DD_BlitSurface(dd, &dd->yuv_pool, NULL, NULL, NULL) != GF_OK) + goto rem_fmt; + } + now = gf_sys_clock() - now; + if (formats[i]== GF_PIXEL_YUV) + force_yv12 = GF_TRUE; + + if (!checkPacked) { + if (now<min_planar) { + min_planar = now; + best_planar = dr->yuv_pixel_format; + } + } else { + if (now<min_packed) { + min_packed = now; + best_packed = dr->yuv_pixel_format; + } + } + +go_on: + if (checkPacked && (i+1==num_yuv)) { + i = -1; + checkPacked = FALSE; + } + continue; + +rem_fmt: + for (j=i; j<num_yuv-1; j++) { + formats[j] = formats[j+1]; + } + i--; + num_yuv--; + } + + SAFE_DD_RELEASE(dd->yuv_pool.pSurface); + memset(&dd->yuv_pool, 0, sizeof(DDSurface)); + dd->yuv_pool.is_yuv = GF_TRUE; + + if (best_planar && (min_planar <= min_packed )) { + dr->yuv_pixel_format = best_planar; + } else { + min_planar = min_packed; + dr->yuv_pixel_format = best_packed; + } + if (force_yv12) + dr->yuv_pixel_format = GF_PIXEL_YUV; + + /*store our options*/ + sprintf(szOpt, "%c%c%c%c", (dr->yuv_pixel_format>>24) & 0xFF, (dr->yuv_pixel_format>>16) & 0xFF, (dr->yuv_pixel_format>>8) & 0xFF, (dr->yuv_pixel_format) & 0xFF); + gf_opts_set_key("core", "pref-yuv4cc", szOpt); + gf_opts_set_key("core", "yuv-overlay", (dr->hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) ? "yes" : "no"); + } + + opt = gf_opts_get_key("core", "hwvmem"); + if (opt && !strcmp(opt, "never")) num_yuv = 0; + + /*too bad*/ + if (!num_yuv) { + dr->hw_caps &= ~(GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_YUV_OVERLAY); + dr->yuv_pixel_format = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] YUV hardware not available\n")); + return; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] Picked YUV format %s - drawn in %d ms\n", gf_4cc_to_str(dr->yuv_pixel_format), min_planar)); + + /*enable YUV->RGB on the backbuffer ?*/ + opt = gf_opts_get_key("core", "offscreen-yuv"); + /*no set is the default*/ + if (!opt) { + opt = "yes"; + gf_opts_set_key("core", "offscreen-yuv", "yes"); + } + if (opt && strcmp(opt, "yes")) dr->hw_caps &= ~GF_VIDEO_HW_HAS_YUV; + else dd->offscreen_yuv_to_rgb = GF_TRUE; + + /*get YUV overlay key*/ + opt = gf_opts_get_key("core", "overlay-color-key"); + /*no set is the default*/ + if (!opt) { + opt = "0101FE"; + gf_opts_set_key("core", "overlay-color-key", "0101FE"); + } + sscanf(opt, "%06x", &dr->overlay_color_key); + if (dr->overlay_color_key) dr->overlay_color_key |= 0xFF000000; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] YUV->RGB enabled: %s - ColorKey enabled: %s (key %x)\n", + (dr->hw_caps & GF_VIDEO_HW_HAS_YUV) ? "Yes" : "No", + dr->overlay_color_key ? "Yes" : "No", dr->overlay_color_key + )); +} + +GF_Err DD_SetBackBufferSize(GF_VideoOutput *dr, u32 width, u32 height, Bool use_system_memory) +{ + GF_Err e; + DDCONTEXT; +#ifndef GPAC_DISABLE_3D + if (dd->output_3d) return GF_BAD_PARAM; +#endif + if (!dd->ddraw_init) { + e = InitDirectDraw(dr, width, height); + if (e) return e; + } + return CreateBackBuffer(dr, width, height, use_system_memory); +} + + +void DD_SetupDDraw(GF_VideoOutput *driv) +{ + driv->hw_caps |= GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_STRETCH; + driv->Blit = DD_Blit; + driv->LockBackBuffer = DD_LockBackBuffer; + driv->LockOSContext = LockOSContext; +} + diff --git a/modules/dx_hw/dx_audio.c b/modules/dx_hw/dx_audio.c new file mode 100644 index 0000000..fe004d7 --- /dev/null +++ b/modules/dx_hw/dx_audio.c @@ -0,0 +1,503 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / DirectX audio and video render module + * + * 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 "dx_hw.h" + +#if (DIRECTSOUND_VERSION >= 0x0800) +#define USE_WAVE_EX +#endif + +#ifdef USE_WAVE_EX +#include <ks.h> +#include <ksmedia.h> +const static GUID GPAC_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001,0x0000,0x0010, + {0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71} +}; +#endif + + +typedef HRESULT (WINAPI *DIRECTSOUNDCREATEPROC)(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter); + +/* + DirectSound audio output +*/ +#define MAX_NUM_BUFFERS 20 + +typedef struct +{ + Bool force_config; + u32 cfg_num_buffers, cfg_duration; + + HWND hWnd; + LPDIRECTSOUND pDS; + WAVEFORMATEX format; + IDirectSoundBuffer *pOutput; + + u32 buffer_size, num_audio_buffer, total_audio_buffer_ms; + + /*notifs*/ + Bool use_notif; + u32 frame_state[MAX_NUM_BUFFERS]; + DSBPOSITIONNOTIFY notif_events[MAX_NUM_BUFFERS]; + HANDLE events[MAX_NUM_BUFFERS]; + + HMODULE hDSoundLib; + DIRECTSOUNDCREATEPROC DirectSoundCreate; +} DSContext; + +#define DSCONTEXT() DSContext *ctx = (DSContext *)dr->opaque; + +void DS_WriteAudio(GF_AudioOutput *dr); +void DS_WriteAudio_Notifs(GF_AudioOutput *dr); + +static GF_Err DS_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration) +{ + HRESULT hr; + + DSCONTEXT(); + ctx->hWnd = (HWND) os_handle; + /*check if we have created a HWND (this requires that video is handled by the DX module*/ + if (!ctx->hWnd) ctx->hWnd = DD_GetGlobalHWND(); + /*too bad, use desktop as window*/ + if (!ctx->hWnd) ctx->hWnd = GetDesktopWindow(); + + ctx->force_config = (num_buffers && total_duration) ? GF_TRUE : GF_FALSE; + ctx->cfg_num_buffers = num_buffers; + ctx->cfg_duration = total_duration; + if (ctx->cfg_num_buffers <= 1) ctx->cfg_num_buffers = 2; + + if ( FAILED( hr = ctx->DirectSoundCreate( NULL, &ctx->pDS, NULL ) ) ) return GF_IO_ERR; + + if( FAILED( hr = ctx->pDS->lpVtbl->SetCooperativeLevel(ctx->pDS, ctx->hWnd, DSSCL_EXCLUSIVE) ) ) { + SAFE_DS_RELEASE( ctx->pDS ); + return GF_IO_ERR; + } + return GF_OK; +} + + +void DS_ResetBuffer(DSContext *ctx) +{ + VOID *pLock = NULL; + DWORD size; + + if( FAILED(ctx->pOutput->lpVtbl->Lock(ctx->pOutput, 0, ctx->buffer_size*ctx->num_audio_buffer, &pLock, &size, NULL, NULL, 0 ) ) ) + return; + memset(pLock, 0, (size_t) size); + ctx->pOutput->lpVtbl->Unlock(ctx->pOutput, pLock, size, NULL, 0L); +} + +void DS_ReleaseBuffer(GF_AudioOutput *dr) +{ + u32 i; + DSCONTEXT(); + + /*stop playing and notif proc*/ + if (ctx->pOutput) ctx->pOutput->lpVtbl->Stop(ctx->pOutput); + SAFE_DS_RELEASE(ctx->pOutput); + + /*use notif, shutdown notifier and event watcher*/ + if (ctx->use_notif) { + for (i=0; i<ctx->num_audio_buffer; i++) CloseHandle(ctx->events[i]); + } + ctx->use_notif = GF_FALSE; +} + +static void DS_Shutdown(GF_AudioOutput *dr) +{ + DSCONTEXT(); + DS_ReleaseBuffer(dr); + SAFE_DS_RELEASE(ctx->pDS ); +} + +/*we assume what was asked is what we got*/ +static GF_Err DS_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) +{ + u32 i; + HRESULT hr; + DSBUFFERDESC dsbBufferDesc; + IDirectSoundNotify *pNotify; +#ifdef USE_WAVE_EX + WAVEFORMATEXTENSIBLE format_ex; +#endif + DSCONTEXT(); + + DS_ReleaseBuffer(dr); + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S24: + case GF_AUDIO_FMT_S32: + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + break; + } + ctx->format.nChannels = *NbChannels; + ctx->format.wBitsPerSample = gf_audio_fmt_bit_depth( *audioFormat); + ctx->format.nSamplesPerSec = *SampleRate; + ctx->format.cbSize = sizeof (WAVEFORMATEX); + ctx->format.wFormatTag = WAVE_FORMAT_PCM; + ctx->format.nBlockAlign = ctx->format.nChannels * ctx->format.wBitsPerSample / 8; + ctx->format.nAvgBytesPerSec = ctx->format.nSamplesPerSec * ctx->format.nBlockAlign; + + if (!ctx->format.nBlockAlign) + return GF_BAD_PARAM; + + if (!ctx->force_config) { + ctx->buffer_size = ctx->format.nBlockAlign * 1024; + ctx->num_audio_buffer = 2; + } else { + ctx->num_audio_buffer = ctx->cfg_num_buffers; + ctx->buffer_size = (ctx->format.nAvgBytesPerSec * ctx->cfg_duration) / (1000 * ctx->cfg_num_buffers); + } + + /*make sure we're aligned*/ + while (ctx->buffer_size % ctx->format.nBlockAlign) ctx->buffer_size++; + + ctx->use_notif = GF_TRUE; + if (gf_opts_get_bool("core", "ds-disable-notif")) + ctx->use_notif = GF_FALSE; + + memset(&dsbBufferDesc, 0, sizeof(DSBUFFERDESC)); + dsbBufferDesc.dwSize = sizeof (DSBUFFERDESC); + dsbBufferDesc.dwBufferBytes = ctx->buffer_size * ctx->num_audio_buffer; + dsbBufferDesc.lpwfxFormat = &ctx->format; + dsbBufferDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME; + if (ctx->use_notif) dsbBufferDesc.dwFlags |= DSBCAPS_CTRLPOSITIONNOTIFY; + +#ifdef USE_WAVE_EX + if (channel_cfg && ctx->format.nChannels>2) { + memset(&format_ex, 0, sizeof(WAVEFORMATEXTENSIBLE)); + format_ex.Format = ctx->format; + format_ex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE); + format_ex.SubFormat = GPAC_KSDATAFORMAT_SUBTYPE_PCM; + format_ex.Samples.wValidBitsPerSample = gf_audio_fmt_bit_depth(*audioFormat); + + format_ex.dwChannelMask = 0; + if (channel_cfg & GF_AUDIO_CH_FRONT_LEFT) format_ex.dwChannelMask |= SPEAKER_FRONT_LEFT; + if (channel_cfg & GF_AUDIO_CH_FRONT_RIGHT) format_ex.dwChannelMask |= SPEAKER_FRONT_RIGHT; + if (channel_cfg & GF_AUDIO_CH_FRONT_CENTER) format_ex.dwChannelMask |= SPEAKER_FRONT_CENTER; + if (channel_cfg & GF_AUDIO_CH_LFE) format_ex.dwChannelMask |= SPEAKER_LOW_FREQUENCY; + if (channel_cfg & GF_AUDIO_CH_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT; + if (channel_cfg & GF_AUDIO_CH_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT; + if (channel_cfg & GF_AUDIO_CH_REAR_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT; + + dsbBufferDesc.lpwfxFormat = (WAVEFORMATEX *) &format_ex; + } +#endif + + + hr = ctx->pDS->lpVtbl->CreateSoundBuffer(ctx->pDS, &dsbBufferDesc, &ctx->pOutput, NULL ); + if (FAILED(hr)) { +retry: + if (ctx->use_notif) gf_opts_set_key("core", "ds-disable-notif", "yes"); + ctx->use_notif = GF_FALSE; + dsbBufferDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; + hr = ctx->pDS->lpVtbl->CreateSoundBuffer(ctx->pDS, &dsbBufferDesc, &ctx->pOutput, NULL ); + if (FAILED(hr)) { + if (ctx->format.nChannels>2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUDIO, ("[DirectSound] failed to configure output for %d channels (error %08x) - falling back to stereo\n", *NbChannels, hr)); + *NbChannels = 2; + return DS_Configure(dr, SampleRate, NbChannels, audioFormat, 0); + } + return GF_IO_ERR; + } + } + + for (i=0; i<ctx->num_audio_buffer; i++) ctx->frame_state[i] = 0; + + if (ctx->use_notif) { + hr = ctx->pOutput->lpVtbl->QueryInterface(ctx->pOutput, &IID_IDirectSoundNotify , (void **)&pNotify); + if (hr == S_OK) { + /*setup the notification positions*/ + for (i=0; i<ctx->num_audio_buffer; i++) { + ctx->events[i] = CreateEvent( NULL, FALSE, FALSE, NULL ); + ctx->notif_events[i].hEventNotify = ctx->events[i]; + ctx->notif_events[i].dwOffset = ctx->buffer_size * i; + } + + /*Tell DirectSound when to notify us*/ + hr = pNotify->lpVtbl->SetNotificationPositions(pNotify, ctx->num_audio_buffer, ctx->notif_events); + + if (hr != S_OK) { + pNotify->lpVtbl->Release(pNotify); + for (i=0; i<ctx->num_audio_buffer; i++) CloseHandle(ctx->events[i]); + SAFE_DS_RELEASE(ctx->pOutput); + goto retry; + } + + pNotify->lpVtbl->Release(pNotify); + } else { + ctx->use_notif = GF_FALSE; + } + } + if (ctx->use_notif) { + dr->WriteAudio = DS_WriteAudio_Notifs; + } else { + dr->WriteAudio = DS_WriteAudio; + } + + ctx->total_audio_buffer_ms = 1000 * ctx->buffer_size * ctx->num_audio_buffer / ctx->format.nAvgBytesPerSec; + + /*reset*/ + DS_ResetBuffer(ctx); + /*play*/ + ctx->pOutput->lpVtbl->Play(ctx->pOutput, 0, 0, DSBPLAY_LOOPING); + return GF_OK; +} + +static Bool DS_RestoreBuffer(LPDIRECTSOUNDBUFFER pDSBuffer) +{ + DWORD dwStatus; + pDSBuffer->lpVtbl->GetStatus(pDSBuffer, &dwStatus ); + if( dwStatus & DSBSTATUS_BUFFERLOST ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] buffer lost\n")); + pDSBuffer->lpVtbl->Restore(pDSBuffer); + pDSBuffer->lpVtbl->GetStatus(pDSBuffer, &dwStatus); + if( dwStatus & DSBSTATUS_BUFFERLOST ) return GF_TRUE; + } + return GF_FALSE; +} + + + +void DS_FillBuffer(GF_AudioOutput *dr, u32 buffer) +{ + HRESULT hr; + VOID *pLock; + u32 pos; + DWORD size; + DSCONTEXT(); + + /*restoring*/ + if (DS_RestoreBuffer(ctx->pOutput)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] restoring sound buffer\n")); + return; + } + + /*lock and fill from current pos*/ + pos = buffer * ctx->buffer_size; + pLock = NULL; + if( FAILED( hr = ctx->pOutput->lpVtbl->Lock(ctx->pOutput, pos, ctx->buffer_size, + &pLock, &size, NULL, NULL, 0L ) ) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] Error locking sound buffer\n")); + return; + } + + assert(size == ctx->buffer_size); + + dr->FillBuffer(dr->audio_renderer, pLock, size); + + /*update current pos*/ + if( FAILED( hr = ctx->pOutput->lpVtbl->Unlock(ctx->pOutput, pLock, size, NULL, 0)) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] Error unlocking sound buffer\n")); + } + ctx->frame_state[buffer] = 1; +} + + +void DS_WriteAudio_Notifs(GF_AudioOutput *dr) +{ + s32 i, inframe, nextframe; + DSCONTEXT(); + + inframe = WaitForMultipleObjects(ctx->num_audio_buffer, ctx->events, 0, 1000); + if (inframe==WAIT_TIMEOUT) return; + inframe -= WAIT_OBJECT_0; + /*reset state*/ + ctx->frame_state[ (inframe + ctx->num_audio_buffer - 1) % ctx->num_audio_buffer] = 0; + + nextframe = (inframe + 1) % ctx->num_audio_buffer; + for (i=nextframe; (i % ctx->num_audio_buffer) != (u32) inframe; i++) { + u32 buf = i % ctx->num_audio_buffer; + if (ctx->frame_state[buf]) continue; + DS_FillBuffer(dr, buf); + } +} + +void DS_WriteAudio(GF_AudioOutput *dr) +{ + u32 retry; + DWORD in_play, cur_play; + DSCONTEXT(); + if (!ctx->pOutput) return; + + /*wait for end of current play buffer*/ + if (ctx->pOutput->lpVtbl->GetCurrentPosition(ctx->pOutput, &in_play, NULL) != DS_OK ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] error getting sound buffer positions\n")); + return; + } + in_play = (in_play / ctx->buffer_size); + retry = 6; + while (retry) { + if (ctx->pOutput->lpVtbl->GetCurrentPosition(ctx->pOutput, &cur_play, NULL) != DS_OK ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectSound] error getting sound buffer positions\n")); + return; + } + cur_play = (cur_play / ctx->buffer_size); + if (cur_play == in_play) { + gf_sleep(20); + retry--; + } else { + /**/ + ctx->frame_state[in_play] = 0; + DS_FillBuffer(dr, in_play); + return; + } + } +} + +static GF_Err DS_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample) +{ + /*all sample rates supported for now ... */ + return GF_OK; +} + +static void DS_Play(GF_AudioOutput *dr, u32 PlayType) +{ + DSCONTEXT(); + switch (PlayType) { + case 0: + ctx->pOutput->lpVtbl->Stop(ctx->pOutput); + break; + case 2: + DS_ResetBuffer(ctx); + case 1: + ctx->pOutput->lpVtbl->Play(ctx->pOutput, 0, 0, DSBPLAY_LOOPING); + break; + } +} + +static void DS_SetVolume(GF_AudioOutput *dr, u32 Volume) +{ + LONG Vol; + DSCONTEXT(); + if (!ctx->pOutput) return; + if (Volume > 100) Vol = DSBVOLUME_MAX; + else if(Volume == 0) Vol = DSBVOLUME_MIN; + else Vol = DSBVOLUME_MIN/2 + Volume * (DSBVOLUME_MAX-DSBVOLUME_MIN/2) / 100; + ctx->pOutput->lpVtbl->SetVolume(ctx->pOutput, Vol); +} + +static void DS_SetPan(GF_AudioOutput *dr, u32 Pan) +{ + LONG dspan; + DSCONTEXT(); + if (!ctx->pOutput) return; + + if (Pan > 100) Pan = 100; + if (Pan > 50) { + dspan = DSBPAN_RIGHT * (Pan - 50) / 50; + } else if (Pan < 50) { + dspan = DSBPAN_LEFT * (50 - Pan) / 50; + } else { + dspan = 0; + } + ctx->pOutput->lpVtbl->SetPan(ctx->pOutput, dspan); +} + + +static void DS_SetPriority(GF_AudioOutput *dr, u32 Priority) +{ +} + +static u32 DS_GetAudioDelay(GF_AudioOutput *dr) +{ + DSCONTEXT(); + return ctx->total_audio_buffer_ms; +} + +static u32 DS_GetTotalBufferTime(GF_AudioOutput *dr) +{ + DSCONTEXT(); + return ctx->total_audio_buffer_ms; +} + +void *NewAudioOutput() +{ + HRESULT hr; + DSContext *ctx; + GF_AudioOutput *driv; + + if( FAILED( hr = CoInitialize(NULL) ) ) return NULL; + + + ctx = (DSContext*)gf_malloc(sizeof(DSContext)); + memset(ctx, 0, sizeof(DSContext)); +#ifdef UNICODE + ctx->hDSoundLib = LoadLibrary(L"dsound.dll"); +#else + ctx->hDSoundLib = LoadLibrary("dsound.dll"); +#endif + if (ctx->hDSoundLib) { + ctx->DirectSoundCreate = (DIRECTSOUNDCREATEPROC) GetProcAddress(ctx->hDSoundLib, "DirectSoundCreate"); + } + if (!ctx->DirectSoundCreate) { + if (ctx->hDSoundLib) FreeLibrary(ctx->hDSoundLib); + gf_free(ctx); + return NULL; + } + + + driv = (GF_AudioOutput*)gf_malloc(sizeof(GF_AudioOutput)); + memset(driv, 0, sizeof(GF_AudioOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "DirectSound Audio Output", "gpac distribution"); + + driv->opaque = ctx; + + driv->Setup = DS_Setup; + driv->Shutdown = DS_Shutdown; + driv->Configure = DS_Configure; + driv->SetVolume = DS_SetVolume; + driv->SetPan = DS_SetPan; + driv->Play = DS_Play; + driv->SetPriority = DS_SetPriority; + driv->GetAudioDelay = DS_GetAudioDelay; + driv->GetTotalBufferTime = DS_GetTotalBufferTime; + driv->WriteAudio = DS_WriteAudio; + driv->QueryOutputSampleRate = DS_QueryOutputSampleRate; + /*never threaded*/ + driv->SelfThreaded = GF_FALSE; + return driv; +} + +void DeleteDxAudioOutput(void *ifce) +{ + GF_AudioOutput *dr = (GF_AudioOutput *)ifce; + DSCONTEXT(); + + if (ctx->hDSoundLib) + FreeLibrary(ctx->hDSoundLib); + gf_free(ctx); + gf_free(ifce); + CoUninitialize(); +} + diff --git a/modules/dx_hw/dx_hw.h b/modules/dx_hw/dx_hw.h new file mode 100644 index 0000000..0af339e --- /dev/null +++ b/modules/dx_hw/dx_hw.h @@ -0,0 +1,239 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / DirectX audio and video render module + * + * 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 _DXHW_H +#define _DXHW_H + +#if defined(__GNUC__) && !defined(DIRECTSOUND_VERSION) +#define DIRECTSOUND_VERSION 0x0500 +#endif + +/*driver interfaces*/ +#include <gpac/modules/audio_out.h> +#include <gpac/modules/video_out.h> +#include <gpac/list.h> +#include <gpac/constants.h> + +//define before thread.h which includes windows.h and winbase.h +#define INITGUID + +#include <gpac/thread.h> + + + +/* +#include <windows.h> +*/ + +#define HAS_DDRAW_H + +#ifdef HAS_DDRAW_H +#include <ddraw.h> +#include <mmsystem.h> +#include <dsound.h> + +#ifndef _WIN32_WCE +#include <vfw.h> +#endif + +/*DirectDraw video output*/ +#if (DIRECTDRAW_VERSION < 0x0700) +#define USE_DX_3 +#endif + + +#ifdef USE_DX_3 +typedef LPDIRECTDRAWSURFACE LPDDRAWSURFACE; +typedef DDSURFACEDESC DDSURFDESC; +#else +typedef LPDIRECTDRAWSURFACE7 LPDDRAWSURFACE; +typedef DDSURFACEDESC2 DDSURFDESC; +#endif +typedef DDSURFDESC *LPDDSURFDESC; + +typedef HRESULT(WINAPI * DIRECTDRAWCREATEPROC) (GUID *, LPDIRECTDRAW *, IUnknown *); + +#endif + + + +#ifdef _WIN32_WCE +# ifndef SWP_ASYNCWINDOWPOS +# define SWP_ASYNCWINDOWPOS 0 +# endif +#endif + + + +#ifdef GPAC_USE_GLES1X +#include "GLES/egl.h" +#elif defined(GPAC_USE_GLES2) +#include "EGL/egl.h" +#endif + +#define EGL_CHECK_ERR {s32 res = eglGetError(); if (res!=12288) GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("EGL Error %d file %s line %d\n", res, __FILE__, __LINE__)); } + +typedef struct +{ + LPDDRAWSURFACE pSurface; + u32 width, height, format, pitch; + Bool is_yuv; +} DDSurface; + + +#if defined(GPAC_USE_TINYGL) +# ifndef GPAC_DISABLE_3D +# define GPAC_DISABLE_3D +# endif +#endif + +#ifndef WM_UNICHAR +#define WM_UNICHAR 0x0109 +#endif //WM_UNICHAR + +typedef struct +{ + HWND os_hwnd, fs_hwnd, cur_hwnd, parent_wnd; + Bool NeedRestore; + Bool switch_res; + +#ifdef USE_DX_3 + LPDIRECTDRAW pDD; + LPDIRECTDRAWSURFACE pPrimary; + LPDIRECTDRAWSURFACE pBack; +#else + LPDIRECTDRAW7 pDD; + LPDIRECTDRAWSURFACE7 pPrimary; + LPDIRECTDRAWSURFACE7 pBack; +#endif + Bool ddraw_init; + Bool yuv_init; + Bool fullscreen; + Bool systems_memory; + Bool force_alpha; + Bool offscreen_yuv_to_rgb; + + u32 width, height; + u32 fs_width, fs_height; + u32 store_width, store_height; + LONG backup_styles; + Bool alt_down, ctrl_down; + Bool on_secondary_screen; + u32 pixelFormat; + u32 video_bpp; + + HDC lock_hdc; + + /*HW surfaces for blitting+stretch*/ + DDSurface rgb_pool, yuv_pool; + + /*if we run in threaded mode*/ + GF_Thread *th; + u32 th_state; + + + Bool owns_hwnd; + u32 off_w, off_h, prev_styles; + LONG_PTR last_mouse_pos; + /*cursors*/ + HCURSOR curs_normal, curs_hand, curs_collide; + u32 cursor_type; + Bool is_setup, disable_vsync; + char *caption; + /*gl*/ +#ifndef GPAC_DISABLE_3D + +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) + NativeDisplayType gl_HDC; + EGLDisplay egldpy; + EGLSurface surface; + EGLConfig eglconfig; + EGLContext eglctx; +#else + HDC gl_HDC, pb_HDC; + HGLRC gl_HRC, pb_HRC; + Bool glext_init; +#endif + Bool output_3d; + HWND bound_hwnd; + Bool gl_double_buffer; + /*0: not init, 1: used, 2: not used*/ + u32 mode_high_bpp; + u8 bpp; +#endif + + Bool has_focus; + LONG_PTR orig_wnd_proc; + + UINT_PTR timer; + u32 last_mouse_move, cursor_type_backup; + Bool windowless, hidden; + + Bool dd_lost; + Bool force_video_mem_for_yuv; + + HMODULE hDDrawLib; + DIRECTDRAWCREATEPROC DirectDrawCreate; +} DDContext; + +void DD_SetupWindow(GF_VideoOutput *dr, Bool hide); +void DD_ShutdownWindow(GF_VideoOutput*dr); +GF_Err DD_ProcessEvent(GF_VideoOutput*dr, GF_Event *evt); + +void DestroyObjects(DDContext *dd); +GF_Err GetDisplayMode(DDContext *dd); +/*2D-only callbacks*/ +void DD_SetupDDraw(GF_VideoOutput *driv); +GF_Err InitDirectDraw(GF_VideoOutput *dr, u32 Width, u32 Height); +void DD_InitYUV(GF_VideoOutput *dr); + +GF_Err DD_SetBackBufferSize(GF_VideoOutput *dr, u32 width, u32 height, Bool use_system_memory); + +GF_Err DD_FlushEx(GF_VideoOutput *dr, GF_Window *dest, Bool wait_for_sync); + +#define MAKERECT(rc, dest) { rc.left = dest->x; rc.top = dest->y; rc.right = rc.left + dest->w; rc.bottom = rc.top + dest->h; } + +/*this is REALLY ugly, to pass the HWND to DSound when we create the window in this module*/ +HWND DD_GetGlobalHWND(); + +#ifndef GPAC_DISABLE_3D +GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_height); +#endif + + +#define SAFE_DD_RELEASE(p) { if(p) { (p)->lpVtbl->Release( (p) ); (p)=NULL; } } + + +void *NewAudioOutput(); +void DeleteDxAudioOutput(void *); + + +#define SAFE_DS_RELEASE(p) { if(p) { p->lpVtbl->Release(p); (p)=NULL; } } + +LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + +#endif diff --git a/modules/dx_hw/dx_hw.rc b/modules/dx_hw/dx_hw.rc new file mode 100644 index 0000000..936bd06 --- /dev/null +++ b/modules/dx_hw/dx_hw.rc @@ -0,0 +1,81 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "windows.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// French (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""windows.h""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Cursor +// + +IDC_HAND_PTR CURSOR "hand.cur" + +IDC_COLLIDE CURSOR "collide.cur" + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_OSMO_ICON ICON "../../share/doc/osmo4.ico" + +#endif // French (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/modules/dx_hw/dx_video.c b/modules/dx_hw/dx_video.c new file mode 100644 index 0000000..68a78f5 --- /dev/null +++ b/modules/dx_hw/dx_video.c @@ -0,0 +1,846 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / DirectX audio and video render module + * + * 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 "dx_hw.h" + +#include <gpac/user.h> + + +#ifdef _WIN32_WCE +#ifdef GPAC_USE_GLES1X +#endif +#elif defined(GPAC_USE_GLES2) +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +//# pragma comment(lib, "libGLESv2") +# pragma comment(lib, "libEGL") + + +#else +#include <GL/gl.h> + +# pragma comment(lib, "opengl32") + + +#endif + + +#define DDCONTEXT DDContext *dd = (DDContext *)dr->opaque; + + + +#ifndef GPAC_DISABLE_3D + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_TEXTURE_RGB_ARB 0x2075 + +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_TYPE_RGBA_ARB 0x202B + +typedef Bool (APIENTRY *CHOOSEPFFORMATARB)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +static CHOOSEPFFORMATARB wglChoosePixelFormatARB = NULL; + +typedef Bool (APIENTRY *GETPIXELFORMATATTRIBIV)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int* piAttributes, int *piValues); +static GETPIXELFORMATATTRIBIV wglGetPixelFormatAttribivARB = NULL; + + +typedef BOOL (APIENTRY *PFNWGLSWAPINTERVALFARPROC)( int ); +PFNWGLSWAPINTERVALFARPROC wglSwapIntervalEXT = NULL; + +#endif + + +static void RestoreWindow(DDContext *dd) +{ + if (!dd->NeedRestore) return; + dd->NeedRestore = GF_FALSE; + +#ifndef GPAC_DISABLE_3D + if (dd->output_3d) { +#ifndef _WIN32_WCE + ChangeDisplaySettings(NULL,0); +#endif + } else +#endif + + dd->pDD->lpVtbl->SetCooperativeLevel(dd->pDD, dd->cur_hwnd, DDSCL_NORMAL); + dd->NeedRestore = GF_FALSE; + +// SetForegroundWindow(dd->cur_hwnd); + SetFocus(dd->cur_hwnd); +} + +void DestroyObjectsEx(DDContext *dd, Bool only_3d) +{ + if (!only_3d) { +// RestoreWindow(dd); + + SAFE_DD_RELEASE(dd->rgb_pool.pSurface); + memset(&dd->rgb_pool, 0, sizeof(DDSurface)); + SAFE_DD_RELEASE(dd->yuv_pool.pSurface); + memset(&dd->yuv_pool, 0, sizeof(DDSurface)); + dd->yuv_pool.is_yuv = GF_TRUE; + + SAFE_DD_RELEASE(dd->pPrimary); + SAFE_DD_RELEASE(dd->pBack); + SAFE_DD_RELEASE(dd->pDD); + dd->ddraw_init = GF_FALSE; + +#ifdef GPAC_DISABLE_3D + } +#else + } + + /*delete openGL context*/ +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) + if (dd->eglctx) eglDestroyContext(dd->egldpy, dd->eglctx); + dd->eglctx = NULL; + if (dd->surface) eglDestroySurface(dd->egldpy, dd->surface); + dd->surface = NULL; + if (dd->gl_HDC) { + if (dd->egldpy) eglTerminate(dd->egldpy); + ReleaseDC(dd->cur_hwnd, (HDC) dd->gl_HDC); + dd->gl_HDC = 0L; + dd->egldpy = NULL; + } +#elif !defined(_WIN32_WCE) + + if (dd->pb_HRC) { + wglMakeCurrent(dd->pb_HDC, NULL); + wglDeleteContext(dd->pb_HRC); + dd->pb_HRC = NULL; + } + if (dd->pb_HDC) { +// wglReleasePbufferDCARB(dd->pbuffer, dd->pb_HDC); + ReleaseDC(dd->bound_hwnd, dd->pb_HDC); + dd->pb_HDC = NULL; + } + + if (dd->gl_HRC) { + //wglMakeCurrent(NULL, NULL); + wglDeleteContext(dd->gl_HRC); + dd->gl_HRC = NULL; + } + if (dd->gl_HDC) { + ReleaseDC(dd->bound_hwnd, dd->gl_HDC); + dd->gl_HDC = NULL; + } +#endif + + +#endif +} + +void DestroyObjects(DDContext *dd) +{ + DestroyObjectsEx(dd, GF_FALSE); +} + +#ifndef GPAC_DISABLE_3D + +GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_height) +{ +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) || !defined(_WIN32_WCE) + const char *sOpt; +#endif + GF_Event evt; + Bool hw_reset = GF_FALSE; + DDCONTEXT + +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) + EGLint major, minor; + EGLint n; + EGLConfig configs[1]; + u32 nb_bits; + u32 i=0; + static int egl_atts[20]; + + sOpt = gf_opts_get_key("core", "gl-bits-comp"); + nb_bits = sOpt ? atoi(sOpt) : 5; + + egl_atts[i++] = EGL_RED_SIZE; + egl_atts[i++] = nb_bits; + egl_atts[i++] = EGL_GREEN_SIZE; + egl_atts[i++] = nb_bits; + egl_atts[i++] = EGL_BLUE_SIZE; + egl_atts[i++] = nb_bits; + /*alpha for compositeTexture*/ + egl_atts[i++] = EGL_ALPHA_SIZE; + egl_atts[i++] = 1; + sOpt = gf_opts_get_key("core", "gl-bits-depth"); + nb_bits = sOpt ? atoi(sOpt) : 5; + egl_atts[i++] = EGL_DEPTH_SIZE; + egl_atts[i++] = nb_bits; + egl_atts[i++] = EGL_STENCIL_SIZE; + egl_atts[i++] = EGL_DONT_CARE; + + egl_atts[i++] = EGL_RENDERABLE_TYPE; + egl_atts[i++] = EGL_OPENGL_ES2_BIT; + + egl_atts[i++] = EGL_NONE; + + + /*already setup*/ + DestroyObjects(dd); + dd->gl_HDC = (NativeDisplayType) GetDC(dd->cur_hwnd); + dd->egldpy = eglGetDisplay(/*dd->gl_HDC*/ EGL_DEFAULT_DISPLAY); + if (!eglInitialize(dd->egldpy, &major, &minor)) return GF_IO_ERR; + if (!eglChooseConfig(dd->egldpy, egl_atts, configs, 1, &n)) return GF_IO_ERR; + dd->eglconfig = configs[0]; + dd->surface = eglCreateWindowSurface(dd->egldpy, dd->eglconfig, dd->cur_hwnd, 0); + if (!dd->surface) return GF_IO_ERR; + +#ifdef GPAC_USE_GLES2 + + i=0; + egl_atts[i++] = EGL_CONTEXT_CLIENT_VERSION; + egl_atts[i++] = 2; + egl_atts[i++] = EGL_NONE; + + eglBindAPI(EGL_OPENGL_ES_API); + dd->eglctx = eglCreateContext(dd->egldpy, dd->eglconfig, NULL, egl_atts); +#else + dd->eglctx = eglCreateContext(dd->egldpy, dd->eglconfig, NULL, NULL); +#endif + + if (!dd->eglctx) { + eglDestroySurface(dd->egldpy, dd->surface); + dd->surface = 0L; + return GF_IO_ERR; + } + if (!eglMakeCurrent(dd->egldpy, dd->surface, dd->surface, dd->eglctx)) { + eglDestroyContext(dd->egldpy, dd->eglctx); + dd->eglctx = 0L; + eglDestroySurface(dd->egldpy, dd->surface); + dd->surface = 0L; + return GF_IO_ERR; + } +#elif !defined(_WIN32_WCE) + PIXELFORMATDESCRIPTOR pfd; + s32 pixelformat; + HWND highbpp_hwnd = NULL; + HWND target_hwnd; + int bits_depth = 16; + u32 i; + Bool use_double_buffer; + + /*already setup*/ + target_hwnd = dd->cur_hwnd; + if ((dd->bound_hwnd == target_hwnd) && dd->gl_HRC) + goto exit; + + hw_reset = GF_TRUE; + dd->bound_hwnd = target_hwnd; + + /*cleanup*/ + DestroyObjectsEx(dd, dd->output_3d ? GF_FALSE : GF_TRUE); + + //first time we init GL: create a dummy window to select pixel format for high bpp - we must do this because + //- we must get a valid GL context to query the extensions for bpp > 8 (regular choosePixelFormat does not work for them) + //- we must call SetPixelFormat to create the GL context + //- it is not possible to call several time SetPixelFormat on the same window with different PF properties ... + if (!dd->mode_high_bpp) { + sOpt = gf_opts_get_key("core", "gl-bits-comp"); + if (!sOpt) { + gf_opts_set_key("core", "gl-bits-comp", "8"); + dd->bpp = 8; + } else { + dd->bpp = atoi(sOpt); + } + if (dd->bpp > 8) { +#ifdef UNICODE + highbpp_hwnd = CreateWindow(L"GPAC DirectDraw Output", L"dummy", WS_POPUP, 0, 0, 128, 128, NULL, NULL, NULL /* GetModuleHandle("gm_dx_hw.dll")*/, NULL); +#else + highbpp_hwnd = CreateWindow("GPAC DirectDraw Output", "dummy", WS_POPUP, 0, 0, 128, 128, NULL, NULL, NULL /* GetModuleHandle("gm_dx_hw.dll")*/, NULL); +#endif + dd->gl_HDC = GetDC(highbpp_hwnd); + + memset(&pfd, 0, sizeof(pfd)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_SUPPORT_OPENGL; + if ( (pixelformat = ChoosePixelFormat(dd->gl_HDC, &pfd)) == FALSE ) return GF_IO_ERR; + + if (SetPixelFormat(dd->gl_HDC, pixelformat, &pfd) == FALSE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DX GL] Cannot select pixel format: error %d- disabling GL\n", GetLastError() )); + return GF_IO_ERR; + } + dd->gl_HRC = wglCreateContext(dd->gl_HDC); + if (!dd->gl_HRC) return GF_IO_ERR; + + HGLRC cur = wglGetCurrentContext(); + if (cur) wglShareLists(cur, dd->gl_HRC); + + wglMakeCurrent(dd->gl_HDC, dd->gl_HRC); + wglChoosePixelFormatARB = (CHOOSEPFFORMATARB) wglGetProcAddress("wglChoosePixelFormatARB"); + wglGetPixelFormatAttribivARB = (GETPIXELFORMATATTRIBIV) wglGetProcAddress("wglGetPixelFormatAttribivARB"); + + wglMakeCurrent(NULL, NULL); + wglDeleteContext(dd->gl_HRC); + dd->gl_HRC = NULL; + ReleaseDC(highbpp_hwnd, dd->gl_HDC); + DestroyWindow(highbpp_hwnd); + + + dd->mode_high_bpp = wglChoosePixelFormatARB ? 1 : 2; + } else { + dd->mode_high_bpp = 2; + } + } + + dd->gl_HDC = GetDC(dd->bound_hwnd); + if (!dd->gl_HDC) return GF_IO_ERR; + + use_double_buffer = GF_FALSE; + if (dd->gl_double_buffer ) { + use_double_buffer = dd->gl_double_buffer; + } else { + sOpt = gf_opts_get_key("core", "gl-doublebuf"); + if (!sOpt || !strcmp(sOpt, "yes")) use_double_buffer = GF_TRUE; + } + + sOpt = gf_opts_get_key("core", "gl-bits-depth"); + if (sOpt) bits_depth = atoi(sOpt); + + memset(&pfd, 0, sizeof(pfd)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + + if (dd->mode_high_bpp == 1) { + int pformats[200]; + u32 nbformats=0; + Bool found = GF_FALSE; + + int hdcAttributes[] = { + WGL_SUPPORT_OPENGL_ARB, TRUE, + WGL_DRAW_TO_WINDOW_ARB, (dd->bound_hwnd != dd->fs_hwnd) ? TRUE : FALSE, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_RED_BITS_ARB, dd->bpp, + WGL_GREEN_BITS_ARB, dd->bpp, + WGL_BLUE_BITS_ARB, dd->bpp, + WGL_ALPHA_BITS_ARB, (dd->bpp==10) ? 2 : 0, + WGL_DEPTH_BITS_ARB, bits_depth, + WGL_DOUBLE_BUFFER_ARB, use_double_buffer ? TRUE : FALSE, + 0,0 + }; + + wglChoosePixelFormatARB(dd->gl_HDC, hdcAttributes, NULL, 200, pformats, &nbformats); + + for (i=0; i<nbformats; i++) { + if (SetPixelFormat(dd->gl_HDC, pformats[i], &pfd) != FALSE) { + found = GF_TRUE; + break; + } + } + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DX GL] Cannot select pixel format: error %d - disabling high color res GL and retrying\n", GetLastError() )); + dd->mode_high_bpp = 2; + return DD_SetupOpenGL(dr, offscreen_width, offscreen_height); + } + + dr->max_screen_bpp = dd->bpp; + + if (wglGetPixelFormatAttribivARB) { + int rb, gb, bb, att; + rb = gb = bb = 0; + att = WGL_RED_BITS_ARB; + wglGetPixelFormatAttribivARB(dd->gl_HDC, pformats[0], 0, 1, &att, &rb); + att = WGL_GREEN_BITS_ARB; + wglGetPixelFormatAttribivARB(dd->gl_HDC, pformats[0], 0, 1, &att, &gb); + att = WGL_BLUE_BITS_ARB; + wglGetPixelFormatAttribivARB(dd->gl_HDC, pformats[0], 0, 1, &att, &bb); + + if ((rb != dd->bpp) || (gb != dd->bpp) || (bb != dd->bpp)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DX GL] Asked for %d bits per pixel but got %d red %d green %d blue\n", dd->bpp, rb, gb, bb )); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DX GL] Setup OpenGL bpp: %d red %d green %d blue\n", rb, gb, bb )); + } + } + + } else { + pfd.dwFlags = PFD_SUPPORT_OPENGL; + if (dd->bound_hwnd != dd->fs_hwnd) pfd.dwFlags |= PFD_DRAW_TO_WINDOW; + + if (use_double_buffer) pfd.dwFlags |= PFD_DOUBLEBUFFER; + pfd.dwLayerMask = PFD_MAIN_PLANE; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = bits_depth; + /*we need alpha support for composite textures...*/ + pfd.cAlphaBits = 8; + + if ( (pixelformat = ChoosePixelFormat(dd->gl_HDC, &pfd)) == FALSE ) return GF_IO_ERR; + + if (SetPixelFormat(dd->gl_HDC, pixelformat, &pfd) == FALSE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DX GL] Cannot select pixel format: error %d- disabling GL\n", GetLastError() )); + return GF_IO_ERR; + } + } + + dd->gl_HRC = wglCreateContext(dd->gl_HDC); + if (!dd->gl_HRC) return GF_IO_ERR; + + if (!dd->glext_init) { + dd->glext_init = GF_TRUE; + wglMakeCurrent(dd->gl_HDC, dd->gl_HRC); + } + + if (dd->disable_vsync) { + if (!wglSwapIntervalEXT) { + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALFARPROC)wglGetProcAddress( "wglSwapIntervalEXT" ); + } + if (wglSwapIntervalEXT) { + wglSwapIntervalEXT(0); + } + } + + if (!wglMakeCurrent(dd->gl_HDC, dd->gl_HRC)) return GF_IO_ERR; + +#endif + + /*special care for Firefox: XUL and OpenGL do not go well together, there is a stack overflow in WM_PAINT + for our plugin window - avoid this by overriding the WindowProc once OpenGL is setup!!*/ + if ((dd->bound_hwnd==dd->os_hwnd) && dd->orig_wnd_proc) +#ifdef _WIN64 + SetWindowLongPtr(dd->os_hwnd, GWLP_WNDPROC, (LPARAM) DD_WindowProc); +#else + SetWindowLong(dd->os_hwnd, GWL_WNDPROC, (DWORD) DD_WindowProc); +#endif + +#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) && !defined(_WIN32_WCE) +exit: +#endif + + if (dd->output_3d) { + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.hw_reset = hw_reset; + dr->on_event(dr->evt_cbk_hdl, &evt); + } + return GF_OK; +} + +#endif + + + +GF_Err DD_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) +{ + RECT rc; + DDCONTEXT + + if (dd->cur_hwnd) { + if (!(init_flags & GF_TERM_INIT_HIDE)) { + ShowWindow(dd->cur_hwnd, SW_SHOW); + } + return GF_OK; + } + dd->os_hwnd = (HWND) os_handle; + DD_SetupWindow(dr, init_flags); + /*fatal error*/ + if (!dd->os_hwnd) return GF_IO_ERR; + dd->cur_hwnd = dd->os_hwnd; + + { + HDC hdc; + hdc = GetDC(dd->os_hwnd); + dr->dpi_x = GetDeviceCaps(hdc, LOGPIXELSX); + dr->dpi_y = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(dd->os_hwnd, hdc); + } + +#ifndef GPAC_DISABLE_3D + dd->output_3d = 0; +#endif + GetWindowRect(dd->cur_hwnd, &rc); + + dd->disable_vsync = gf_opts_get_bool("core", "disable-vsync"); + + return GF_OK; +} + +static void DD_Shutdown(GF_VideoOutput *dr) +{ + DDCONTEXT + + /*force destroy of opengl*/ + DestroyObjects(dd); + + DD_ShutdownWindow(dr); +} + +void DD_ShowTaskbar(Bool show) +{ +#ifdef UNICODE + HWND tbwnd = FindWindow(L"Shell_TrayWnd", NULL); + HWND swnd = FindWindow(L"Button", NULL); +#else + HWND tbwnd = FindWindow("Shell_TrayWnd", NULL); + HWND swnd = FindWindow("Button", NULL); +#endif + + if (tbwnd != NULL) { + ShowWindow(tbwnd, show ? SW_SHOW : SW_HIDE); + UpdateWindow(tbwnd); + } + if (swnd != NULL) { + // Vista + ShowWindow(swnd, show ? SW_SHOW : SW_HIDE); + UpdateWindow(swnd); + } +} +static GF_Err DD_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 *outHeight) +{ + GF_Err e; + DDCONTEXT; + + if (bOn == dd->fullscreen) return GF_OK; + if (!dd->fs_hwnd) return GF_NOT_SUPPORTED; + + dd->fullscreen = bOn; + + if (!dd->width ||!dd->height) return GF_OK; + + /*whenever changing card display mode relocate fastest YUV format for blit (since it depends + on the dest pixel format)*/ + dd->yuv_init = GF_FALSE; + if (dd->fullscreen) { + dd->switch_res = gf_opts_get_bool("core", "switch-vres"); + /*get current or best fitting mode*/ + if (GetDisplayMode(dd) != GF_OK) return GF_IO_ERR; + } + + if (dd->NeedRestore) RestoreWindow(dd); + /*destroy all objects*/ + if (dd->os_hwnd!=dd->fs_hwnd) DestroyObjects(dd); + + if (dd->timer) KillTimer(dd->cur_hwnd, dd->timer); + dd->timer = 0; + if (dd->os_hwnd != dd->fs_hwnd) { + ShowWindow(dd->cur_hwnd, SW_HIDE); + dd->cur_hwnd = dd->fullscreen ? dd->fs_hwnd : dd->os_hwnd; + ShowWindow(dd->cur_hwnd, SW_SHOW); + } else { + ShowWindow(dd->cur_hwnd, SW_HIDE); + SetWindowLong(dd->os_hwnd, GWL_STYLE, dd->fullscreen ? WS_POPUP : dd->backup_styles); + ShowWindow(dd->cur_hwnd, SW_SHOW); + } + + + dd->on_secondary_screen = GF_FALSE; + /*Setup FS*/ + if (dd->fullscreen) { + int X = 0; + int Y = 0; +#if(WINVER >= 0x0500) + HMONITOR hMonitor; + MONITORINFOEX minfo; + + DD_ShowTaskbar(GF_FALSE); + + /*get monitor our windows is on*/ + hMonitor = MonitorFromWindow(dd->os_hwnd, MONITOR_DEFAULTTONEAREST); + if (hMonitor) { + memset(&minfo, 0, sizeof(MONITORINFOEX)); + minfo.cbSize = sizeof(MONITORINFOEX); + /*get monitor top-left for fullscreen switch, and adjust width and height*/ + GetMonitorInfo(hMonitor, (LPMONITORINFO) &minfo); + dd->fs_width = minfo.rcWork.right - minfo.rcWork.left; + dd->fs_height = minfo.rcWork.bottom - minfo.rcWork.top; + X = minfo.rcWork.left; + Y = minfo.rcWork.top; + + if (dd->fs_height+100 > dr->max_screen_height) { + dd->fs_height = dr->max_screen_height; + Y = 0; + } + if (!(minfo.dwFlags & MONITORINFOF_PRIMARY)) dd->on_secondary_screen = GF_TRUE; + } +#endif + + SetWindowPos(dd->cur_hwnd, NULL, X, Y, dd->fs_width, dd->fs_height, SWP_SHOWWINDOW | SWP_NOZORDER /*| SWP_ASYNCWINDOWPOS*/); + } else if (dd->os_hwnd==dd->fs_hwnd) { + SetWindowPos(dd->os_hwnd, NULL, 0, 0, dd->store_width+dd->off_w, dd->store_height+dd->off_h, SWP_SHOWWINDOW | SWP_NOZORDER /*| SWP_ASYNCWINDOWPOS*/); + } + if (!dd->fullscreen || dd->on_secondary_screen) { + DD_ShowTaskbar(GF_TRUE); + } + + +#ifndef GPAC_DISABLE_3D + if (dd->output_3d) { + e = DD_SetupOpenGL(dr, 0, 0); + } else +#endif + { + if (!dd->fullscreen && (dd->os_hwnd==dd->fs_hwnd)) { +// SetWindowPos(dd->os_hwnd, NULL, 0, 0, dd->store_width+dd->off_w, dd->store_height+dd->off_h, SWP_NOZORDER | SWP_NOMOVE | SWP_ASYNCWINDOWPOS); + } + /*first time FS, store*/ + if (!dd->store_width) { + dd->store_width = dd->width; + dd->store_height = dd->height; + } + if (dd->fullscreen) { + e = InitDirectDraw(dr, dd->fs_width, dd->fs_height); + } else { + e = InitDirectDraw(dr, dd->store_width, dd->store_height); + } + } + + if (bOn) { + dd->store_width = *outWidth; + dd->store_height = *outHeight; + *outWidth = dd->fs_width; + *outHeight = dd->fs_height; + } else { + *outWidth = dd->store_width; + *outHeight = dd->store_height; + } + SetForegroundWindow(dd->cur_hwnd); + SetFocus(dd->cur_hwnd); + return e; +} + + +GF_Err DD_Flush(GF_VideoOutput *dr, GF_Window *dest) +{ + RECT rc; + HRESULT hr; + DDCONTEXT; + + if (!dd) return GF_BAD_PARAM; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX] Flushing video output\n")); + +#ifndef GPAC_DISABLE_3D + + if (dd->output_3d) { +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) + if (dd->surface) eglSwapBuffers(dd->egldpy, dd->surface); +#else + SwapBuffers(dd->gl_HDC); +#endif + return GF_OK; + } +#endif + + + if (!dd->ddraw_init) return GF_BAD_PARAM; + + if (!dd->fullscreen && dd->windowless) { + HDC hdc; + /*lock backbuffer HDC*/ + dr->LockOSContext(dr, GF_TRUE); + /*get window hdc and copy from backbuffer to window*/ + hdc = GetDC(dd->os_hwnd); + BitBlt(hdc, 0, 0, dd->width, dd->height, dd->lock_hdc, 0, 0, SRCCOPY); + ReleaseDC(dd->os_hwnd, hdc); + /*unlock backbuffer HDC*/ + dr->LockOSContext(dr, GF_FALSE); + return GF_OK; + } + + if (!dd->disable_vsync) + dd->pDD->lpVtbl->WaitForVerticalBlank(dd->pDD, DDWAITVB_BLOCKBEGIN, NULL); + + if (dest) { + POINT pt; + pt.x = dest->x; + pt.y = dest->y; + ClientToScreen(dd->cur_hwnd, &pt); + dest->x = pt.x; + dest->y = pt.y; + MAKERECT(rc, dest); + hr = dd->pPrimary->lpVtbl->Blt(dd->pPrimary, &rc, dd->pBack, NULL, DDBLT_WAIT, NULL); + } else { + hr = dd->pPrimary->lpVtbl->Blt(dd->pPrimary, NULL, dd->pBack, NULL, DDBLT_WAIT, NULL); + } + if (hr == DDERR_SURFACELOST) { + dd->pPrimary->lpVtbl->Restore(dd->pPrimary); + dd->pBack->lpVtbl->Restore(dd->pBack); + } + return FAILED(hr) ? GF_IO_ERR : GF_OK; +} + + + +HRESULT WINAPI EnumDisplayModes( LPDDSURFDESC lpDDDesc, LPVOID lpContext) +{ + DDContext *dd = (DDContext *) lpContext; + + //check W and H + if (dd->width <= lpDDDesc->dwWidth && dd->height <= lpDDDesc->dwHeight + //check FSW and FSH + && dd->fs_width > lpDDDesc->dwWidth && dd->fs_height > lpDDDesc->dwHeight) { + + if (lpDDDesc->dwHeight == 200) + return DDENUMRET_OK; + + dd->fs_width = lpDDDesc->dwWidth; + dd->fs_height = lpDDDesc->dwHeight; + + return DDENUMRET_CANCEL; + } + return DDENUMRET_OK; +} + +GF_Err GetDisplayMode(DDContext *dd) +{ + if (dd->switch_res && dd->DirectDrawCreate) { + HRESULT hr; + Bool temp_dd = GF_FALSE; + if (!dd->pDD) { + LPDIRECTDRAW ddraw; + dd->DirectDrawCreate(NULL, &ddraw, NULL); + ddraw->lpVtbl->QueryInterface(ddraw, &IID_IDirectDraw7, (LPVOID *)&dd->pDD); + temp_dd = GF_TRUE; + } + //we start with a hugde res and downscale + dd->fs_width = dd->fs_height = 50000; + + hr = dd->pDD->lpVtbl->EnumDisplayModes(dd->pDD, 0L, NULL, dd, EnumDisplayModes); + + if (temp_dd) SAFE_DD_RELEASE(dd->pDD); + if (FAILED(hr)) return GF_IO_ERR; + } else { + dd->fs_width = GetSystemMetrics(SM_CXSCREEN); + dd->fs_height = GetSystemMetrics(SM_CYSCREEN); + } + return GF_OK; +} + + + +static void *NewDXVideoOutput() +{ + DDContext *pCtx; + GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "DirectX Video Output", "gpac distribution"); + + pCtx = (DDContext*)gf_malloc(sizeof(DDContext)); + memset(pCtx, 0, sizeof(DDContext)); + driv->opaque = pCtx; + driv->Flush = DD_Flush; + driv->Setup = DD_Setup; + driv->Shutdown = DD_Shutdown; + driv->SetFullScreen = DD_SetFullScreen; + driv->ProcessEvent = DD_ProcessEvent; + +#ifdef UNICODE + pCtx->hDDrawLib = LoadLibrary(L"ddraw.dll"); +#else + pCtx->hDDrawLib = LoadLibrary("ddraw.dll"); +#endif + if (pCtx->hDDrawLib) { + pCtx->DirectDrawCreate = (DIRECTDRAWCREATEPROC) GetProcAddress(pCtx->hDDrawLib, "DirectDrawCreate"); + } + + driv->max_screen_width = GetSystemMetrics(SM_CXSCREEN); + driv->max_screen_height = GetSystemMetrics(SM_CYSCREEN); + driv->max_screen_bpp = 8; + driv->hw_caps = GF_VIDEO_HW_OPENGL | GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA; + + DD_SetupDDraw(driv); + + return (void *)driv; +} + +static void DeleteVideoOutput(void *ifce) +{ + GF_VideoOutput *driv = (GF_VideoOutput *) ifce; + DDContext *dd = (DDContext *)driv->opaque; + + if (dd->fullscreen) { + DD_ShowTaskbar(GF_TRUE); + } + + if (dd->hDDrawLib) { + FreeLibrary(dd->hDDrawLib); + } + + if (dd->caption) gf_free(dd->caption); + + gf_free(dd); + gf_free(driv); +} + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface*)NewDXVideoOutput(); + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) return (GF_BaseInterface*)NewAudioOutput(); + return NULL; +} +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + DeleteVideoOutput((GF_VideoOutput *)ifce); + break; + case GF_AUDIO_OUTPUT_INTERFACE: + DeleteDxAudioOutput(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( dx_out ) diff --git a/modules/dx_hw/dx_window.c b/modules/dx_hw/dx_window.c new file mode 100644 index 0000000..4a1bdd9 --- /dev/null +++ b/modules/dx_hw/dx_window.c @@ -0,0 +1,1129 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / DirectX audio and video render module + * + * 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 "dx_hw.h" +#include <gpac/user.h> +#include <gpac/utf.h> +#include "resource.h" + +/*crude redef of winuser.h due to windows/winsock2 conflicts*/ +#ifndef WM_MOUSEWHEEL +#define WM_MOUSEWHEEL 0x020A +#define WHEEL_DELTA 120 +#endif + +/*mouse hiding timeout in fullscreen, in milliseconds*/ +#define MOUSE_HIDE_TIMEOUT 1000 + +static const GF_VideoOutput *the_video_output = NULL; + +void DD_SetCursor(GF_VideoOutput *dr, u32 cursor_type); + + +#ifdef _WIN32_WCE +static void DD_GetCoordinates(DWORD lParam, GF_Event *evt) +{ + evt->mouse.x = LOWORD(lParam); + evt->mouse.y = HIWORD(lParam); +} +#else + +static void DD_GetCoordinates(LPARAM lParam, GF_Event *evt) +{ + POINTS pt = MAKEPOINTS(lParam); + evt->mouse.x = pt.x; + evt->mouse.y = pt.y; +} +#endif + +typedef struct +{ + u32 win_key; + u32 gf_key; + u32 gf_flags; +} WINKeyToGPAC; + +static WINKeyToGPAC WINKeys[] = +{ + {VK_BACK, GF_KEY_BACKSPACE, 0}, + {VK_TAB, GF_KEY_TAB, 0 }, + {VK_CLEAR, GF_KEY_CLEAR, 0 }, + {VK_RETURN, GF_KEY_ENTER, 0 }, + {VK_SHIFT, GF_KEY_SHIFT, 0 }, + {VK_CONTROL, GF_KEY_CONTROL, 0 }, + {VK_MENU, GF_KEY_ALT, 0 }, + {VK_PAUSE, GF_KEY_PAUSE, 0 }, + {VK_CAPITAL, GF_KEY_CAPSLOCK, 0 }, + {VK_KANA, GF_KEY_KANAMODE, 0 }, + {VK_JUNJA, GF_KEY_JUNJAMODE, 0 }, + {VK_FINAL, GF_KEY_FINALMODE, 0 }, + {VK_KANJI, GF_KEY_KANJIMODE, 0 }, + {VK_ESCAPE, GF_KEY_ESCAPE, 0 }, + {VK_CONVERT, GF_KEY_CONVERT, 0 }, + {VK_SPACE, GF_KEY_SPACE, 0 }, + {VK_PRIOR, GF_KEY_PAGEUP, 0 }, + {VK_NEXT, GF_KEY_PAGEDOWN, 0 }, + {VK_END, GF_KEY_END, 0 }, + {VK_HOME, GF_KEY_HOME, 0 }, + {VK_LEFT, GF_KEY_LEFT, 0 }, + {VK_UP, GF_KEY_UP, 0 }, + {VK_RIGHT, GF_KEY_RIGHT, 0 }, + {VK_DOWN, GF_KEY_DOWN, 0 }, + {VK_SELECT, GF_KEY_SELECT, 0 }, + {VK_PRINT, GF_KEY_PRINTSCREEN, 0 }, + { VK_SNAPSHOT, GF_KEY_PRINTSCREEN, 0 }, + {VK_EXECUTE, GF_KEY_EXECUTE, 0 }, + {VK_INSERT, GF_KEY_INSERT, 0 }, + {VK_DELETE, GF_KEY_DEL, 0 }, + {VK_HELP, GF_KEY_HELP, 0 }, +#if !defined(_WIN32_WCE) && !defined(__GNUC__) + {VK_OEM_PLUS, GF_KEY_PLUS, 0 }, + {VK_OEM_MINUS, GF_KEY_PLUS, 0 }, +#endif +#ifndef _WIN32_WCE + {VK_NONCONVERT, GF_KEY_NONCONVERT, 0 }, + {VK_ACCEPT, GF_KEY_ACCEPT, 0 }, + {VK_MODECHANGE, GF_KEY_MODECHANGE, 0 }, +#endif + {VK_NUMPAD0, GF_KEY_0, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD1, GF_KEY_1, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD2, GF_KEY_2, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD3, GF_KEY_3, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD4, GF_KEY_4, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD5, GF_KEY_5, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD6, GF_KEY_6, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD7, GF_KEY_7, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD8, GF_KEY_8, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD9, GF_KEY_9, GF_KEY_EXT_NUMPAD }, + {VK_MULTIPLY, GF_KEY_STAR, GF_KEY_EXT_NUMPAD }, + {VK_ADD, GF_KEY_PLUS, GF_KEY_EXT_NUMPAD }, + {VK_SEPARATOR, GF_KEY_FULLSTOP, GF_KEY_EXT_NUMPAD }, + {VK_SUBTRACT, GF_KEY_HYPHEN, GF_KEY_EXT_NUMPAD }, + {VK_DECIMAL, GF_KEY_COMMA, GF_KEY_EXT_NUMPAD }, + {VK_DIVIDE, GF_KEY_SLASH, GF_KEY_EXT_NUMPAD }, + {VK_F1, GF_KEY_F1, 0}, + {VK_F2, GF_KEY_F2, 0 }, + {VK_F3, GF_KEY_F3, 0 }, + {VK_F4, GF_KEY_F4, 0 }, + {VK_F5, GF_KEY_F5, 0 }, + {VK_F6, GF_KEY_F6, 0 }, + {VK_F7, GF_KEY_F7, 0 }, + {VK_F8, GF_KEY_F8, 0 }, + {VK_F9, GF_KEY_F9, 0 }, + {VK_F10, GF_KEY_F10, 0 }, + {VK_F11, GF_KEY_F11, 0 }, + {VK_F12, GF_KEY_F12, 0 }, + {VK_F13, GF_KEY_F13, 0 }, + {VK_F14, GF_KEY_F14, 0 }, + {VK_F15, GF_KEY_F15, 0 }, + {VK_F16, GF_KEY_F16, 0 }, + {VK_F17, GF_KEY_F17, 0 }, + {VK_F18, GF_KEY_F18, 0 }, + {VK_F19, GF_KEY_F19, 0 }, + {VK_F20, GF_KEY_F20, 0 }, + {VK_F21, GF_KEY_F21, 0 }, + {VK_F22, GF_KEY_F22, 0 }, + {VK_F23, GF_KEY_F23, 0 }, + {VK_F24, GF_KEY_F24, 0 }, + {VK_NUMLOCK, GF_KEY_NUMLOCK, 0 }, + {VK_SCROLL, GF_KEY_SCROLL, 0 }, + {VK_LSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_LEFT}, + {VK_RSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_RIGHT }, + {VK_LCONTROL, GF_KEY_CONTROL, GF_KEY_EXT_LEFT }, + {VK_RCONTROL, GF_KEY_CONTROL, GF_KEY_EXT_RIGHT }, + {VK_LMENU, GF_KEY_ALT, GF_KEY_EXT_LEFT }, + {VK_RMENU, GF_KEY_ALT, GF_KEY_EXT_RIGHT }, +#if(WINVER >= 0x0400) + {VK_PROCESSKEY, GF_KEY_PROCESS, 0}, +#endif /* WINVER >= 0x0400 */ + {VK_ATTN, GF_KEY_ATTN, 0 }, + {VK_CRSEL, GF_KEY_CRSEL, 0 }, + {VK_EXSEL, GF_KEY_EXSEL, 0 }, + {VK_EREOF, GF_KEY_ERASEEOF, 0 }, + {VK_PLAY, GF_KEY_PLAY, 0 }, + {VK_ZOOM, GF_KEY_ZOOM, 0 }, + //{VK_NONAME, GF_KEY_NONAME, 0}, + //{VK_PA1, GF_KEY_PA1, 0}, + {VK_OEM_CLEAR, GF_KEY_CLEAR, 0 }, +}; + +static u32 num_win_keys = sizeof(WINKeys) / sizeof(WINKeyToGPAC); + +static void w32_translate_key(WPARAM wParam, LPARAM lParam, GF_EventKey *evt) +{ + u32 i; + evt->flags = 0; + evt->hw_code = (u32)wParam; + + for (i = 0; i<num_win_keys; i++) { + if (WINKeys[i].win_key == (u32) wParam) { + evt->key_code = WINKeys[i].gf_key; + evt->flags = WINKeys[i].gf_flags; + return; + } + } + + if ((wParam>=0x30) && (wParam<=0x39)) + evt->key_code = GF_KEY_0 + (u32) (wParam-0x30); + else if ((wParam>=0x41) && (wParam<=0x5A)) + evt->key_code = GF_KEY_A + (u32) (wParam-0x41); + /*DOM 3 Events: Implementations that are unable to identify a key must use the key identifier "Unidentified".*/ + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DX] Unrecognized key %X\n", wParam)); + evt->key_code = GF_KEY_UNIDENTIFIED; + } +} + + +static void mouse_move(DDContext *ctx, GF_VideoOutput *vout) +{ + if (ctx->fullscreen) { + ctx->last_mouse_move = gf_sys_clock(); + if (ctx->cursor_type==GF_CURSOR_HIDE) DD_SetCursor(vout, ctx->cursor_type_backup); + } +} + +static void mouse_start_timer(DDContext *ctx, HWND hWnd, GF_VideoOutput *vout) +{ + if (ctx->fullscreen) { + if (!ctx->timer) ctx->timer = SetTimer(hWnd, 10, 1000, NULL); + mouse_move(ctx, vout); + } +} + +void grab_mouse(DDContext *ctx, GF_VideoOutput *vout) +{ + if (ctx->fullscreen) DD_SetCursor(vout, GF_CURSOR_NORMAL); + SetCapture(ctx->cur_hwnd); + mouse_move(ctx, vout); +} +void release_mouse(DDContext *ctx, HWND hWnd, GF_VideoOutput *vout) +{ + ReleaseCapture(); + mouse_start_timer(ctx, hWnd, vout); +} + +LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + Bool ret = GF_TRUE; + GF_Event evt; + DDContext *ctx; +#ifdef _WIN64 + GF_VideoOutput *vout = (GF_VideoOutput *) GetWindowLongPtr(hWnd, GWLP_USERDATA); +#else + GF_VideoOutput *vout = (GF_VideoOutput *) GetWindowLong(hWnd, GWL_USERDATA); +#endif + if (!vout) return DefWindowProc(hWnd, msg, wParam, lParam); + ctx = (DDContext *)vout->opaque; + + switch (msg) { + case WM_SIZE: + /*always notify GPAC since we're not sure the owner of the window is listening to these events*/ + if (wParam==SIZE_MINIMIZED) { + evt.type = GF_EVENT_SHOWHIDE; + evt.show.show_type = 0; + ctx->hidden = GF_TRUE; + vout->on_event(vout->evt_cbk_hdl, &evt); + } else { + if (ctx->hidden && wParam==SIZE_RESTORED) { + ctx->hidden = GF_FALSE; + evt.type = GF_EVENT_SHOWHIDE; + evt.show.show_type = 1; + vout->on_event(vout->evt_cbk_hdl, &evt); + } + evt.type = GF_EVENT_SIZE; + evt.size.width = LOWORD(lParam); + evt.size.height = HIWORD(lParam); + vout->on_event(vout->evt_cbk_hdl, &evt); + } + break; + case WM_MOVE: + evt.type = GF_EVENT_MOVE; + evt.move.x = LOWORD(lParam); + evt.move.y = HIWORD(lParam); + vout->on_event(vout->evt_cbk_hdl, &evt); + break; + case WM_CLOSE: + if (hWnd==ctx->os_hwnd) { + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + vout->on_event(vout->evt_cbk_hdl, &evt); + } + break; + case WM_DESTROY: + if (ctx->owns_hwnd || (hWnd==ctx->fs_hwnd)) { + PostQuitMessage (0); + } else if (ctx->orig_wnd_proc) { + /*restore window proc*/ +#ifdef _WIN64 + SetWindowLongPtr(ctx->os_hwnd, GWLP_WNDPROC, ctx->orig_wnd_proc); +#else + SetWindowLong(ctx->os_hwnd, GWL_WNDPROC, ctx->orig_wnd_proc); +#endif + ctx->orig_wnd_proc = 0L; + } + break; + case WM_DISPLAYCHANGE: + ctx->dd_lost = GF_TRUE; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.back_buffer = GF_TRUE; + vout->on_event(vout->evt_cbk_hdl, &evt); + break; + + case WM_ACTIVATE: + if (!ctx->on_secondary_screen && ctx->fullscreen && (LOWORD(wParam)==WA_INACTIVE) + && (hWnd==ctx->fs_hwnd) + ) { + evt.type = GF_EVENT_SHOWHIDE; + vout->on_event(vout->evt_cbk_hdl, &evt); + } + /*fallthrough*/ +#ifndef _WIN32_WCE + case WM_ACTIVATEAPP: +#endif + if (hWnd==ctx->os_hwnd) { + ctx->has_focus = GF_TRUE; + SetFocus(hWnd); + } + break; + case WM_KILLFOCUS: + if (hWnd==ctx->os_hwnd) ctx->has_focus = GF_FALSE; + break; + case WM_SETFOCUS: + if (hWnd==ctx->os_hwnd) ctx->has_focus = GF_TRUE; + break; + case WM_IME_SETCONTEXT: + if ((hWnd==ctx->os_hwnd) && (wParam!=0) && !ctx->fullscreen) SetFocus(hWnd); + break; + + case WM_ERASEBKGND: + /*the erasebkgnd message causes flickering when resizing the window, we discard it*/ + if (ctx->is_setup) { + break; + } + case WM_PAINT: + if (ctx->cur_hwnd==hWnd) { + evt.type = GF_EVENT_REFRESH; + vout->on_event(vout->evt_cbk_hdl, &evt); + } + /*this avoids 100% cpu usage in Firefox*/ + return DefWindowProc (hWnd, msg, wParam, lParam); + + case WM_SETCURSOR: + if (ctx->cur_hwnd==hWnd) DD_SetCursor(vout, ctx->cursor_type); + break; + + case WM_DROPFILES: + { + char szFile[GF_MAX_PATH]; + u32 i; + + HDROP hDrop = (HDROP) wParam; + evt.type = GF_EVENT_DROPFILE; + evt.open_file.nb_files = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); + evt.open_file.files = (char**)gf_malloc(sizeof(char *)*evt.open_file.nb_files); + for (i=0; i<evt.open_file.nb_files; i++) { +#ifdef UNICODE + /* TODO: Fix by converting to WC */ + u32 res = DragQueryFile(hDrop, i, szFile, GF_MAX_PATH); +#else + u32 res = DragQueryFile(hDrop, i, szFile, GF_MAX_PATH); +#endif + evt.open_file.files[i] = res ? gf_strdup(szFile) : NULL; + } + DragFinish(hDrop); + /*send message*/ + vout->on_event(vout->evt_cbk_hdl, &evt); + for (i=0; i<evt.open_file.nb_files; i++) { + if (evt.open_file.files[i]) gf_free(evt.open_file.files[i]); + } + gf_free(evt.open_file.files); + } + break; + + case WM_MOUSEMOVE: + if (ctx->cur_hwnd!=hWnd) break; + + if (ctx->last_mouse_pos != lParam) { + ctx->last_mouse_pos = lParam; + DD_SetCursor(vout, (ctx->cursor_type==GF_CURSOR_HIDE) ? ctx->cursor_type_backup : ctx->cursor_type); + evt.type = GF_EVENT_MOUSEMOVE; + DD_GetCoordinates(lParam, &evt); + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + mouse_start_timer(ctx, hWnd, vout); + } + break; + + case WM_TIMER: + if (wParam==10) { + if (ctx->fullscreen && (ctx->cursor_type!=GF_CURSOR_HIDE)) { + if (gf_sys_clock() > MOUSE_HIDE_TIMEOUT + ctx->last_mouse_move) { + ctx->cursor_type_backup = ctx->cursor_type; + DD_SetCursor(vout, GF_CURSOR_HIDE); + KillTimer(hWnd, ctx->timer); + ctx->timer = 0; + } + } + } + break; + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + grab_mouse(ctx, vout); + evt.type = GF_EVENT_MOUSEDOWN; + DD_GetCoordinates(lParam, &evt); + evt.mouse.button = GF_MOUSE_LEFT; + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + if (!ctx->has_focus && (hWnd==ctx->os_hwnd)) { + ctx->has_focus = GF_TRUE; + SetFocus(ctx->os_hwnd); + } + break; + case WM_LBUTTONUP: + release_mouse(ctx, hWnd, vout); + evt.type = GF_EVENT_MOUSEUP; + DD_GetCoordinates(lParam, &evt); + evt.mouse.button = GF_MOUSE_LEFT; + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + break; + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + grab_mouse(ctx, vout); + evt.type = GF_EVENT_MOUSEDOWN; + DD_GetCoordinates(lParam, &evt); + evt.mouse.button = GF_MOUSE_RIGHT; + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + if (!ctx->has_focus && (hWnd==ctx->os_hwnd)) { + ctx->has_focus = GF_TRUE; + SetFocus(ctx->os_hwnd); + } + break; + case WM_RBUTTONUP: + release_mouse(ctx, hWnd, vout); + evt.type = GF_EVENT_MOUSEUP; + DD_GetCoordinates(lParam, &evt); + evt.mouse.button = GF_MOUSE_RIGHT; + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + mouse_start_timer(ctx, hWnd, vout); + break; + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + grab_mouse(ctx, vout); + evt.type = GF_EVENT_MOUSEDOWN; + evt.mouse.button = GF_MOUSE_MIDDLE; + DD_GetCoordinates(lParam, &evt); + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + if (!ctx->has_focus && (hWnd==ctx->os_hwnd)) { + ctx->has_focus = GF_TRUE; + SetFocus(ctx->os_hwnd); + } + break; + case WM_MBUTTONUP: + release_mouse(ctx, hWnd, vout); + evt.type = GF_EVENT_MOUSEUP; + DD_GetCoordinates(lParam, &evt); + evt.mouse.button = GF_MOUSE_MIDDLE; + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + mouse_start_timer(ctx, hWnd, vout); + break; + case WM_MOUSEWHEEL: + if (ctx->cur_hwnd==hWnd) { + DD_SetCursor(vout, (ctx->cursor_type==GF_CURSOR_HIDE) ? ctx->cursor_type_backup : ctx->cursor_type); + evt.type = GF_EVENT_MOUSEWHEEL; + DD_GetCoordinates(lParam, &evt); + evt.mouse.wheel_pos = FLT2FIX( ((Float) (s16) HIWORD(wParam)) / WHEEL_DELTA ); + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + mouse_start_timer(ctx, hWnd, vout); + } + break; + case WM_HSCROLL: + if (ctx->cur_hwnd==hWnd) { + DD_SetCursor(vout, (ctx->cursor_type==GF_CURSOR_HIDE) ? ctx->cursor_type_backup : ctx->cursor_type); + evt.type = GF_EVENT_MOUSEWHEEL; + evt.mouse.button = 1; + switch (LOWORD(wParam)) { + case SB_LEFT: + evt.mouse.wheel_pos = -1; + break; + case SB_RIGHT: + evt.mouse.wheel_pos = +1; + break; + case SB_LINELEFT: + evt.mouse.wheel_pos = -5; + break; + case SB_LINERIGHT: + evt.mouse.wheel_pos = +5; + break; + case SB_PAGELEFT: + evt.mouse.wheel_pos = -10; + break; + case SB_PAGERIGHT: + evt.mouse.wheel_pos = +10; + break; + default: + break; + } + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + mouse_start_timer(ctx, hWnd, vout); + } + break; + + /*there's a bug on alt state (we miss one event)*/ + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYUP: + case WM_KEYDOWN: + w32_translate_key(wParam, lParam, &evt.key); + evt.type = ((msg==WM_SYSKEYDOWN) || (msg==WM_KEYDOWN)) ? GF_EVENT_KEYDOWN : GF_EVENT_KEYUP; + if (evt.key.key_code==GF_KEY_ALT) ctx->alt_down = (evt.type==GF_EVENT_KEYDOWN) ? GF_TRUE : GF_FALSE; + if (evt.key.key_code==GF_KEY_CONTROL) ctx->ctrl_down = (evt.type==GF_EVENT_KEYDOWN) ? GF_TRUE : GF_FALSE; + if ((ctx->os_hwnd==ctx->fs_hwnd) && ctx->alt_down && (evt.key.key_code==GF_KEY_F4)) { + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + } + else if (ctx->ctrl_down && (evt.type==GF_EVENT_KEYUP) && (evt.key.key_code==GF_KEY_V)) { + HGLOBAL hglbCopy; + if (!IsClipboardFormatAvailable(CF_TEXT)) break; + if (!OpenClipboard(ctx->cur_hwnd)) break; + + hglbCopy = GetClipboardData(CF_TEXT); + if (hglbCopy) { +#ifdef UNICODE + LPTSTR lptstrCopy = (wchar_t *) GlobalLock(hglbCopy); + evt.type = GF_EVENT_PASTE_TEXT; + /* TODO: Convert to UTF-8 */ + evt.clipboard.text = lptstrCopy; +#else + LPTSTR lptstrCopy = (char *)GlobalLock(hglbCopy); + evt.type = GF_EVENT_PASTE_TEXT; + evt.clipboard.text = lptstrCopy; +#endif + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + GlobalUnlock(hglbCopy); + } + CloseClipboard(); + break; + } + else if (ctx->ctrl_down && (evt.type==GF_EVENT_KEYUP) && (evt.key.key_code==GF_KEY_C)) { + evt.type = GF_EVENT_COPY_TEXT; + if ((vout->on_event(vout->evt_cbk_hdl, &evt)==GF_TRUE) && evt.clipboard.text) { + size_t len; + HGLOBAL hglbCopy; + LPTSTR lptstrCopy; + if (!IsClipboardFormatAvailable(CF_TEXT)) break; + if (!OpenClipboard(ctx->cur_hwnd)) break; + EmptyClipboard(); + + len = strlen(evt.clipboard.text); + if (!len) break; + + hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(char)); +#ifdef UNICODE + /* TODO fix encoding*/ + lptstrCopy = (wchar_t *)GlobalLock(hglbCopy); + memcpy(lptstrCopy, evt.clipboard.text, len * sizeof(char)); + lptstrCopy[len] = 0; +#else + lptstrCopy = (char *) GlobalLock(hglbCopy); + memcpy(lptstrCopy, evt.clipboard.text, len * sizeof(char)); + lptstrCopy[len] = 0; +#endif + GlobalUnlock(hglbCopy); + SetClipboardData(CF_TEXT, hglbCopy); + CloseClipboard(); + gf_free(evt.clipboard.text); + break; + } + } + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + + if ( !ctx->ctrl_down && !ctx->alt_down + && evt.key.key_code != GF_KEY_CONTROL && evt.key.key_code != GF_KEY_ALT ) + ret = GF_TRUE; + break; + + case WM_UNICHAR: + case WM_CHAR: + evt.type = GF_EVENT_TEXTINPUT; + evt.character.unicode_char = (u32) wParam; + ret = vout->on_event(vout->evt_cbk_hdl, &evt); + break; + /* + case WM_CANCELMODE: + case WM_CAPTURECHANGED: + case WM_NCHITTEST: + return 0; + */ + default: + return DefWindowProc (hWnd, msg, wParam, lParam); + } + + if (!ret &&(ctx->os_hwnd==hWnd) && ctx->orig_wnd_proc) + return CallWindowProc((WNDPROC) ctx->orig_wnd_proc, hWnd, msg, wParam, lParam); + return 0; +} + +#ifndef WS_EX_LAYERED +#define WS_EX_LAYERED 0x80000 +#endif +#ifndef LWA_COLORKEY +#define LWA_COLORKEY 0x00000001 +#endif +#ifndef LWA_ALPHA +#define LWA_ALPHA 0x00000002 +#endif +typedef BOOL (WINAPI* typSetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD); + + +static void SetWindowless(GF_VideoOutput *vout, HWND hWnd) +{ +#ifdef _WIN32_WCE + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DX Out] Windowloess mode not supported on Window CE\n")); + return; +#else +/* use VerifyVersionInfo() instead of GetVersionEx? */ +#if !defined(__GNUC__) +#pragma warning(disable : 4996) +#endif + const char *opt; + u32 a, r, g, b; + COLORREF ckey; + typSetLayeredWindowAttributes _SetLayeredWindowAttributes; + HMODULE hUser32; + u32 isWin2K; + OSVERSIONINFO Version = {sizeof(OSVERSIONINFO)}; + GetVersionEx(&Version); + isWin2K = (Version.dwPlatformId == VER_PLATFORM_WIN32_NT && Version.dwMajorVersion >= 5); + if (!isWin2K) return; + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] Enabling windowless mode\n")); +#ifdef UNICODE + hUser32 = GetModuleHandle(L"USER32.DLL"); +#else + hUser32 = GetModuleHandle("USER32.DLL"); +#endif + if (hUser32 == NULL) return; + + _SetLayeredWindowAttributes = (typSetLayeredWindowAttributes) GetProcAddress(hUser32,"SetLayeredWindowAttributes"); + if (_SetLayeredWindowAttributes == NULL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DX Out] Win32 layered windows not supported\n")); + return; + } + + SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); + + /*get background ckey*/ + opt = gf_opts_get_key("filter@compositor", "ckey"); + if (!opt) { + gf_opts_set_key("filter@compositor", "ckey", "FFFEFEFE"); + opt = "FFFEFEFE"; + } + + sscanf(opt, "%02X%02X%02X%02X", &a, &r, &g, &b); + ckey = RGB(r, g, b); + if (a<255) + _SetLayeredWindowAttributes(hWnd, ckey, (u8) a, LWA_COLORKEY|LWA_ALPHA); + else + _SetLayeredWindowAttributes(hWnd, ckey, 0, LWA_COLORKEY); + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] Using color key %s\n", opt)); +#endif +} + + +Bool DD_InitWindows(GF_VideoOutput *vout, DDContext *ctx) +{ + u32 flags; + Bool use_fs_wnd = GF_TRUE; +#ifndef _WIN32_WCE + RECT rc; +#endif + WNDCLASS wc; + HINSTANCE hInst; + +#ifndef _WIN32_WCE +#ifdef UNICODE + hInst = GetModuleHandle(L"gm_dx_hw.dll"); +#else + hInst = GetModuleHandle("gm_dx_hw.dll"); +#endif +#else + hInst = GetModuleHandle(_T("gm_dx_hw.dll")); +#endif + + memset(&wc, 0, sizeof(WNDCLASS)); +#ifndef _WIN32_WCE + wc.style = CS_BYTEALIGNWINDOW; + wc.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(IDI_OSMO_ICON) ); +#ifdef UNICODE + wc.lpszClassName = L"GPAC DirectDraw Output"; +#else + wc.lpszClassName = "GPAC DirectDraw Output"; +#endif +#else + wc.lpszClassName = _T("GPAC DirectDraw Output"); +#endif + wc.hInstance = hInst; + wc.lpfnWndProc = DD_WindowProc; + wc.hCursor = LoadCursor (NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject (BLACK_BRUSH); + RegisterClass (&wc); + + flags = ctx->switch_res; + ctx->switch_res = GF_FALSE; + ctx->force_alpha = (flags & GF_TERM_WINDOW_TRANSPARENT) ? GF_TRUE : GF_FALSE; + + if (!ctx->os_hwnd) { +#ifndef _WIN32_WCE + u32 styles; +#endif + if (flags & GF_TERM_WINDOWLESS) ctx->windowless = GF_TRUE; + + +#ifdef _WIN32_WCE + ctx->os_hwnd = CreateWindow(_T("GPAC DirectDraw Output"), _T("GPAC DirectDraw Output"), WS_POPUP, 0, 0, 120, 100, NULL, NULL, hInst, NULL); +#else + + if (flags & GF_TERM_WINDOW_NO_DECORATION) { + styles = WS_POPUP | WS_THICKFRAME; + } else if (ctx->windowless) { + styles = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP; + } else { + styles = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW; + } + use_fs_wnd = GF_FALSE; +#ifdef UNICODE + ctx->os_hwnd = CreateWindow(L"GPAC DirectDraw Output", L"GPAC DirectDraw Output", styles, 0, 0, 120, 100, NULL, NULL, hInst, NULL); +#else + ctx->os_hwnd = CreateWindow("GPAC DirectDraw Output", "GPAC DirectDraw Output", styles, 0, 0, 120, 100, NULL, NULL, hInst, NULL); +#endif + ctx->backup_styles = styles; +#endif + if (ctx->os_hwnd == NULL) { + return GF_FALSE; + } + DragAcceptFiles(ctx->os_hwnd, TRUE); + + if (flags & GF_TERM_INIT_HIDE) { + ShowWindow(ctx->os_hwnd, SW_HIDE); + } else { + SetForegroundWindow(ctx->os_hwnd); + ShowWindow(ctx->os_hwnd, SW_SHOW); + } + + /*get border & title bar sizes*/ +#ifdef _WIN32_WCE + ctx->off_w = 0; + ctx->off_h = 0; +#else + rc.left = rc.top = 0; + rc.right = rc.bottom = 100; + AdjustWindowRect(&rc, styles, 0); + ctx->off_w = rc.right - rc.left - 100; + ctx->off_h = rc.bottom - rc.top - 100; +#endif + ctx->owns_hwnd = GF_TRUE; + + if (ctx->windowless) SetWindowless(vout, ctx->os_hwnd); + } + + if (use_fs_wnd) { +#ifdef _WIN32_WCE + ctx->fs_hwnd = CreateWindow(_T("GPAC DirectDraw Output"), _T("GPAC DirectDraw FS Output"), WS_POPUP, 0, 0, 120, 100, NULL, NULL, hInst, NULL); +#else +#ifdef UNICODE + ctx->fs_hwnd = CreateWindow(L"GPAC DirectDraw Output", L"GPAC DirectDraw FS Output", WS_POPUP, 0, 0, 120, 100, NULL, NULL, hInst, NULL); +#else + ctx->fs_hwnd = CreateWindow("GPAC DirectDraw Output", "GPAC DirectDraw FS Output", WS_POPUP, 0, 0, 120, 100, NULL, NULL, hInst, NULL); +#endif +#endif + if (!ctx->fs_hwnd) { + return GF_FALSE; + } + ShowWindow(ctx->fs_hwnd, SW_HIDE); +#ifdef _WIN64 + SetWindowLongPtr(ctx->fs_hwnd, GWLP_USERDATA, (LONG_PTR) vout); +#else + SetWindowLong(ctx->fs_hwnd, GWL_USERDATA, (LONG) vout); +#endif + } else { + ctx->fs_hwnd = ctx->os_hwnd; + } + + /*if visible set focus*/ + if (!ctx->switch_res) SetFocus(ctx->os_hwnd); + + ctx->switch_res = GF_FALSE; +#ifdef _WIN64 + SetWindowLongPtr(ctx->os_hwnd, GWLP_USERDATA, (LONG_PTR) vout); +#else + SetWindowLong(ctx->os_hwnd, GWL_USERDATA, (LONG) vout); +#endif + + /*load cursors*/ + ctx->curs_normal = LoadCursor(NULL, IDC_ARROW); + ctx->curs_hand = LoadCursor(hInst, MAKEINTRESOURCE(IDC_HAND_PTR)); + ctx->curs_collide = LoadCursor(hInst, MAKEINTRESOURCE(IDC_COLLIDE)); + ctx->cursor_type = GF_CURSOR_NORMAL; + return GF_TRUE; +} + +u32 DD_WindowThread(void *par) +{ + MSG msg; + + GF_VideoOutput *vout = (GF_VideoOutput*)par; + DDContext *ctx = (DDContext *)vout->opaque; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[DirectXOutput] Entering thread ID %d\n", gf_th_id() )); + + if (DD_InitWindows(vout, ctx)) { + s32 msg_ok=1; + ctx->th_state = 1; + while (msg_ok) { + msg_ok = GetMessage (&(msg), NULL, 0, 0); + if (msg_ok == -1) msg_ok = 0; + if (msg.message == WM_DESTROY) PostQuitMessage(0); //WM_DESTROY: exit + TranslateMessage (&(msg)); + DispatchMessage (&(msg)); + + if (ctx->caption) { +#ifdef UNICODE + char *str_src = ctx->caption; + wchar_t *wcaption; + size_t len; + size_t len_res; + len = (strlen(str_src) + 1)*sizeof(wchar_t); + wcaption = (wchar_t *)gf_malloc(len); + len_res = gf_utf8_mbstowcs(wcaption, len, &str_src); + if (len_res != -1) { + SetWindowTextW(ctx->os_hwnd, wcaption); + } + gf_free(wcaption); +#else + SetWindowText(ctx->os_hwnd, ctx->caption); +#endif + gf_free(ctx->caption); + ctx->caption = NULL; + } + + } + } + ctx->th_state = 2; + return 0; +} + + +void DD_SetupWindow(GF_VideoOutput *dr, u32 flags) +{ + DDContext *ctx = (DDContext *)dr->opaque; + + if (ctx->os_hwnd) { + /*override window proc*/ + if (!(flags & GF_TERM_NO_WINDOWPROC_OVERRIDE) ) { +#ifdef _WIN64 + ctx->orig_wnd_proc = GetWindowLongPtr(ctx->os_hwnd, GWLP_WNDPROC); + SetWindowLongPtr(ctx->os_hwnd, GWLP_WNDPROC, (LONG_PTR) DD_WindowProc); +#else + ctx->orig_wnd_proc = GetWindowLong(ctx->os_hwnd, GWL_WNDPROC); + SetWindowLong(ctx->os_hwnd, GWL_WNDPROC, (LONG) DD_WindowProc); +#endif + } + ctx->parent_wnd = GetParent(ctx->os_hwnd); + } + ctx->switch_res = flags; + + if (flags & GF_TERM_WINDOW_NO_THREAD) { + DD_InitWindows(dr, ctx); + } else { + /*create event thread*/ + ctx->th = gf_th_new("DirectX Video"); + gf_th_run(ctx->th, DD_WindowThread, dr); + while (!ctx->th_state) + gf_sleep(1); + } + if (!the_video_output) the_video_output = dr; +} + +static void dd_closewindow(HWND hWnd) +{ + PostMessage(hWnd, WM_DESTROY, 0, 0); +} + +void DD_ShutdownWindow(GF_VideoOutput *dr) +{ + DDContext *ctx = (DDContext *)dr->opaque; + + if (ctx->owns_hwnd) { + dd_closewindow(ctx->os_hwnd); + } else if (ctx->orig_wnd_proc) { + /*restore window proc*/ +#ifdef _WIN64 + SetWindowLongPtr(ctx->os_hwnd, GWLP_WNDPROC, ctx->orig_wnd_proc); +#else + SetWindowLong(ctx->os_hwnd, GWL_WNDPROC, ctx->orig_wnd_proc); +#endif + ctx->orig_wnd_proc = 0L; + } + + if (ctx->fs_hwnd != ctx->os_hwnd) { + dd_closewindow(ctx->fs_hwnd); +#ifdef _WIN64 + SetWindowLongPtr(ctx->fs_hwnd, GWLP_USERDATA, (LONG_PTR) NULL); + SetWindowLongPtr(ctx->fs_hwnd, GWLP_WNDPROC, (LONG_PTR) DefWindowProc); +#else + SetWindowLong(ctx->fs_hwnd, GWL_USERDATA, (LONG) NULL); + SetWindowLong(ctx->fs_hwnd, GWL_WNDPROC, (DWORD) DefWindowProc); +#endif + } + + if (ctx->th) { + while (ctx->th_state!=2) + gf_sleep(10); + + gf_th_del(ctx->th); + ctx->th = NULL; + } + + /*special care for Firefox: the windows created by our NP plugin may still be called + after the shutdown of the plugin !!*/ +#ifdef _WIN64 + SetWindowLongPtr(ctx->os_hwnd, GWLP_USERDATA, (LONG_PTR) NULL); +#else + SetWindowLong(ctx->os_hwnd, GWL_USERDATA, (LONG) NULL); +#endif + +#ifdef _WIN32_WCE + UnregisterClass(_T("GPAC DirectDraw Output"), GetModuleHandle(_T("gm_dx_hw.dll")) ); +#else +#ifdef UNICODE + UnregisterClass(L"GPAC DirectDraw Output", GetModuleHandle(L"gm_dx_hw.dll")); +#else + UnregisterClass("GPAC DirectDraw Output", GetModuleHandle("gm_dx_hw.dll")); +#endif +#endif + ctx->os_hwnd = NULL; + ctx->fs_hwnd = NULL; + the_video_output = NULL; +} + +void DD_SetCursor(GF_VideoOutput *dr, u32 cursor_type) +{ + DDContext *ctx = (DDContext *)dr->opaque; + if (cursor_type==GF_CURSOR_HIDE) { + if (ctx->cursor_type!=GF_CURSOR_HIDE) { + ShowCursor(FALSE); + ctx->cursor_type = cursor_type; + } + return; + } + if (ctx->cursor_type==GF_CURSOR_HIDE) ShowCursor(TRUE); + ctx->cursor_type = cursor_type; + + switch (cursor_type) { + case GF_CURSOR_ANCHOR: + case GF_CURSOR_TOUCH: + case GF_CURSOR_ROTATE: + case GF_CURSOR_PROXIMITY: + case GF_CURSOR_PLANE: + SetCursor(ctx->curs_hand); + break; + case GF_CURSOR_COLLIDE: + SetCursor(ctx->curs_collide); + break; + default: + SetCursor(ctx->curs_normal); + break; + } +} + +HWND DD_GetGlobalHWND() +{ + if (!the_video_output) return NULL; + return ((DDContext*)the_video_output->opaque)->os_hwnd; +} + +static u32 get_sys_col(int idx) +{ + u32 res; + DWORD val = GetSysColor(idx); + res = (val)&0xFF; + res<<=8; + res |= (val>>8)&0xFF; + res<<=8; + res |= (val>>16)&0xFF; + return res; +} + + +/*Note: all calls to SetWindowPos are made in a non-blocking way using SWP_ASYNCWINDOWPOS. This avoids deadlocks +when the compositor request a size change and the DX window thread has grabbed the main compositor mutex. +This typically happens when switching playlist items as fast as possible*/ +GF_Err DD_ProcessEvent(GF_VideoOutput*dr, GF_Event *evt) +{ + DDContext *ctx = (DDContext *)dr->opaque; + + if (!evt) { + if (!ctx->th) { + MSG msg; + + if (ctx->caption) { +#ifdef UNICODE + /* TODO: Fix by converting UTF-8 to WC */ + SetWindowText(ctx->os_hwnd, ctx->caption); +#else + SetWindowText(ctx->os_hwnd, ctx->caption); +#endif + gf_free(ctx->caption); + ctx->caption = NULL; + } + + while (PeekMessage (&(msg), NULL, 0, 0, PM_REMOVE)) { + TranslateMessage (&(msg)); + DispatchMessage (&(msg)); + } + } + return GF_OK; + } + + switch (evt->type) { + case GF_EVENT_SET_CURSOR: + DD_SetCursor(dr, evt->cursor.cursor_type); + break; + case GF_EVENT_SET_CAPTION: +#ifndef _WIN32_WCE + if (evt->caption.caption && !ctx->caption) ctx->caption = gf_strdup(evt->caption.caption); +#endif + break; + case GF_EVENT_MOVE: + if (evt->move.relative == 2) { + u32 x, y, fsw, fsh; + x = y = 0; + fsw = GetSystemMetrics(SM_CXSCREEN); + fsh = GetSystemMetrics(SM_CYSCREEN); + + if (evt->move.align_x==1) x = (fsw - ctx->width) / 2; + else if (evt->move.align_x==2) x = fsw - ctx->width; + + if (evt->move.align_y==1) y = (fsh - ctx->height) / 2; + else if (evt->move.align_y==2) y = fsh - ctx->height; + + SetWindowPos(ctx->os_hwnd, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_ASYNCWINDOWPOS); + } + else if (evt->move.relative) { + POINT pt; + pt.x = pt.y = 0; + MapWindowPoints(ctx->os_hwnd, NULL, &pt, 1); + SetWindowPos(ctx->os_hwnd, NULL, evt->move.x + pt.x, evt->move.y + pt.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_ASYNCWINDOWPOS); + } else { + SetWindowPos(ctx->os_hwnd, NULL, evt->move.x, evt->move.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_ASYNCWINDOWPOS); + } + break; + case GF_EVENT_SHOWHIDE: + ShowWindow(ctx->os_hwnd, evt->show.show_type ? SW_SHOW : SW_HIDE); + break; + /*if scene resize resize window*/ + case GF_EVENT_SIZE: + if (ctx->owns_hwnd) { + if (ctx->fullscreen) { + ctx->store_width = evt->size.width; + ctx->store_height = evt->size.height; + } else { + if (ctx->windowless) + SetWindowPos(ctx->os_hwnd, NULL, 0, 0, evt->size.width, evt->size.height, SWP_NOZORDER | SWP_NOMOVE | SWP_ASYNCWINDOWPOS); + else + SetWindowPos(ctx->os_hwnd, NULL, 0, 0, evt->size.width + ctx->off_w, evt->size.height + ctx->off_h, SWP_NOZORDER | SWP_NOMOVE | SWP_ASYNCWINDOWPOS); + } + } + break; + case GF_EVENT_SET_GL: +#ifndef GPAC_DISABLE_3D + if (ctx->pb_HDC) { + if (!wglMakeCurrent(ctx->pb_HDC, ctx->pb_HRC)) + return GF_IO_ERR; + return GF_OK; + } + if (!wglMakeCurrent(ctx->gl_HDC, ctx->gl_HRC)) + return GF_IO_ERR; + return GF_OK; +#endif + /*HW setup*/ + case GF_EVENT_VIDEO_SETUP: + if (ctx->dd_lost) { + ctx->dd_lost = GF_FALSE; + DestroyObjects(ctx); + } + ctx->is_setup = GF_TRUE; + if (!evt->setup.use_opengl) { +#ifndef GPAC_DISABLE_3D + ctx->output_3d = GF_FALSE; +#endif + return DD_SetBackBufferSize(dr, evt->setup.width, evt->setup.height, evt->setup.system_memory); + } else { +#ifndef GPAC_DISABLE_3D + ctx->output_3d = GF_TRUE; + ctx->width = evt->setup.width; + ctx->height = evt->setup.height; + ctx->gl_double_buffer = evt->setup.back_buffer; + return DD_SetupOpenGL(dr, 0, 0); +#else + return GF_NOT_SUPPORTED; +#endif + } + + case GF_EVENT_SYS_COLORS: + evt->sys_cols.sys_colors[0] = get_sys_col(COLOR_ACTIVEBORDER); + evt->sys_cols.sys_colors[1] = get_sys_col(COLOR_ACTIVECAPTION); + evt->sys_cols.sys_colors[2] = get_sys_col(COLOR_APPWORKSPACE); + evt->sys_cols.sys_colors[3] = get_sys_col(COLOR_BACKGROUND); + evt->sys_cols.sys_colors[4] = get_sys_col(COLOR_BTNFACE); + evt->sys_cols.sys_colors[5] = get_sys_col(COLOR_BTNHIGHLIGHT); + evt->sys_cols.sys_colors[6] = get_sys_col(COLOR_BTNSHADOW); + evt->sys_cols.sys_colors[7] = get_sys_col(COLOR_BTNTEXT); + evt->sys_cols.sys_colors[8] = get_sys_col(COLOR_CAPTIONTEXT); + evt->sys_cols.sys_colors[9] = get_sys_col(COLOR_GRAYTEXT); + evt->sys_cols.sys_colors[10] = get_sys_col(COLOR_HIGHLIGHT); + evt->sys_cols.sys_colors[11] = get_sys_col(COLOR_HIGHLIGHTTEXT); + evt->sys_cols.sys_colors[12] = get_sys_col(COLOR_INACTIVEBORDER); + evt->sys_cols.sys_colors[13] = get_sys_col(COLOR_INACTIVECAPTION); + evt->sys_cols.sys_colors[14] = get_sys_col(COLOR_INACTIVECAPTIONTEXT); + evt->sys_cols.sys_colors[15] = get_sys_col(COLOR_INFOBK); + evt->sys_cols.sys_colors[16] = get_sys_col(COLOR_INFOTEXT); + evt->sys_cols.sys_colors[17] = get_sys_col(COLOR_MENU); + evt->sys_cols.sys_colors[18] = get_sys_col(COLOR_MENUTEXT); + evt->sys_cols.sys_colors[19] = get_sys_col(COLOR_SCROLLBAR); + evt->sys_cols.sys_colors[20] = get_sys_col(COLOR_3DDKSHADOW); + evt->sys_cols.sys_colors[21] = get_sys_col(COLOR_3DFACE); + evt->sys_cols.sys_colors[22] = get_sys_col(COLOR_3DHIGHLIGHT); + evt->sys_cols.sys_colors[23] = get_sys_col(COLOR_3DLIGHT); + evt->sys_cols.sys_colors[24] = get_sys_col(COLOR_3DSHADOW); + evt->sys_cols.sys_colors[25] = get_sys_col(COLOR_WINDOW); + evt->sys_cols.sys_colors[26] = get_sys_col(COLOR_WINDOWFRAME); + evt->sys_cols.sys_colors[27] = get_sys_col(COLOR_WINDOWTEXT); + return GF_OK; + } + return GF_OK; +} diff --git a/modules/dx_hw/hand.cur b/modules/dx_hw/hand.cur new file mode 100644 index 0000000000000000000000000000000000000000..0a3cc6e9ec100655e500533613ec46e1cabb9632 GIT binary patch literal 2238 zcmeH{p>A725Jkt8rCO0eLyQcfpc)S{Y84)695%lYSexV%EG-KNDE1fFtWt)DG*YFo zAcwgsn4X=NIF|g9u$0u!?7qFTJNJ0jj>re;$kCDH>w8aRz|&{qi1<o$1d-RYn^Vg$ zC5b$pHWMR{vRp1@`Ex1jA6L@tcBR+rNx$Eh!^1-v3<feB4(0gxSVp6fOePaKJw26^ zlM@+_$1<DEC`jZqYbM5&Madaa2$x(cnxZM1RxO$>hHmJFZs>+yj}`QSURV_Lg3ct4 z?&yy02#y}CgLQag#i;lzg_J`oA!*N`FenTPgTjzVFenTPgTkONBqj_BgTkONC=7`X zgTkONC=3dN1{xd+hgOb=>;MXLeHX$B^!kxuG0Y7kqo~1>Z6Ikp7%T=$wgnD@!{9JD z432CS90r5IU@+9y85{<K!QglWW3~{V=FK|WTe|}l-i2M+YP<_f1=E5-fvMnKNEV6| zybIoW9qEmZf5E@tU$3a(U+7JNp}<h+L%lY-lAj)hnXz6YJ#hRSHiyIU%d4#%^uXcp z%E1TA>*kQd;;=Xnj$wWUU~vvQn;gMm;g6sWcjB;k?MJfIZ$rHWG#CPgfFWQA7y^d; zI>F#D1PlR#!$28={V)U!0Ykt*X#yRFfFWQA00MufSa2}dWYjR$21jJ=y2oU-TFKej znVg@W%f-co%;$5tzP^^lVj(v-H*$M>D|dHya({m>4-XHr*=%I}C*<n#lHc)^?$+Nx zov4?qTDLAk4*tL1p#yX}UoTm`k$*}@v=?|I|D0|=zmsp<u$ykpAMEVd2L$@HJh?aW z2irUk`}I+>#N*hu;W^)y-8tW$ex-7A;03I0wSS%8g6u!+T+OSGn@+@2tohGT<5$MF aSV|L9W_gTH%(;(q>yN}20*BrB&H67hVV}|f literal 0 HcmV?d00001 diff --git a/modules/dx_hw/resource.h b/modules/dx_hw/resource.h new file mode 100644 index 0000000..0c5b2f3 --- /dev/null +++ b/modules/dx_hw/resource.h @@ -0,0 +1,23 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by dx_hw.rc +// +#define IDC_HAND_PTR 103 +#define IDC_COLLIDE 104 +#define IDI_ICON1 105 +#define IDI_OSMO_ICON 105 +#define IDC_HAND_CLOSE 148 +#define IDC_HAND_OPEN 149 +#define IDC_ZOOM_IN 150 +#define IDC_ZOOM_OUT 151 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/modules/filter_export.cpp b/modules/filter_export.cpp new file mode 100644 index 0000000..262ad9d --- /dev/null +++ b/modules/filter_export.cpp @@ -0,0 +1,36 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC + * + * 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. + * + */ + +/*this file is only used with Win32&MSVC to export the module interface symbols from each module DLL*/ +#include <gpac/setup.h> + +#if defined(_WIN32_WCE) || defined(_WIN64) +#define EXPORT_SYMBOL(a) "/export:"#a +#else +#define EXPORT_SYMBOL(a) "/export:_"#a +#endif + +#pragma comment (linker, EXPORT_SYMBOL(RegisterFilter) ) + diff --git a/modules/ft_font/Makefile b/modules/ft_font/Makefile new file mode 100644 index 0000000..7800201 --- /dev/null +++ b/modules/ft_font/Makefile @@ -0,0 +1,57 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/ft_font + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(FT_CFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= ft_font.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_ft_font$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +#LDFLAGS+=-export-symbols ft_font.def +endif + + +ifneq ($(STATICBUILD),yes) +LINKVAR=-L../../bin/gcc -lgpac $(FT_LIBS) +else +LINKVAR=-L../../bin/gcc -lgpac_static $(FT_LIBS) +endif + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKVAR) $(EXTRALIBS) $(LDFLAGS) +ifeq ($(STATICBUILD),yes) + $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_ft_font-static$(DYN_LIB_SUFFIX) $(OBJS) $(LINKVAR) $(EXTRALIBS) $(LDFLAGS) +endif + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/ft_font/ft_font.c b/modules/ft_font/ft_font.c new file mode 100644 index 0000000..7b537e2 --- /dev/null +++ b/modules/ft_font/ft_font.c @@ -0,0 +1,838 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2022 + * All rights reserved + * + * This file is part of GPAC / FreeType font engine module + * + * 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/modules/font.h> +#include <gpac/list.h> +#include <gpac/utf.h> +#include <gpac/tools.h> + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) +# pragma comment(lib, "freetype") +# elif defined (WIN32) +# pragma comment(lib, "freetype") +# endif +#endif + + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +/*TrueType tables*/ +#include FT_TRUETYPE_TABLES_H + +typedef struct +{ + FT_Library library; + FT_Face active_face; + + GF_List *font_dirs; + + GF_List *loaded_fonts; + + /*default fonts*/ + char *font_serif, *font_sans, *font_fixed, *font_default; + Bool cache_checked; +} FTBuilder; + +static const char * BEST_FIXED_FONTS[] = { + "Courier New", + "Courier", + "Monaco", + "Bitstream Vera Monospace", + "Droid Sans Mono", + NULL +}; + +static const char * BEST_SERIF_FONTS[] = { + "Times New Roman", + "Bitstream Vera", + "Times", + "Droid Serif", + NULL +}; + +static const char * BEST_SANS_FONTS[] = { + "Arial", + "Tahoma", + "Verdana", + "Helvetica", + "Bitstream Vera Sans", + "Frutiger", + "Droid Sans", + NULL +}; + +/** + * Choose the best font in the list of fonts + */ +static Bool isBestFontFor(const char * listOfFonts[], const char * currentBestFont, const char * fontName) { + u32 i; + assert( fontName ); + assert( listOfFonts ); + for (i = 0 ; listOfFonts[i]; i++) { + const char * best = listOfFonts[i]; + if (!stricmp(best, fontName)) + return GF_TRUE; + if (currentBestFont && !stricmp(best, currentBestFont)) + return GF_FALSE; + } + /* Nothing has been found, the font is the best if none has been choosen before */ + return currentBestFont == NULL; +} + +void setBestFont(const char * listOfFonts[], char ** currentBestFont, const char * fontName) { + if (isBestFontFor(listOfFonts, *currentBestFont, fontName)) { + if (*currentBestFont) + gf_free(*currentBestFont); + *currentBestFont = NULL; + } + if (! (*currentBestFont)) { + *currentBestFont = gf_strdup(fontName); + } +} + +static Bool ft_enum_fonts(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) +{ + char *szfont; + FT_Face face; + u32 num_faces, i; + GF_FontReader *dr = cbck; + FTBuilder *ftpriv = dr->udta; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[FreeType] Enumerating font %s (%s)\n", file_name, file_path)); + + if (FT_New_Face(ftpriv->library, file_path, 0, & face )) return 0; + if (!face || !face->family_name) return 0; + + num_faces = (u32) face->num_faces; + /*locate right font in collection if several*/ + for (i=0; i<num_faces; i++) { + + /*only scan scalable fonts*/ + if (face->face_flags & FT_FACE_FLAG_SCALABLE) { + Bool bold, italic; + szfont = gf_malloc(sizeof(char)* (strlen(face->family_name)+100)); + if (!szfont) continue; + strcpy(szfont, face->family_name); + + /*remember first font found which looks like a alphabetical one*/ + if (!ftpriv->font_default) { + 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) ftpriv->font_default = gf_strdup(szfont); + } + + bold = italic = 0; + + if (face->style_name) { + char *name = gf_strdup(face->style_name); + strupr(name); + if (strstr(name, "BOLD")) bold = 1; + if (strstr(name, "ITALIC")) italic = 1; + /*if font is not regular style, append all styles blindly*/ + if (!strstr(name, "REGULAR")) { + strcat(szfont, " "); + strcat(szfont, face->style_name); + } + gf_free(name); + } else { + if (face->style_flags & FT_STYLE_FLAG_BOLD) bold = 1; + if (face->style_flags & FT_STYLE_FLAG_ITALIC) italic = 1; + + if (bold) strcat(szfont, " Bold"); + if (italic) strcat(szfont, " Italic"); + } + gf_opts_set_key("FontCache", szfont, file_path); + + /*try to assign default fixed fonts*/ + if (!bold && !italic) { + strcpy(szfont, face->family_name); + strlwr(szfont); + + if (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) { + setBestFont(BEST_FIXED_FONTS, &(ftpriv->font_fixed), face->family_name); + } + setBestFont(BEST_SERIF_FONTS, &(ftpriv->font_serif), face->family_name); + setBestFont(BEST_SANS_FONTS, &(ftpriv->font_sans), face->family_name); + } + gf_free(szfont); + } + + FT_Done_Face(face); + if (i+1==num_faces) return 0; + + /*load next font in collection*/ + if (FT_New_Face(ftpriv->library, file_path, i+1, & face )) return 0; + if (!face) return 0; + } + return 0; +} + +static Bool ft_enum_fonts_dir(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[FreeType] Scanning directory %s (%s)\n", file_name, file_path)); + gf_enum_directory(file_path, 0, ft_enum_fonts, cbck, "ttf;ttc"); + return (gf_enum_directory(file_path, 1, ft_enum_fonts_dir, cbck, NULL)==GF_OK) ? GF_FALSE : GF_TRUE; +} + + +static void ft_rescan_fonts(GF_FontReader *dr) +{ + u32 i, count; + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + + ftpriv->cache_checked = GF_TRUE; + + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] Rescaning %d font directories\n", gf_list_count(ftpriv->font_dirs) )); + + gf_opts_del_section("FontCache"); + gf_opts_del_section("temp_freetype"); + gf_opts_set_key("core", "rescan-fonts", "no"); + + if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); + ftpriv->font_fixed = NULL; + if (ftpriv->font_sans) gf_free(ftpriv->font_sans); + ftpriv->font_sans = NULL; + if (ftpriv->font_serif) gf_free(ftpriv->font_serif); + ftpriv->font_serif = NULL; + + if (ftpriv->font_default) gf_free(ftpriv->font_default); + ftpriv->font_default = NULL; + + + count = gf_list_count(ftpriv->font_dirs); + for (i=0; i<count; i++) { + char *font_dir = gf_list_get(ftpriv->font_dirs, i); + + if (gf_dir_exists(font_dir)) { + gf_enum_directory(font_dir, 0, ft_enum_fonts, dr, "ttf;ttc"); + gf_enum_directory(font_dir, 1, ft_enum_fonts_dir, dr, NULL); + } + } + + if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); + ftpriv->font_fixed = NULL; + if (ftpriv->font_sans) gf_free(ftpriv->font_sans); + ftpriv->font_sans = NULL; + if (ftpriv->font_serif) gf_free(ftpriv->font_serif); + ftpriv->font_serif = NULL; + + /* let's check we have fonts that match our default Bold/Italic/BoldItalic conventions*/ + count = gf_opts_get_key_count("FontCache"); + for (i=0; i<count; i++) { + const char *opt; + char fkey[GF_MAX_PATH]; + const char *key = gf_opts_get_key_name("FontCache", i); + opt = gf_opts_get_key("FontCache", key); + if (!strchr(opt, '/') && !strchr(opt, '\\')) continue; + + if (strstr(key, "Bold")) continue; + if (strstr(key, "Italic")) continue; + + strcpy(fkey, key); + strcat(fkey, " Italic"); + opt = gf_opts_get_key("FontCache", fkey); + if (!opt) continue; + + strcpy(fkey, key); + strcat(fkey, " Bold"); + opt = gf_opts_get_key("FontCache", fkey); + if (!opt) continue; + + strcpy(fkey, key); + strcat(fkey, " Bold Italic"); + opt = gf_opts_get_key("FontCache", fkey); + if (!opt) continue; + + strcpy(fkey, key); + strlwr(fkey); + + /*this font is suited for our case*/ + if (isBestFontFor(BEST_FIXED_FONTS, ftpriv->font_fixed, key) || (!ftpriv->font_fixed && (strstr(fkey, "fixed") || strstr(fkey, "mono")) ) ) { + if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); + ftpriv->font_fixed = gf_strdup(key); + } + + if (isBestFontFor(BEST_SANS_FONTS, ftpriv->font_sans, key) || (!ftpriv->font_sans && strstr(fkey, "sans")) ) { + if (ftpriv->font_sans) gf_free(ftpriv->font_sans); + ftpriv->font_sans = gf_strdup(key); + } + + if (isBestFontFor(BEST_SERIF_FONTS, ftpriv->font_serif, key) || (!ftpriv->font_serif && strstr(fkey, "serif")) ) { + if (ftpriv->font_serif) gf_free(ftpriv->font_serif); + ftpriv->font_serif = gf_strdup(key); + } + } + + if (!ftpriv->font_serif) ftpriv->font_serif = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); + if (!ftpriv->font_sans) ftpriv->font_sans = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); + if (!ftpriv->font_fixed) ftpriv->font_fixed = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); + + gf_opts_set_key("FontCache", "FontFixed", ftpriv->font_fixed); + gf_opts_set_key("FontCache", "FontSerif", ftpriv->font_serif); + gf_opts_set_key("FontCache", "FontSans", ftpriv->font_sans); + + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] Font directories scanned\n")); +} + + + +static GF_Err ft_init_font_engine(GF_FontReader *dr) +{ + const char *sOpt; + u32 rescan = 0; + GF_Err e; + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + + /*inits freetype*/ + if (FT_Init_FreeType(&ftpriv->library) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[FreeType] Cannot initialize FreeType\n")); + return GF_IO_ERR; + } + + if (!gf_opts_get_key_count("FontCache")) + rescan = 1; + else { + sOpt = gf_opts_get_key("core", "rescan-fonts"); + if (!sOpt || !strcmp(sOpt, "yes") ) + rescan = 1; + } + +rescan_font_dirs: + sOpt = gf_opts_get_key("core", "font-dirs"); + if (!sOpt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[FreeType] No fonts directory indicated!")); + return GF_BAD_PARAM; + } + + while (sOpt) { + char dir[GF_MAX_PATH]; + char *sep = (char *) strchr(sOpt, ','); + if (sep) sep[0] = 0; + + strcpy(dir, sOpt); + while ( (dir[strlen(dir)-1] == '\n') || (dir[strlen(dir)-1] == '\r') ) + dir[strlen(dir)-1] = 0; + + if (dir[strlen(dir)-1] != GF_PATH_SEPARATOR) { + char ext[2]; + ext[0] = GF_PATH_SEPARATOR; + ext[1] = 0; + strcat(dir, ext); + } + + gf_list_add(ftpriv->font_dirs, gf_strdup(dir) ); + + if (!sep) break; + sep[0] = ','; + sOpt = sep+1; + } + +rescan_fonts: + if (rescan) + ft_rescan_fonts(dr); + + if (!ftpriv->font_serif) { + sOpt = gf_opts_get_key("FontCache", "FontSerif"); + ftpriv->font_serif = gf_strdup(sOpt ? sOpt : ""); + } + + if (!ftpriv->font_sans) { + sOpt = gf_opts_get_key("FontCache", "FontSans"); + ftpriv->font_sans = gf_strdup(sOpt ? sOpt : ""); + } + + if (!ftpriv->font_fixed) { + sOpt = gf_opts_get_key("FontCache", "FontFixed"); + ftpriv->font_fixed = gf_strdup(sOpt ? sOpt : ""); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[FreeType] Init OK - %d font directory (first %s)\n", gf_list_count(ftpriv->font_dirs), gf_list_get(ftpriv->font_dirs, 0) )); + + e = dr->set_font(dr, ftpriv->font_serif, 0); + if (!e) e = dr->set_font(dr, ftpriv->font_sans, 0); + if (!e) e = dr->set_font(dr, ftpriv->font_fixed, 0); + if (!e) return GF_OK; + + if (rescan==3) + return e; + + if (ftpriv->font_serif) gf_free(ftpriv->font_serif); + ftpriv->font_serif = NULL; + if (ftpriv->font_sans) gf_free(ftpriv->font_sans); + ftpriv->font_sans = NULL; + if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); + ftpriv->font_fixed = NULL; + + //error and we rescanned font dirs, restore default fonts + if (e && ftpriv->cache_checked) + rescan = 2; + + if (!rescan) { + sOpt = gf_opts_get_key("core", "font-dirs"); + rescan = 2; + if (sOpt) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[FreeType] Default fonts not valid, rescanning font directories %s\n", sOpt)); + goto rescan_fonts; + } + } + if (rescan==2) { + void gf_get_default_font_dir(char szPath[GF_MAX_PATH]); + + char szPath[GF_MAX_PATH]; + //check if font directory is default one, if not reset and rescan + gf_get_default_font_dir(szPath); + sOpt = gf_opts_get_key("core", "font-dirs"); + if (sOpt && !strcmp(sOpt, szPath)) + return e; + + GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[FreeType] No fonts found in %s, restoring default directories %s and rescanning\n", sOpt, szPath)); + gf_opts_set_key("core", "font-dirs", szPath); + rescan = 3; + while (gf_list_count(ftpriv->font_dirs)) { + gf_free(gf_list_pop_back(ftpriv->font_dirs)); + } + goto rescan_font_dirs; + } + + return GF_OK; +} + +static GF_Err ft_shutdown_font_engine(GF_FontReader *dr) +{ + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + + ftpriv->active_face = NULL; + /*reset loaded fonts*/ + while (gf_list_count(ftpriv->loaded_fonts)) { + FT_Face face = gf_list_pop_front(ftpriv->loaded_fonts); + FT_Done_Face(face); + } + + /*exit FT*/ + if (ftpriv->library) FT_Done_FreeType(ftpriv->library); + ftpriv->library = NULL; + return GF_OK; +} + + +static Bool ft_check_face(FT_Face font, const char *fontName, u32 styles) +{ + u32 ft_style, loc_styles; + char *name; + + if (fontName && stricmp(font->family_name, fontName)) return 0; + ft_style = 0; + if (font->style_name) { + name = gf_strdup(font->style_name); + strupr(name); + if (strstr(name, "BOLD")) ft_style |= GF_FONT_WEIGHT_BOLD; + if (strstr(name, "ITALIC")) ft_style |= GF_FONT_ITALIC; + gf_free(name); + } else { + if (font->style_flags & FT_STYLE_FLAG_BOLD) ft_style |= GF_FONT_WEIGHT_BOLD; + if (font->style_flags & FT_STYLE_FLAG_ITALIC) ft_style |= GF_FONT_ITALIC; + } + name = gf_strdup(font->family_name); + strupr(name); + if (strstr(name, "BOLD")) ft_style |= GF_FONT_WEIGHT_BOLD; + if (strstr(name, "ITALIC")) ft_style |= GF_FONT_ITALIC; + gf_free(name); + + loc_styles = styles & GF_FONT_WEIGHT_MASK; + if (loc_styles>=GF_FONT_WEIGHT_BOLD) + styles = (styles & 0x00000007) | GF_FONT_WEIGHT_BOLD; + else + styles = (styles & 0x00000007); + + if (ft_style==styles) + return 1; + return 0; +} + +static FT_Face ft_font_in_cache(FTBuilder *ft, const char *fontName, u32 styles) +{ + u32 i=0; + FT_Face font; + + while ((font = gf_list_enum(ft->loaded_fonts, &i))) { + if (ft_check_face(font, fontName, styles)) return font; + } + return NULL; +} + + + +static GF_Err ft_set_font(GF_FontReader *dr, const char *OrigFontName, u32 styles) +{ + char *fname; + char *fontName; + const char *opt; + Bool is_def_font = GF_FALSE; + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + + fontName = (char *) OrigFontName; + ftpriv->active_face = NULL; + + opt = gf_opts_get_key("temp_freetype", OrigFontName); + if (opt) return GF_NOT_SUPPORTED; + + if (!fontName || !strlen(fontName) || !stricmp(fontName, "SERIF")) { + fontName = ftpriv->font_serif; + is_def_font = GF_TRUE; + OrigFontName = ""; + } + else if (!stricmp(fontName, "SANS") || !stricmp(fontName, "sans-serif")) { + fontName = ftpriv->font_sans; + is_def_font = GF_TRUE; + OrigFontName = "SANS"; + } + else if (!stricmp(fontName, "TYPEWRITER") || !stricmp(fontName, "monospace")) { + fontName = ftpriv->font_fixed; + is_def_font = GF_TRUE; + OrigFontName = "TYPEWRITER"; + } + + /*first look in loaded fonts*/ + ftpriv->active_face = ft_font_in_cache(ftpriv, fontName, styles); + if (ftpriv->active_face) return GF_OK; + + //we likely have a problem with the font cache, rebuild if + if (!fontName || !strlen(fontName)) { + if (is_def_font && !ftpriv->cache_checked) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] No default font set, rescanning fonts\n")); + ft_rescan_fonts(dr); + return ft_set_font(dr, OrigFontName, styles); + } + return GF_NOT_SUPPORTED; + } + fname = gf_malloc(sizeof(char) * (strlen(fontName) + 50)); + + { + int checkStyles = (styles & GF_FONT_WEIGHT_BOLD) | (styles & GF_FONT_ITALIC); +checkFont: + strcpy(fname, fontName); + if (styles & GF_FONT_WEIGHT_BOLD & checkStyles) strcat(fname, " Bold"); + if (styles & GF_FONT_ITALIC & checkStyles) strcat(fname, " Italic"); + + opt = gf_opts_get_key("FontCache", fname); + + if (opt) { + FT_Face face; + gf_free(fname); + if (FT_New_Face(ftpriv->library, opt, 0, & face )) return GF_IO_ERR; + if (!face) return GF_IO_ERR; + gf_list_add(ftpriv->loaded_fonts, face); + ftpriv->active_face = face; + return GF_OK; + } + if (checkStyles) { + /* If we tried font + bold + italic -> we will try font + [bold | italic] + If we tried font + [bold | italic] -> we try font + */ + if (checkStyles == (GF_FONT_WEIGHT_BOLD | GF_FONT_ITALIC)) + checkStyles = GF_FONT_WEIGHT_BOLD; + else if (checkStyles == GF_FONT_WEIGHT_BOLD && (styles & GF_FONT_ITALIC)) + checkStyles = GF_FONT_ITALIC; + else if (checkStyles == GF_FONT_WEIGHT_BOLD || checkStyles == GF_FONT_ITALIC) + checkStyles = 0; + goto checkFont; + } + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] Font %s (%s) not found\n", fontName, fname)); + gf_free(fname); + gf_opts_set_key("temp_freetype", OrigFontName, "not found"); + return GF_NOT_SUPPORTED; +} + +static GF_Err ft_get_font_info(GF_FontReader *dr, char **font_name, u32 *em_size, s32 *ascent, s32 *descent, s32 *underline, s32 *line_spacing, s32 *max_advance_h, s32 *max_advance_v) +{ + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + if (!ftpriv->active_face) return GF_BAD_PARAM; + + *em_size = ftpriv->active_face->units_per_EM; + *ascent = ftpriv->active_face->ascender; + *descent = ftpriv->active_face->descender; + *underline = ftpriv->active_face->underline_position; + *line_spacing = ftpriv->active_face->height; + *font_name = gf_strdup(ftpriv->active_face->family_name); + *max_advance_h = ftpriv->active_face->max_advance_width; + *max_advance_v = ftpriv->active_face->max_advance_height; + return GF_OK; +} + + +static GF_Err ft_get_glyphs(GF_FontReader *dr, const char *utf_string, u32 *glyph_buffer, u32 *io_glyph_buffer_size, const char *xml_lang, Bool *is_rtl) +{ + u32 len; + u32 i; + u16 *conv; + char *utf8 = (char*) utf_string; + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + + if (!ftpriv->active_face) return GF_BAD_PARAM; + + /*TODO: glyph substitution / ligature */ + + len = utf_string ? (u32) strlen(utf_string) : 0; + if (!len) { + *io_glyph_buffer_size = 0; + return GF_OK; + } + if (*io_glyph_buffer_size < len+1) { + *io_glyph_buffer_size = len+1; + return GF_BUFFER_TOO_SMALL; + } + len = gf_utf8_mbstowcs((u16*) glyph_buffer, *io_glyph_buffer_size, (const char **) &utf8); + if (len == GF_UTF8_FAIL) return GF_IO_ERR; + if (utf8) return GF_IO_ERR; + + /*perform bidi relayout*/ + conv = (u16*) glyph_buffer; + *is_rtl = gf_utf8_reorder_bidi(conv, len); + /*move 16bit buffer to 32bit*/ + for (i=len; i>0; i--) { + glyph_buffer[i-1] = (u32) conv[i-1]; + } + *io_glyph_buffer_size = len; + return GF_OK; +} + + + + + +typedef struct +{ + FTBuilder *ftpriv; + GF_Path *path; + s32 last_x, last_y; +} ft_outliner; + +#if defined(GPAC_CONFIG_IOS) || defined(GPAC_CONFIG_ANDROID) +#define FTCST +#else +#define FTCST const +#endif + + + +static int ft_move_to(FTCST FT_Vector *to, void *user) +{ + ft_outliner *ftol = (ft_outliner *)user; + gf_path_add_move_to(ftol->path, INT2FIX(to->x), INT2FIX(to->y) ); + ftol->last_x = (s32) to->x; + ftol->last_y = (s32) to->y; + return 0; +} + +static int ft_line_to(FTCST FT_Vector *to, void *user) +{ + ft_outliner *ftol = (ft_outliner *)user; + if ( (ftol->last_x == to->x) && (ftol->last_y == to->y)) { + gf_path_close(ftol->path); + } else { + gf_path_add_line_to(ftol->path, INT2FIX(to->x), INT2FIX(to->y) ); + } + return 0; +} + +static int ft_conic_to(FTCST FT_Vector * control, FTCST FT_Vector *to, void *user) +{ + ft_outliner *ftol = (ft_outliner *)user; + gf_path_add_quadratic_to(ftol->path, INT2FIX(control->x), INT2FIX(control->y), INT2FIX(to->x), INT2FIX(to->y) ); + if ( (ftol->last_x == to->x) && (ftol->last_y == to->y)) gf_path_close(ftol->path); + return 0; +} + +static int ft_cubic_to(FTCST FT_Vector *c1, FTCST FT_Vector *c2, FTCST FT_Vector *to, void *user) +{ + ft_outliner *ftol = (ft_outliner *)user; + gf_path_add_cubic_to(ftol->path, INT2FIX(c1->x), INT2FIX(c1->y), INT2FIX(c2->x), INT2FIX(c2->y), INT2FIX(to->x), INT2FIX(to->y) ); + if ( (ftol->last_x == to->x) && (ftol->last_y == to->y)) gf_path_close(ftol->path); + return 0; +} + + +static GF_Glyph *ft_load_glyph(GF_FontReader *dr, u32 glyph_name) +{ + GF_Glyph *glyph; + u32 glyph_idx; + FT_BBox bbox; + FT_OutlineGlyph outline; + ft_outliner outl; + FT_Outline_Funcs ft_outl_funcs; + + FTBuilder *ftpriv = (FTBuilder *)dr->udta; + if (!ftpriv->active_face || !glyph_name) return NULL; + + FT_Select_Charmap(ftpriv->active_face, FT_ENCODING_UNICODE); + + glyph_idx = FT_Get_Char_Index(ftpriv->active_face, glyph_name); + /*missing glyph*/ + if (!glyph_idx) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[FreeType] Glyph not found for char %d in font %s (style %s)\n", glyph_name, ftpriv->active_face->family_name, ftpriv->active_face->style_name)); + return NULL; + } + + /*work in design units*/ + FT_Load_Glyph(ftpriv->active_face, glyph_idx, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP); + + FT_Get_Glyph(ftpriv->active_face->glyph, (FT_Glyph*)&outline); + + /*oops not vectorial...*/ + if (outline->root.format==FT_GLYPH_FORMAT_BITMAP) return NULL; + + + GF_SAFEALLOC(glyph, GF_Glyph); + if (!glyph) return NULL; + GF_SAFEALLOC(glyph->path, GF_Path); + if (!glyph->path) { + gf_free(glyph); + return NULL; + } + /*setup outliner*/ + ft_outl_funcs.shift = 0; + ft_outl_funcs.delta = 0; + ft_outl_funcs.move_to = ft_move_to; + ft_outl_funcs.line_to = ft_line_to; + ft_outl_funcs.conic_to = ft_conic_to; + ft_outl_funcs.cubic_to = ft_cubic_to; + outl.path = glyph->path; + outl.ftpriv = ftpriv; + + /*FreeType is marvelous and gives back the right advance on space char !!!*/ + FT_Outline_Decompose(&outline->outline, &ft_outl_funcs, &outl); + + FT_Glyph_Get_CBox((FT_Glyph) outline, ft_glyph_bbox_unscaled, &bbox); + + glyph->ID = glyph_name; + glyph->utf_name = glyph_name; + glyph->horiz_advance = (s32) ftpriv->active_face->glyph->metrics.horiAdvance; + glyph->vert_advance = (s32) ftpriv->active_face->glyph->metrics.vertAdvance; + /* + glyph->x = bbox.xMin; + glyph->y = bbox.yMax; + */ + glyph->width = (u32) ftpriv->active_face->glyph->metrics.width; + glyph->height = (u32) ftpriv->active_face->glyph->metrics.height; + FT_Done_Glyph((FT_Glyph) outline); + return glyph; +} + + +static GF_FontReader *ft_load() +{ + GF_FontReader *dr; + FTBuilder *ftpriv; + dr = gf_malloc(sizeof(GF_FontReader)); + memset(dr, 0, sizeof(GF_FontReader)); + GF_REGISTER_MODULE_INTERFACE(dr, GF_FONT_READER_INTERFACE, "FreeType Font Reader", "gpac distribution"); + + ftpriv = gf_malloc(sizeof(FTBuilder)); + memset(ftpriv, 0, sizeof(FTBuilder)); + + ftpriv->font_dirs = gf_list_new(); + + ftpriv->loaded_fonts = gf_list_new(); + + dr->udta = ftpriv; + + + dr->init_font_engine = ft_init_font_engine; + dr->shutdown_font_engine = ft_shutdown_font_engine; + dr->set_font = ft_set_font; + dr->get_font_info = ft_get_font_info; + dr->get_glyphs = ft_get_glyphs; + dr->load_glyph = ft_load_glyph; + return dr; +} + + +static void ft_delete(GF_BaseInterface *ifce) +{ + GF_FontReader *dr = (GF_FontReader *) ifce; + FTBuilder *ftpriv = dr->udta; + + while (gf_list_count(ftpriv->font_dirs)) { + char *font = gf_list_pop_back(ftpriv->font_dirs); + if (font) + gf_free(font); + } + + gf_list_del(ftpriv->font_dirs); + + if (ftpriv->font_serif) gf_free(ftpriv->font_serif); + if (ftpriv->font_sans) gf_free(ftpriv->font_sans); + if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); + if (ftpriv->font_default) gf_free(ftpriv->font_default); + assert(!gf_list_count(ftpriv->loaded_fonts) ); + + gf_list_del(ftpriv->loaded_fonts); + + gf_free(dr->udta); + gf_free(dr); +} + +#ifndef GPAC_STANDALONE_RENDER_2D + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_FONT_READER_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_FONT_READER_INTERFACE) return (GF_BaseInterface *)ft_load(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_FONT_READER_INTERFACE: + ft_delete(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( ftfont ) + +#endif + diff --git a/modules/ft_font/ft_font.h b/modules/ft_font/ft_font.h new file mode 100644 index 0000000..68ef27f --- /dev/null +++ b/modules/ft_font/ft_font.h @@ -0,0 +1,30 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / FreeType font engine module + * + * 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 _FT_FONT_H_ +#define _FT_FONT_H_ + + +#endif /*_FT_FONT_H_*/ diff --git a/modules/ios_cam/CameraObject.h b/modules/ios_cam/CameraObject.h new file mode 100644 index 0000000..e3470f7 --- /dev/null +++ b/modules/ios_cam/CameraObject.h @@ -0,0 +1,64 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / iOS camera module + * + * 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 <Foundation/Foundation.h> +#import <AVFoundation/AVFoundation.h> + +#include "cam_wrap.h" + +@interface CameraObject : NSObject + <AVCaptureVideoDataOutputSampleBufferDelegate> +{ + // AVCaptureSession *session; + // AVCaptureDevice *device; + AVCaptureDeviceInput *input; + AVCaptureVideoDataOutput *output; + + int desiredWidth; + int desiredHeight; + int desiredColor; + + int m_width; + int m_height; + int m_color; + int m_stride; + + GetPixelsCallback *callback; +} + +@property (retain) AVCaptureSession *captureSession; + +- (int) setupFormat:(int)width :(int)height :(int)color; +- (int) getFormat:(unsigned int*)width :(unsigned int*)height :(int*)color :(int*)stride; +- (int) startCam; +- (int) stopCam; +- (int) isCamStarted; +- (int) setCallback:(GetPixelsCallback*) func; + +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection; + +@end diff --git a/modules/ios_cam/CameraObject.m b/modules/ios_cam/CameraObject.m new file mode 100644 index 0000000..3271a27 --- /dev/null +++ b/modules/ios_cam/CameraObject.m @@ -0,0 +1,170 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / iOS camera module + * + * 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 "CameraObject.h" + +@implementation CameraObject + +- (id)init { + if ((self = [super init])) { + @autoreleasepool { + AVCaptureSession* tmp = [[AVCaptureSession alloc] init]; + [self setCaptureSession:tmp]; + [tmp release]; + + input = NULL; + output = NULL; + callback = NULL; + + m_width = 640; + m_height = 480; + m_color = kCVPixelFormatType_32BGRA; + m_stride = m_width * 4; + } + } + return self; +} + +- (void)dealloc{ + + [super dealloc]; +} + +- (int) setupFormat:(int)width :(int)height :(int)color +{ + desiredWidth = width; + desiredHeight = height; + desiredColor = color; + return 0; +} + +- (int) getFormat:(unsigned int*)width :(unsigned int*)height :(int*)color :(int*)stride +{ + if ( !m_width || !m_height ) + return 1; + *width = m_width; + *height = m_height; + *color = m_color; + *stride = m_stride; + + return 0; +} + +- (int) startCam +{ + NSError *error = nil; + + // Configure the session to produce lower resolution video frames, if your + // processing algorithm can cope. We'll specify medium quality for the + // chosen device. + + [self captureSession].sessionPreset = AVCaptureSessionPreset640x480; + + // Find a suitable AVCaptureDevice + AVCaptureDevice *device = [AVCaptureDevice + defaultDeviceWithMediaType:AVMediaTypeVideo]; + + // Create a device input with the device and add it to the session. + input = [AVCaptureDeviceInput deviceInputWithDevice:device + error:&error]; + if (!input) { + // Handling the error appropriately. + } + [[self captureSession] addInput:input]; + + // Create a VideoDataOutput and add it to the session + output = [[[AVCaptureVideoDataOutput alloc] init] autorelease]; + [[self captureSession] addOutput:output]; + + // Configure your output. + dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL); + [output setSampleBufferDelegate:self queue:queue]; + dispatch_release(queue); + + // Specify the pixel format + output.videoSettings = + [NSDictionary dictionaryWithObject: + [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + + + // If you wish to cap the frame rate to a known value, such as 15 fps, set + // minFrameDuration. + output.minFrameDuration = CMTimeMake(1, 15); + + // Start the session running to start the flow of data + [[self captureSession] startRunning]; + + return 0; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput +didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + if ( !callback ) + return; + + CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(pixelBuffer, 0); + + void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); + + //size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); + // Get the pixel buffer width and height + //size_t width = CVPixelBufferGetWidth(pixelBuffer); + //size_t height = CVPixelBufferGetHeight(pixelBuffer); + + unsigned int size = m_height * m_stride;//CVPixelBufferGetDataSize(pixelBuffer); + + callback(baseAddress, size); + + CVPixelBufferUnlockBaseAddress(pixelBuffer,0); +} + +- (int) setCallback:(GetPixelsCallback*) func +{ + callback = func; + + return 0; +} + +- (int) stopCam +{ + [[self captureSession] stopRunning]; + [[self captureSession] removeInput: input]; + input = NULL; + [[self captureSession] removeOutput: output]; + output = NULL; + + return 0; +} + +- (int) isCamStarted +{ + return [self captureSession].running; +} + +@end diff --git a/modules/ios_cam/cam_wrap.h b/modules/ios_cam/cam_wrap.h new file mode 100644 index 0000000..0321ef6 --- /dev/null +++ b/modules/ios_cam/cam_wrap.h @@ -0,0 +1,55 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / iOS camera module + * + * 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 _cam_wrap_h +#define _cam_wrap_h + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (GetPixelsCallback)(unsigned char* pixels, unsigned int size); + +void* CAM_CreateInstance(); + +int CAM_DestroyInstance(void** inst); + +int CAM_SetupFormat(void* inst, int width, int height, int color); + +int CAM_GetCurrentFormat(void* inst, unsigned int* width, unsigned int* height, int* color, int* stride); + +int CAM_Start(void* inst); + +int CAM_Stop(void* inst); + +int CAM_IsStarted(void* inst); + +int CAM_SetCallback(void* inst, GetPixelsCallback *callback); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/ios_cam/cam_wrap.m b/modules/ios_cam/cam_wrap.m new file mode 100644 index 0000000..723af8f --- /dev/null +++ b/modules/ios_cam/cam_wrap.m @@ -0,0 +1,82 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / iOS camera module + * + * 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 "cam_wrap.h" +#include <stdlib.h> + +#include "CameraObject.h" + +void* CAM_CreateInstance(){ + void* obj = [[CameraObject alloc] init]; + return obj; +} + +int CAM_DestroyInstance(void** inst){ + [(id)*inst dealloc]; + *inst = NULL; + return 0; +} + +int CAM_SetupFormat(void* inst, int width, int height, int color) { + if ( !inst ) + return 1; + + return [(id)inst setupFormat: width :height :color]; +} + +int CAM_GetCurrentFormat(void* inst, unsigned int* width, unsigned int* height, int* color, int* stride) { + if ( !inst ) + return 1; + + return [(id)inst getFormat: width :height :color :stride]; +} + +int CAM_Start(void* inst){ + if ( !inst ) + return 1; + + return [(id)inst startCam]; +} + +int CAM_Stop(void* inst){ + if ( !inst ) + return 1; + + return [(id)inst stopCam]; +} + +int CAM_IsStarted(void* inst){ + if ( !inst ) + return 1; + + return [(id)inst isCamStarted]; +} + +int CAM_SetCallback(void* inst, GetPixelsCallback *callback){ + if ( !inst ) + return 1; + + return [(id)inst setCallback: callback]; +} diff --git a/modules/ios_cam/ios_cam.c b/modules/ios_cam/ios_cam.c new file mode 100644 index 0000000..16466fb --- /dev/null +++ b/modules/ios_cam/ios_cam.c @@ -0,0 +1,435 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / iOS camera module + * + * 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/terminal.h> +#include <gpac/internal/terminal_dev.h> +#include <gpac/internal/compositor_dev.h> +#include <gpac/modules/codec.h> +#include <gpac/constants.h> +#include <gpac/modules/service.h> +#include <gpac/thread.h> +#include <gpac/media_tools.h> + +#include "cam_wrap.h" + + +#define CAM_PIXEL_FORMAT GF_PIXEL_BGR_32 +//GF_PIXEL_RGB_32 +#define CAM_PIXEL_SIZE 4.f +//4 +#define CAM_WIDTH 640 +#define CAM_HEIGHT 480 + +//GF_PIXEL_RGB_24; + +typedef struct +{ + GF_InputService *input; + /*the service we're responsible for*/ + GF_ClientService *service; + LPNETCHANNEL channel; + + /*input file*/ + u32 time_scale; + + u32 base_track_id; + + struct _tag_terminal *term; + + u32 cntr; + + u32 width; + u32 height; + + Bool started; + + void* camInst; + +} IOSCamCtx; + +IOSCamCtx* globReader = NULL; + +void camStartCamera(IOSCamCtx *read); +void camStopCamera(IOSCamCtx *read); +void processFrameBuf( unsigned char* data, unsigned int dataSize); + +Bool CAM_CanHandleURL(GF_InputService *plug, const char *url) +{ + if (!strnicmp(url, "hw://camera", 11)) return 1; + + return 0; +} + +GF_Err CAM_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) +{ + IOSCamCtx *read; + if (!plug || !plug->priv || !serv) return GF_SERVICE_ERROR; + read = (IOSCamCtx *) plug->priv; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_ConnectService: %d\n", gf_th_id())); + + globReader = read; + + read->input = plug; + read->service = serv; + read->base_track_id = 1; + read->time_scale = 1000; + + read->term = serv->term; + + read->camInst = CAM_CreateInstance(); + CAM_SetCallback(read->camInst, processFrameBuf); + + /*reply to user*/ + gf_service_connect_ack(serv, NULL, GF_OK); + //if (read->no_service_desc) isor_declare_objects(read); + + return GF_OK; +} + +GF_Err CAM_CloseService(GF_InputService *plug) +{ + GF_Err reply; + IOSCamCtx *read; + if (!plug || !plug->priv) return GF_SERVICE_ERROR; + read = (IOSCamCtx *) plug->priv; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_CloseService: %d\n", gf_th_id())); + + reply = GF_OK; + + CAM_DestroyInstance(&read->camInst); + + gf_service_disconnect_ack(read->service, NULL, reply); + return GF_OK; +} + +u32 getWidth(IOSCamCtx *read); +u32 getHeight(IOSCamCtx *read); + +static GF_Descriptor *CAM_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) +{ + u32 trackID; + IOSCamCtx *read; + char *buf; + u32 buf_size; + s32 color; + s32 stride; + if (!plug || !plug->priv) return NULL; + read = (IOSCamCtx *) plug->priv; + + trackID = read->base_track_id; + read->base_track_id = 0; + + if (trackID && (expect_type==GF_MEDIA_OBJECT_VIDEO) ) { + GF_ESD *esd; + GF_ObjectDescriptor *od; + GF_BitStream *bs; + od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); + od->objectDescriptorID = 1; + + esd = gf_odf_desc_esd_new(0); + esd->slConfig->timestampResolution = 1000; + esd->decoderConfig->streamType = GF_STREAM_VISUAL; + esd->ESID = 1; + esd->decoderConfig->objectTypeIndication = GPAC_OTI_RAW_MEDIA_STREAM; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + CAM_GetCurrentFormat(read->camInst, &read->width, &read->height, &color, &stride); + + gf_bs_write_u32(bs, CAM_PIXEL_FORMAT); // fourcc + gf_bs_write_u16(bs, read->width); // width + gf_bs_write_u16(bs, read->height); // height + gf_bs_write_u32(bs, read->height * stride); // framesize + gf_bs_write_u32(bs, stride); // stride + gf_bs_write_u8(bs, 1); // is_flipped + + gf_bs_align(bs); + gf_bs_get_content(bs, &buf, &buf_size); + gf_bs_del(bs); + + esd->decoderConfig->decoderSpecificInfo->data = buf; + esd->decoderConfig->decoderSpecificInfo->dataLength = buf_size; + + gf_list_add(od->ESDescriptors, esd); + return (GF_Descriptor *) od; + } + + return NULL; +} + +GF_Err CAM_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) +{ + GF_Err e; + IOSCamCtx *read; + if (!plug || !plug->priv) return GF_SERVICE_ERROR; + read = (IOSCamCtx *) plug->priv; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_ConnectChannel: %d\n", gf_th_id())); + + e = GF_OK; + if (upstream) { + e = GF_ISOM_INVALID_FILE; + } + + read->channel = channel; + + camStartCamera(read); + + gf_service_connect_ack(read->service, channel, e); + return e; +} + +GF_Err CAM_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) +{ + GF_Err e; + IOSCamCtx *read; + if (!plug || !plug->priv) return GF_SERVICE_ERROR; + read = (IOSCamCtx *) plug->priv; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] CAM_DisconnectChannel: %d\n", gf_th_id())); + + e = GF_OK; + + camStopCamera(read); + + gf_service_disconnect_ack(read->service, channel, e); + return e; +} + +int* decodeYUV420SP( char* yuv420sp, int width, int height) +{ + int frameSize = width * height; + int j, yp, i, y, y1192, r, g, b; + int ti, tj; + + int* rgb = (int*)gf_malloc(width*height*4); + for (j = 0, yp = 0, tj=height-1; j < height; j++, tj--) + { + int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; + for (i = 0, ti=0; i < width; i++, yp++, ti+=width) + { + y = (0xff & ((int) yuv420sp[yp])) - 16; + if (y < 0) y = 0; + if ((i & 1) == 0) + { + v = (0xff & yuv420sp[uvp++]) - 128; + u = (0xff & yuv420sp[uvp++]) - 128; + } + + y1192 = 1192 * y; + r = (y1192 + 1634 * v); + g = (y1192 - 833 * v - 400 * u); + b = (y1192 + 2066 * u); + + if (r < 0) + r = 0; + else if (r > 262143) + r = 262143; + if (g < 0) + g = 0; + else if (g > 262143) + g = 262143; + if (b < 0) + b = 0; + else if (b > 262143) + b = 262143; + + rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) + | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); + // rgb[ti+tj] = 0xff000000 | ((r << 6) & 0xff0000) + // | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); + } + } + return rgb; +} + +void processFrameBuf( unsigned char* data, unsigned int dataSize) +{ + IOSCamCtx* ctx = globReader; + GF_SLHeader hdr; + u32 cts = 0; + + cts = gf_term_get_time(ctx->term); + + memset(&hdr, 0, sizeof(hdr)); + hdr.compositionTimeStampFlag = 1; + hdr.compositionTimeStamp = cts; + gf_service_send_packet(ctx->service, ctx->channel, (void*)data, dataSize, &hdr, GF_OK); + +} + +void camStartCamera(IOSCamCtx *read) +{ + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] startCamera: %d\n", gf_th_id())); + + //CAM_Start(read->camInst); +} + +void camStopCamera(IOSCamCtx *read) +{ + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] stopCamera: %d\n", gf_th_id())); + + //CAM_Stop(read->camInst); +} + +void pauseCamera(IOSCamCtx *read) +{ + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] pauseCamera: %d\n", gf_th_id())); + + read->started = 0; + CAM_Stop(read->camInst); +} + +void resumeCamera(IOSCamCtx *read) +{ + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] resumeCamera: %d\n", gf_th_id())); + + read->started = 1; + CAM_Start(read->camInst); +} + +GF_Err CAM_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) +{ + IOSCamCtx *read; + char *buf; + u32 buf_size; + if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR; + read = (IOSCamCtx *) plug->priv; + + if (com->command_type==GF_NET_SERVICE_INFO) { + return GF_OK; + } + if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) { + return GF_NOT_SUPPORTED; + } + if (!com->base.on_channel) return GF_NOT_SUPPORTED; + + switch (com->command_type) { + case GF_NET_CHAN_INTERACTIVE: + return GF_OK; + case GF_NET_CHAN_BUFFER: + com->buffer.max = com->buffer.min = 0; + return GF_OK; + case GF_NET_CHAN_PLAY: + resumeCamera(read); + return GF_OK; + case GF_NET_CHAN_STOP: + pauseCamera(read); + return GF_OK; + /*nothing to do on MP4 for channel config*/ + case GF_NET_CHAN_CONFIG: + return GF_OK; + case GF_NET_CHAN_GET_PIXEL_AR: + return 1<<16;//gf_isom_get_pixel_aspect_ratio(read->mov, ch->track, 1, &com->par.hSpacing, &com->par.vSpacing); + case GF_NET_CHAN_GET_DSI: + { + GF_BitStream *bs; + s32 color; + s32 stride; + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cam get DSI\n")); + /*it may happen that there are conflicting config when using ESD URLs...*/ + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + CAM_GetCurrentFormat(read->camInst, &read->width, &read->height, &color, &stride); + + gf_bs_write_u32(bs, CAM_PIXEL_FORMAT); // fourcc + gf_bs_write_u16(bs, read->width); // width + gf_bs_write_u16(bs, read->height); // height + gf_bs_write_u32(bs, read->height * stride); // framesize + gf_bs_write_u32(bs, stride); // stride + + gf_bs_align(bs); + gf_bs_get_content(bs, &buf, &buf_size); + gf_bs_del(bs); + + com->get_dsi.dsi = buf; + com->get_dsi.dsi_len = buf_size; + return GF_OK; + } + default: + return GF_NOT_SUPPORTED; + } + return GF_NOT_SUPPORTED; +} + +GF_InputService *CAM_client_load() +{ + IOSCamCtx *reader; + GF_InputService *plug; + GF_SAFEALLOC(plug, GF_InputService); + GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC Camera Plugin", "gpac distribution") + plug->CanHandleURL = CAM_CanHandleURL; + plug->ConnectService = CAM_ConnectService; + plug->CloseService = CAM_CloseService; + plug->GetServiceDescriptor = CAM_GetServiceDesc; + plug->ConnectChannel = CAM_ConnectChannel; + plug->DisconnectChannel = CAM_DisconnectChannel; + plug->ServiceCommand = CAM_ServiceCommand; + + GF_SAFEALLOC(reader, IOSCamCtx); + plug->priv = reader; + return plug; +} + +void CAM_client_del(GF_BaseInterface *bi) +{ + GF_InputService *plug = (GF_InputService *) bi; + IOSCamCtx *read = (IOSCamCtx *)plug->priv; + + gf_free(read); + gf_free(bi); +} + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_NET_CLIENT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_NET_CLIENT_INTERFACE) + return (GF_BaseInterface *)CAM_client_load(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_NET_CLIENT_INTERFACE: + CAM_client_del(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( ios_cam ) diff --git a/modules/ios_mpegv/SensorAcces.m b/modules/ios_mpegv/SensorAcces.m new file mode 100644 index 0000000..317baf6 --- /dev/null +++ b/modules/ios_mpegv/SensorAcces.m @@ -0,0 +1,209 @@ +// +// ios_mpegv.m +// ios_mpegv +// +// Created by mac on 3/8/13. +// +// + +#import "SensorAccess.h" + +@implementation SensorAccess + +- (id)init { + if ((self = [super init])) { + @autoreleasepool { + self.motionManager = [[[CMMotionManager alloc] init] autorelease]; + self.queue = [[[NSOperationQueue alloc] init] autorelease]; + + self.locMngr = [[[CLLocationManager alloc] init] autorelease]; + self.locMngr.delegate = self; + self.locMngr.desiredAccuracy = kCLLocationAccuracyBest; + self.locMngr.distanceFilter = 2; + + sensorCallbak = NULL; + userData = NULL; + sensorType = -1; + gpsActive = 0; + } + } + return self; +} + +- (void)dealloc{ + self.locMngr.delegate = NULL; + [super dealloc]; +} + +- (int) setSensorType :(int) type +{ + sensorType = type; + return 0; +} + +- (int) getSensorType +{ + return sensorType; +} + +- (int) setSensorCallback:(SensorDataCallback*)callback :(void*)user +{ + sensorCallbak = callback; + userData = user; + return 0; +} + +- (int) startSensor +{ + switch (sensorType) { + case SENSOR_ACCELEROMETER: + if ( ![[self motionManager] isAccelerometerAvailable] ) + return 1; + [[self motionManager] startAccelerometerUpdatesToQueue:[self queue] withHandler:^(CMAccelerometerData* data, NSError* error) { + if ( sensorCallbak ) { + char cdata[100]; + sprintf(cdata, "%f;%f;%f;", data.acceleration.x, data.acceleration.y, data.acceleration.z); + sensorCallbak(userData, cdata); + } + } + ]; + return 0; + break; + case SENSOR_GYROSCOPE: + if ( ![[self motionManager] isGyroAvailable] ) + return 1; + [[self motionManager] startGyroUpdatesToQueue:[self queue] withHandler:^(CMGyroData* data, NSError* error) { + if ( sensorCallbak ) { + char cdata[100]; + sprintf(cdata, "%f;%f;%f;", data.rotationRate.x, data.rotationRate.y, data.rotationRate.z); + sensorCallbak(userData, cdata); + } + } + ]; + return 0; + break; + case SENSOR_MAGNETOMETER: + if ( ![[self motionManager] isMagnetometerAvailable] ) + return 1; + [[self motionManager] startMagnetometerUpdatesToQueue:[self queue] withHandler:^(CMMagnetometerData* data, NSError* error) { + if ( sensorCallbak ) { + char cdata[100]; + sprintf(cdata, "%f;%f;%f;", data.magneticField.x, data.magneticField.y, data.magneticField.z); + sensorCallbak(userData, cdata); + } + } + ]; + return 0; + break; + case SENSOR_GPS: + [self.locMngr startUpdatingLocation]; + gpsActive = 1; + return 0; + break; + } + + return 1; +} + +- (int) stopSensor +{ + switch (sensorType) { + case SENSOR_ACCELEROMETER: + [[self motionManager] stopAccelerometerUpdates]; + return 0; + break; + case SENSOR_GYROSCOPE: + [[self motionManager] stopGyroUpdates]; + return 0; + break; + case SENSOR_MAGNETOMETER: + [[self motionManager] stopMagnetometerUpdates]; + return 0; + case SENSOR_GPS: + [self.locMngr stopUpdatingLocation]; + gpsActive = 0; + return 0; + break; + } + return 1; +} + +- (int) isSensorStarted +{ + switch (sensorType) { + case SENSOR_ACCELEROMETER: + return [[self motionManager] isAccelerometerActive]; + break; + case SENSOR_GYROSCOPE: + return [[self motionManager] isGyroActive]; + break; + case SENSOR_MAGNETOMETER: + return [[self motionManager] isMagnetometerActive]; + case SENSOR_GPS: + return gpsActive; + break; + } + return 0; +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)location fromLocation:(CLLocation *)oldLocation { + char cdata[100]; + sprintf(cdata, "%f;%f;%f;%f;%f;", (float)location.coordinate.latitude, (float)location.coordinate.longitude, (float)location.altitude, + (float)location.horizontalAccuracy, 0.f); + sensorCallbak(userData, cdata); +} + +@end + +void* SENS_CreateInstance() { + void* obj = [[SensorAccess alloc] init]; + return obj; +} + +int SENS_DestroyInstance(void** inst) { + [(id)*inst dealloc]; + *inst = NULL; + return 0; +} + +int SENS_SetSensorType(void* inst, int type) { + if ( !inst ) + return 1; + + return [(id)inst setSensorType:type]; +} + +int SENS_GetCurrentSensorType(void* inst) { + if ( !inst ) + return 1; + + return [(id)inst getSensorType]; +} + +int SENS_Start(void* inst) { + if ( !inst ) + return 1; + + return [(id)inst startSensor]; +} + +int SENS_Stop(void* inst) { + if ( !inst ) + return 1; + + return [(id)inst stopSensor]; +} + +int SENS_IsStarted(void* inst) { + if ( !inst ) + return 1; + + return [(id)inst isSensorStarted]; +} + +int SENS_SetCallback(void* inst, SensorDataCallback *callback, void* user) { + if ( !inst ) + return 1; + + return [(id)inst setSensorCallback:callback :user]; +} diff --git a/modules/ios_mpegv/SensorAccess.h b/modules/ios_mpegv/SensorAccess.h new file mode 100644 index 0000000..bff9957 --- /dev/null +++ b/modules/ios_mpegv/SensorAccess.h @@ -0,0 +1,34 @@ +// +// ios_mpegv.h +// ios_mpegv +// +// Created by mac on 3/8/13. +// +// + +#import <Foundation/Foundation.h> +#import <CoreMotion/CoreMotion.h> +#import <CoreLocation/CoreLocation.h> + +#include "sensor_wrap.h" + +@interface SensorAccess : NSObject <CLLocationManagerDelegate> +{ + SensorDataCallback* sensorCallbak; + void* userData; + int sensorType; + int gpsActive; +} + +@property (nonatomic, retain) CMMotionManager *motionManager; +@property (nonatomic, retain) NSOperationQueue *queue; +@property (nonatomic, retain) CLLocationManager *locMngr; + +- (int) setSensorType :(int) type; +- (int) getSensorType; +- (int) setSensorCallback:(SensorDataCallback*)callback :(void*) user; +- (int) startSensor; +- (int) stopSensor; +- (int) isSensorStarted; + +@end diff --git a/modules/ios_mpegv/ios_mpegv-Prefix.pch b/modules/ios_mpegv/ios_mpegv-Prefix.pch new file mode 100644 index 0000000..1834020 --- /dev/null +++ b/modules/ios_mpegv/ios_mpegv-Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'ios_mpegv' target in the 'ios_mpegv' project +// + +#ifdef __OBJC__ + #import <Foundation/Foundation.h> +#endif diff --git a/modules/ios_mpegv/ios_mpegv.c b/modules/ios_mpegv/ios_mpegv.c new file mode 100644 index 0000000..40bf267 --- /dev/null +++ b/modules/ios_mpegv/ios_mpegv.c @@ -0,0 +1,306 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Ivica Arsov, Jean Le Feuvre + * Copyright (c) Mines-Telecom 2009- + * All rights reserved + * + * This file is part of GPAC / MPEG-V Input sensor for android + * + * 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. + * + */ + +/*driver interfaces*/ + +#include <gpac/list.h> +#include <gpac/constants.h> + +#include <gpac/setup.h> + +#include <gpac/modules/codec.h> +#include <gpac/scenegraph_vrml.h> + +#include <gpac/thread.h> + +#include "sensor_wrap.h" + +typedef struct +{ + char sensor[50]; + u16 sensorIOSType; + u8 isAttached; + + GF_Mutex* mx; + + void* inst; + +} MPEGVSensorContext; + +#define MPEGVSCTX MPEGVSensorContext *rc = (MPEGVSensorContext *)dr->udta + +Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) +{ + MPEGVSCTX; + + //"MPEG-V:siv:OrientationSensorType" + + if ( strnicmp(urn, "MPEG-V", 6) ) + return 0; + + if ( strlen(urn) <= 6 ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[MPEG-V] No sensor type specified\n")); + return 0; + } + + if ( strnicmp(urn+6, ":siv:", 5) ) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[MPEG-V] Not valid sensor type specified\n")); + return 0; + } + + strcpy(rc->sensor, urn+11); + + if ( !strcmp(rc->sensor, "OrientationSensorType") ) + { + //AddField(dr, GF_SG_VRML_SFVEC3F, "Orientation"); + + //rc->sensorIOSType = 3; + + return 0; + } + else if ( !strcmp(rc->sensor, "AccelerationSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "Acceleration"); + + rc->sensorIOSType = SENSOR_ACCELEROMETER; + + return 1; + } + else if ( !strcmp(rc->sensor, "AngularVelocitySensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "AngularVelocity"); + + rc->sensorIOSType = SENSOR_GYROSCOPE; + + return 1; + } + else if ( !strcmp(rc->sensor, "MagneticFieldSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "MagneticField"); + + rc->sensorIOSType = SENSOR_MAGNETOMETER; + + return 1; + } + else if ( !strcmp(rc->sensor, "LightSensorType") ) + { + //AddField(dr, GF_SG_VRML_SFFLOAT, "Light"); + + //rc->sensorIOSType = 5; + + return 0; + } + else if ( !strcmp(rc->sensor, "AtmosphericPressureSensorType") ) + { + //AddField(dr, GF_SG_VRML_SFFLOAT, "AtmosphericPressure"); + + //rc->sensorIOSType = 6; + + return 0; + } + else if ( !strcmp(rc->sensor, "RotationVectorSensorType") ) + { + //AddField(dr, GF_SG_VRML_SFVEC3F, "RotationVector"); + + //rc->sensorIOSType = 11; + + return 0; + } + else if ( !strcmp(rc->sensor, "GlobalPositionSensorType") ) + { + AddField(dr, GF_SG_VRML_SFVEC3F, "Position"); + AddField(dr, GF_SG_VRML_SFFLOAT, "Accuracy"); + AddField(dr, GF_SG_VRML_SFFLOAT, "Bearing"); + + rc->sensorIOSType = SENSOR_GPS; + + return 1; + } + else + { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[MPEG-V_IN] Unsupported sensor type: %s\n", rc->sensor)); + return 0; + } +} + +void MPEGVSensorCallback( void* ptr, const char* data) +{ + GF_BitStream *bs; + char *buf; + u32 buf_size; + float x, y, z, a, b; + struct __input_device * dr = (struct __input_device *)ptr; + MPEGVSCTX; + + if (!gf_mx_try_lock(rc->mx)) + return; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + if ( rc->sensorIOSType == SENSOR_ACCELEROMETER + || rc->sensorIOSType == SENSOR_GYROSCOPE + //|| rc->sensorIOSType == 3 + || rc->sensorIOSType == SENSOR_MAGNETOMETER ) + { + sscanf(data, "%f;%f;%f;", &x, &y, &z); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + gf_bs_write_float(bs, y); + gf_bs_write_float(bs, z); + } + else if ( rc->sensorIOSType == 5 + || rc->sensorIOSType == 6 ) + { + sscanf(data, "%f;", &x); + + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + } + else if ( rc->sensorIOSType == 11 ) + { + sscanf(data, "%f;%f;%f;", &x, &y, &z); + + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + gf_bs_write_float(bs, y); + gf_bs_write_float(bs, z); + /*gf_bs_write_float(bs, q);*/ + } + else if ( rc->sensorIOSType == SENSOR_GPS ) + { + sscanf(data, "%f;%f;%f;%f;%f;", &x, &y, &z, &a, &b); + + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, x); + gf_bs_write_float(bs, y); + gf_bs_write_float(bs, z); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, a); + gf_bs_write_int(bs, 1, 1); + gf_bs_write_float(bs, b); + /*gf_bs_write_float(bs, q);*/ + } + + gf_bs_align(bs); + gf_bs_get_content(bs, &buf, &buf_size); + gf_bs_del(bs); + + dr->DispatchFrame(dr, (u8*)buf, buf_size); + gf_free(buf); + + gf_mx_v(rc->mx); +} + +void MPEGVS_Start(struct __input_device * dr) +{ + MPEGVSCTX; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Start: %d\n", gf_th_id())); + + if ( rc->inst ) { + SENS_Stop(rc->inst); + SENS_DestroyInstance(&rc->inst); + } + + rc->inst = SENS_CreateInstance(); + SENS_SetSensorType(rc->inst, rc->sensorIOSType); + SENS_SetCallback(rc->inst, MPEGVSensorCallback, dr); + SENS_Start(rc->inst); +} + +void MPEGVS_Stop(struct __input_device * dr) +{ + MPEGVSCTX; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Stop: %d\n", gf_th_id())); + + SENS_Stop(rc->inst); + SENS_DestroyInstance(&rc->inst); +} + +GF_InputSensorDevice* NewMPEGVSInputSesor() +{ + MPEGVSensorContext* ctx; + GF_InputSensorDevice* driv; + + driv = (GF_InputSensorDevice *) gf_malloc(sizeof(GF_InputSensorDevice)); + memset(driv, 0, sizeof(GF_InputSensorDevice)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_INPUT_DEVICE_INTERFACE, "MPEG-V Sensors Input Module", "gpac distribution"); + + driv->RegisterDevice = MPEGVS_RegisterDevice; + driv->Start = MPEGVS_Start; + driv->Stop = MPEGVS_Stop; + + ctx = (MPEGVSensorContext*) gf_malloc (sizeof(MPEGVSensorContext)); + memset(ctx, 0, sizeof(MPEGVSensorContext)); + ctx->mx = gf_mx_new(NULL); + + driv->udta = (void*)ctx; + + return driv; +} + +void DeleteMPEGVSInputSensor(GF_InputSensorDevice* dev) +{ + MPEGVS_Stop(dev); + MPEGVSensorContext* ctx = (MPEGVSensorContext*)dev->udta; + gf_mx_del(ctx->mx); + gf_free(dev->udta); + gf_free(dev); +} + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_INPUT_DEVICE_INTERFACE, + 0 + }; + return si; +} + +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_INPUT_DEVICE_INTERFACE) return (GF_BaseInterface *) NewMPEGVSInputSesor(); + return NULL; +} + +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_INPUT_DEVICE_INTERFACE: + DeleteMPEGVSInputSensor((GF_InputSensorDevice *)ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( ios_mpegv ) diff --git a/modules/ios_mpegv/sensor_wrap.h b/modules/ios_mpegv/sensor_wrap.h new file mode 100644 index 0000000..9666d40 --- /dev/null +++ b/modules/ios_mpegv/sensor_wrap.h @@ -0,0 +1,43 @@ +// +// sensor_wrap.h +// gpac4ios +// +// Created by mac on 3/8/13. +// +// + +#ifndef sensor_wrap_h +#define sensor_wrap_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define SENSOR_ACCELEROMETER 0 +#define SENSOR_GYROSCOPE 1 +#define SENSOR_MAGNETOMETER 2 +#define SENSOR_GPS 3 + +typedef void (SensorDataCallback)(void* user, const char* data); + +void* SENS_CreateInstance(); + +int SENS_DestroyInstance(void** inst); + +int SENS_SetSensorType(void* inst, int type); + +int SENS_GetCurrentSensorType(void* inst); + +int SENS_Start(void* inst); + +int SENS_Stop(void* inst); + +int SENS_IsStarted(void* inst); + +int SENS_SetCallback(void* inst, SensorDataCallback *callback, void* user); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/jack/Makefile b/modules/jack/Makefile new file mode 100644 index 0000000..6423d76 --- /dev/null +++ b/modules/jack/Makefile @@ -0,0 +1,42 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/jack + +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 + +#common obj +OBJS= jack.o + +SRCS := $(OBJS:.o=.c) + + +LIB=gm_jack$(DYN_LIB_SUFFIX) + +all: $(LIB) + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -L$(prefix)/$(libdir) -ljack + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/jack/jack.c b/modules/jack/jack.c new file mode 100644 index 0000000..cc73226 --- /dev/null +++ b/modules/jack/jack.c @@ -0,0 +1,501 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Pierre Souchay , Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2008-2019 + * All rights reserved + * + * Jack audio output module : output audio thru the jackd daemon + * + * 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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <strings.h> +#include <jack/types.h> +#include <jack/jack.h> +#include <jack/ringbuffer.h> +#include <gpac/modules/audio_out.h> + +#define MAX_JACK_CLIENT_NAME_SZ 128 +/* + * This structure defines the handle to a Jack driver + */ +typedef struct +{ + char jackClientName[MAX_JACK_CLIENT_NAME_SZ]; + jack_client_t *jack; + jack_port_t **jackPorts; + jack_nframes_t currentBlockSize; + u32 numChannels; + char *buffer; + u32 bufferSz; + u32 bytesPerSample; + Bool isActive; + Bool autoConnect; + jack_default_audio_sample_t **channels; + float volume; +} JackContext; + +static void +Jack_cleanup (JackContext * ctx) +{ + u32 channels; + if (ctx == NULL) + return; + + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[Jack] Jack_cleanup\n")); + if (ctx->jack != NULL && ctx->isActive) + { + jack_deactivate (ctx->jack); + } + if (ctx->buffer != NULL) + { + gf_free(ctx->buffer); + ctx->bufferSz = 0; + ctx->buffer = NULL; + } + if (ctx->jackPorts != NULL) + { + for (channels = 0; channels < ctx->numChannels; channels++) + { + if (ctx->jackPorts[channels] != NULL) + jack_port_unregister (ctx->jack, ctx->jackPorts[channels]); + ctx->jackPorts[channels] = NULL; + } + gf_free(ctx->jackPorts); + ctx->jackPorts = NULL; + } + if (ctx->jack != NULL) + { + jack_client_close (ctx->jack); + } + if (ctx->channels != NULL) + { + gf_free(ctx->channels); + ctx->channels = NULL; + } + ctx->numChannels = 0; + ctx->currentBlockSize = 0; + memset (ctx->jackClientName, 0, MAX_JACK_CLIENT_NAME_SZ); + ctx->jack = NULL; + ctx->isActive = 0; +} + +/** + * The callback called by the jack thread + */ +static int +process_callback (jack_nframes_t nframes, void *arg) +{ + unsigned int channel, i; + size_t toRead; + size_t bytesToRead; + GF_AudioOutput *dr = (GF_AudioOutput *) arg; + JackContext *ctx; + if (dr == NULL) + { + // Should not happen + return 1; + } + ctx = dr->opaque; + toRead = nframes * ctx->numChannels; + bytesToRead = toRead * ctx->bytesPerSample; + dr->FillBuffer (dr->audio_renderer, (void *) ctx->buffer, + bytesToRead); + + if (ctx->bytesPerSample == 2) + { + short *tmpBuffer; + tmpBuffer = (short *) ctx->buffer; + for (channel = 0; channel < nframes; channel += ctx->numChannels) + { + for (i = 0; i < ctx->numChannels; i++) + ctx->channels[i][channel] = + (float) (ctx->volume / 32768.0 * + (tmpBuffer[channel * ctx->numChannels + i])); + } + } + else + { + for (channel = 0; channel < nframes; channel += ctx->numChannels) + { + for (i = 0; i < ctx->numChannels; i++) + ctx->channels[i][channel] = + (float) (ctx->volume / 255.0 * + (ctx->buffer[channel * ctx->numChannels + i])); + } + } + return 0; +} + +/** + * Called when jackbuffer size change + */ +static int +onBufferSizeChanged (jack_nframes_t nframes, void *arg) +{ + GF_AudioOutput *dr = (GF_AudioOutput *) arg; + JackContext *ctx; + size_t realBuffSize; + u32 channel; + if (dr == NULL) + { + // Should not happen + return 1; + } + ctx = dr->opaque; + realBuffSize = nframes * ctx->numChannels * sizeof (short); + if (ctx->buffer != NULL && ctx->bufferSz == realBuffSize) + return 0; + if (ctx->channels != NULL) + gf_free(ctx->channels); + ctx->channels = NULL; + ctx->channels = gf_calloc (ctx->numChannels, sizeof (jack_default_audio_sample_t *)); + if (ctx->channels == NULL) + { + Jack_cleanup (ctx); + return 2; + } + for (channel = 0; channel < ctx->numChannels; channel++) + { + ctx->channels[channel] = + jack_port_get_buffer (ctx->jackPorts[channel], nframes); + if (ctx->channels[channel] == NULL) + { + Jack_cleanup (ctx); + return 3; + } + } + + if (ctx->buffer != NULL) + gf_free(ctx->buffer); + ctx->buffer = gf_calloc (realBuffSize, sizeof (char)); + if (ctx->buffer == NULL) + { + Jack_cleanup (ctx); + return 4; + } + ctx->bufferSz = realBuffSize; + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[Jack] onBufferSizeChanged : resized to %d.\n", realBuffSize)); + if (ctx->buffer == NULL) + { + ctx->bufferSz = 0; + Jack_cleanup (ctx); + return 5; + } + return 0; +} + +static GF_Err +Jack_Setup (GF_AudioOutput * dr, void *os_handle, u32 num_buffers, + u32 total_duration) +{ + JackContext *ctx = (JackContext *) dr->opaque; + jack_status_t status; + 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() ); + + ctx->autoConnect = GF_TRUE; + if (gf_opts_get_bool("Jack", "NoAutoConnect")) + ctx->autoConnect = GF_FALSE; + + if (gf_opts_get_bool("Jack", "NoStartServer")) + options |= JackNoStartServer; + + ctx->jack = jack_client_open (ctx->jackClientName, options, &status, NULL); + if (status & JackNameNotUnique) + { + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[Jack] Cannot open connection to jackd as %s since name was not unique.\n", + ctx->jackClientName)); + Jack_cleanup (ctx); + return GF_IO_ERR; + } + + if (ctx->jack == NULL) + { + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[Jack] Cannot open connection to jackd as %s.\n", + ctx->jackClientName)); + return GF_IO_ERR; + } + return GF_OK; +} + +static void +Jack_Shutdown (GF_AudioOutput * dr) +{ + JackContext *ctx = (JackContext *) dr->opaque; + Jack_cleanup (ctx); +} + +#define JACK_PORT_NAME_MAX_SZ 128 + +static GF_Err +Jack_Configure(GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, u32 *audioFormat, u64 channel_cfg) +{ + u32 channels; + u32 i; + char port_name[JACK_PORT_NAME_MAX_SZ]; + JackContext *ctx = (JackContext *) dr->opaque; + if (!ctx) + return GF_BAD_PARAM; + + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + ctx->bytesPerSample = 1; + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + case GF_AUDIO_FMT_S16: + ctx->bytesPerSample = 2; + break; + } + ctx->numChannels = *NbChannels; + *SampleRate = jack_get_sample_rate (ctx->jack); + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[Jack] Jack_ConfigureOutput channels=%d, srate=%d audio format %s\n", + *NbChannels, *SampleRate, gf_audio_fmt_name(*audioFormat) )); + if (ctx->jackPorts == NULL) + ctx->jackPorts = gf_calloc (ctx->numChannels, sizeof (jack_port_t *)); + if (ctx->jackPorts == NULL) + { + goto exit_cleanup; + } + if (!ctx->isActive) + { + for (channels = 0; channels < ctx->numChannels; channels++) + { + snprintf (port_name, JACK_PORT_NAME_MAX_SZ, "playback_%d", + channels + 1); + ctx->jackPorts[channels] = + jack_port_register (ctx->jack, port_name, JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (ctx->jackPorts[channels] == NULL) + goto exit_cleanup; + } +// onBufferSizeChanged (jack_get_buffer_size (ctx->jack), dr); +// if (!ctx->jack) return GF_IO_ERR; + + jack_set_buffer_size_callback (ctx->jack, onBufferSizeChanged, dr); + jack_set_process_callback (ctx->jack, process_callback, dr); + } + ctx->currentBlockSize = jack_get_buffer_size (ctx->jack); + if (!ctx->isActive) + { + jack_activate (ctx->jack); + if (ctx->autoConnect) + { + const char **matching_outputs = + jack_get_ports (ctx->jack, NULL, NULL, + JackPortIsInput | JackPortIsPhysical | + JackPortIsTerminal); + if (matching_outputs != NULL) + { + channels = 0; + i = 0; + while (matching_outputs[i] != NULL + && channels < ctx->numChannels) + { + if (!jack_connect (ctx->jack, + jack_port_name (ctx-> + jackPorts[channels++]), + matching_outputs[i])) + { + GF_LOG (GF_LOG_INFO, GF_LOG_MMIO, + ("[Jack] Jack_ConfigureOutput: Failed to connect port[%d] to %s.\n", + channels - 1, matching_outputs[i])); + } + i++; + } + } + jack_free (matching_outputs); + } + ctx->isActive = GF_TRUE; + } + return GF_OK; +exit_cleanup: + Jack_cleanup (ctx); + return GF_IO_ERR; +} + +static void +Jack_SetVolume (GF_AudioOutput * dr, u32 Volume) +{ + JackContext *ctx = (JackContext *) dr->opaque; + if (ctx == NULL) + { + return; + } + /* We support ajust the volume to more than 100, up to +6dB even + * if frontends don't support it, it may be useful */ + if (Volume > 400) + Volume = 400; + ctx->volume = (float) Volume / 100.0; + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[Jack] Jack_SetVolume: Volume set to %d%%.\n", Volume)); +} + +static void +Jack_SetPan (GF_AudioOutput * dr, u32 Pan) +{ + GF_LOG (GF_LOG_INFO, GF_LOG_MMIO, ("[Jack] Jack_SetPan: Not supported.\n")); + +} + +static void +Jack_SetPriority (GF_AudioOutput * dr, u32 Priority) +{ + /** + * Jack manages the priority itself, we don't need + * to interfere here... + */ +} + +static u32 +Jack_GetAudioDelay (GF_AudioOutput * dr) +{ + jack_nframes_t max = 0; + jack_nframes_t latency; + u32 channel; + JackContext *ctx = (JackContext *) dr->opaque; + if (ctx == NULL) + { + return 0; + } + jack_recompute_total_latencies (ctx->jack); + for (channel = 0; channel < ctx->numChannels; channel++) + { + latency = + jack_port_get_total_latency (ctx->jack, ctx->jackPorts[channel]); + if (latency > max) + max = latency; + } + channel = max * 1000 / jack_get_sample_rate (ctx->jack); + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[Jack] Jack_GetAudioDelay latency = %d ms.\n", channel)); + return channel; +} + +static GF_Err +Jack_QueryOutputSampleRate (GF_AudioOutput * dr, u32 * desired_sr, + u32 * NbChannels, u32 * nbBitsPerSample) +{ + JackContext *ctx = (JackContext *) dr->opaque; + if (!ctx) + return GF_IO_ERR; + *desired_sr = jack_get_sample_rate (ctx->jack); + *NbChannels = 2; + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[Jack] Jack output sample rate %d\n", *desired_sr)); + return GF_OK; +} + +void * +NewJackOutput () +{ + JackContext *ctx; + GF_AudioOutput *driv; + GF_SAFEALLOC (ctx, JackContext); + if (!ctx) + return NULL; + GF_SAFEALLOC (driv, GF_AudioOutput); + if (!driv) + { + gf_free(ctx); + return NULL; + } + driv->opaque = ctx; + driv->SelfThreaded = 1; + driv->Setup = Jack_Setup; + driv->Shutdown = Jack_Shutdown; + driv->Configure = Jack_Configure; + driv->GetAudioDelay = Jack_GetAudioDelay; + driv->SetVolume = Jack_SetVolume; + driv->SetPan = Jack_SetPan; + driv->SetPriority = Jack_SetPriority; + driv->QueryOutputSampleRate = Jack_QueryOutputSampleRate; + + ctx->jack = NULL; + ctx->numChannels = 0; + ctx->jackPorts = NULL; + ctx->currentBlockSize = 0; + ctx->numChannels = 0; + ctx->buffer = NULL; + ctx->bufferSz = 0; + ctx->bytesPerSample = 0; + ctx->isActive = GF_FALSE; + ctx->autoConnect = GF_FALSE; + ctx->volume = 1.0; + + GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, + "Jack Audio Output", "gpac distribution"); + return driv; +} + +void +DeleteJackOutput (void *ifce) +{ + GF_AudioOutput *dr = (GF_AudioOutput *) ifce; + JackContext *ctx = (JackContext *) dr->opaque; + Jack_cleanup (ctx); + gf_free(ctx); + dr->opaque = NULL; + gf_free(dr); +} + +/* + * ******************************************************************** + * interface + */ + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface (u32 InterfaceType) +{ + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) + { + return NewJackOutput (); + } + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface (GF_BaseInterface * ifce) +{ + if (ifce->InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) + DeleteJackOutput ((GF_AudioOutput *) ifce); +} + +GPAC_MODULE_STATIC_DECLARATION( jack ) diff --git a/modules/modules_export.cpp b/modules/modules_export.cpp new file mode 100644 index 0000000..6e25fe2 --- /dev/null +++ b/modules/modules_export.cpp @@ -0,0 +1,38 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC + * + * 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. + * + */ + +/*this file is only used with Win32&MSVC to export the module interface symbols from each module DLL*/ +#include <gpac/setup.h> + +#if defined(_WIN32_WCE) || defined(_WIN64) +#define EXPORT_SYMBOL(a) "/export:"#a +#else +#define EXPORT_SYMBOL(a) "/export:_"#a +#endif + +#pragma comment (linker, EXPORT_SYMBOL(QueryInterfaces) ) +#pragma comment (linker, EXPORT_SYMBOL(LoadInterface) ) +#pragma comment (linker, EXPORT_SYMBOL(ShutdownInterface) ) + diff --git a/modules/pulseaudio/Makefile b/modules/pulseaudio/Makefile new file mode 100644 index 0000000..a65a1d2 --- /dev/null +++ b/modules/pulseaudio/Makefile @@ -0,0 +1,42 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/pulseaudio + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= pulseaudio.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_pulseaudio$(DYN_LIB_SUFFIX) + +all: $(LIB) + +$(LIB): $(OBJS) + echo $(LDFLAGS) + $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -lpulse -lpulse-simple + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/pulseaudio/pulseaudio.c b/modules/pulseaudio/pulseaudio.c new file mode 100644 index 0000000..233efa3 --- /dev/null +++ b/modules/pulseaudio/pulseaudio.c @@ -0,0 +1,287 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Pierre Souchay , Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2008-2019 + * All rights reserved + * + * + * PulseAudio output module : output audio thru the PulseAudio daemon + * + * 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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <poll.h> +#include <pulse/pulseaudio.h> +#include <pulse/simple.h> +#include <gpac/modules/audio_out.h> + +typedef struct +{ + pa_simple *playback_handle; + pa_sample_spec sample_spec; + const char *output_name; + const char *output_description; + u32 errors; + u32 consecutive_zero_reads; +} PulseAudioContext; + +static void +free_pulseaudio_resources (GF_AudioOutput * dr) +{ + PulseAudioContext *ctx; + if (dr == NULL) + return; + ctx = (PulseAudioContext *) dr->opaque; + if (ctx == NULL) + return; + if (ctx->playback_handle != NULL) + { + pa_simple_free (ctx->playback_handle); + } + ctx->playback_handle = NULL; +} + +static GF_Err +PulseAudio_Setup (GF_AudioOutput * dr, void *os_handle, + u32 num_buffers, u32 total_duration) +{ + const char *opt; + PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; + if (ctx == NULL) + return GF_BAD_PARAM; + opt = gf_opts_get_key("PulseAudio", "Name"); + ctx->output_name = opt ? ctx->output_name : "GPAC"; + + opt = gf_opts_get_key("PulseAudio", "Description"); + ctx->output_description = opt ? opt : "GPAC Output"; + return GF_OK; +} + +static void +PulseAudio_Shutdown (GF_AudioOutput * dr) +{ + int pa_error = 0; + PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; + if (ctx == NULL) + return; + if (ctx->playback_handle) + { + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[PulseAudio] Closing PulseAudio output\n")); + pa_simple_drain (ctx->playback_handle, &pa_error); + if (pa_error) + { + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[PulseAudio] Error while closing PulseAudio output: %s\n", + pa_strerror (pa_error))); + + } + } +} + +static GF_Err +PulseAudio_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) +{ + int pa_error = 0; + PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; + if (ctx->playback_handle != NULL) + { + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, + ("[PulseAudio] PulseAudio output already configured !\n")); + /* Should not happen */ + pa_simple_flush (ctx->playback_handle, &pa_error); + pa_simple_free (ctx->playback_handle); + ctx->playback_handle = NULL; + } + + //only support for PCM 16 + *audioFormat = GF_AUDIO_FMT_S16; + + ctx->consecutive_zero_reads = 0; + ctx->sample_spec.format = PA_SAMPLE_S16NE; + ctx->sample_spec.channels = *NbChannels; + ctx->sample_spec.rate = *SampleRate; + ctx->playback_handle = pa_simple_new (NULL, + ctx->output_name, + PA_STREAM_PLAYBACK, + NULL, + ctx->output_description, + &(ctx->sample_spec), + NULL, NULL, &pa_error); + if (ctx->playback_handle == NULL || pa_error != 0) + { + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[PulseAudio] Error while allocating PulseAudio output: %s.\n", + pa_strerror (pa_error))); + return GF_IO_ERR; + } + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[PulseAudio] Initialized - sampling rate %d - %d channels\n", ctx->sample_spec.rate, ctx->sample_spec.channels)); + return GF_OK; +} + +#define BUFF_SIZE 8192 + +static void +PulseAudio_WriteAudio (GF_AudioOutput * dr) +{ + char data[BUFF_SIZE]; + int written = 0; + int pa_error = 0; + PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; + if (ctx == NULL || ctx->playback_handle == NULL) + { + if (ctx == NULL || ctx->errors == 0) + { + if (ctx != NULL) + ctx->errors++; + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[PulseAudio] unable to connect to a PulseAudio daemon!\n")) + } + return; + } + written = dr->FillBuffer (dr->audio_renderer, data, BUFF_SIZE / 4); + if (written <= 0) + { + ctx->consecutive_zero_reads++; + if (ctx->consecutive_zero_reads < 5) { + gf_sleep(5); + } else if (ctx->consecutive_zero_reads < 25) { + gf_sleep(10); + } else { + gf_sleep(33); + } + return; + } + ctx->consecutive_zero_reads = 0; + /*written = */pa_simple_write (ctx->playback_handle, data, written, &pa_error); + if (pa_error != 0) + { + if (ctx->errors < 1) + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[PulseAudio] Write failure: %s\n", pa_strerror (pa_error))); + ctx->errors++; + } + else + { + ctx->errors = 0; + } +} + +static u32 +PulseAudio_GetAudioDelay (GF_AudioOutput * dr) +{ + pa_usec_t delay = 0; + int pa_error = 0; + u32 ms_delay = 0; + PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; + if (ctx == NULL || ctx->playback_handle == NULL) + { + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[PulseAudio] missing connection to pulseaudio daemon!\n")) + return 0; + } + delay = pa_simple_get_latency (ctx->playback_handle, &pa_error); + if (pa_error) + { + GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, + ("[PulseAudio] Error while retrieving pulseaudio delay: %s.\n", + pa_strerror (pa_error))); + return 0; + } + ms_delay = (u32) (delay / 1000); + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[PulseAudio] Audio delay: %llu us.\n", + delay)); + return ms_delay; +} + + +void * +NewPulseAudioOutput () +{ + PulseAudioContext *ctx; + GF_AudioOutput *driv; + GF_SAFEALLOC (ctx, PulseAudioContext); + if (!ctx) + return NULL; + + GF_SAFEALLOC (driv, GF_AudioOutput); + if (!driv) + { + gf_free(ctx); + return NULL; + } + driv->opaque = ctx; + ctx->playback_handle = NULL; + ctx->errors = 0; + driv->SelfThreaded = 0; + driv->Setup = PulseAudio_Setup; + driv->Shutdown = PulseAudio_Shutdown; + driv->Configure = PulseAudio_Configure; + driv->GetAudioDelay = PulseAudio_GetAudioDelay; + driv->WriteAudio = PulseAudio_WriteAudio; + GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, "PulseAudio", "gpac distribution"); + return driv; +} + +void +DeletePulseAudioOutput (void *ifce) +{ + GF_AudioOutput *dr = (GF_AudioOutput *) ifce; + free_pulseaudio_resources (dr); + if (dr != NULL) { + if (dr->opaque) + gf_free(dr->opaque); + dr->opaque = NULL; + gf_free(dr); + } +} + + +/* + * ******************************************************************** + * interface + */ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces(u32 InterfaceType) +{ + static u32 si [] = { + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface (u32 InterfaceType) +{ + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) + return NewPulseAudioOutput (); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface (GF_BaseInterface * ifce) +{ + if (ifce->InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) + DeletePulseAudioOutput ((GF_AudioOutput *) ifce); +} + +GPAC_MODULE_STATIC_DECLARATION( pulseaudio ) diff --git a/modules/sdl_out/Makefile b/modules/sdl_out/Makefile new file mode 100644 index 0000000..ab04298 --- /dev/null +++ b/modules/sdl_out/Makefile @@ -0,0 +1,52 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/sdl_out + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(SDL_CFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +LINKFLAGS=-L../../bin/gcc -lgpac $(SDL_LIBS) + +ifeq ($(CONFIG_DARWIN),yes) +LINKFLAGS+=-framework OpenGL +endif + +#common obj +OBJS=sdl_out.o audio.o video.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_sdl_out$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +#LDFLAGS+=-export-symbols sdl_out.def +endif + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/sdl_out/audio.c b/modules/sdl_out/audio.c new file mode 100644 index 0000000..630416c --- /dev/null +++ b/modules/sdl_out/audio.c @@ -0,0 +1,280 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / SDL audio and video module + * + * 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 "sdl_out.h" + +#define SDLAUD() SDLAudCtx *ctx = (SDLAudCtx *)dr->opaque + +static void sdl_close_audio() { + SDL_CloseAudio(); +} + + +void sdl_fill_audio(void *udata, Uint8 *stream, int len) +{ + GF_AudioOutput *dr = (GF_AudioOutput *)udata; + SDLAUD(); + if (ctx->volume != SDL_MIX_MAXVOLUME) { + u32 written; + if (ctx->alloc_size < (u32) len) { + ctx->audioBuff = (Uint8*)gf_realloc( ctx->audioBuff, sizeof(Uint8) * len); + ctx->alloc_size = len; + } + memset(stream, 0, len); + written = dr->FillBuffer(dr->audio_renderer, ctx->audioBuff, (u32) len); + if (written) + SDL_MixAudio(stream, ctx->audioBuff, len, ctx->volume); + } else { + dr->FillBuffer(dr->audio_renderer, stream, (u32) len); + } +} + + +static GF_Err SDLAud_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration) +{ + u32 flags; + SDL_AudioSpec want_format, got_format; + SDLAUD(); + + /*init sdl*/ + if (!SDLOUT_InitSDL()) return GF_IO_ERR; + + flags = SDL_WasInit(SDL_INIT_AUDIO); + if (!(flags & SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO)<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Audio output initialization error\n")); + SDLOUT_CloseSDL(); + return GF_IO_ERR; + } + } + /*check we can open the device*/ + memset(&want_format, 0, sizeof(SDL_AudioSpec)); + want_format.freq = 44100; + want_format.format = AUDIO_S16SYS; + want_format.channels = 2; + want_format.samples = 1024; + want_format.callback = sdl_fill_audio; + want_format.userdata = dr; + if ( SDL_OpenAudio(&want_format, &got_format) < 0 ) { + sdl_close_audio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + SDLOUT_CloseSDL(); + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Audio output format not supported\n")); + return GF_IO_ERR; + } + sdl_close_audio(); + ctx->is_init = GF_TRUE; + ctx->num_buffers = num_buffers; + ctx->total_duration = total_duration; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Audio output setup\n")); + return GF_OK; +} + +static void SDLAud_Shutdown(GF_AudioOutput *dr) +{ + SDLAUD(); + sdl_close_audio(); + if (ctx->is_init) { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + SDLOUT_CloseSDL(); + ctx->is_init = GF_FALSE; + } +} + +void SDL_DeleteAudio(void *ifce) { + SDLAudCtx *ctx; + GF_AudioOutput * dr; + if (!ifce) + return; + dr = ( GF_AudioOutput *) ifce; + ctx = (SDLAudCtx *)dr->opaque; + if (!ctx) + return; + if (ctx->audioBuff) + gf_free(ctx->audioBuff); + ctx->audioBuff = NULL; + gf_free( ctx ); + dr->opaque = NULL; + gf_free( dr ); +} + +static GF_Err SDLAud_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) +{ + s32 nb_samples; + SDL_AudioSpec want_format, got_format; + SDLAUD(); + + sdl_close_audio(); + ctx->is_running = GF_FALSE; + + memset(&want_format, 0, sizeof(SDL_AudioSpec)); + want_format.freq = *SampleRate; + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_U8P: + want_format.format = AUDIO_U8; + break; + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S16P: + want_format.format = AUDIO_S16; + break; +#if SDL_VERSION_ATLEAST(2,0,0) + case GF_AUDIO_FMT_S32: + case GF_AUDIO_FMT_S32P: + want_format.format = AUDIO_S32; + break; + case GF_AUDIO_FMT_FLT: + case GF_AUDIO_FMT_FLTP: + want_format.format = AUDIO_F32; + break; +#endif + default: + want_format.format = AUDIO_S16; + break; + } + want_format.channels = *NbChannels; + want_format.callback = sdl_fill_audio; + want_format.userdata = dr; + + if (ctx->num_buffers && ctx->total_duration) { + nb_samples = want_format.freq * ctx->total_duration; + nb_samples /= (1000 * ctx->num_buffers); + if (nb_samples % 2) nb_samples++; + } else { + nb_samples = 1024; + } + + /*respect SDL need for power of 2*/ + want_format.samples = 1; + while (want_format.samples*2<nb_samples) want_format.samples *= 2; + + if ( SDL_OpenAudio(&want_format, &got_format) < 0 ) return GF_IO_ERR; + ctx->is_running = GF_TRUE; + ctx->delay_ms = (got_format.samples * 1000) / got_format.freq; + ctx->total_size = got_format.samples; + *SampleRate = got_format.freq; + *NbChannels = got_format.channels; + + switch (got_format.format) { + case AUDIO_S8: + case AUDIO_U8: + *audioFormat = GF_AUDIO_FMT_U8; + break; + case AUDIO_S16: + *audioFormat = GF_AUDIO_FMT_S16; + break; +#if SDL_VERSION_ATLEAST(2,0,0) + case AUDIO_S32: + *audioFormat = GF_AUDIO_FMT_S32; + break; + case AUDIO_F32: + *audioFormat = GF_AUDIO_FMT_FLT; + break; +#endif + default: + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Error, unhandled audio format %s, requesting PCM s16\n", got_format.format)); + break; + } + /*and play*/ + SDL_PauseAudio(0); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Audio output setup - SampleRate %d Nb Channels %d - %d ms delay\n", got_format.freq, got_format.channels, ctx->delay_ms)); + return GF_OK; +} + +static u32 SDLAud_GetAudioDelay(GF_AudioOutput *dr) +{ + SDLAUD(); + return ctx->delay_ms; +} + +static u32 SDLAud_GetTotalBufferTime(GF_AudioOutput *dr) +{ + SDLAUD(); + return ctx->delay_ms; +} + +static void SDLAud_SetVolume(GF_AudioOutput *dr, u32 Volume) +{ + SDLAUD(); + if (Volume == 100) + ctx->volume = SDL_MIX_MAXVOLUME; + else + ctx->volume = Volume * SDL_MIX_MAXVOLUME / 100; +} + +static void SDLAud_SetPan(GF_AudioOutput *dr, u32 pan) +{ + /*not supported by SDL*/ +} + +static void SDLAud_Play(GF_AudioOutput *dr, u32 PlayType) +{ + SDL_PauseAudio(PlayType ? 0 : 1); +} + +static void SDLAud_SetPriority(GF_AudioOutput *dr, u32 priority) +{ + /*not supported by SDL*/ +} + +static GF_Err SDLAud_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample) +{ + /*cannot query supported formats in SDL...*/ + return GF_OK; +} + +void *SDL_NewAudio() +{ + SDLAudCtx *ctx; + GF_AudioOutput *dr; + + + ctx = (SDLAudCtx*)gf_malloc(sizeof(SDLAudCtx)); + memset(ctx, 0, sizeof(SDLAudCtx)); + + dr = (GF_AudioOutput*)gf_malloc(sizeof(GF_AudioOutput)); + memset(dr, 0, sizeof(GF_AudioOutput)); + GF_REGISTER_MODULE_INTERFACE(dr, GF_AUDIO_OUTPUT_INTERFACE, "SDL Audio Output", "gpac distribution"); + + dr->opaque = ctx; + + dr->Setup = SDLAud_Setup; + dr->Shutdown = SDLAud_Shutdown; + dr->Configure = SDLAud_Configure; + dr->SetVolume = SDLAud_SetVolume; + dr->SetPan = SDLAud_SetPan; + dr->Play = SDLAud_Play; + dr->SetPriority = SDLAud_SetPriority; + dr->GetAudioDelay = SDLAud_GetAudioDelay; + dr->GetTotalBufferTime = SDLAud_GetTotalBufferTime; + + dr->QueryOutputSampleRate = SDLAud_QueryOutputSampleRate; + /*always threaded*/ + dr->SelfThreaded = GF_TRUE; + ctx->audioBuff = NULL; + ctx->volume = SDL_MIX_MAXVOLUME; + return dr; +} + diff --git a/modules/sdl_out/cursors.c b/modules/sdl_out/cursors.c new file mode 100644 index 0000000..819c0e6 --- /dev/null +++ b/modules/sdl_out/cursors.c @@ -0,0 +1,63 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / DirectX audio and video render module + * + * 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. + * + */ + + +#if 0 +static char hand_data[] = +{ + 0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,2,2,1,2,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,2,2,1,2,2,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,0,1,2,2,1,2,2,1,2,2,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,1,1,2,2,2,2,2,2,2,2,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; +#endif diff --git a/modules/sdl_out/sdl_out.c b/modules/sdl_out/sdl_out.c new file mode 100644 index 0000000..46885df --- /dev/null +++ b/modules/sdl_out/sdl_out.c @@ -0,0 +1,96 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / SDL audio and video module + * + * 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 "sdl_out.h" + +static Bool is_init = GF_FALSE; +static u32 num_users = 0; + +#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) +#if SDL_VERSION_ATLEAST(2,0,0) +#pragma comment(lib, "SDL2") +#else +#pragma comment(lib, "SDL") +#endif +#endif + +Bool SDLOUT_InitSDL() +{ + if (is_init) { + num_users++; + return GF_TRUE; + } + if (SDL_Init(0) < 0) return GF_FALSE; + is_init = GF_TRUE; + num_users++; + return GF_TRUE; +} + +void SDLOUT_CloseSDL() +{ + if (!is_init) return; + assert(num_users); + num_users--; + if (!num_users) SDL_Quit(); + return; +} + + +/*interface query*/ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + +/*interface create*/ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface*)SDL_NewVideo(); + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) return (GF_BaseInterface*)SDL_NewAudio(); + return NULL; +} + +/*interface destroy*/ +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + SDL_DeleteVideo(ifce); + break; + case GF_AUDIO_OUTPUT_INTERFACE: + SDL_DeleteAudio(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( sdl_out ) diff --git a/modules/sdl_out/sdl_out.h b/modules/sdl_out/sdl_out.h new file mode 100644 index 0000000..363c37c --- /dev/null +++ b/modules/sdl_out/sdl_out.h @@ -0,0 +1,128 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / SDL audio and video module + * + * 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_SDL_OUT_H_ +#define _GF_SDL_OUT_H_ + +/*driver interfaces*/ +#include <gpac/modules/audio_out.h> +#include <gpac/modules/video_out.h> +#include <gpac/thread.h> + +/*SDL*/ +#include <SDL.h> + +/*SDL init routines*/ +Bool SDLOUT_InitSDL(); +void SDLOUT_CloseSDL(); + +/*when not threaded on win32, locks happen randomly when setting the window's caption*/ +#ifdef WIN32 +//#define SDL_WINDOW_THREAD +#endif + +typedef enum { + SDL_STATE_STOPPED = 0, + SDL_STATE_RUNNING, + SDL_STATE_STOP_REQ, + SDL_STATE_WAIT_FOR_THREAD_TERMINATION +} GF_SDL_STATE; + +typedef struct +{ +#ifdef SDL_WINDOW_THREAD + GF_Thread *sdl_th; + GF_SDL_STATE sdl_th_state; +#endif + GF_Mutex *evt_mx; + Bool is_init, fullscreen; + /*fullscreen display size*/ + u32 fs_width, fs_height; + /*backbuffer size before entering fullscreen mode (used for restore)*/ + u32 store_width, store_height; + /*cursors*/ + SDL_Cursor *curs_def, *curs_hand, *curs_collide; + Bool use_systems_memory; + + + Bool disable_vsync; + +#if SDL_VERSION_ATLEAST(2,0,0) + char szCaption[100]; + Bool enable_defer_mode; + Bool needs_clear; + Bool needs_bb_flush; + Bool needs_bb_grab; + + SDL_GLContext gl_context; + SDL_Renderer *renderer; + SDL_Window *screen; + + SDL_Texture *tx_back_buffer; + u8 *back_buffer_pixels; + + SDL_Texture *pool_rgb, *pool_rgba, *pool_yuv; + +#else + SDL_Surface *screen; + SDL_Surface *back_buffer; + + SDL_Surface *pool_rgb, *pool_rgba; + SDL_Overlay *yuv_overlay; +#endif + + u32 width, height; + + SDL_Surface *offscreen_gl; + + Bool output_3d; + void *os_handle; + + Bool force_alpha, hidden; + + u32 last_mouse_move; + Bool cursor_on; + + Bool ctrl_down, alt_down, meta_down; +} SDLVidCtx; + +void SDL_DeleteVideo(void *ifce); +void *SDL_NewVideo(); + + +/* + SDL audio +*/ +typedef struct +{ + u32 num_buffers, total_duration, delay_ms, total_size, volume, alloc_size; + Bool is_init, is_running; + u8 * audioBuff; +} SDLAudCtx; + +void SDL_DeleteAudio(void *ifce); +void *SDL_NewAudio(); + +#endif diff --git a/modules/sdl_out/video.c b/modules/sdl_out/video.c new file mode 100644 index 0000000..1502d3e --- /dev/null +++ b/modules/sdl_out/video.c @@ -0,0 +1,2078 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre - Romain Bouqueau +* Copyright (c) Telecom ParisTech 2000-2022 +* All rights reserved +* +* This file is part of GPAC / SDL audio and video module +* +* 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 "sdl_out.h" +#include <gpac/user.h> + +#ifdef WIN32 +#include <windows.h> +#endif + +#ifdef GPAC_CONFIG_IOS +#include <OpenGLES/ES1/gl.h> +#include <OpenGLES/ES1/glext.h> +#endif + +#ifndef _WIN32_WCE +#include <locale.h> +#endif + +/*cursors data*/ +static char hand_data[] = +{ + 0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,2,2,1,2,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,1,2,2,1,2,2,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,0,1,2,2,1,2,2,1,2,2,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,1,1,2,2,2,2,2,2,2,2,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + + +static char collide_data[] = +{ + 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,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,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,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,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,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,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,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 +}; + + + + +#define SDLVID() SDLVidCtx *ctx = (SDLVidCtx *)dr->opaque + + + +#if SDL_VERSION_ATLEAST(2,0,0) +void SDLVid_SetCaption(SDL_Window* window) +{ + if (SDL_GetCurrentVideoDriver()) { + char szCap[1024]; + sprintf(szCap, "SDL Video Output (%s)", SDL_GetCurrentVideoDriver()); + SDL_SetWindowTitle(window, szCap); + } else { + SDL_SetWindowTitle(window, "SDL Video Output"); + } +} +#else + +#define SDLK_KP_0 SDLK_KP0 +#define SDLK_KP_1 SDLK_KP1 +#define SDLK_KP_2 SDLK_KP2 +#define SDLK_KP_3 SDLK_KP3 +#define SDLK_KP_4 SDLK_KP4 +#define SDLK_KP_5 SDLK_KP5 +#define SDLK_KP_6 SDLK_KP6 +#define SDLK_KP_7 SDLK_KP7 +#define SDLK_KP_8 SDLK_KP8 +#define SDLK_KP_9 SDLK_KP9 +#define SDLK_NUMLOCKCLEAR SDLK_NUMLOCK +#define SDLK_SCROLLLOCK SDLK_SCROLLOCK +#define SDLK_APPLICATION SDLK_COMPOSE +#define SDLK_PRINTSCREEN SDLK_PRINT +#define SDL_WINDOW_RESIZABLE SDL_RESIZABLE + +static u32 video_modes[] = +{ + 320, 200, + 320, 240, + 400, 300, + 600, 400, + 800, 600, + 1024, 768, + 1152, 864, + 1280, 1024 +}; +static const u32 nb_video_modes = 8; + +void SDLVid_SetCaption() +{ + char szName[100]; + if (SDL_VideoDriverName(szName, 100)) { + char szCap[1024]; + sprintf(szCap, "SDL Video Output (%s)", szName); + SDL_WM_SetCaption(szCap, NULL); + } else { + SDL_WM_SetCaption("SDL Video Output", NULL); + } +} +#endif + + + +SDL_Cursor *SDLVid_LoadCursor(char *maskdata) +{ + s32 ind, i, j; + u8 data[4*32]; + u8 mask[4*32]; + + ind = -1; + for (i=0; i<32; i++) { + for (j=0; j<32; j++) { + if (j%8) { + data[ind] <<= 1; + mask[ind] <<= 1; + } else { + ind++; + data[ind] = mask[ind] = 0; + } + switch (maskdata[j+32*i]) { + /*black*/ + case 1: + data[ind] |= 0x01; + /*white*/ + case 2: + mask[ind] |= 0x01; + break; + } + } + } + return SDL_CreateCursor(data, mask, 32, 32, 0, 0); +} + +typedef struct +{ + u32 sdl_key; + u32 gf_key; + u32 gf_flags; +} SDLKeyToGPAC; + +static SDLKeyToGPAC SDLKeys[] = +{ + {SDLK_BACKSPACE, GF_KEY_BACKSPACE, 0}, + {SDLK_TAB, GF_KEY_TAB, 0}, + {SDLK_CLEAR, GF_KEY_CLEAR, 0}, + {SDLK_PAUSE, GF_KEY_PAUSE, 0}, + {SDLK_ESCAPE, GF_KEY_ESCAPE, 0}, + {SDLK_SPACE, GF_KEY_SPACE, 0}, + {SDLK_KP_ENTER, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_RETURN, GF_KEY_ENTER, 0}, + {SDLK_KP_MULTIPLY, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_ASTERISK, GF_KEY_STAR, 0}, + {SDLK_KP_PLUS, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_PLUS, GF_KEY_PLUS, 0}, + {SDLK_KP_MINUS, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_MINUS, GF_KEY_HYPHEN, 0}, + {SDLK_KP_DIVIDE, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_SLASH, GF_KEY_SLASH, 0}, + {SDLK_KP_0, GF_KEY_0, GF_KEY_EXT_NUMPAD}, + {SDLK_0, GF_KEY_0, 0}, + {SDLK_KP_1, GF_KEY_1, GF_KEY_EXT_NUMPAD}, + {SDLK_1, GF_KEY_1, 0}, + {SDLK_KP_2, GF_KEY_2, GF_KEY_EXT_NUMPAD}, + {SDLK_2, GF_KEY_2, 0}, + {SDLK_KP_3, GF_KEY_3, GF_KEY_EXT_NUMPAD}, + {SDLK_3, GF_KEY_3, 0}, + {SDLK_KP_4, GF_KEY_4, GF_KEY_EXT_NUMPAD}, + {SDLK_4, GF_KEY_4, 0}, + {SDLK_KP_5, GF_KEY_5, GF_KEY_EXT_NUMPAD}, + {SDLK_5, GF_KEY_5, 0}, + {SDLK_KP_6, GF_KEY_6, GF_KEY_EXT_NUMPAD}, + {SDLK_6, GF_KEY_6, 0}, + {SDLK_KP_7, GF_KEY_7, GF_KEY_EXT_NUMPAD}, + {SDLK_7, GF_KEY_7, 0}, + {SDLK_KP_8, GF_KEY_8, GF_KEY_EXT_NUMPAD}, + {SDLK_8, GF_KEY_8, 0}, + {SDLK_KP_9, GF_KEY_9, GF_KEY_EXT_NUMPAD}, + {SDLK_9, GF_KEY_9, 0}, + {SDLK_KP_PERIOD, GF_KEY_FULLSTOP, GF_KEY_EXT_NUMPAD}, + {SDLK_PERIOD, GF_KEY_FULLSTOP, 0}, + {SDLK_KP_EQUALS, GF_KEY_EQUALS, GF_KEY_EXT_NUMPAD}, + {SDLK_EQUALS, GF_KEY_EQUALS, 0}, + {SDLK_EXCLAIM, GF_KEY_EXCLAMATION, 0}, + {SDLK_QUOTEDBL, GF_KEY_QUOTATION, 0}, + {SDLK_HASH, GF_KEY_NUMBER, 0}, + {SDLK_DOLLAR, GF_KEY_DOLLAR, 0}, + {SDLK_AMPERSAND, GF_KEY_AMPERSAND, 0}, + {SDLK_QUOTE, GF_KEY_APOSTROPHE, 0}, + {SDLK_LEFTPAREN, GF_KEY_LEFTPARENTHESIS, 0}, + {SDLK_RIGHTPAREN, GF_KEY_RIGHTPARENTHESIS, 0}, + {SDLK_COMMA, GF_KEY_COMMA, 0}, + {SDLK_COLON, GF_KEY_COLON, 0}, + {SDLK_SEMICOLON, GF_KEY_SEMICOLON, 0}, + {SDLK_LESS, GF_KEY_LESSTHAN, 0}, + {SDLK_GREATER, GF_KEY_GREATERTHAN, 0}, + {SDLK_QUESTION, GF_KEY_QUESTION, 0}, + {SDLK_AT, GF_KEY_AT, 0}, + {SDLK_LEFTBRACKET, GF_KEY_LEFTSQUAREBRACKET, 0}, + {SDLK_RIGHTBRACKET, GF_KEY_RIGHTSQUAREBRACKET, 0}, + {SDLK_BACKSLASH, GF_KEY_BACKSLASH, 0}, + {SDLK_UNDERSCORE, GF_KEY_UNDERSCORE, 0}, + {SDLK_BACKQUOTE, GF_KEY_GRAVEACCENT, 0}, + {SDLK_DELETE, GF_KEY_DEL, 0}, + {SDLK_UNDO, GF_KEY_UNDO, 0}, + {SDLK_UP, GF_KEY_UP, 0}, + {SDLK_DOWN, GF_KEY_DOWN, 0}, + {SDLK_RIGHT, GF_KEY_RIGHT, 0}, + {SDLK_LEFT, GF_KEY_LEFT, 0}, + {SDLK_INSERT, GF_KEY_INSERT, 0}, + {SDLK_HOME, GF_KEY_HOME, 0}, + {SDLK_END, GF_KEY_END, 0}, + {SDLK_PAGEUP, GF_KEY_PAGEUP, 0}, + {SDLK_PAGEDOWN, GF_KEY_PAGEDOWN, 0}, + {SDLK_F1, GF_KEY_F1, 0}, + {SDLK_F2, GF_KEY_F2, 0}, + {SDLK_F3, GF_KEY_F3, 0}, + {SDLK_F4, GF_KEY_F4, 0}, + {SDLK_F5, GF_KEY_F5, 0}, + {SDLK_F6, GF_KEY_F6, 0}, + {SDLK_F7, GF_KEY_F7, 0}, + {SDLK_F8, GF_KEY_F8, 0}, + {SDLK_F9, GF_KEY_F9, 0}, + {SDLK_F10, GF_KEY_F10, 0}, + {SDLK_F11, GF_KEY_F11, 0}, + {SDLK_F12, GF_KEY_F12, 0}, + {SDLK_F13, GF_KEY_F13, 0}, + {SDLK_F14, GF_KEY_F14, 0}, + {SDLK_F15, GF_KEY_F15, 0}, + {SDLK_NUMLOCKCLEAR, GF_KEY_NUMLOCK, 0}, + {SDLK_CAPSLOCK, GF_KEY_CAPSLOCK, 0}, + {SDLK_SCROLLLOCK, GF_KEY_SCROLL, 0}, + {SDLK_RSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_RIGHT}, + {SDLK_LSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_LEFT}, + {SDLK_LCTRL, GF_KEY_CONTROL, GF_KEY_EXT_LEFT}, + {SDLK_RCTRL, GF_KEY_CONTROL, GF_KEY_EXT_RIGHT}, + {SDLK_LALT, GF_KEY_ALT, GF_KEY_EXT_LEFT}, + {SDLK_RALT, GF_KEY_ALT, GF_KEY_EXT_RIGHT}, +#if (SDL_MAJOR_VERSION<=1) && (SDL_MINOR_VERSION<3) + {SDLK_LMETA, GF_KEY_META, GF_KEY_EXT_LEFT}, + {SDLK_LSUPER, GF_KEY_META, GF_KEY_EXT_LEFT}, +#else + {SDLK_LGUI, GF_KEY_META, GF_KEY_EXT_LEFT}, +#endif + +#if (SDL_MAJOR_VERSION<=1) && (SDL_MINOR_VERSION<3) + {SDLK_RMETA, GF_KEY_META, GF_KEY_EXT_RIGHT}, + {SDLK_RSUPER, GF_KEY_META, GF_KEY_EXT_RIGHT}, +#else + {SDLK_RGUI, GF_KEY_META, GF_KEY_EXT_RIGHT}, +#endif + + {SDLK_MODE, GF_KEY_MODECHANGE, 0}, + {SDLK_APPLICATION, GF_KEY_COMPOSE, 0}, + {SDLK_HELP, GF_KEY_HELP, 0}, + {SDLK_PRINTSCREEN, GF_KEY_PRINTSCREEN, 0} +}; + +static u32 num_sdl_keys = sizeof(SDLKeys) / sizeof(SDLKeyToGPAC); + +static void sdl_translate_key(u32 SDLkey, GF_EventKey *evt) +{ + u32 i; + evt->flags = 0; + evt->hw_code = SDLkey; + for (i=0; i<num_sdl_keys; i++) { + if (SDLKeys[i].sdl_key == SDLkey) { + evt->key_code = SDLKeys[i].gf_key; + evt->flags = SDLKeys[i].gf_flags; + return; + } + } + if ((SDLkey>=0x30) && (SDLkey<=0x39)) + evt->key_code = GF_KEY_0 + SDLkey-0x30; + else if ((SDLkey>=0x41) && (SDLkey<=0x5A)) + evt->key_code = GF_KEY_A + SDLkey-0x41; + else if ((SDLkey>=0x61) && (SDLkey<=0x7A)) + evt->key_code = GF_KEY_A + SDLkey-0x61; + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[SDL] Unrecognized key %X\n", SDLkey)); + evt->key_code = GF_KEY_UNIDENTIFIED; + } +} + +#if 0 +void SDLVid_SetHack(void *os_handle, Bool set_on) +{ + char buf[50]; + if (set_on && os_handle) { + sprintf(buf, "SDL_WINDOWID=%u", (u32) os_handle); + } else { + strcpy(buf, "SDL_WINDOWID="); + } +#ifdef WIN32 + putenv(buf); +#else + if (set_on) unsetenv("SDL_WINDOWID="); + else putenv(buf); +#endif +} +#endif + +static void SDLVid_DestroyObjects(SDLVidCtx *ctx) +{ +#if SDL_VERSION_ATLEAST(2,0,0) + + if (ctx->pool_rgb) SDL_DestroyTexture(ctx->pool_rgb); + ctx->pool_rgb = NULL; + if (ctx->pool_rgba) SDL_DestroyTexture(ctx->pool_rgba); + ctx->pool_rgba = NULL; + if (ctx->pool_yuv) SDL_DestroyTexture(ctx->pool_yuv); + ctx->pool_yuv = NULL; + + if (ctx->tx_back_buffer) SDL_DestroyTexture(ctx->tx_back_buffer); + ctx->tx_back_buffer = NULL; + if (ctx->back_buffer_pixels) gf_free(ctx->back_buffer_pixels); + ctx->back_buffer_pixels = NULL; + +#else + if (ctx->back_buffer) SDL_FreeSurface(ctx->back_buffer); + ctx->back_buffer = NULL; + if (ctx->pool_rgb) SDL_FreeSurface(ctx->pool_rgb); + ctx->pool_rgb = NULL; + if (ctx->pool_rgba) SDL_FreeSurface(ctx->pool_rgba); + ctx->pool_rgba = NULL; + SDL_FreeYUVOverlay(ctx->yuv_overlay); + ctx->yuv_overlay=NULL; +#endif +} + +#if SDL_VERSION_ATLEAST(2,0,0) +#ifdef GPAC_CONFIG_IOS +#define SDL_WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE +#define SDL_FULLSCREEN_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE +#define SDL_GL_WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE +#define SDL_GL_FULLSCREEN_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE +#else +#define SDL_WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE +#define SDL_FULLSCREEN_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN +#define SDL_GL_WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE +#define SDL_GL_FULLSCREEN_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN +#endif +#else +#ifdef GPAC_CONFIG_IOS +#define SDL_WINDOW_FLAGS SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE | SDL_NOFRAME +#define SDL_FULLSCREEN_FLAGS SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_FULLSCREEN | SDL_NOFRAME +#define SDL_GL_WINDOW_FLAGS SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_RESIZABLE | SDL_NOFRAME +#define SDL_GL_FULLSCREEN_FLAGS SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_FULLSCREEN | SDL_NOFRAME +#else +#define SDL_WINDOW_FLAGS SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE +#define SDL_FULLSCREEN_FLAGS SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_FULLSCREEN +#define SDL_GL_WINDOW_FLAGS SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_RESIZABLE +#define SDL_GL_FULLSCREEN_FLAGS SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_FULLSCREEN +#endif +#endif + +#if SDL_VERSION_ATLEAST(2,0,0) && !defined(GPAC_CONFIG_IOS) +#include <gpac/media_tools.h> +void SDLVid_SetIcon(SDLVidCtx *ctx) +{ + u8 *buffer, *dec_buf; + u32 size, w, h, pf, Bpp, dst_size=0; + const char cfg[GF_MAX_PATH]; + if (!gf_opts_default_shared_directory((char *) cfg)) + return; + + strcat((char *) cfg, "/res/gpac.png"); + if (gf_file_load_data(cfg, &buffer, &size) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDLOut] failed to load icon file %s\n", cfg )); + return; + } + gf_img_png_dec(buffer, size, &w, &h, &pf, NULL, &dst_size); + Bpp = gf_pixel_get_bytes_per_pixel(pf); + dec_buf = gf_malloc(sizeof(char)*dst_size); + gf_img_png_dec(buffer, size, &w, &h, &pf, dec_buf, &dst_size); + //RGBA only + SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(dec_buf, w, h, Bpp*8, w*Bpp, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + if (!surface) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDLOut] failed to create surface from icon: %s\n", SDL_GetError() )); + } else { + SDL_SetWindowIcon(ctx->screen, surface); + SDL_FreeSurface(surface); + } + gf_free(buffer); + gf_free(dec_buf); +} +#endif + + +GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) +{ + Bool hw_reset; + SDLVID(); + GF_Event evt; + + /*lock X mutex to make sure the event queue is not being processed*/ + gf_mx_p(ctx->evt_mx); + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Resizing window %dx%d\n", width, height)); + + if (ctx->output_3d) { + u32 flags, nb_bits; + const char *opt; + + if (ctx->screen && (ctx->width==width) && (ctx->height==height) ) { + gf_mx_v(ctx->evt_mx); + return GF_OK; + } + +#if SDL_VERSION_ATLEAST(2,0,0) + flags = SDL_GL_WINDOW_FLAGS; + if (ctx->os_handle) flags &= ~SDL_WINDOW_RESIZABLE; + if (ctx->fullscreen) flags |= SDL_FULLSCREEN_FLAGS; +#else + flags = SDL_GL_WINDOW_FLAGS; + if (ctx->os_handle) flags &= ~SDL_RESIZABLE; + if (ctx->fullscreen) flags |= SDL_FULLSCREEN_FLAGS; + if (!ctx->screen) ctx->screen = SDL_SetVideoMode(width, height, 0, flags); +#endif + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + opt = gf_opts_get_key("core", "gl-bits-depth"); + nb_bits = opt ? atoi(opt) : 16; + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, nb_bits); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); + opt = gf_opts_get_key("core", "gl-bits-comp"); + nb_bits = opt ? atoi(opt) : 8; + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, nb_bits); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, nb_bits); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, nb_bits); + + assert(width); + assert(height); + +#if SDL_VERSION_ATLEAST(2,0,0) + if (ctx->hidden) + flags |= SDL_WINDOW_HIDDEN; + + hw_reset = GF_FALSE; + +#ifdef GPAC_USE_GLES2 + /* Set the correct attributes for MASK and MAJOR version */ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); +#endif + + if (!ctx->screen) { + if (!(ctx->screen = SDL_CreateWindow("", 0, 0, width, height, flags))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot create window: %s\n", SDL_GetError())); + gf_mx_v(ctx->evt_mx); + return GF_IO_ERR; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Window created\n")); + +#if SDL_VERSION_ATLEAST(2,0,0) && !defined(GPAC_CONFIG_IOS) + SDLVid_SetIcon(ctx); +#endif + + /*creating a window, at least on OSX, changes the locale and screws up float parsing !! + force setting the local back and pray that it will be changed before any other atof/strtod is called + + !! it also looks like the locale is not enforced synchronously on OSX: adding + assert(strtod(0.5)) in gf_malloc proto would lead to random crashes + */ +#ifndef _WIN32_WCE + setlocale( LC_NUMERIC, "C" ); +#endif + } + + if ( !ctx->gl_context ) { + //in case we created a headless context before loading vout, create a shared context + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + if (!(ctx->gl_context = SDL_GL_CreateContext(ctx->screen))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot initialize gl context: %s\n", SDL_GetError())); + gf_mx_v(ctx->evt_mx); + return GF_IO_ERR; + } + if (SDL_GL_MakeCurrent(ctx->screen, ctx->gl_context)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot make context current: %s\n", SDL_GetError())); + gf_mx_v(ctx->evt_mx); + return GF_IO_ERR; + } + hw_reset = GF_TRUE; + } + + if (!ctx->disable_vsync) + ctx->disable_vsync = gf_opts_get_bool("core", "disable-vsync"); + + if (ctx->disable_vsync) { +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) +#else + SDL_GL_SetSwapInterval(0); +#endif + } + + SDL_SetWindowSize(ctx->screen, width, height); + +#else + hw_reset = GF_TRUE; + ctx->screen = SDL_SetVideoMode(width, height, 0, flags); + if (!ctx->screen) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot create window: %s\n", SDL_GetError())); + gf_mx_v(ctx->evt_mx); + return GF_IO_ERR; + } +#endif + ctx->width = width; + ctx->height = height; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.hw_reset = hw_reset; + dr->on_event(dr->evt_cbk_hdl, &evt); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] 3D Setup done\n")); + } else { + u32 flags; + +#ifdef GPAC_CONFIG_IOS + flags = SDL_FULLSCREEN_FLAGS; + //SDL readme says it would make us faster + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); +#else + flags = SDL_WINDOW_FLAGS | SDL_WINDOW_RESIZABLE; + if (ctx->os_handle) flags &= ~SDL_WINDOW_RESIZABLE; +#endif + +#if SDL_VERSION_ATLEAST(2,0,0) + +#ifdef GPAC_CONFIG_IOS + //still some issues with render to tager and landscape orientation, we need to reset everything ... + if (ctx->enable_defer_mode) { + if (ctx->renderer) SDL_DestroyRenderer(ctx->renderer); + ctx->renderer=NULL; + if (ctx->screen) SDL_DestroyWindow(ctx->screen); + ctx->screen=NULL; + } +#endif + +#if SDL_VERSION_ATLEAST(2,0,0) + if (ctx->hidden) + flags |= SDL_WINDOW_HIDDEN; +#endif + + if (!ctx->screen) { + ctx->screen = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + + if (!ctx->screen) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot create window: %s\n", SDL_GetError())); + gf_mx_v(ctx->evt_mx); + return GF_IO_ERR; + } + +#if SDL_VERSION_ATLEAST(2,0,0) && !defined(GPAC_CONFIG_IOS) + SDLVid_SetIcon(ctx); +#endif + + /*see above note*/ +#ifndef _WIN32_WCE + setlocale( LC_NUMERIC, "C" ); +#endif + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Window created\n")); + SDL_RaiseWindow(ctx->screen); + } + if ( !ctx->renderer ) { + u32 flags = SDL_RENDERER_ACCELERATED; + if (! gf_opts_get_bool("core", "disable-vsync")) { + flags |= SDL_RENDERER_PRESENTVSYNC; + } + + + if (!(ctx->renderer = SDL_CreateRenderer(ctx->screen, -1, flags))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot create renderer: %s\n", SDL_GetError())); + gf_mx_v(ctx->evt_mx); + return GF_IO_ERR; + } + } +#ifndef GPAC_CONFIG_IOS + SDL_SetWindowSize(ctx->screen, width, height); +#endif + SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); + SDL_RenderClear(ctx->renderer); + +#else + ctx->screen = SDL_SetVideoMode(width, height, 0, flags); +#endif + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] 2D Setup done\n")); + } + gf_mx_v(ctx->evt_mx); + return ctx->screen ? GF_OK : GF_IO_ERR; +} + +static void SDLVid_SetCursor(GF_VideoOutput *dr, u32 cursor_type) +{ +#ifndef GPAC_CONFIG_IOS + SDLVID(); + switch (cursor_type) { + case GF_CURSOR_ANCHOR: + case GF_CURSOR_TOUCH: + case GF_CURSOR_ROTATE: + case GF_CURSOR_PROXIMITY: + case GF_CURSOR_PLANE: + SDL_SetCursor(ctx->curs_hand); + break; + case GF_CURSOR_COLLIDE: + SDL_SetCursor(ctx->curs_collide); + break; + default: + SDL_SetCursor(ctx->curs_def); + break; + } +#endif +} + +static Bool SDLVid_InitializeWindow(SDLVidCtx *ctx, GF_VideoOutput *dr) +{ + u32 flags; +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_DisplayMode vinf; +#else +#if SDL_VERSION_ATLEAST(1, 2, 10) + const SDL_VideoInfo *vinf; +#endif +#endif + +#ifdef WIN32 + putenv("directx"); +#endif + + flags = SDL_WasInit(SDL_INIT_VIDEO); + if (!(flags & SDL_INIT_VIDEO)) { + if (SDL_InitSubSystem(SDL_INIT_VIDEO)) { + return GF_FALSE; + } + } + + ctx->curs_def = SDL_GetCursor(); + ctx->curs_hand = SDLVid_LoadCursor(hand_data); + ctx->curs_collide = SDLVid_LoadCursor(collide_data); +#if SDL_VERSION_ATLEAST(2,0,0) + +#else + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); +#endif + + ctx->last_mouse_move = SDL_GetTicks(); + ctx->cursor_on = GF_TRUE; + + /*save display resolution - SDL seems to get the screen resolution if asked for video info before + changing the video mode - to check on other platforms*/ +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_GetDesktopDisplayMode(0,&vinf); + dr->max_screen_width = vinf.w; + dr->max_screen_height = vinf.h; + dr->max_screen_bpp = 8; +#else + +#if SDL_VERSION_ATLEAST(1, 2, 10) + vinf = SDL_GetVideoInfo(); + if (vinf) { + dr->max_screen_width = vinf->current_w; + dr->max_screen_height = vinf->current_h; + } + dr->max_screen_bpp = 8; +#else + { + SDL_Rect** modes; + modes = SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_HWSURFACE); + assert( (modes != (SDL_Rect**)0)); + if ( modes == (SDL_Rect**)-1 ) { + fprintf(stderr, "SDL : DONT KNOW WHICH MODE TO USE, using 640x480\n"); + dr->max_screen_width = 640; + dr->max_screen_height = 480; + } else { + int i; + dr->max_screen_width = 0; + for (i=0; modes[i]; ++i) { + int w = modes[i]->w; + if (w > dr->max_screen_width) { + dr->max_screen_width = w; + dr->max_screen_height = modes[i]->h; + } + } + } + } + dr->max_screen_bpp = 8; +#endif /* versions prior to 1.2.10 do not have the size of screen */ +#endif + + if (!ctx->os_handle) +#if SDL_VERSION_ATLEAST(2,0,0) + SDLVid_SetCaption(ctx->screen); +#else + SDLVid_SetCaption(); +#endif + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Video output initialized - screen resolution %d %d\n", dr->max_screen_width, dr->max_screen_height)); + + return GF_TRUE; +} + +static void SDLVid_ResetWindow(SDLVidCtx *ctx) +{ + SDLVid_DestroyObjects(ctx); +#if SDL_VERSION_ATLEAST(2,0,0) + if ( ctx->gl_context ) { + SDL_GL_DeleteContext(ctx->gl_context); + ctx->gl_context = NULL; + } + if ( ctx->renderer ) { + SDL_DestroyRenderer(ctx->renderer); + ctx->renderer = NULL; + } + + /*iOS SDL2 has a nasty bug that breaks switching between 2D and GL context if we don't re-init the video subsystem*/ +#ifdef GPAC_CONFIG_IOS + if ( ctx->screen ) { + SDL_DestroyWindow(ctx->screen); + ctx->screen=NULL; + } + SDL_QuitSubSystem(SDL_INIT_VIDEO); + SDL_InitSubSystem(SDL_INIT_VIDEO); +#endif + +#endif +} + +static void SDLVid_ShutdownWindow(SDLVidCtx *ctx) +{ + SDLVid_DestroyObjects(ctx); + SDLVid_ResetWindow(ctx); + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + +#if defined SDL_TEXTINPUTEVENT_TEXT_SIZE /*&& !defined GPAC_CONFIG_IOS*/ +#include <gpac/utf.h> +#endif + + +Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) +{ + SDL_Event sdl_evt; + GF_Event gpac_evt; + +#ifdef GPAC_CONFIG_IOS + while (SDL_WaitEventTimeout(&sdl_evt, 0)) { +#else + while (SDL_PollEvent(&sdl_evt)) { +#endif + + switch (sdl_evt.type) { +#if SDL_VERSION_ATLEAST(2,0,0) + case SDL_WINDOWEVENT: + switch (sdl_evt.window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + gpac_evt.type = GF_EVENT_SIZE; + gpac_evt.size.width = sdl_evt.window.data1; + gpac_evt.size.height = sdl_evt.window.data2; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_SHOWN: + gpac_evt.type = GF_EVENT_REFRESH; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_WINDOWEVENT_MOVED: + gpac_evt.type = GF_EVENT_MOVE; + gpac_evt.move.x = sdl_evt.window.data1; + gpac_evt.move.y = sdl_evt.window.data2; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_WINDOWEVENT_CLOSE: + memset(&gpac_evt, 0, sizeof(GF_Event)); + gpac_evt.type = GF_EVENT_QUIT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + return GF_FALSE; + case SDL_WINDOWEVENT_MINIMIZED: + memset(&gpac_evt, 0, sizeof(GF_Event)); + gpac_evt.type = GF_EVENT_SHOWHIDE; + gpac_evt.show.show_type = 0; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + return GF_FALSE; + } + break; +#else + case SDL_VIDEORESIZE: + gpac_evt.type = GF_EVENT_SIZE; + gpac_evt.size.width = sdl_evt.resize.w; + gpac_evt.size.height = sdl_evt.resize.h; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_VIDEOEXPOSE: + gpac_evt.type = GF_EVENT_REFRESH; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; +#endif + + case SDL_QUIT: + memset(&gpac_evt, 0, sizeof(GF_Event)); + gpac_evt.type = GF_EVENT_QUIT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + return GF_FALSE; + +#ifdef SDL_TEXTINPUTEVENT_TEXT_SIZE + /*keyboard*/ + case SDL_TEXTINPUT: /* Since SDL 1.3, text-input is handled in a specific event */ + { + u32 len = (u32) strlen( sdl_evt.text.text); + u32 ucs4_len; + assert( len < 5 ); + ucs4_len = utf8_to_ucs4 (&(gpac_evt.character.unicode_char), len, (unsigned char*)(sdl_evt.text.text)); + if (ucs4_len) { + gpac_evt.type = GF_EVENT_TEXTINPUT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + } + break; + } +#endif /* SDL_TEXTINPUTEVENT_TEXT_SIZE */ + case SDL_KEYDOWN: + case SDL_KEYUP: + sdl_translate_key(sdl_evt.key.keysym.sym, &gpac_evt.key); + gpac_evt.type = (sdl_evt.key.type==SDL_KEYDOWN) ? GF_EVENT_KEYDOWN : GF_EVENT_KEYUP; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + if (gpac_evt.key.key_code==GF_KEY_CONTROL) ctx->ctrl_down = (sdl_evt.key.type==SDL_KEYDOWN) ? GF_TRUE : GF_FALSE; + else if (gpac_evt.key.key_code==GF_KEY_ALT) ctx->alt_down = (sdl_evt.key.type==SDL_KEYDOWN) ? GF_TRUE : GF_FALSE; + else if (gpac_evt.key.key_code==GF_KEY_META) ctx->meta_down = (sdl_evt.key.type==SDL_KEYDOWN) ? GF_TRUE : GF_FALSE; + +#ifdef SDL_TEXTINPUTEVENT_TEXT_SIZE + if (sdl_evt.type==SDL_KEYDOWN) { + if ((gpac_evt.key.key_code==GF_KEY_ENTER) || (gpac_evt.key.key_code==GF_KEY_BACKSPACE)) { + gpac_evt.type = GF_EVENT_TEXTINPUT; + gpac_evt.character.unicode_char = (gpac_evt.key.key_code==GF_KEY_ENTER) ? '\r' : '\b'; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + } + } +#endif + + +#if SDL_VERSION_ATLEAST(2,0,0) + + if ((gpac_evt.type==GF_EVENT_KEYUP) && (gpac_evt.key.key_code==GF_KEY_V) +#if defined(__DARWIN__) || defined(__APPLE__) + && ctx->meta_down +#else + && ctx->ctrl_down +#endif + ) { + gpac_evt.type = GF_EVENT_PASTE_TEXT; + gpac_evt.clipboard.text = (char *) SDL_GetClipboardText(); + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + SDL_free(gpac_evt.clipboard.text); + } + else if ((gpac_evt.type==GF_EVENT_KEYUP) && (gpac_evt.key.key_code==GF_KEY_C) +#if defined(__DARWIN__) || defined(__APPLE__) + && ctx->meta_down +#else + && ctx->ctrl_down +#endif + ) { + gpac_evt.type = GF_EVENT_COPY_TEXT; + if (dr->on_event(dr->evt_cbk_hdl, &gpac_evt) && gpac_evt.clipboard.text) { + SDL_SetClipboardText(gpac_evt.clipboard.text ); + gf_free(gpac_evt.clipboard.text); + } + } +#endif + +#ifndef SDL_TEXTINPUTEVENT_TEXT_SIZE + if ((sdl_evt.key.type==SDL_KEYDOWN) + && sdl_evt.key.keysym.unicode + && ((sdl_evt.key.keysym.unicode=='\r') || (sdl_evt.key.keysym.unicode=='\n') || (sdl_evt.key.keysym.unicode=='\b') || (sdl_evt.key.keysym.unicode=='\t') ) + ) { + gpac_evt.character.unicode_char = sdl_evt.key.keysym.unicode; + gpac_evt.type = GF_EVENT_TEXTINPUT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + } +#else +#endif + break; + + /*mouse*/ + case SDL_MOUSEMOTION: + ctx->last_mouse_move = SDL_GetTicks(); + gpac_evt.type = GF_EVENT_MOUSEMOVE; + gpac_evt.mouse.x = sdl_evt.motion.x; + gpac_evt.mouse.y = sdl_evt.motion.y; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + ctx->last_mouse_move = SDL_GetTicks(); + gpac_evt.mouse.x = sdl_evt.motion.x; + gpac_evt.mouse.y = sdl_evt.motion.y; + gpac_evt.type = (sdl_evt.type==SDL_MOUSEBUTTONUP) ? GF_EVENT_MOUSEUP : GF_EVENT_MOUSEDOWN; + switch (sdl_evt.button.button) { + case SDL_BUTTON_LEFT: + gpac_evt.mouse.button = GF_MOUSE_LEFT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_BUTTON_MIDDLE: + gpac_evt.mouse.button = GF_MOUSE_MIDDLE; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_BUTTON_RIGHT: + gpac_evt.mouse.button = GF_MOUSE_RIGHT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; +#ifdef SDL_BUTTON_WHEELUP + case SDL_BUTTON_WHEELUP: + case SDL_BUTTON_WHEELDOWN: + /*SDL handling is not perfect there, it just says up/down but no info on how much + the wheel was rotated...*/ + gpac_evt.mouse.wheel_pos = (sdl_evt.button.button==SDL_BUTTON_WHEELUP) ? FIX_ONE : -FIX_ONE; + gpac_evt.type = GF_EVENT_MOUSEWHEEL; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; +#endif + } + break; +#if SDL_VERSION_ATLEAST(2,0,0) + case SDL_MOUSEWHEEL: + /*SDL handling is not perfect there, it just says up/down but no info on how much + the wheel was rotated...*/ + gpac_evt.mouse.wheel_pos = INT2FIX(sdl_evt.wheel.y); + gpac_evt.type = GF_EVENT_MOUSEWHEEL; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + + case SDL_MULTIGESTURE: + gpac_evt.mtouch.x = FLT2FIX(sdl_evt.mgesture.x); + gpac_evt.mtouch.y = FLT2FIX(sdl_evt.mgesture.y); + gpac_evt.mtouch.num_fingers = sdl_evt.mgesture.numFingers; + gpac_evt.mtouch.rotation = sdl_evt.mgesture.dTheta; + gpac_evt.mtouch.pinch = sdl_evt.mgesture.dDist; + gpac_evt.type = GF_EVENT_MULTITOUCH; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + + case SDL_DROPFILE: + gpac_evt.type = GF_EVENT_DROPFILE; + gpac_evt.open_file.nb_files = 1; + gpac_evt.open_file.files = &sdl_evt.drop.file; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + + +#endif + + } + } + return GF_TRUE; +} + +#ifdef SDL_WINDOW_THREAD +u32 SDLVid_EventProc(void *par) +{ +#if 0 + u32 last_mouse_move; +#endif + Bool ret; + GF_VideoOutput *dr = (GF_VideoOutput *)par; + SDLVID(); + + if (!SDLVid_InitializeWindow(ctx, dr)) { + ctx->sdl_th_state = SDL_STATE_STOP_REQ; + return 1; + } + + ctx->sdl_th_state = SDL_STATE_RUNNING; + while (ctx->sdl_th_state==SDL_STATE_RUNNING) { + /*after much testing: we must ensure nothing is using the event queue when resizing window. + -- under X, it throws Xlib "unexpected async reply" under linux, therefore we don't wait events, + we check for events and execute them if any + -- under Win32, the SDL_SetVideoMode deadlocks, so we don't force exclusive access to events + */ +#ifndef WIN32 + gf_mx_p(ctx->evt_mx); +#endif + + ret = SDLVid_ProcessMessageQueue(ctx, dr); + +#ifndef WIN32 + gf_mx_v(ctx->evt_mx); +#endif + + /*looks like this hides the cursor for ever when switching back from FS*/ +#if 0 + if (ctx->fullscreen && (last_mouse_move + 2000 < SDL_GetTicks()) ) { + if (cursor_on) SDL_ShowCursor(0); + cursor_on = 0; + } else if (!cursor_on) { + SDL_ShowCursor(1); + cursor_on = 1; + } +#endif + + /*QUIT message has been processed*/ + if (!ret) { + ctx->sdl_th_state = SDL_STATE_STOP_REQ; + break; + } + + gf_sleep(2); + } + + while (ctx->sdl_th_state == SDL_STATE_STOP_REQ) + gf_sleep(10); + + SDLVid_ShutdownWindow(ctx); + ctx->sdl_th_state = SDL_STATE_STOPPED; + + return 0; +} +#endif /*SDL_WINDOW_THREAD*/ + + +GF_Err SDLVid_Setup(struct _video_out *dr, void *os_handle, void *os_display, u32 init_flags) +{ + Bool show_window = GF_TRUE; + SDLVID(); + /*we don't allow SDL hack, not stable enough*/ + //if (os_handle) SDLVid_SetHack(os_handle, 1); + + ctx->os_handle = os_handle; + if (!ctx->is_init) { + ctx->output_3d = GF_FALSE; + show_window = GF_TRUE; + } + + ctx->force_alpha = (init_flags & GF_TERM_WINDOW_TRANSPARENT) ? GF_TRUE : GF_FALSE; + ctx->hidden = (init_flags & GF_TERM_INIT_HIDE) ? GF_TRUE : GF_FALSE; + + if (!ctx->hidden && show_window) { +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_ShowWindow(ctx->screen); +#else +#endif + } + + if (!SDLOUT_InitSDL()) + return GF_IO_ERR; + +#ifdef SDL_WINDOW_THREAD + ctx->sdl_th_state = SDL_STATE_STOPPED; + gf_th_run(ctx->sdl_th, SDLVid_EventProc, dr); + + while (!ctx->sdl_th_state) + gf_sleep(10); + + if (ctx->sdl_th_state==SDL_STATE_STOP_REQ) { + + SDLOUT_CloseSDL(); + ctx->sdl_th_state = SDL_STATE_STOPPED; + return GF_IO_ERR; + } +#else + if (!SDLVid_InitializeWindow(ctx, dr)) { + SDL_QuitSubSystem(SDL_INIT_VIDEO); + SDLOUT_CloseSDL(); + return GF_IO_ERR; + } +#endif + + //coverage +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + GF_Event evt; + sdl_translate_key(SDLK_BACKSPACE, &evt.key); + SDLVid_SetCursor(dr, GF_CURSOR_NORMAL); + } +#endif + + ctx->is_init = GF_TRUE; + return GF_OK; +} + +static void SDLVid_Shutdown(GF_VideoOutput *dr) +{ + SDLVID(); + /*remove all surfaces*/ + + if (!ctx->is_init) return; +#ifdef SDL_WINDOW_THREAD + if (ctx->sdl_th_state==SDL_STATE_RUNNING) { + SDL_Event evt; + evt.type = SDL_QUIT; + SDL_PushEvent(&evt); + } + /*wait until thread say it is stopped*/ + ctx->sdl_th_state = SDL_STATE_WAIT_FOR_THREAD_TERMINATION; + while (ctx->sdl_th_state != SDL_STATE_STOPPED) + gf_sleep(10); + +#else + SDLVid_ShutdownWindow(ctx); +#endif + + SDLOUT_CloseSDL(); + ctx->is_init = GF_FALSE; +} + + +GF_Err SDLVid_SetFullScreen(GF_VideoOutput *dr, Bool bFullScreenOn, u32 *screen_width, u32 *screen_height) +{ + int bpp; + u32 pref_bpp; + SDLVID(); +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_DisplayMode goodMode; + u32 numDisplayModes; + u32 mask; +#endif + + if (ctx->fullscreen==bFullScreenOn) return GF_OK; + + /*lock to get sure the event queue is not processed under X*/ + gf_mx_p(ctx->evt_mx); + ctx->fullscreen = bFullScreenOn; + +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_GetCurrentDisplayMode(0, &goodMode); + SDL_PixelFormatEnumToMasks(goodMode.format, &bpp, &mask, &mask, &mask, &mask); + pref_bpp = bpp; +#else + pref_bpp = bpp = ctx->screen->format->BitsPerPixel; +#endif + + if (ctx->fullscreen) { +#if ! ( SDL_VERSION_ATLEAST(2,0,0) ) + u32 flags = ctx->output_3d ? SDL_GL_FULLSCREEN_FLAGS : SDL_FULLSCREEN_FLAGS; +#endif + Bool switch_res = gf_opts_get_bool("core", "switch-vres"); + if (!dr->max_screen_width || !dr->max_screen_height) switch_res = GF_TRUE; + + ctx->store_width = *screen_width; + ctx->store_height = *screen_height; + if (switch_res) { + u32 i; + ctx->fs_width = *screen_width; + ctx->fs_height = *screen_height; + +#if SDL_VERSION_ATLEAST(2,0,0) + numDisplayModes = SDL_GetNumDisplayModes(0); + for(i=0; i<numDisplayModes; i++) { + SDL_GetDisplayMode(0, i, &goodMode); + if ((ctx->fs_width <= (u32) goodMode.w) && (ctx->fs_height <= (u32) goodMode.h)) { + s32 bppDisp; + ctx->fs_width = goodMode.w; + ctx->fs_height = goodMode.h; + SDL_PixelFormatEnumToMasks(goodMode.format, &bppDisp, &mask, &mask, &mask, &mask); + pref_bpp = bppDisp; + break; + } + } +#else + for(i=0; i<nb_video_modes; i++) { + if (ctx->fs_width<=video_modes[2*i] && ctx->fs_height<=video_modes[2*i + 1]) { + if ((pref_bpp = SDL_VideoModeOK(video_modes[2*i], video_modes[2*i+1], bpp, flags))) { + ctx->fs_width = video_modes[2*i]; + ctx->fs_height = video_modes[2*i + 1]; + break; + } + } + } +#endif + } else { +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_GetCurrentDisplayMode(0, &goodMode); +#endif + ctx->fs_width = dr->max_screen_width; + ctx->fs_height = dr->max_screen_height; + } +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_SetWindowDisplayMode(ctx->screen, &goodMode); + SDL_SetWindowFullscreen(ctx->screen, SDL_WINDOW_FULLSCREEN_DESKTOP); +#else + ctx->screen = SDL_SetVideoMode(ctx->fs_width, ctx->fs_height, pref_bpp, flags); +#endif + /*we switched bpp, clean all objects*/ + if (bpp != pref_bpp) SDLVid_DestroyObjects(ctx); + *screen_width = ctx->fs_width; + *screen_height = ctx->fs_height; + /*GL has changed*/ + if (ctx->output_3d) { + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.use_opengl = GF_TRUE; + dr->on_event(dr->evt_cbk_hdl, &evt); + } + } else { +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_SetWindowFullscreen(ctx->screen, 0); +#endif + SDLVid_ResizeWindow(dr, ctx->store_width, ctx->store_height); + *screen_width = ctx->store_width; + *screen_height = ctx->store_height; + } + gf_mx_v(ctx->evt_mx); + if (!ctx->screen) return GF_IO_ERR; + return GF_OK; +} + +GF_Err SDLVid_SetBackbufferSize(GF_VideoOutput *dr, u32 newWidth, u32 newHeight, Bool system_mem) +{ + const char *opt; + SDLVID(); +#if SDL_VERSION_ATLEAST(2,0,0) + +#else + u32 col; +#endif + + if (ctx->output_3d) return GF_BAD_PARAM; + + opt = gf_opts_get_key("core", "hwvmem"); + if (system_mem) { + if (opt && !strcmp(opt, "always")) system_mem = GF_FALSE; + } else { + if (opt && !strcmp(opt, "never")) system_mem = GF_TRUE; + } + ctx->use_systems_memory = system_mem; + + + /*clear screen*/ +#if SDL_VERSION_ATLEAST(2,0,0) + + + if (ctx->tx_back_buffer) SDL_DestroyTexture(ctx->tx_back_buffer); + if (ctx->back_buffer_pixels) gf_free(ctx->back_buffer_pixels); + + ctx->tx_back_buffer = SDL_CreateTexture(ctx->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, newWidth, newHeight); + ctx->back_buffer_pixels = gf_malloc(sizeof(char)*3*newWidth*newHeight); + + + SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); + SDL_RenderClear(ctx->renderer); + SDL_RenderPresent(ctx->renderer); + +#else + if(ctx->screen) { + col = SDL_MapRGB(ctx->screen->format, 0, 0, 0); + SDL_FillRect(ctx->screen, NULL, col); + SDL_Flip(ctx->screen); + } + if (ctx->back_buffer && ((u32) ctx->back_buffer->w==newWidth) && ((u32) ctx->back_buffer->h==newHeight)) { + return GF_OK; + } + if (ctx->back_buffer) SDL_FreeSurface(ctx->back_buffer); + + if (ctx->screen) ctx->back_buffer = SDL_CreateRGBSurface(ctx->use_systems_memory ? SDL_SWSURFACE : SDL_HWSURFACE, newWidth, newHeight, ctx->screen->format->BitsPerPixel, ctx->screen->format->Rmask, ctx->screen->format->Gmask, ctx->screen->format->Bmask, 0); + + if (!ctx->back_buffer) return GF_IO_ERR; +#endif + ctx->width = newWidth; + ctx->height = newHeight; + + return GF_OK; +} + +u32 SDLVid_MapPixelFormat(SDL_PixelFormat *format, Bool force_alpha) +{ + if (!format || format->palette) return 0; + switch (format->BitsPerPixel) { + case 16: + if ((format->Rmask==0x7c00) && (format->Gmask==0x03e0) && (format->Bmask==0x001f) ) return GF_PIXEL_RGB_555; + if ((format->Rmask==0xf800) && (format->Gmask==0x07e0) && (format->Bmask==0x001f) ) return GF_PIXEL_RGB_565; + return 0; + case 24: + if (format->Rmask==0x00FF0000) return GF_PIXEL_RGB; + if (format->Rmask==0x000000FF) return GF_PIXEL_BGR; + return 0; + case 32: + if (format->Amask==0xFF000000) return GF_PIXEL_ARGB; + if (format->Rmask==0x00FF0000) return force_alpha ? GF_PIXEL_ARGB : GF_PIXEL_RGBX; + if (format->Rmask==0x000000FF) return force_alpha ? GF_PIXEL_RGBA : GF_PIXEL_BGRX; + return 0; + default: + return 0; + } +} + +#if SDL_VERSION_ATLEAST(2,0,0) +static GF_Err SDLVid_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *video_info, Bool do_lock) +{ + SDLVID(); + + if (do_lock) { + memset(video_info, 0, sizeof(GF_VideoSurface)); + video_info->width = ctx->width; + video_info->height = ctx->height; + video_info->pitch_x = 0; + video_info->pitch_y = ctx->width*3; + video_info->video_buffer = ctx->back_buffer_pixels; + video_info->pixel_format = GF_PIXEL_RGB; + video_info->is_hardware_memory = 0; + if (ctx->needs_bb_grab) { + SDL_RenderReadPixels(ctx->renderer, NULL, SDL_PIXELFORMAT_RGB24, video_info->video_buffer, video_info->pitch_y); + ctx->needs_bb_grab = 0; + } + } else { + SDL_UpdateTexture(ctx->tx_back_buffer, NULL, video_info->video_buffer, video_info->pitch_y); + SDL_RenderCopy(ctx->renderer, ctx->tx_back_buffer, NULL, NULL); + } + return GF_OK; +} + +#else +static GF_Err SDLVid_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *video_info, Bool do_lock) +{ + SDLVID(); + + if (!ctx->back_buffer) return GF_BAD_PARAM; + if (do_lock) { + if (!video_info) return GF_BAD_PARAM; + if (SDL_LockSurface(ctx->back_buffer)<0) return GF_IO_ERR; + memset(video_info, 0, sizeof(GF_VideoSurface)); + video_info->width = ctx->back_buffer->w; + video_info->height = ctx->back_buffer->h; + video_info->pitch_x = 0; + video_info->pitch_y = ctx->back_buffer->pitch; + video_info->video_buffer = (char*)ctx->back_buffer->pixels; + video_info->pixel_format = SDLVid_MapPixelFormat(ctx->back_buffer->format, ctx->force_alpha); + video_info->is_hardware_memory = !ctx->use_systems_memory; + } else { + SDL_UnlockSurface(ctx->back_buffer); + } + return GF_OK; +} +#endif + + +#if SDL_VERSION_ATLEAST(2,0,0) + +//for CGLSetParameter +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) +#include <OpenGL/OpenGL.h> +#endif + +static GF_Err SDLVid_Flush(GF_VideoOutput *dr, GF_Window *dest) +{ + SDLVID(); + /*if resizing don't process otherwise we may deadlock*/ + if (!ctx->screen) return GF_OK; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] swapping video buffers\n")); + + if (ctx->output_3d) { + //with SDL2 we have to disable vsync by overriding swap interval +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) + if (ctx->disable_vsync) { + GLint sync = 0; + CGLContextObj gl_ctx = CGLGetCurrentContext(); + CGLSetParameter(gl_ctx, kCGLCPSwapInterval, &sync); + } +#endif + SDL_GL_SwapWindow(ctx->screen); + return GF_OK; + } + + if (ctx->enable_defer_mode) { + if (ctx->needs_bb_flush) { + SDL_UpdateTexture(ctx->tx_back_buffer, NULL, ctx->back_buffer_pixels, 3*ctx->width); + SDL_RenderCopy(ctx->renderer, ctx->tx_back_buffer, NULL, NULL); + } + SDL_RenderReadPixels(ctx->renderer, NULL, SDL_PIXELFORMAT_RGB24, ctx->back_buffer_pixels, 3*ctx->width); + ctx->needs_bb_grab = 0; + ctx->needs_bb_flush = 0; + SDL_RenderPresent(ctx->renderer); + //push back texture after SDL flip + SDL_RenderCopy(ctx->renderer, ctx->tx_back_buffer, NULL, NULL); + } else { + ctx->needs_clear = 1; + SDL_RenderPresent(ctx->renderer); + } + + + return GF_OK; +} + +#else + +static GF_Err SDLVid_Flush(GF_VideoOutput *dr, GF_Window *dest) +{ + SDL_Rect rc; + SDLVID(); + /*if resizing don't process otherwise we may deadlock*/ + if (!ctx->screen) return GF_OK; + + //GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Flush\n")); + + if (ctx->output_3d) { + SDL_GL_SwapBuffers(); + return GF_OK; + } + if (!ctx->back_buffer) return GF_BAD_PARAM; + + if ((dest->w != (u32) ctx->back_buffer->w) || (dest->h != (u32) ctx->back_buffer->h)) { + GF_VideoSurface src, dst; + + SDL_LockSurface(ctx->back_buffer); + memset(&src, 0, sizeof(GF_VideoSurface)); + src.height = ctx->back_buffer->h; + src.width = ctx->back_buffer->w; + src.pitch_x = 0; + src.pitch_y = ctx->back_buffer->pitch; + src.pixel_format = SDLVid_MapPixelFormat(ctx->back_buffer->format, ctx->force_alpha); + src.video_buffer = (char*)ctx->back_buffer->pixels; + + SDL_LockSurface(ctx->screen); + dst.height = ctx->screen->h; + dst.width = ctx->screen->w; + dst.pitch_x = 0; + dst.pitch_y = ctx->screen->pitch; + dst.pixel_format = SDLVid_MapPixelFormat(ctx->screen->format, GF_FALSE); + dst.video_buffer = (char*)ctx->screen->pixels; + + gf_stretch_bits(&dst, &src, dest, NULL, 0xFF, GF_FALSE, NULL, NULL); + SDL_UnlockSurface(ctx->screen); + SDL_UnlockSurface(ctx->back_buffer); + + } else { + rc.x = dest->x; + rc.y = dest->y; + rc.w = dest->w; + rc.h = dest->h; + SDL_BlitSurface(ctx->back_buffer, NULL, ctx->screen, &rc); + } + SDL_Flip(ctx->screen); + return GF_OK; +} +#endif + + +#ifdef WIN32 +static u32 get_sys_col(int idx) +{ + u32 res; + DWORD val = GetSysColor(idx); + res = (val)&0xFF; + res<<=8; + res |= (val>>8)&0xFF; + res<<=8; + res |= (val>>16)&0xFF; + return res; +} +#endif + +static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) +{ + if (!evt) { +#ifndef SDL_WINDOW_THREAD + SDLVID(); + SDLVid_ProcessMessageQueue(ctx, dr); +#endif + return GF_OK; + } + switch (evt->type) { + case GF_EVENT_SET_CURSOR: +#ifndef GPAC_CONFIG_IOS + SDLVid_SetCursor(dr, evt->cursor.cursor_type); +#endif + break; + case GF_EVENT_SET_CAPTION: +#ifdef SDL_WINDOW_THREAD + { + SDLVID(); + if (ctx->sdl_th_state != SDL_STATE_RUNNING) + break; + } +#endif +#if SDL_VERSION_ATLEAST(2,0,0) + { +#if !defined(GPAC_CONFIG_IOS) + SDLVID(); + SDL_SetWindowTitle(ctx->screen, evt->caption.caption); + SDLVid_ProcessEvent(dr, NULL); +#endif + } +#else + SDL_WM_SetCaption(evt->caption.caption, NULL); +#endif + break; + case GF_EVENT_SHOWHIDE: + /*the only way to have proper show/hide with SDL is to shutdown the video system and reset it up + which we don't want to do since the setup MUST occur in the rendering thread for some configs (OpenGL)*/ + return GF_NOT_SUPPORTED; + case GF_EVENT_SIZE: + { + SDLVID(); +#ifdef GPAC_CONFIG_IOS + if (ctx->fullscreen) { + } else { + SDLVid_ResizeWindow(dr, evt->size.width, evt->size.height); + } +#else + if (ctx->fullscreen) { + //ctx->store_width = evt->size.width; + //ctx->store_height = evt->size.height; + } else { + SDLVid_ResizeWindow(dr, evt->size.width, evt->size.height); + } +#endif + } + break; + case GF_EVENT_MOVE: + +#if !defined(GPAC_CONFIG_IOS) && SDL_VERSION_ATLEAST(2,0,0) + { + SDLVID(); + + if (ctx->fullscreen) return GF_OK; + + if (evt->move.relative == 2) { + } + else if (evt->move.relative) { + s32 x, y; + x = y = 0; + SDL_GetWindowPosition(ctx->screen, &x, &y); + SDL_SetWindowPosition(ctx->screen, x + evt->move.x, y + evt->move.y); + } else { + SDL_SetWindowPosition(ctx->screen, evt->move.x, evt->move.y); + } + } +#endif + break; + case GF_EVENT_VIDEO_SETUP: + { + SDLVID(); + ctx->disable_vsync=evt->setup.disable_vsync; + if (!evt->setup.use_opengl) { + /*force a resetup of the window*/ + if (ctx->output_3d) { + ctx->width = ctx->height = 0; + ctx->output_3d = GF_FALSE; + SDLVid_ResetWindow(ctx); + SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); + } else { +#if SDL_VERSION_ATLEAST(2,0,0) + SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); +#endif + } + ctx->output_3d = GF_FALSE; + return SDLVid_SetBackbufferSize(dr, evt->setup.width, evt->setup.height, evt->setup.system_memory); + } else { + /*force a resetup of the window*/ + if (!ctx->output_3d) { + ctx->width = ctx->height = 0; + SDLVid_ResetWindow(ctx); + } + ctx->output_3d = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Setting up 3D in SDL.\n")); +#ifdef GPAC_CONFIG_IOS +// return SDLVid_ResizeWindow(dr, dr->max_screen_width, dr->max_screen_height); + return SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); +#else + return SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); +#endif + } + } + break; + case GF_EVENT_SYS_COLORS: +#ifdef WIN32 + evt->sys_cols.sys_colors[0] = get_sys_col(COLOR_ACTIVEBORDER); + evt->sys_cols.sys_colors[1] = get_sys_col(COLOR_ACTIVECAPTION); + evt->sys_cols.sys_colors[2] = get_sys_col(COLOR_APPWORKSPACE); + evt->sys_cols.sys_colors[3] = get_sys_col(COLOR_BACKGROUND); + evt->sys_cols.sys_colors[4] = get_sys_col(COLOR_BTNFACE); + evt->sys_cols.sys_colors[5] = get_sys_col(COLOR_BTNHIGHLIGHT); + evt->sys_cols.sys_colors[6] = get_sys_col(COLOR_BTNSHADOW); + evt->sys_cols.sys_colors[7] = get_sys_col(COLOR_BTNTEXT); + evt->sys_cols.sys_colors[8] = get_sys_col(COLOR_CAPTIONTEXT); + evt->sys_cols.sys_colors[9] = get_sys_col(COLOR_GRAYTEXT); + evt->sys_cols.sys_colors[10] = get_sys_col(COLOR_HIGHLIGHT); + evt->sys_cols.sys_colors[11] = get_sys_col(COLOR_HIGHLIGHTTEXT); + evt->sys_cols.sys_colors[12] = get_sys_col(COLOR_INACTIVEBORDER); + evt->sys_cols.sys_colors[13] = get_sys_col(COLOR_INACTIVECAPTION); + evt->sys_cols.sys_colors[14] = get_sys_col(COLOR_INACTIVECAPTIONTEXT); + evt->sys_cols.sys_colors[15] = get_sys_col(COLOR_INFOBK); + evt->sys_cols.sys_colors[16] = get_sys_col(COLOR_INFOTEXT); + evt->sys_cols.sys_colors[17] = get_sys_col(COLOR_MENU); + evt->sys_cols.sys_colors[18] = get_sys_col(COLOR_MENUTEXT); + evt->sys_cols.sys_colors[19] = get_sys_col(COLOR_SCROLLBAR); + evt->sys_cols.sys_colors[20] = get_sys_col(COLOR_3DDKSHADOW); + evt->sys_cols.sys_colors[21] = get_sys_col(COLOR_3DFACE); + evt->sys_cols.sys_colors[22] = get_sys_col(COLOR_3DHIGHLIGHT); + evt->sys_cols.sys_colors[23] = get_sys_col(COLOR_3DLIGHT); + evt->sys_cols.sys_colors[24] = get_sys_col(COLOR_3DSHADOW); + evt->sys_cols.sys_colors[25] = get_sys_col(COLOR_WINDOW); + evt->sys_cols.sys_colors[26] = get_sys_col(COLOR_WINDOWFRAME); + evt->sys_cols.sys_colors[27] = get_sys_col(COLOR_WINDOWTEXT); + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif + + case GF_EVENT_TEXT_EDITING_START: + case GF_EVENT_TEXT_EDITING_END: +#if defined(GPAC_CONFIG_IOS) && SDL_VERSION_ATLEAST(2,0,0) + if (evt->type==GF_EVENT_TEXT_EDITING_START) { + SDL_StartTextInput(); + } else { + SDL_StopTextInput(); + } + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif + + case GF_EVENT_SET_GL: + { +#if SDL_VERSION_ATLEAST(2,0,0) + SDLVID(); + if (SDL_GL_MakeCurrent(ctx->screen, ctx->gl_context)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot make context current: %s\n", SDL_GetError())); + return GF_IO_ERR; + } +#endif + } + return GF_OK; + } + return GF_OK; +} + +#if SDL_VERSION_ATLEAST(2,0,0) +static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) +{ + SDLVID(); + Bool need_copy=0; + u32 format; + s32 acc, w, h; + int res; + Bool set_blend=0; + SDL_Rect dstrc; + SDL_Texture **pool; + SDL_Rect srcrc, *src_ptr=NULL; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] Bliting surface (overlay type %d)\n", overlay_type)); + + if (ctx->needs_bb_flush) { + SDL_UpdateTexture(ctx->tx_back_buffer, NULL, ctx->back_buffer_pixels, 3*ctx->width); + SDL_RenderCopy(ctx->renderer, ctx->tx_back_buffer, NULL, NULL); + ctx->needs_bb_grab = 1; + } + + ctx->needs_bb_grab = 1; + if (ctx->needs_clear) { + SDL_RenderClear(ctx->renderer); + ctx->needs_clear = 0; + } + + dstrc.w = dst_wnd->w; + dstrc.h = dst_wnd->h; + dstrc.x = dst_wnd->x; + dstrc.y = dst_wnd->y; + + if (src_wnd) { + srcrc.x = src_wnd->x; + srcrc.y = src_wnd->y; + srcrc.w = src_wnd->w; + srcrc.h = src_wnd->h; + src_ptr = &srcrc; + } + + //this is a clear (not very elegant ...) + if ((video_src->width<=2) && (video_src->height<=2)) { + u8 *pix =(u8 *) video_src->video_buffer; + if (video_src->pixel_format == GF_PIXEL_RGB) { + SDL_SetRenderDrawColor(ctx->renderer, pix[0], pix[1], pix[2], 0xFF); + } else { + SDL_SetRenderDrawColor(ctx->renderer, pix[0], pix[1], pix[2], pix[3]); + } + res = SDL_RenderFillRect(ctx->renderer, &dstrc); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL2] Clear error: %s\n", SDL_GetError())); + return GF_IO_ERR; + } + return GF_OK; + } + + switch (video_src->pixel_format) { + case GF_PIXEL_RGB: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_RGB24; + break; + case GF_PIXEL_XRGB: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_BGRX8888; + break; + case GF_PIXEL_RGBX: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_BGR888; + break; + case GF_PIXEL_XBGR: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_RGBX8888; + break; + case GF_PIXEL_BGRX: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_RGB888; + break; + case GF_PIXEL_RGBA: + pool = &ctx->pool_rgba; + format=SDL_PIXELFORMAT_ABGR8888; + set_blend=1; + break; + case GF_PIXEL_YUV: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_IYUV; + break; + case GF_PIXEL_YVU: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_YV12; + break; + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444_10: + case GF_PIXEL_YUV422_10: + case GF_PIXEL_YUV_10: + need_copy=1; + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_YV12; + break; + /*FIXME we need to upgrade our SDL build*/ +#if !defined(GPAC_CONFIG_IOS) && SDL_VERSION_ATLEAST(2,0,0) + case GF_PIXEL_NV12: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_NV12; + break; + case GF_PIXEL_NV21: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_NV21; + break; +#endif + case GF_PIXEL_UYVY: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_UYVY; + break; + case GF_PIXEL_YUYV: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_YUY2; + break; + default: + return GF_NOT_SUPPORTED; + } + + + if (*pool ) { + SDL_QueryTexture(*pool, &format, &acc, &w, &h); + if ((w != video_src->width) || (h != video_src->height) ) { + SDL_DestroyTexture(*pool); + *pool = NULL; + } + } + if (!(*pool)) { + (*pool) = SDL_CreateTexture(ctx->renderer, format, SDL_TEXTUREACCESS_STREAMING, video_src->width, video_src->height); + if (!(*pool)) return GF_NOT_SUPPORTED; + } + + SDL_QueryTexture((*pool), &format, &acc, &w, &h); + + if (need_copy) { + GF_VideoSurface dst_v; + u8 *pixels; + int pitch; + GF_Window swnd; + /*copy pixels*/ + if (SDL_LockTexture(*pool, NULL, (void**)&pixels, &pitch) < 0) { + return GF_NOT_SUPPORTED; + } + if (!src_wnd) { + swnd.x = swnd.y=0; + swnd.w = video_src->width; + swnd.h = video_src->height; + src_wnd = &swnd; + } + + memset(&dst_v, 0, sizeof(GF_VideoSurface)); + dst_v.video_buffer = pixels; + dst_v.u_ptr = pixels + h*pitch; + dst_v.v_ptr = pixels + 5*h*pitch/4; + dst_v.pitch_y = pitch; + dst_v.pixel_format = GF_PIXEL_YUV; + dst_v.width = video_src->width; + dst_v.height = video_src->height; + + gf_stretch_bits(&dst_v, video_src, NULL, src_wnd, 0xFF, GF_FALSE, NULL, NULL); + + SDL_UnlockTexture(*pool); + } else { + SDL_UpdateTexture(*pool, NULL, video_src->video_buffer, video_src->pitch_y); + } + + if (set_blend || (video_src->global_alpha!=0xFF)) { + res = SDL_SetTextureBlendMode(*pool, SDL_BLENDMODE_BLEND); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL2] Cannot change texture blend mode: %s\n", SDL_GetError())); + return GF_IO_ERR; + } + res = SDL_SetTextureAlphaMod(*pool, video_src->global_alpha); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL2] Cannot change global alpha of texture: %s\n", SDL_GetError())); + return GF_IO_ERR; + } + } else { + res = SDL_SetTextureBlendMode(*pool, SDL_BLENDMODE_NONE); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL2] Cannot change texture blend mode: %s\n", SDL_GetError())); + return GF_IO_ERR; + } + } + + res = SDL_RenderCopy(ctx->renderer, *pool, src_ptr, &dstrc); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL2] Blit error: %s\n", SDL_GetError())); + return GF_IO_ERR; + } + return GF_OK; +} + +#else +static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) +{ + SDLVID(); + u32 amask = 0; + u32 bpp; + GF_Err e = GF_OK; + u32 i; + u8 *dst, *src; + SDL_Rect dstrc; + SDL_Surface **pool; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] Bliting surface (overlay type %d)\n", overlay_type)); + + if (overlay_type) { + GF_VideoSurface dst_v; + if (!video_src) { + if (ctx->yuv_overlay) { + SDL_FreeYUVOverlay(ctx->yuv_overlay); + ctx->yuv_overlay=NULL; + } + return GF_OK; + } + if (!ctx->yuv_overlay || (ctx->yuv_overlay->w != src_wnd->w) || (ctx->yuv_overlay->h != src_wnd->h) ) { + if (ctx->yuv_overlay) SDL_FreeYUVOverlay(ctx->yuv_overlay); + + ctx->yuv_overlay = SDL_CreateYUVOverlay(src_wnd->w, src_wnd->h, SDL_YV12_OVERLAY, ctx->screen); + if (!ctx->yuv_overlay) return GF_NOT_SUPPORTED; + } + /*copy pixels*/ + SDL_LockYUVOverlay(ctx->yuv_overlay); + + + memset(&dst_v, 0, sizeof(GF_VideoSurface)); + dst_v.video_buffer = (char *) ctx->yuv_overlay->pixels[0]; + dst_v.u_ptr = (char *) ctx->yuv_overlay->pixels[1]; + dst_v.v_ptr = (char *) ctx->yuv_overlay->pixels[2]; + dst_v.pitch_y = ctx->yuv_overlay->pitches[0]; + dst_v.pixel_format = GF_PIXEL_YUV; + dst_v.width = video_src->width; + dst_v.height = video_src->height; + + gf_stretch_bits(&dst_v, video_src, NULL, src_wnd, 0xFF, GF_FALSE, NULL, NULL); + + SDL_UnlockYUVOverlay(ctx->yuv_overlay); + + dstrc.w = dst_wnd->w; + dstrc.h = dst_wnd->h; + dstrc.x = dst_wnd->x; + dstrc.y = dst_wnd->y; + SDL_DisplayYUVOverlay(ctx->yuv_overlay, &dstrc); + return GF_OK; + } + + /*SDL doesn't support stretching ...*/ + if ((src_wnd->w != dst_wnd->w) || (src_wnd->h!=dst_wnd->h)) + return GF_NOT_SUPPORTED; + + switch (video_src->pixel_format) { + case GF_PIXEL_RGB: + pool = &ctx->pool_rgb; + bpp = 3; + break; + case GF_PIXEL_RGBA: + pool = &ctx->pool_rgba; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + amask = 0x000000FF; +#else + amask = 0xFF000000; +#endif + bpp = 4; + break; + default: + return GF_NOT_SUPPORTED; + } + if (! *pool || ((*pool)->w < (int) src_wnd->w) || ((*pool)->h < (int) src_wnd->h) ) { + if ((*pool)) SDL_FreeSurface((*pool)); + + (*pool) = SDL_CreateRGBSurface(ctx->use_systems_memory ? SDL_SWSURFACE : SDL_HWSURFACE, + src_wnd->w, src_wnd->h, 8*bpp, + 0x000000FF, 0x0000FF00, 0x00FF0000, amask); + + if (! (*pool) ) return GF_IO_ERR; + } + + SDL_LockSurface(*pool); + + dst = (u8 *) ( (*pool)->pixels); + src = (u8*)video_src->video_buffer + video_src->pitch_y*src_wnd->y + src_wnd->x*bpp; + for (i=0; i<src_wnd->h; i++) { + memcpy(dst, src, bpp * src_wnd->w); + src += video_src->pitch_y; + dst += (*pool)->pitch; + } + SDL_UnlockSurface(*pool); + + dstrc.w = dst_wnd->w; + dstrc.h = dst_wnd->h; + dstrc.x = dst_wnd->x; + dstrc.y = dst_wnd->y; + + if (SDL_BlitSurface(*pool, NULL, ctx->back_buffer, &dstrc)) + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Blit error: %s\n", SDL_GetError())); + + return e; +} + +#endif + +void *SDL_NewVideo() +{ +#if SDL_VERSION_ATLEAST(2,0,0) + const char *opt; +#endif + SDLVidCtx *ctx; + GF_VideoOutput *driv; + + driv = (GF_VideoOutput*)gf_malloc(sizeof(GF_VideoOutput)); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "SDL Video Output", "gpac distribution"); + + ctx = (SDLVidCtx*)gf_malloc(sizeof(SDLVidCtx)); + memset(ctx, 0, sizeof(SDLVidCtx)); +#ifdef SDL_WINDOW_THREAD + ctx->sdl_th = gf_th_new("SDLVideo"); +#endif + ctx->evt_mx = gf_mx_new("SDLEvents"); + + + driv->opaque = ctx; + driv->Setup = SDLVid_Setup; + driv->Shutdown = SDLVid_Shutdown; + driv->SetFullScreen = SDLVid_SetFullScreen; + driv->Flush = SDLVid_Flush; + driv->ProcessEvent = SDLVid_ProcessEvent; + /*no offscreen opengl with SDL*/ + driv->hw_caps |= GF_VIDEO_HW_OPENGL; + + /*no YUV hardware blitting in SDL (only overlays)*/ + driv->hw_caps |= GF_VIDEO_HW_HAS_RGB ; + +#if SDL_VERSION_ATLEAST(2,0,0) + + driv->hw_caps |= GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_STRETCH | GF_VIDEO_HW_HAS_RGBA; + + + opt = gf_opts_get_key("core", "sdl-defer"); + ctx->enable_defer_mode = 0; + if (opt && !strcmp(opt, "yes")) + ctx->enable_defer_mode = 1; + + if (! ctx->enable_defer_mode) + driv->hw_caps |= GF_VIDEO_HW_DIRECT_ONLY; +#else + driv->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY; +#endif + + +#ifdef GPAC_ENABLE_COVERAGE + SDLVid_MapPixelFormat(NULL, GF_FALSE); +#endif + + driv->Blit = SDL_Blit; + driv->LockBackBuffer = SDLVid_LockBackBuffer; + driv->LockOSContext = NULL; +#ifndef SDL_TEXTINPUTEVENT_TEXT_SIZE + SDL_EnableUNICODE(1); +#else + SDL_StartTextInput(); +#endif /* SDL_TEXTINPUTEVENT_TEXT_SIZE */ + + return driv; +} + +void SDL_DeleteVideo(void *ifce) +{ + GF_VideoOutput *dr = (GF_VideoOutput *)ifce; + SDLVID(); +#ifdef SDL_WINDOW_THREAD + gf_th_del(ctx->sdl_th); +#endif + gf_mx_del(ctx->evt_mx); + gf_free(ctx); + gf_free(dr); +} + diff --git a/modules/sdl_out/video2d.c b/modules/sdl_out/video2d.c new file mode 100644 index 0000000..9ed42fb --- /dev/null +++ b/modules/sdl_out/video2d.c @@ -0,0 +1,29 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / SDL audio and video module + * + * 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 "sdl_out.h" +#include <gpac/constants.h> + + diff --git a/modules/test_filter/Makefile b/modules/test_filter/Makefile new file mode 100644 index 0000000..d8b53d5 --- /dev/null +++ b/modules/test_filter/Makefile @@ -0,0 +1,43 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/test_filter + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS=test_filter.o + +SRCS := $(OBJS:.o=.c) + +LIB=gf_test_filter$(DYN_LIB_SUFFIX) + + +all: $(LIB) + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) $(LDFLAGS) -L../../bin/gcc -lgpac + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/test_filter/test_filter.c b/modules/test_filter/test_filter.c new file mode 100644 index 0000000..bdc53fc --- /dev/null +++ b/modules/test_filter/test_filter.c @@ -0,0 +1,115 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2021 + * All rights reserved + * + * This file is part of GPAC / test sink 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/avparse.h> +#include <gpac/constants.h> + + +typedef struct +{ + Bool opt1; +} TestContext; + + +static GF_Err testfilter_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + TestContext *ctx = (TestContext *)gf_filter_get_udta(filter); + //if first time we see the pid, send a play event + TestContext *pctx = (TestContext *)gf_filter_pid_get_udta(pid); + if (!pctx && !is_remove) { + GF_FilterEvent evt; + gf_filter_pid_set_udta(pid, ctx); + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + } + + return GF_OK; +} + + +static Bool testfilter_process_event(GF_Filter *filter, const GF_FilterEvent *fevt) +{ + return GF_FALSE; +} + + +static GF_Err testfilter_process(GF_Filter *filter) +{ + u32 i, count = gf_filter_get_ipid_count(filter); + for (i = 0; i < count; i++) { + GF_FilterPid *pid = gf_filter_get_ipid(filter, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(pid); + gf_filter_pid_drop_packet(pid); + } + return GF_OK; +} + +static GF_Err testfilter_initialize(GF_Filter *filter) +{ + TestContext *ctx = (TestContext *) gf_filter_get_udta(filter); + return GF_OK; +} + +static void testfilter_finalize(GF_Filter *filter) +{ + TestContext *ctx = (TestContext *) gf_filter_get_udta(filter); +} + +static const GF_FilterCapability TestFilterCaps[] = +{ + //anything but file + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE) +}; + +#define OFFS(_n) #_n, offsetof(TestContext, _n) + +static const GF_FilterArgs TestFilterArgs[] = +{ + { OFFS(opt1), "some boolean option", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +GF_FilterRegister TestFilterRegister = { + .name = "testfilter", + GF_FS_SET_DESCRIPTION("Test Filter") + GF_FS_SET_HELP("This filter tests loading of filters from dynmaic libraries") + .private_size = sizeof(TestContext), + SETCAPS(TestFilterCaps), + .args = TestFilterArgs, + .initialize = testfilter_initialize, + .finalize = testfilter_finalize, + .configure_pid = testfilter_configure_pid, + .process = testfilter_process, + .process_event = testfilter_process_event, +}; + +GPAC_MODULE_EXPORT +GF_FilterRegister *RegisterFilter(GF_FilterSession *session) +{ + return &TestFilterRegister; +} + diff --git a/modules/test_filter/test_filter.vcxproj b/modules/test_filter/test_filter.vcxproj new file mode 100644 index 0000000..a5e1749 --- /dev/null +++ b/modules/test_filter/test_filter.vcxproj @@ -0,0 +1,292 @@ +<?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|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{042D3628-67F3-4B6C-8CC0-CD9AFA526974}</ProjectGuid> + <RootNamespace>test_filter</RootNamespace> + <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> + <ProjectName>test_filter</ProjectName> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental> + <GenerateManifest Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</GenerateManifest> + <GenerateManifest Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</GenerateManifest> + <EmbedManifest Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</EmbedManifest> + <EmbedManifest Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</EmbedManifest> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">gf_$(ProjectName)</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Midl> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TargetEnvironment>Win32</TargetEnvironment> + <TypeLibraryName>.\obj/test_filter_deb/test_filter.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + </Link> + <Manifest> + <OutputManifestFile>$(IntDir)$(TargetFileName).embed.manifest</OutputManifestFile> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Midl> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TypeLibraryName>.\obj/test_filter_deb/test_filter.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + </Link> + <Manifest> + <OutputManifestFile>$(IntDir)$(TargetFileName).embed.manifest</OutputManifestFile> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Midl> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TargetEnvironment>Win32</TargetEnvironment> + <TypeLibraryName>.\obj/test_filter_rel/test_filter.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + </Link> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + <OutputFile>$(IntDir)$(ProjectName).bsc</OutputFile> + </Bscmake> + <Manifest> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Midl> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TypeLibraryName>.\obj/test_filter_rel/test_filter.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Full</Optimization> + <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + </Link> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + <OutputFile>$(IntDir)$(ProjectName).bsc</OutputFile> + </Bscmake> + <Manifest> + <AdditionalManifestFiles>$(SolutionDir)gpac_manifest.xml %(AdditionalManifestFiles)</AdditionalManifestFiles> + </Manifest> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\modules\filter_export.cpp" /> + <ClCompile Include="..\..\modules\test_filter\test_filter.c" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\build\msvc14\libgpac_dll.vcxproj"> + <Project>{d3540754-e0cf-4604-ac11-82de9bd4d814}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/modules/validator/Makefile b/modules/validator/Makefile new file mode 100644 index 0000000..b9983d3 --- /dev/null +++ b/modules/validator/Makefile @@ -0,0 +1,49 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/validator + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS=validator.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_validator$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +endif + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) +ifeq ($(STATICBUILD),yes) + $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_validator-static$(DYN_LIB_SUFFIX) $(OBJS) -L../../bin/gcc -lgpac_static $(LDFLAGS) +endif + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/validator/README.TXT b/modules/validator/README.TXT new file mode 100644 index 0000000..072d4fa --- /dev/null +++ b/modules/validator/README.TXT @@ -0,0 +1,49 @@ +The validator allows recording user interactions and taking snapshots when playing back interactive content; and replaying the content simulating the interactions, generating and comparing snapshots. To activate it you need to modify the GPAC configuration as follows: + +Add a section: +[Validator] +Mode=Play + +Mode can be: Play, Record, or Disable + +The validator will try to load a trace file, a single file or a playlist. + +** Trace files are indicated with +Trace=SomeTrace + +A trace file does not contain any association with the source file nor screenshots, only mouse and keyboard events are used. +In trace mode the renderer is not reconfigured. + +** Single files are indicated with: +XVS=/PATH/TOfile.xvs +XVS means XML Validation Sequence. An example of XVS is: + +<TestValidationScript file="/PATH/TO/animate-elem-09-t.svg" > +<snapshot time="237" image="animate-elem-09-t-reference-000.png" /> +<mousemove time="4749.000000" x="99" y="354" /> +<mousemove time="4749.000000" x="103" y="343" /> +<mousemove time="4749.000000" x="108" y="333" /> +</TestValidationScript> + + +** Playlist of XVS are givin with: + +XVL=/PATH/TO/svg1.1f2.xvl +XVL means XML Validation List. An example of XVL is: + +<TestSuiteValidationScript content-base="/PATH/TO/svg" > +<Test scenario="animate-elem-02-t.xvs" content="animate-elem-02-t.svg" /> +<Test scenario="animate-elem-03-t.xvs" content="animate-elem-03-t.svg" /> +<Test scenario="animate-dom-01-f.xvs" content="animate-dom-01-f.svg" /> +<Test scenario="animate-dom-02-f.xvs" content="animate-dom-02-f.svg" /> +</TestSuiteValidationScript> + +When recording and playing back, the GPAC player rendering is switched to 5 FPS without antialiasing, except for Trace mode. + +When recording, you can trigger some actions as follows (except for trace mode): +CTRL+Insert: takes a snapshot and records it as a PNG. In replay mode, a PNG will be taken at the same scene time and the PNG will be compared. +CTRL+Fin: Quit +CTRL+F1: Takes snapshot at next frame change. +Page Down: Ends current XVS and switches to next in the XVL + +Note: after recording, upon closing the player, the Validator mode is automatically switched to Play to avoid losing recorded interactions, except in trace mode. diff --git a/modules/validator/validator.c b/modules/validator/validator.c new file mode 100644 index 0000000..1159bf2 --- /dev/null +++ b/modules/validator/validator.c @@ -0,0 +1,1199 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Cyril Concolato, Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2022 + * All rights reserved + * + * This file is part of GPAC / Test Suite Validator Recorder 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/modules/compositor_ext.h> +#include <gpac/filters.h> +#include <gpac/internal/compositor_dev.h> +#include <gpac/internal/media_dev.h> +#include <gpac/xml.h> +#include <gpac/options.h> + +typedef struct __validation_module +{ + GF_Compositor *compositor; + + Bool is_recording; + Bool trace_mode; + + /* Clock used to synchronize events in recording and playback*/ + GF_ObjectManager *root_odm; + + /* Next event to process */ + Bool next_event_snapshot; + GF_Event next_event; + u32 xvs_event_index; + u32 next_time; + Bool evt_loaded; + + GF_VideoListener video_listener; + + /* XML Validation List (the list of files to be tested) */ + char *xvl_filename; + GF_DOMParser *xvl_parser; + GF_XMLNode *xvl_node; + GF_XMLNode *xvs_node_in_xvl; + u32 xvl_node_index; + + /* Pointer to the current validation script file being tested */ + char *xvs_filename; + GF_DOMParser *xvs_parser; + GF_XMLNode *xvs_node; + Bool xvs_result; + Bool owns_root; + + /* test sequence */ + char *test_base; + char *test_filename; + + Bool snapshot_next_frame; + u32 snapshot_number; + + GF_FSEventListener evt_filter; +} GF_Validator; + +static void validator_xvs_close(GF_Validator *validator); +static Bool validator_xvs_next(GF_Validator *validator, Bool reverse); + +#ifndef GPAC_DISABLE_AV_PARSERS + +static void validator_xvs_add_snapshot_node(GF_Validator *validator, const char *filename, u32 scene_time) +{ + GF_XMLNode *snap_node; + GF_XMLAttribute *att; + GF_SAFEALLOC(snap_node, GF_XMLNode); + if (!snap_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate snapshot\n")); + return; + } + snap_node->name = gf_strdup("snapshot"); + snap_node->attributes = gf_list_new(); + + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate snapshot\n")); + return; + } + att->name = gf_strdup("time"); + att->value = (char*)gf_malloc(100); + sprintf(att->value, "%d", scene_time); + gf_list_add(snap_node->attributes, att); + + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate snapshot\n")); + return; + } + att->name = gf_strdup("image"); + att->value = gf_strdup(filename); + gf_list_add(snap_node->attributes, att); + gf_list_add(validator->xvs_node->content, snap_node); + + /* adding an extra text node for line break in serialization */ + GF_SAFEALLOC(snap_node, GF_XMLNode); + if (!snap_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate snapshot\n")); + return; + } + snap_node->type = GF_XML_TEXT_TYPE; + snap_node->name = gf_strdup("\n"); + gf_list_add(validator->xvs_node->content, snap_node); +} + +static char *validator_get_snapshot_name(GF_Validator *validator, Bool is_reference, u32 number) +{ + char *name = validator->test_filename ? validator->test_filename : validator->xvs_filename; + char *dot; + char dumpname[GF_MAX_PATH]; + dot = gf_file_ext_start(name); + dot[0] = 0; + sprintf(dumpname, "%s-%s-%03d.png", name, (is_reference?"reference":"newest"), number); + dot[0] = '.'; + return gf_strdup(dumpname); +} + +static char *validator_create_snapshot(GF_Validator *validator) +{ + GF_Err e; + GF_VideoSurface fb; + GF_Compositor *compositor = validator->compositor; + char *dumpname; + + dumpname = validator_get_snapshot_name(validator, validator->is_recording, validator->snapshot_number); + + e = gf_sc_get_screen_buffer(compositor, &fb, 0); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Error dumping screen buffer %s\n", gf_error_to_string(e))); + } else { + u32 dst_size = fb.width*fb.height*3; + char *dst = (char*)gf_malloc(sizeof(char)*dst_size); + + e = gf_img_png_enc(fb.video_buffer, fb.width, fb.height, fb.pitch_y, fb.pixel_format, dst, &dst_size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Error encoding PNG %s\n", gf_error_to_string(e))); + } else { + FILE *png = gf_fopen(dumpname, "wb"); + if (!png) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Error writing file %s\n", dumpname)); + } else { + gf_fwrite(dst, dst_size, png); + gf_fclose(png); + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Validator] Writing file %s\n", dumpname)); + } + } + if (dst) gf_free(dst); + gf_sc_release_screen_buffer(compositor, &fb); + } + validator->snapshot_number++; + return dumpname; +} + +static GF_Err validator_file_dec(char *png_filename, u32 *hint_codecid, u32 *width, u32 *height, u32 *pixel_format, char **dst, u32 *dst_size) +{ + u32 fsize, codecid; + char *data; + GF_Err e; + + codecid = 0; + if (!hint_codecid || ! *hint_codecid) { + char *ext = gf_file_ext_start(png_filename); + if (!ext) return GF_NOT_SUPPORTED; + if (!stricmp(ext, ".png")) codecid = GF_CODECID_PNG; + else if (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg")) codecid = GF_CODECID_JPEG; + } else { + codecid = *hint_codecid; + } + + e = gf_file_load_data(png_filename, (u8 **)&data, &fsize); + if (e) return e; + + e = GF_NOT_SUPPORTED; + *dst_size = 0; + if (codecid == GF_CODECID_JPEG) { +#ifdef GPAC_HAS_JPEG + e = gf_img_jpeg_dec(data, fsize, width, height, pixel_format, NULL, dst_size, 0); + if (*dst_size) { + *dst = gf_malloc(*dst_size); + return gf_img_jpeg_dec(data, fsize, width, height, pixel_format, *dst, dst_size, 0); + } +#endif + } else if (codecid == GF_CODECID_PNG) { +#ifdef GPAC_HAS_PNG + e = gf_img_png_dec(data, fsize, width, height, pixel_format, NULL, dst_size); + if (*dst_size) { + *dst = gf_malloc(*dst_size); + return gf_img_png_dec(data, fsize, width, height, pixel_format, *dst, dst_size); + } +#endif + } + return e; +} + +static Bool validator_compare_snapshots(GF_Validator *validator) +{ + char *ref_name, *new_name; + u32 ref_width, ref_height, ref_pixel_format, ref_data_size; + u32 new_width, new_height, new_pixel_format, new_data_size; + char *ref_data=NULL, *new_data=NULL; + Bool result = GF_FALSE; + GF_Err e; + u32 i; + + u32 snap_number = validator->snapshot_number - 1; + ref_name = validator_get_snapshot_name(validator, GF_TRUE, snap_number); + new_name = validator_get_snapshot_name(validator, GF_FALSE, snap_number); + + e = validator_file_dec(ref_name, NULL, &ref_width, &ref_height, &ref_pixel_format, &ref_data, &ref_data_size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Cannot decode PNG file %s\n", ref_name)); + goto end; + } + e = validator_file_dec(new_name, NULL, &new_width, &new_height, &new_pixel_format, &new_data, &new_data_size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Cannot decode PNG file %s\n", new_name)); + goto end; + } + if (!ref_data) ref_data_size = 0; + if (!new_data) new_data_size = 0; + + if (ref_width != new_width) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different widths: %d vs %d\n", ref_width, new_width)); + goto end; + } + if (ref_height != new_height) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different heights: %d vs %d\n", ref_height, new_height)); + goto end; + } + if (ref_pixel_format != new_pixel_format) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different pixel formats: %d vs %d\n", ref_pixel_format, new_pixel_format)); + goto end; + } + if (ref_data_size != new_data_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different pixel formats: %d vs %d\n", ref_pixel_format, new_pixel_format)); + goto end; + } + + for (i = 0; i<ref_data_size; i++) { + if (ref_data[i] != new_data[i]) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different pixel values at position %d: %d vs %d\n", i, ref_data[i], new_data[i])); + break; + } + } + if (i==ref_data_size) result = GF_TRUE; + +end: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Validator] PNG Comparison result: %s\n", (result?"Same":"Different"))); + if (ref_name) gf_free(ref_name); + if (new_name) gf_free(new_name); + if (ref_data) gf_free(ref_data); + if (new_data) gf_free(new_data); + return result; +} +#endif + +static void validator_on_video_frame(void *udta, u32 time) +{ + GF_Validator *validator = (GF_Validator *)udta; + if (validator->snapshot_next_frame) { +#ifndef GPAC_DISABLE_AV_PARSERS + char *snap_name = validator_create_snapshot(validator); + validator_xvs_add_snapshot_node(validator, snap_name, gf_clock_time(validator->root_odm->ck)); + gf_free(snap_name); +#endif + validator->snapshot_next_frame = GF_FALSE; + } +} + +static void validator_on_video_reconfig(void *udta, u32 width, u32 height, u8 bpp) +{ +} + +Bool validator_on_event_play(void *udta, GF_Event *event, Bool consumed_by_compositor) +{ + GF_Validator *validator = (GF_Validator *)udta; + switch (event->type) { + case GF_EVENT_CONNECT: + if (event->connect.is_connected) { + if (!validator->trace_mode) { +//deprecated gf_sc_add_video_listener(validator->compositor, &validator->video_listener); + } + + validator->root_odm = validator->compositor->root_scene->root_od; + } + break; + case GF_EVENT_CLICK: + case GF_EVENT_MOUSEUP: + case GF_EVENT_MOUSEDOWN: + case GF_EVENT_MOUSEOVER: + case GF_EVENT_MOUSEOUT: + case GF_EVENT_MOUSEMOVE: + case GF_EVENT_MOUSEWHEEL: + case GF_EVENT_KEYDOWN: + case GF_EVENT_TEXTINPUT: + return GF_TRUE; + case GF_EVENT_KEYUP: + if ((event->key.key_code == GF_KEY_END)&&(event->key.flags & GF_KEY_MOD_CTRL)) { + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + gf_sc_on_event(validator->compositor, &evt); + } + return GF_TRUE; + } + return GF_FALSE; +} + +static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event) +{ + GF_XMLNode *evt_node; + GF_XMLAttribute *att; + + GF_SAFEALLOC(evt_node, GF_XMLNode); + if (!evt_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event\n")); + return; + } + + switch (event->type) { + case GF_EVENT_CLICK: + case GF_EVENT_MOUSEUP: + case GF_EVENT_MOUSEDOWN: + case GF_EVENT_MOUSEOVER: + case GF_EVENT_MOUSEOUT: + case GF_EVENT_MOUSEMOVE: + case GF_EVENT_MOUSEWHEEL: + case GF_EVENT_KEYUP: + case GF_EVENT_KEYDOWN: + case GF_EVENT_TEXTINPUT: +#ifndef GPAC_DISABLE_SVG + evt_node->name = gf_strdup(gf_dom_event_get_name(event->type)); +#endif + break; + } + + if (!evt_node->name) { + gf_free(evt_node); + return; + } + + evt_node->attributes = gf_list_new(); + + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event time\n")); + return; + } + att->name = gf_strdup("time"); + att->value = (char*)gf_malloc(100); + sprintf(att->value, "%f", gf_scene_get_time(validator->compositor->root_scene)*1000); + gf_list_add(evt_node->attributes, att); + + switch (event->type) { + case GF_EVENT_CLICK: + case GF_EVENT_MOUSEUP: + case GF_EVENT_MOUSEDOWN: + case GF_EVENT_MOUSEOVER: + case GF_EVENT_MOUSEOUT: + case GF_EVENT_MOUSEMOVE: + case GF_EVENT_MOUSEWHEEL: + if (event->type == GF_EVENT_MOUSEDOWN || event->type == GF_EVENT_MOUSEUP) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("button"); + switch (event->mouse.button) { + case 0: + att->value = gf_strdup("Left"); + break; + case 1: + att->value = gf_strdup("Middle"); + break; + case 2: + att->value = gf_strdup("Right"); + break; + } + gf_list_add(evt_node->attributes, att); + } + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("x"); + att->value = (char*)gf_malloc(100); + sprintf(att->value, "%d", event->mouse.x); + gf_list_add(evt_node->attributes, att); + + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("y"); + att->value = (char*)gf_malloc(100); + sprintf(att->value, "%d", event->mouse.y); + gf_list_add(evt_node->attributes, att); + if (event->type == GF_EVENT_MOUSEWHEEL) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("wheel_pos"); + att->value = (char*)gf_malloc(100); + sprintf(att->value, "%f", FIX2FLT( event->mouse.wheel_pos) ); + gf_list_add(evt_node->attributes, att); + } + if (event->mouse.key_states & GF_KEY_MOD_SHIFT) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("shift"); + att->value = gf_strdup("true"); + gf_list_add(evt_node->attributes, att); + } + if (event->mouse.key_states & GF_KEY_MOD_CTRL) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("ctrl"); + att->value = gf_strdup("true"); + gf_list_add(evt_node->attributes, att); + } + if (event->mouse.key_states & GF_KEY_MOD_ALT) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("alt"); + att->value = gf_strdup("true"); + gf_list_add(evt_node->attributes, att); + } + break; + /*Key Events*/ + case GF_EVENT_KEYUP: + case GF_EVENT_KEYDOWN: + case GF_EVENT_LONGKEYPRESS: + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("key_identifier"); +#ifndef GPAC_DISABLE_SVG + att->value = gf_strdup(gf_dom_get_key_name(event->key.key_code)); +#endif + gf_list_add(evt_node->attributes, att); + if (event->key.flags & GF_KEY_MOD_SHIFT) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("shift"); + att->value = gf_strdup("true"); + gf_list_add(evt_node->attributes, att); + } + if (event->key.flags & GF_KEY_MOD_CTRL) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("ctrl"); + att->value = gf_strdup("true"); + gf_list_add(evt_node->attributes, att); + } + if (event->key.flags & GF_KEY_MOD_ALT) { + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("alt"); + att->value = gf_strdup("true"); + gf_list_add(evt_node->attributes, att); + } + break; + case GF_EVENT_TEXTINPUT: + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event info\n")); + return; + } + att->name = gf_strdup("unicode-char"); + att->value = (char*)gf_malloc(100); + sprintf(att->value, "%d", event->character.unicode_char); + gf_list_add(evt_node->attributes, att); + break; + } + gf_list_add(validator->xvs_node->content, evt_node); + /* adding an extra text node for line break in serialization */ + GF_SAFEALLOC(evt_node, GF_XMLNode); + if (!evt_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate event\n")); + return; + } + evt_node->type = GF_XML_TEXT_TYPE; + evt_node->name = gf_strdup("\n"); + gf_list_add(validator->xvs_node->content, evt_node); +} + +Bool validator_on_event_record(void *udta, GF_Event *event, Bool consumed_by_compositor) +{ + GF_Validator *validator = (GF_Validator *)udta; + Bool rec_event = GF_TRUE; + switch (event->type) { + case GF_EVENT_CONNECT: + if (event->connect.is_connected) { + if (!validator->trace_mode) { +//deprecated gf_sc_add_video_listener(validator->compositor, &validator->video_listener); + } + validator->root_odm = validator->compositor->root_scene->root_od; + } + break; + case GF_EVENT_KEYDOWN: + if (event->key.key_code == GF_KEY_INSERT) { + rec_event = GF_FALSE; + } else if (event->key.key_code == GF_KEY_PAGEDOWN) { + rec_event = GF_FALSE; + } else if (event->key.key_code == GF_KEY_PAGEUP) { + rec_event = GF_FALSE; + } else if (event->key.key_code == GF_KEY_END) { + rec_event = GF_FALSE; + } else if (event->key.key_code == GF_KEY_CONTROL) { + rec_event = GF_FALSE; + } else if (event->key.flags & GF_KEY_MOD_CTRL) { + rec_event = GF_FALSE; + } + break; + case GF_EVENT_KEYUP: + if (event->key.flags & GF_KEY_MOD_CTRL) { + rec_event = GF_FALSE; + if (event->key.key_code == GF_KEY_INSERT) { +#ifndef GPAC_DISABLE_AV_PARSERS + char *snap_name = validator_create_snapshot(validator); + validator_xvs_add_snapshot_node(validator, snap_name, gf_clock_time(validator->root_odm->ck)); + gf_free(snap_name); +#endif + } else if (event->key.key_code == GF_KEY_END) { + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + gf_sc_on_event(validator->compositor, &evt); + } else if (event->key.key_code == GF_KEY_F1) { + validator->snapshot_next_frame = GF_TRUE; + } + } else if (event->key.key_code == GF_KEY_PAGEDOWN) { + rec_event = GF_FALSE; + validator_xvs_close(validator); + gf_sc_disconnect(validator->compositor); +//deprecated gf_sc_remove_video_listener(validator->compositor, &validator->video_listener); + validator_xvs_next(validator, GF_FALSE); + } else if (event->key.key_code == GF_KEY_PAGEUP) { + rec_event = GF_FALSE; + validator_xvs_close(validator); + gf_sc_disconnect(validator->compositor); +//deprecated gf_sc_remove_video_listener(validator->compositor, &validator->video_listener); + validator_xvs_next(validator, GF_TRUE); + } else if (event->key.key_code == GF_KEY_CONTROL) { + rec_event = GF_FALSE; + } + break; + } + if (rec_event) { + validator_xvs_add_event_dom(validator, event); + } + return GF_FALSE; +} + +static void validator_xvl_open(GF_Validator *validator) +{ + GF_Err e; + u32 att_index; + validator->xvl_parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(validator->xvl_parser, validator->xvl_filename, NULL, NULL); + if (e != GF_OK) { + gf_xml_dom_del(validator->xvl_parser); + validator->xvl_parser = NULL; + return; + } + validator->xvl_node = gf_xml_dom_get_root(validator->xvl_parser); + if (!validator->xvl_node) { + gf_xml_dom_del(validator->xvl_parser); + validator->xvl_parser = NULL; + return; + } + att_index = 0; + while (1) { + GF_XMLAttribute *att = (GF_XMLAttribute*)gf_list_get(validator->xvl_node->attributes, att_index); + if (!att) break; + if (!strcmp(att->name, "content-base")) { + validator->test_base = gf_strdup(att->value); + } + att_index++; + } +} + +static void validator_xvl_close(GF_Validator *validator) +{ + if (validator->xvl_parser) { + /* writing the validation results */ + if (!validator->is_recording) { + FILE *xvl_fp; + char *xvl_content; + char result_filename[GF_MAX_PATH]; + char *dot; + xvl_content = gf_xml_dom_serialize(validator->xvl_node, GF_FALSE, GF_FALSE); + dot = gf_file_ext_start(validator->xvl_filename); + dot[0] = 0; + sprintf(result_filename, "%s-result.xml", validator->xvl_filename); + dot[0] = '.'; + xvl_fp = gf_fopen(result_filename, "wt"); + gf_fwrite(xvl_content, strlen(xvl_content), xvl_fp); + gf_fclose(xvl_fp); + gf_free(xvl_content); + } + gf_xml_dom_del(validator->xvl_parser); + validator->xvl_parser = NULL; + validator->xvl_filename = NULL; + } +} + +static void validator_xvl_get_next_xvs(GF_Validator *validator, Bool reverse) +{ + u32 xvl_att_index; + validator->xvs_node = NULL; + validator->xvs_filename = NULL; + validator->test_filename = NULL; + while (1) { + validator->xvs_node_in_xvl = (GF_XMLNode*)gf_list_get(validator->xvl_node->content, validator->xvl_node_index); + if (!validator->xvs_node_in_xvl) { + return; + } + if (validator->xvs_node_in_xvl->type != GF_XML_NODE_TYPE) { + if (!reverse) validator->xvl_node_index++; + else validator->xvl_node_index--; + continue; + } + xvl_att_index = 0; + while(1) { + GF_XMLAttribute *att = (GF_XMLAttribute*)gf_list_get(validator->xvs_node_in_xvl->attributes, xvl_att_index); + if (!att) break; + if (!strcmp(att->name, "scenario")) { + validator->xvs_filename = att->value; + } else if (!strcmp(att->name, "content")) { + validator->test_filename = att->value; + } + xvl_att_index++; + } + if (!reverse) validator->xvl_node_index++; + else validator->xvl_node_index--; + break; + } +} + +static Bool validator_xvs_open(GF_Validator *validator) +{ + GF_Err e; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Validator] Opening Validation Script: %s\n", validator->xvs_filename)); + validator->snapshot_number = 0; + validator->xvs_parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(validator->xvs_parser, validator->xvs_filename, NULL, NULL); + if (e != GF_OK) { + if (validator->is_recording) { + GF_SAFEALLOC(validator->xvs_node, GF_XMLNode); + if (!validator->xvs_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate root node\n")); + return 0; + } + + validator->xvs_node->name = gf_strdup("TestValidationScript"); + validator->xvs_node->attributes = gf_list_new(); + validator->xvs_node->content = gf_list_new(); + validator->owns_root = GF_TRUE; + } else { + gf_xml_dom_del(validator->xvs_parser); + validator->xvs_parser = NULL; + return GF_FALSE; + } + } else { + validator->xvs_node = gf_xml_dom_get_root(validator->xvs_parser); + } + /* Get the file name from the XVS if not found in the XVL */ + if (!validator->test_filename) { + GF_XMLAttribute *att; + GF_XMLAttribute *att_file; + u32 att_index = 0; + att_file = NULL; + while (1) { + att = (GF_XMLAttribute*)gf_list_get(validator->xvs_node->attributes, att_index); + if (!att) { + break; + } else if (!strcmp(att->name, "file")) { + att_file = att; + } + att_index++; + } + + if (!att_file) { + gf_xml_dom_del(validator->xvs_parser); + validator->xvs_parser = NULL; + validator->xvs_node = NULL; + return GF_FALSE; + } else { + char *sep; + sep = strrchr(att_file->value, GF_PATH_SEPARATOR); + if (!sep) { + validator->test_filename = att_file->value; + } else { + sep[0] = 0; + validator->test_base = gf_strdup(att_file->value); + sep[0] = GF_PATH_SEPARATOR; + validator->test_filename = sep+1; + } + } + } + if (validator->is_recording) { + GF_XMLNode *node; + /* Removing prerecorded interactions */ + while (gf_list_count(validator->xvs_node->content)) { + GF_XMLNode *child = (GF_XMLNode *)gf_list_last(validator->xvs_node->content); + gf_list_rem_last(validator->xvs_node->content); + gf_xml_dom_node_del(child); + } + /* adding an extra text node for line break in serialization */ + GF_SAFEALLOC(node, GF_XMLNode); + if (!node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate node\n")); + return GF_FALSE; + } + node->type = GF_XML_TEXT_TYPE; + node->name = gf_strdup("\n"); + gf_list_add(validator->xvs_node->content, node); + } else { + validator->xvs_result = GF_TRUE; + } + return GF_TRUE; +} + +static void validator_xvs_close(GF_Validator *validator) +{ + if (validator->xvs_parser) { + if (validator->is_recording) { + FILE *xvs_fp; + char *xvs_content; + GF_XMLAttribute *att_file = NULL; + u32 att_index = 0; + + if (!validator->trace_mode) { + GF_XMLAttribute *att; + while (1) { + att = (GF_XMLAttribute*)gf_list_get(validator->xvs_node->attributes, att_index); + if (!att) { + break; + } else if (!strcmp(att->name, "file")) { + att_file = att; + } + att_index++; + } + + if (!att_file) { + GF_SAFEALLOC(att, GF_XMLAttribute); + if (!att) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate file attribute\n")); + return; + } + att->name = gf_strdup("file"); + gf_list_add(validator->xvs_node->attributes, att); + } else { + att = att_file; + if (att->value) gf_free(att->value); + } + if (validator->test_base) { + char filename[100]; + sprintf(filename, "%s%c%s", validator->test_base, GF_PATH_SEPARATOR, validator->test_filename); + att->value = gf_strdup(filename); + } else { + att->value = gf_strdup(validator->test_filename); + } + } + xvs_content = gf_xml_dom_serialize(validator->xvs_node, GF_FALSE, GF_FALSE); + xvs_fp = gf_fopen(validator->xvs_filename, "wt"); + gf_fwrite(xvs_content, strlen(xvs_content), xvs_fp); + gf_fclose(xvs_fp); + gf_free(xvs_content); + if (validator->owns_root) + gf_xml_dom_node_del(validator->xvs_node); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Validator] XVS Result : %s\n", (validator->xvs_result?"Success":"Failure"))); + if (validator->xvs_node_in_xvl) { + GF_XMLAttribute *att; + GF_XMLAttribute *att_result = NULL; + u32 att_index = 0; + while (1) { + att = (GF_XMLAttribute*)gf_list_get(validator->xvs_node_in_xvl->attributes, att_index); + if (!att) { + break; + } else if (!strcmp(att->name, "result")) { + att_result = att; + } + att_index++; + } + if (!att_result) { + GF_SAFEALLOC(att_result, GF_XMLAttribute); + if (!att_result) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Failed to allocate result attribute\n")); + return; + } + att_result->name = gf_strdup("result"); + gf_list_add(validator->xvs_node_in_xvl->attributes, att_result); + } + if (att_result->value) gf_free(att_result->value); + att_result->value = gf_strdup(validator->xvs_result ? "pass" : "fail"); + } + } + gf_xml_dom_del(validator->xvs_parser); + validator->xvs_parser = NULL; + } + validator->xvs_node = NULL; + validator->xvs_node_in_xvl = NULL; + validator->xvs_filename = NULL; + validator->test_filename = NULL; + validator->root_odm = NULL; + validator->xvs_event_index = 0; + validator->snapshot_number = 0; +} + +static void validator_test_open(GF_Validator *validator) +{ + if (!validator->trace_mode) { + char filename[100]; + if (validator->test_base) + sprintf(filename, "%s%c%s", validator->test_base, GF_PATH_SEPARATOR, validator->test_filename); + else + sprintf(filename, "%s", validator->test_filename); + +//deprecated gf_sc_add_video_listener(validator->compositor, &validator->video_listener); + if (validator->is_recording) + validator->snapshot_next_frame = GF_TRUE; + gf_sc_connect_from_time_ex(validator->compositor, filename, 0, 0, 0, NULL); + + } +// validator->ck = validator->compositor->root_scene->scene_codec ? validator->compositor->root_scene->scene_codec->ck : validator->compositor->root_scene->dyn_ck; +} + +static Bool validator_xvs_next(GF_Validator *validator, Bool reverse) +{ + if (validator->xvl_node) { + validator_xvl_get_next_xvs(validator, reverse); + if (validator->xvs_filename) { + validator_xvs_open(validator); + if (!validator->xvs_node) { + return GF_FALSE; + } + if (validator->test_filename) { + validator_test_open(validator); + } else { + validator_xvs_close(validator); + return GF_FALSE; + } + } else { + return GF_FALSE; + } + return GF_TRUE; + } else { + return GF_FALSE; + } +} + +static Bool validator_load_event(GF_Validator *validator) +{ + GF_XMLNode *event_node; + u32 att_index; + + memset(&validator->next_event, 0, sizeof(GF_Event)); + validator->evt_loaded = GF_FALSE; + validator->next_event_snapshot = GF_FALSE; + + if (!validator->xvs_node) { + validator->compositor->validator_mode = GF_FALSE; + return GF_FALSE; + } + + while (1) { + event_node = (GF_XMLNode*)gf_list_get(validator->xvs_node->content, validator->xvs_event_index); + if (!event_node) { + return GF_FALSE; + } else if (event_node->type == GF_XML_NODE_TYPE) { + validator->xvs_event_index++; + break; + } else { + validator->xvs_event_index++; + } + } + + if (!strcmp(event_node->name, "snapshot")) { + validator->next_event_snapshot = GF_TRUE; + } else { +#ifndef GPAC_DISABLE_SVG + validator->next_event.type = gf_dom_event_type_by_name(event_node->name); + if (validator->next_event.type == GF_EVENT_UNKNOWN) +#endif + { + return GF_TRUE; + } + } + + att_index = 0; + while (1) { + GF_XMLAttribute *att = (GF_XMLAttribute*)gf_list_get(event_node->attributes, att_index); + if (!att) break; + if (!strcmp(att->name, "time")) { + validator->next_time = atoi(att->value); + } else if (!strcmp(att->name, "button")) { + if (!strcmp(att->value, "Left")) { + validator->next_event.mouse.button = 0; + } else if (!strcmp(att->value, "Middle")) { + validator->next_event.mouse.button = 1; + } else if (!strcmp(att->value, "Right")) { + validator->next_event.mouse.button = 2; + } + } else if (!strcmp(att->name, "x")) { + validator->next_event.mouse.x = atoi(att->value); + } else if (!strcmp(att->name, "y")) { + validator->next_event.mouse.y = atoi(att->value); + } else if (!strcmp(att->name, "wheel_pos")) { + validator->next_event.mouse.wheel_pos = FLT2FIX(atof(att->value)); + } else if (!strcmp(att->name, "shift") && !strcmp(att->value, "true")) { + validator->next_event.mouse.key_states |= GF_KEY_MOD_SHIFT; + } else if (!strcmp(att->name, "alt") && !strcmp(att->value, "true")) { + validator->next_event.mouse.key_states |= GF_KEY_MOD_ALT; + } else if (!strcmp(att->name, "ctrl") && !strcmp(att->value, "true")) { + validator->next_event.mouse.key_states |= GF_KEY_MOD_CTRL; +#ifndef GPAC_DISABLE_SVG + } else if (!strcmp(att->name, "key_identifier")) { + validator->next_event.key.key_code = gf_dom_get_key_type(att->value); +#endif + } else if (!strcmp(att->name, "unicode-char")) { + validator->next_event.character.unicode_char = atoi(att->value); + } + att_index++; + } + validator->evt_loaded = GF_TRUE; + validator->compositor->sys_frames_pending = GF_TRUE; + return GF_TRUE; +} + +static Bool validator_process(GF_CompositorExt *termext, u32 action, void *param) +{ + const char *opt; + GF_Validator *validator = (GF_Validator*)termext->udta; + + switch (action) { + + /* Upon starting of the terminal, we parse (possibly an XVL file), an XVS file, and start the first test sequence */ + case GF_COMPOSITOR_EXT_START: + validator->compositor = (GF_Compositor *) param; + + /* Check if the validator should be loaded and in which mode */ + opt = gf_opts_get_key("Validator", "Mode"); + if (!opt) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("Validator missing configuration, stopping.\n")); + return GF_FALSE; + } else if (!strcmp(opt, "Play") ) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator starting in playback mode.\n")); + validator->is_recording = GF_FALSE; + //this will indicate to the compositor to increment scene time even though no new changes + validator->compositor->validator_mode = GF_TRUE; + } else if (!strcmp(opt, "Record")) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator starting in recording mode.\n")); + validator->is_recording = GF_TRUE; + } else if (!strcmp(opt, "Disable")) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator is disabled.\n")); + return GF_FALSE; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("Validator configuration using wrong mode, stopping.\n")); + return GF_FALSE; + } + + gf_opts_set_key("Validator", "Mode", "Disable"); + + /* initializes the validator and starts */ + validator->xvs_filename = NULL; + validator->xvl_filename = (char *)gf_opts_get_key("Validator", "XVL"); + if (!validator->xvl_filename) { + validator->xvs_filename = (char *)gf_opts_get_key("Validator", "XVS"); + if (!validator->xvs_filename) { + validator->xvs_filename = (char *)gf_opts_get_key("Validator", "Trace"); + if (!validator->xvs_filename) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("Validator configuration without input, stopping.\n")); + return GF_FALSE; + } + validator->test_filename = validator->xvs_filename; + validator->trace_mode = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator using trace file: %s\n", validator->xvs_filename)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator using scenario file: %s\n", validator->xvs_filename)); + } + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator using scenario playlist: %s\n", validator->xvl_filename)); + } + + /* TODO: if start returns 0, the module is not loaded, so the above init (filter registration) is not removed, + should probably return 1 all the time, to make sure stop is called */ + if (validator->xvl_filename) { + validator_xvl_open(validator); + if (!validator->xvl_node) { + return GF_FALSE; + } + validator_xvs_next(validator, GF_FALSE); + if (!validator->xvs_node) { + return GF_FALSE; + } + } else if (validator->xvs_filename) { + validator_xvs_open(validator); + if (!validator->xvs_node) { + return GF_FALSE; + } + if (validator->test_filename) { + validator_test_open(validator); + } else { + validator_xvs_close(validator); + return GF_FALSE; + } + } else { + return GF_FALSE; + } + + validator->evt_filter.udta = validator; + if (!validator->is_recording) { + validator->evt_filter.on_event = validator_on_event_play; + termext->caps |= GF_COMPOSITOR_EXTENSION_NOT_THREADED; + } else { + validator->evt_filter.on_event = validator_on_event_record; + } + gf_filter_add_event_listener(validator->compositor->filter, &validator->evt_filter); + validator->video_listener.udta = validator; + validator->video_listener.on_video_frame = validator_on_video_frame; + validator->video_listener.on_video_reconfig = validator_on_video_reconfig; + + + if (!validator->is_recording) { + validator_load_event(validator); + } + return GF_TRUE; + + /* when the terminal stops, we close the XVS parser and XVL parser if any, restore the config, + and free all validator data (the validator will be destroyed when the module is unloaded) + Note: we don't need to disconnect the terminal since it's already stopping */ + case GF_COMPOSITOR_EXT_STOP: + gf_filter_remove_event_listener(validator->compositor->filter, &validator->evt_filter); + validator_xvs_close(validator); + validator_xvl_close(validator); + validator->compositor = NULL; + if (validator->test_base) { + gf_free(validator->test_base); + validator->test_base = NULL; + } + /*auto-disable the recording by default*/ + if (!validator->trace_mode) { + if (validator->is_recording ) { + gf_opts_set_key("Validator", "Mode", "Play"); + } else { + gf_opts_set_key("Validator", "Mode", "Disable"); + } + } + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Stopping validator\n")); + break; + + /* When called in the main loop of the terminal, we don't do anything in the recording mode. + In the playing/validating mode, we need to check if an event needs to be dispatched or if snapshots need to be made, + until there is no more event, in which case we trigger either the load of the next XVS or the quit */ + case GF_COMPOSITOR_EXT_PROCESS: + /* if the time is right, dispatch the event and load the next one */ + while (!validator->is_recording && validator->evt_loaded && validator->root_odm && validator->root_odm->ck && (validator->next_time <= gf_clock_time(validator->root_odm->ck) )) { + Bool has_more_events; + //u32 diff = gf_clock_time(validator->ck) - validator->next_time; + //GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Time diff: evt_time=%d clock_time = %d, diff=%d\n", validator->next_time, gf_clock_time(validator->ck), diff)); + if (validator->next_event_snapshot) { +#ifndef GPAC_DISABLE_AV_PARSERS + Bool res; + char *snap_name = validator_create_snapshot(validator); + gf_free(snap_name); + res = validator_compare_snapshots(validator); + validator->xvs_result &= res; + validator->next_event_snapshot = GF_FALSE; +#endif + } else { + gf_sc_on_event(validator->compositor, &validator->next_event); + } + has_more_events = validator_load_event(validator); + if (!has_more_events) { + validator_xvs_close(validator); + if (! validator->trace_mode) { + gf_sc_disconnect(validator->compositor); +//deprecated gf_sc_remove_video_listener(validator->compositor, &validator->video_listener); + validator_xvs_next(validator, GF_FALSE); + if (!validator->xvs_node) { + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + gf_sc_on_event(validator->compositor, &evt); + } else { + if (!validator->is_recording) { + validator_load_event(validator); + } + } + } + } + } + break; + } + return GF_FALSE; +} + + +GF_CompositorExt *validator_new() +{ + GF_CompositorExt *dr; + GF_Validator *validator; + dr = (GF_CompositorExt*)gf_malloc(sizeof(GF_CompositorExt)); + memset(dr, 0, sizeof(GF_CompositorExt)); + GF_REGISTER_MODULE_INTERFACE(dr, GF_COMPOSITOR_EXT_INTERFACE, "GPAC Test Validator", "gpac distribution"); + + GF_SAFEALLOC(validator, GF_Validator); + if (!validator) { + gf_free(dr); + return NULL; + } + dr->process = validator_process; + dr->udta = validator; + return dr; +} + + +void validator_delete(GF_BaseInterface *ifce) +{ + GF_CompositorExt *dr = (GF_CompositorExt *) ifce; + GF_Validator *validator = (GF_Validator*)dr->udta; + gf_free(validator); + gf_free(dr); +} + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_COMPOSITOR_EXT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_COMPOSITOR_EXT_INTERFACE) return (GF_BaseInterface *)validator_new(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_COMPOSITOR_EXT_INTERFACE: + validator_delete(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( validator ) diff --git a/modules/wav_out/Makefile b/modules/wav_out/Makefile new file mode 100644 index 0000000..20920bc --- /dev/null +++ b/modules/wav_out/Makefile @@ -0,0 +1,44 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/wav_out + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -DDISABLE_WAVE_EX + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS=wav_out.o + + +SRCS := $(OBJS:.o=.c) + +LIB=gm_wav_audio$(DYN_LIB_SUFFIX) +#LDFLAGS+=-export-symbols wav_out.def + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/wav_out/wav_out.c b/modules/wav_out/wav_out.c new file mode 100644 index 0000000..7c5c1db --- /dev/null +++ b/modules/wav_out/wav_out.c @@ -0,0 +1,509 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / wave audio render module + * + * 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/modules/audio_out.h> +#include <windows.h> + +#define MAX_AUDIO_BUFFER 30 + +typedef struct +{ + HWAVEOUT hwo; + WAVEHDR wav_hdr[MAX_AUDIO_BUFFER]; + WAVEFORMATEX fmt; + + u32 num_buffers; + u32 buffer_size; + + Bool exit_request; + HANDLE event; + + u32 vol, pan; + u32 delay, total_length_ms; + + Bool force_config; + u32 cfg_num_buffers, cfg_duration; + + char *wav_buf; +} WAVContext; + + + +#if !defined(DISABLE_WAVE_EX) && !defined(_WIN32_WCE) +/*IF YOU CAN'T COMPILE WAVEOUT TRY TO COMMENT THIS - note waveOut & multichannel is likely not to work*/ +#define USE_WAVE_EXT +#endif + + +#ifdef USE_WAVE_EXT + +/*for channel codes*/ +#include <gpac/constants.h> + +#ifndef WAVE_FORMAT_EXTENSIBLE +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +#ifndef SPEAKER_FRONT_LEFT +# define SPEAKER_FRONT_LEFT 0x1 +# define SPEAKER_FRONT_RIGHT 0x2 +# define SPEAKER_FRONT_CENTER 0x4 +# define SPEAKER_LOW_FREQUENCY 0x8 +# define SPEAKER_BACK_LEFT 0x10 +# define SPEAKER_BACK_RIGHT 0x20 +# define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +# define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +# define SPEAKER_BACK_CENTER 0x100 +# define SPEAKER_SIDE_LEFT 0x200 +# define SPEAKER_SIDE_RIGHT 0x400 +# define SPEAKER_TOP_CENTER 0x800 +# define SPEAKER_TOP_FRONT_LEFT 0x1000 +# define SPEAKER_TOP_FRONT_CENTER 0x2000 +# define SPEAKER_TOP_FRONT_RIGHT 0x4000 +# define SPEAKER_TOP_BACK_LEFT 0x8000 +# define SPEAKER_TOP_BACK_CENTER 0x10000 +# define SPEAKER_TOP_BACK_RIGHT 0x20000 +# define SPEAKER_RESERVED 0x80000000 +#endif + +#ifndef _WAVEFORMATEXTENSIBLE_ +typedef struct { + WAVEFORMATEX Format; + union { + WORD wValidBitsPerSample; /* bits of precision */ + WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */ + WORD wReserved; /* If neither applies, set to zero. */ + } Samples; + DWORD dwChannelMask; /* which channels are */ + /* present in stream */ + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif + +const static GUID GPAC_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001,0x0000,0x0010, {0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71} }; + +#pragma message("Using multichannel audio extensions") + +#endif + + + +#define WAVCTX() WAVContext *ctx = (WAVContext *)dr->opaque; + +static GF_Err WAV_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration) +{ + WAVCTX(); + + ctx->force_config = (num_buffers && total_duration) ? GF_TRUE : GF_FALSE; + ctx->cfg_num_buffers = num_buffers; + if (ctx->cfg_num_buffers <= 1) ctx->cfg_num_buffers = 2; + ctx->cfg_duration = total_duration; + if (!ctx->force_config) ctx->num_buffers = 6; + + return GF_OK; +} + +static void CALLBACK WaveProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + GF_AudioOutput *dr = (GF_AudioOutput *) dwInstance; + WAVCTX(); + + if (uMsg != WOM_DONE) return; + if (ctx->exit_request) return; + SetEvent(ctx->event); +} + + +static void close_waveform(GF_AudioOutput *dr) +{ + WAVCTX(); + + if (!ctx->event) return; + + /*brute-force version, actually much safer on winCE*/ +#ifdef _WIN32_WCE + ctx->exit_request = GF_TRUE; + SetEvent(ctx->event); + waveOutReset(ctx->hwo); + waveOutClose(ctx->hwo); + if (ctx->wav_buf) gf_free(ctx->wav_buf); + ctx->wav_buf = NULL; + CloseHandle(ctx->event); + ctx->event = NULL; + ctx->exit_request = GF_FALSE; +#else + ctx->exit_request = GF_TRUE; + SetEvent(ctx->event); + if (ctx->hwo) { + u32 i; + Bool not_done; + MMRESULT res; + /*wait for all buffers to complete, otherwise this locks waveOutReset*/ + while (1) { + not_done = GF_FALSE; + for (i=0 ; i< ctx->num_buffers; i++) { + if (! (ctx->wav_hdr[i].dwFlags & WHDR_DONE)) { + not_done = GF_TRUE; + break; + } + } + if (!not_done) break; + gf_sleep(60); + } + /*waveOutReset gives unpredictable results on PocketPC, so just close right away*/ + while (1) { + res = waveOutClose(ctx->hwo); + if (res == MMSYSERR_NOERROR) break; + } + ctx->hwo = NULL; + } + if (ctx->wav_buf) gf_free(ctx->wav_buf); + ctx->wav_buf = NULL; + CloseHandle(ctx->event); + ctx->event = NULL; + ctx->exit_request = GF_FALSE; +#endif +} + +static void WAV_Shutdown(GF_AudioOutput *dr) +{ + close_waveform(dr); +} + + +/*we assume what was asked is what we got*/ +static GF_Err WAV_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) +{ + u32 i, retry; + HRESULT hr; + WAVEFORMATEX *fmt; +#ifdef USE_WAVE_EXT + WAVEFORMATEXTENSIBLE format_ex; +#endif + + WAVCTX(); + + if (!ctx) return GF_BAD_PARAM; + + /*reset*/ + close_waveform(dr); + +#ifndef USE_WAVE_EXT + if (*NbChannels>2) *NbChannels=2; +#endif + + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S24: + case GF_AUDIO_FMT_S32: + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + break; + } + + memset (&ctx->fmt, 0, sizeof(ctx->fmt)); + ctx->fmt.cbSize = sizeof(WAVEFORMATEX); + ctx->fmt.wFormatTag = WAVE_FORMAT_PCM; + ctx->fmt.nChannels = *NbChannels; + ctx->fmt.wBitsPerSample = gf_audio_fmt_bit_depth(*audioFormat); + ctx->fmt.nSamplesPerSec = *SampleRate; + ctx->fmt.nBlockAlign = ctx->fmt.wBitsPerSample * ctx->fmt.nChannels / 8; + ctx->fmt.nAvgBytesPerSec = *SampleRate * ctx->fmt.nBlockAlign; + fmt = &ctx->fmt; + +#ifdef USE_WAVE_EXT + if (channel_cfg && ctx->fmt.nChannels>2) { + memset(&format_ex, 0, sizeof(WAVEFORMATEXTENSIBLE)); + format_ex.Format = ctx->fmt; + format_ex.Format.cbSize = 22; + format_ex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + format_ex.SubFormat = GPAC_KSDATAFORMAT_SUBTYPE_PCM; + format_ex.Samples.wValidBitsPerSample = ctx->fmt.wBitsPerSample; + format_ex.dwChannelMask = 0; + if (channel_cfg & GF_AUDIO_CH_FRONT_LEFT) format_ex.dwChannelMask |= SPEAKER_FRONT_LEFT; + if (channel_cfg & GF_AUDIO_CH_FRONT_RIGHT) format_ex.dwChannelMask |= SPEAKER_FRONT_RIGHT; + if (channel_cfg & GF_AUDIO_CH_FRONT_CENTER) format_ex.dwChannelMask |= SPEAKER_FRONT_CENTER; + if (channel_cfg & GF_AUDIO_CH_LFE) format_ex.dwChannelMask |= SPEAKER_LOW_FREQUENCY; + if (channel_cfg & GF_AUDIO_CH_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT; + if (channel_cfg & GF_AUDIO_CH_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT; + if (channel_cfg & GF_AUDIO_CH_REAR_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT; + fmt = (WAVEFORMATEX *) &format_ex; + } +#endif + + /* Open a waveform device for output using window callback. */ + retry = 10; + while (retry) { + hr = waveOutOpen((LPHWAVEOUT)&ctx->hwo, WAVE_MAPPER, &ctx->fmt, (DWORD_PTR) WaveProc, (DWORD_PTR) dr, + CALLBACK_FUNCTION | WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT + ); + + if (hr == MMSYSERR_NOERROR) break; + /*couldn't open audio*/ + if (hr != MMSYSERR_ALLOCATED) return GF_IO_ERR; + retry--; + } + if (hr != MMSYSERR_NOERROR) return GF_IO_ERR; + ctx->fmt.nBlockAlign = fmt->nBlockAlign; + ctx->fmt.nAvgBytesPerSec = fmt->nAvgBytesPerSec; + + if (!ctx->force_config) { + /*one wave buffer size*/ + ctx->buffer_size = 1024 * ctx->fmt.nBlockAlign; + ctx->num_buffers = 2; + } else { + ctx->num_buffers = ctx->cfg_num_buffers; + ctx->buffer_size = (ctx->fmt.nAvgBytesPerSec * ctx->cfg_duration) / (1000 * ctx->cfg_num_buffers); + } + + ctx->event = CreateEvent( NULL, FALSE, FALSE, NULL); + + /*make sure we're aligned*/ + while (ctx->buffer_size % ctx->fmt.nBlockAlign) ctx->buffer_size++; + + ctx->wav_buf = (char*)gf_malloc(ctx->buffer_size*ctx->num_buffers*sizeof(char)); + memset(ctx->wav_buf, 0, ctx->buffer_size*ctx->num_buffers*sizeof(char)); + + /*setup wave headers*/ + for (i=0 ; i < ctx->num_buffers; i++) { + memset(& ctx->wav_hdr[i], 0, sizeof(WAVEHDR)); + ctx->wav_hdr[i].dwBufferLength = ctx->buffer_size; + ctx->wav_hdr[i].lpData = & ctx->wav_buf[i*ctx->buffer_size]; + ctx->wav_hdr[i].dwFlags = WHDR_DONE; + waveOutPrepareHeader(ctx->hwo, &ctx->wav_hdr[i], sizeof(WAVEHDR)); + waveOutWrite(ctx->hwo, &ctx->wav_hdr[i], sizeof(WAVEHDR)); + Sleep(1); + } + ctx->total_length_ms = 1000 * ctx->num_buffers * ctx->buffer_size / ctx->fmt.nAvgBytesPerSec; + /*initial delay is full buffer size*/ + ctx->delay = ctx->total_length_ms; + return GF_OK; +} + +static void WAV_WriteAudio(GF_AudioOutput *dr) +{ + LPWAVEHDR hdr; + u32 i; + WAVCTX(); + + if (!ctx->hwo) return; + + WaitForSingleObject(ctx->event, INFINITE); + + if (ctx->exit_request) return; + + for (i=0; i<ctx->num_buffers; i++) { + /*get buffer*/ + hdr = &ctx->wav_hdr[i]; + + if (hdr->dwFlags & WHDR_DONE) { + waveOutUnprepareHeader(ctx->hwo, hdr, sizeof(WAVEHDR)); + + hdr->dwBufferLength = ctx->buffer_size; + /*update delay*/ + ctx->delay = 1000 * i * ctx->buffer_size / ctx->fmt.nAvgBytesPerSec; + /*fill it*/ + hdr->dwBufferLength = dr->FillBuffer(dr->audio_renderer, hdr->lpData, ctx->buffer_size); + hdr->dwFlags = 0; + waveOutPrepareHeader(ctx->hwo, hdr, sizeof(WAVEHDR)); + /*write it*/ + waveOutWrite(ctx->hwo, hdr, sizeof(WAVEHDR)); + } + } +} + +static void WAV_Play(GF_AudioOutput *dr, u32 PlayType) +{ +#if 0 + u32 i; + WAVCTX(); + + switch (PlayType) { + case 0: + waveOutPause(ctx->hwo); + break; + case 2: + for (i=0; i<ctx->num_buffers; i++) { + LPWAVEHDR hdr = &ctx->wav_hdr[i]; + memset(&hdr->lpData, 0, sizeof(char)*ctx->buffer_size); + } + case 1: + waveOutRestart(ctx->hwo); + break; + } +#endif +} + +#ifndef _WIN32_WCE +static void set_vol_pan(WAVContext *ctx) +{ + WORD rV, lV; + /*in wave, volume & pan are specified as a DWORD. LOW word is LEFT channel, HIGH is right - iPaq doesn't support that*/ + lV = (WORD) (ctx->vol * ctx->pan / 100); + rV = (WORD) (ctx->vol * (100 - ctx->pan) / 100); + + waveOutSetVolume(ctx->hwo, MAKELONG(lV, rV) ); +} + +static void WAV_SetVolume(GF_AudioOutput *dr, u32 Volume) +{ + WAVCTX(); + if (Volume > 100) Volume = 100; + ctx->vol = Volume * 0xFFFF / 100; + set_vol_pan(ctx); +} + +static void WAV_SetPan(GF_AudioOutput *dr, u32 Pan) +{ + WAVCTX(); + if (Pan > 100) Pan = 100; + ctx->pan = Pan; + set_vol_pan(ctx); +} + +#else +/*too much trouble with iPaq ...*/ +static void WAV_SetVolume(GF_AudioOutput *dr, u32 Volume) { } +static void WAV_SetPan(GF_AudioOutput *dr, u32 Pan) { } + +#endif + + +static GF_Err WAV_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample) +{ + /*iPaq's output frequencies available*/ +#ifdef _WIN32_WCE + switch (*desired_samplerate) { + case 11025: + case 22050: + *desired_samplerate = 22050; + break; + case 8000: + case 16000: + case 32000: + *desired_samplerate = 44100; + break; + case 24000: + case 48000: + *desired_samplerate = 44100; + break; + case 44100: + *desired_samplerate = 44100; + break; + default: + break; + } + return GF_OK; +#else + return GF_OK; +#endif +} + +static u32 WAV_GetAudioDelay(GF_AudioOutput *dr) +{ + WAVCTX(); + return ctx->delay; +} + +static u32 WAV_GetTotalBufferTime(GF_AudioOutput *dr) +{ + WAVCTX(); + return ctx->total_length_ms; +} + + +void *NewWAVRender() +{ + GF_AudioOutput *driv; + WAVContext *ctx = (WAVContext*)gf_malloc(sizeof(WAVContext)); + memset(ctx, 0, sizeof(WAVContext)); + ctx->num_buffers = 10; + ctx->pan = 50; + ctx->vol = 100; + driv = (GF_AudioOutput*)gf_malloc(sizeof(GF_AudioOutput)); + memset(driv, 0, sizeof(GF_AudioOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "Windows MME Output", "gpac distribution") + + driv->opaque = ctx; + + driv->SelfThreaded = GF_FALSE; + driv->Setup = WAV_Setup; + driv->Shutdown = WAV_Shutdown; + driv->Configure= WAV_Configure; + driv->GetAudioDelay = WAV_GetAudioDelay; + driv->GetTotalBufferTime = WAV_GetTotalBufferTime; + driv->SetVolume = WAV_SetVolume; + driv->SetPan = WAV_SetPan; + driv->Play = WAV_Play; + driv->QueryOutputSampleRate = WAV_QueryOutputSampleRate; + driv->WriteAudio = WAV_WriteAudio; + + return driv; +} + +void DeleteWAVRender(void *ifce) +{ + GF_AudioOutput *dr = (GF_AudioOutput *) ifce; + WAVContext *ctx = (WAVContext *)dr->opaque; + gf_free(ctx); + gf_free(dr); +} + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_AUDIO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) + return (GF_BaseInterface*)NewWAVRender(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_AUDIO_OUTPUT_INTERFACE: + DeleteWAVRender((GF_AudioOutput *) ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( wave_out ) diff --git a/modules/x11_out/Makefile b/modules/x11_out/Makefile new file mode 100644 index 0000000..841e4e8 --- /dev/null +++ b/modules/x11_out/Makefile @@ -0,0 +1,84 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/x11_out + +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 + +CFLAGS+=-I"$(SRC_PATH)/include" + + +ifeq ($(X11_INC_PATH),) +else +CFLAGS+=-I$(X11_INC_PATH) +endif + +ifeq ($(X11_LIB_PATH),) +else +EXTRALIBS+=-L$(X11_LIB_PATH) +endif + +ifeq ($(USE_X11_XV),yes) +CFLAGS+=-DGPAC_HAS_X11_XV +EXTRALIBS+=-lXv +endif + +ifeq ($(USE_X11_SHM),yes) +CFLAGS+=-DGPAC_HAS_X11_SHM +EXTRALIBS+=-lXext +endif + +ifeq ($(HAS_OPENGL),yes) +ifeq ($(GPAC_USE_TINYGL),yes) +else +CFLAGS+=$(OGL_INCLS) +EXTRALIBS+=$(OGL_LIBS) +ifeq ($(CONFIG_DARWIN),yes) +EXTRALIBS+=-lGL -lGLU +endif +endif +endif + +#common obj +OBJS=x11_out.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_x11_out$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +#LDFLAGS+=-export-symbols +endif + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -lX11 -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) +ifeq ($(STATICBUILD),yes) + $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_x11_out-static$(DYN_LIB_SUFFIX) $(OBJS) -lX11 -L../../bin/gcc -lgpac_static $(EXTRALIBS) $(LDFLAGS) +endif + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/x11_out/x11_out.c b/modules/x11_out/x11_out.c new file mode 100644 index 0000000..e641dd8 --- /dev/null +++ b/modules/x11_out/x11_out.c @@ -0,0 +1,1656 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2020 + * All rights reserved + * + * This file is part of GPAC / X11 video output module + * + * 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 "x11_out.h" +#include <gpac/constants.h> +#include <gpac/utf.h> +#include <gpac/user.h> +#include <sys/time.h> +#include <X11/XKBlib.h> +#include <X11/Xatom.h> +#include <errno.h> + +enum +{ + GF_PIXEL_IYUV = GF_4CC('I', 'Y', 'U', 'V'), + GF_PIXEL_I420 = GF_4CC('I', '4', '2', '0'), + /*!YUV packed format*/ + GF_PIXEL_UYNV = GF_4CC('U', 'Y', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_YUNV = GF_4CC('Y', 'U', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_V422 = GF_4CC('V', '4', '2', '2'), + + GF_PIXEL_YV12 = GF_4CC('Y', 'V', '1', '2'), + GF_PIXEL_Y422 = GF_4CC('Y', '4', '2', '2'), + GF_PIXEL_YUY2 = GF_4CC('Y', 'U', 'Y', '2'), +}; + + +void X11_SetupWindow (GF_VideoOutput * vout); + +#ifdef GPAC_HAS_OPENGL +PFNGLXSWAPINTERVALEXTPROC my_glXSwapIntervalEXT; +PFNGLXSWAPINTERVALMESAPROC my_glXSwapIntervalMESA; +PFNGLXSWAPINTERVALSGIPROC my_glXSwapIntervalSGI; +#endif + +#ifdef GPAC_HAS_X11_XV +static void X11_DestroyOverlay(XWindow *xwin) +{ + if (xwin->overlay) XFree(xwin->overlay); + xwin->overlay = NULL; + xwin->xv_pf_format = 0; + if (xwin->display && (xwin->xvport>=0)) { + XvUngrabPort(xwin->display, xwin->xvport, CurrentTime ); + xwin->xvport = -1; + } +} + +static Bool is_same_yuv(u32 pf, u32 a_pf) +{ + if (pf==a_pf) return 1; +#if 0 + switch (pf) { + case GF_PIXEL_YV12: + case GF_PIXEL_I420: + case GF_PIXEL_IYUV: + switch (a_pf) { + case GF_PIXEL_YV12: + case GF_PIXEL_I420: + case GF_PIXEL_IYUV: + return 1; + default: + return 0; + } + case GF_PIXEL_UYVY: + case GF_PIXEL_UYNV: + case GF_PIXEL_Y422: + switch (a_pf) { + case GF_PIXEL_UYVY: + case GF_PIXEL_UYNV: + case GF_PIXEL_Y422: + return 1; + default: + return 0; + } + case GF_PIXEL_YUY2: + case GF_PIXEL_YUNV: + switch (a_pf) { + case GF_PIXEL_YUY2: + case GF_PIXEL_YUNV: + return 1; + default: + return 0; + } + } +#endif + return 0; +} +static u32 X11_GetPixelFormat(u32 pf) +{ + return GF_4CC((pf)&0xff, (pf>>8)&0xff, (pf>>16)&0xff, (pf>>24)&0xff); +} +static int X11_GetXVideoPort(GF_VideoOutput *vout, u32 pixel_format, Bool check_color) +{ + XWindow *xwin = (XWindow *)vout->opaque; + Bool has_color_key = 0; + XvAdaptorInfo *adaptors; + unsigned int i; + unsigned int nb_adaptors; + int selected_port; + + if (XvQueryExtension(xwin->display, &i, &i, &i, &i, &i ) != Success) return -1; + if (XvQueryAdaptors(xwin->display, DefaultRootWindow(xwin->display), &nb_adaptors, &adaptors) != Success) return -1; + + selected_port = -1; + for (i=0; i < nb_adaptors; i++) { + XvImageFormatValues *formats; + int j, num_formats, port; + + if( !( adaptors[i].type & XvInputMask ) || !(adaptors[i].type & XvImageMask ) ) + continue; + + /* Check for our format... */ + formats = XvListImageFormats(xwin->display, adaptors[i].base_id, &num_formats); + + for (j=0; j<num_formats && (selected_port == -1 ); j++) { + XvAttribute *attr=NULL; + int k, nb_attributes; + u32 pformat = X11_GetPixelFormat(formats[j].id); + + if( !is_same_yuv(pformat, pixel_format) ) continue; + + /* Grab first port supporting this format */ + for (port=adaptors[i].base_id; (port < (int)(adaptors[i].base_id + adaptors[i].num_ports) ) && (selected_port == -1); port++) { + if (port==xwin->xvport) continue; + + attr = XvQueryPortAttributes(xwin->display, port, &nb_attributes); + for (k=0; k<nb_attributes; k++ ) { + if (!strcmp(attr[k].name, "XV_COLORKEY")) { + const Atom ckey = XInternAtom(xwin->display, "XV_COLORKEY", False); + XvGetPortAttribute(xwin->display, port, ckey, &vout->overlay_color_key); + has_color_key = 1; + vout->overlay_color_key |= 0xFF000000; + } + /* else if (!strcmp(attr[k].name, "XV_AUTOPAINT_COLORKEY")) { + const Atom paint = XInternAtom(xwin->display, "XV_AUTOPAINT_COLORKEY", False); + XvSetPortAttribute(xwin->display, port, paint, 1); + } + */ + } + if (attr) { + free(attr); + attr=NULL; + } + + if (check_color && !has_color_key) continue; + + if (XvGrabPort(xwin->display, port, CurrentTime) == Success) { + selected_port = port; + xwin->xv_pf_format = formats[j].id; + } + } + if (selected_port == -1 ) + continue; + + } + if (formats != NULL) XFree(formats); + } + if (nb_adaptors > 0) + XvFreeAdaptorInfo(adaptors); + + return selected_port; +} +static GF_Err X11_InitOverlay(GF_VideoOutput *vout, u32 VideoWidth, u32 VideoHeight) +{ + XWindow *xwin = (XWindow *)vout->opaque; + if (xwin->overlay && (VideoWidth<=xwin->overlay->width) && (VideoHeight<=xwin->overlay->height)) { + return GF_OK; + } + + X11_DestroyOverlay(xwin); + + xwin->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 0); + if (xwin->xvport<0) + xwin->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YUY2, 0); + + if (xwin->xvport<0) { + return GF_NOT_SUPPORTED; + } + + /* Create overlay image */ + xwin->overlay = XvCreateImage(xwin->display, xwin->xvport, xwin->xv_pf_format, NULL, VideoWidth, VideoHeight); + if (!xwin->overlay) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Xv Overlay Creation Failure\n")); + return GF_IO_ERR; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Overlay init %d x %d - pixel format %s - XV port %d\n", + VideoWidth, VideoHeight, + gf_4cc_to_str(vout->yuv_pixel_format), xwin->xvport )); + + return GF_OK; +} + +GF_Err X11_Blit(struct _video_out *vout, GF_VideoSurface *video_src, GF_Window *src, GF_Window *dest, u32 overlay_type) +{ + XvImage *overlay; + int xvport; + Drawable dst_dr; + GF_Err e; + Window cur_wnd; + XWindow *xwin = (XWindow *)vout->opaque; + + if (!video_src) { + if (overlay_type && xwin->xvport) { + } + return GF_OK; + } + + if (video_src->pixel_format != GF_PIXEL_YV12) return GF_NOT_SUPPORTED; + cur_wnd = xwin->fullscreen ? xwin->full_wnd : xwin->wnd; + /*init if needed*/ + if ((xwin->xvport<0) || !xwin->overlay) { + e = X11_InitOverlay(vout, video_src->width, video_src->height); + if (e) return e; + if (!xwin->overlay) return GF_IO_ERR; + } + + /*different size, recreate an image*/ + if (xwin->overlay && ((xwin->overlay->width != video_src->width) || (xwin->overlay->height != video_src->height))) { + XFree(xwin->overlay); + xwin->overlay = XvCreateImage(xwin->display, xwin->xvport, xwin->xv_pf_format, NULL, video_src->width, video_src->height); + if (!xwin->overlay) return GF_IO_ERR; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[X11] Blit surface to dest %d x %d - overlay type %s\n", dest->w, dest->h, + (overlay_type==0)? "none" : ((overlay_type==1) ? "Top-Level" : "ColorKey") + )); + + overlay = xwin->overlay; + xvport = xwin->xvport; + + overlay->data = video_src->video_buffer; + + overlay->num_planes = 3; + overlay->pitches[0] = video_src->width; + overlay->pitches[1] = xwin->overlay->pitches[2] = video_src->width/2; + overlay->offsets[0] = 0; + overlay->offsets[1] = video_src->width*video_src->height; + overlay->offsets[2] = 5*video_src->width*video_src->height/4; + + dst_dr = cur_wnd; + if (!overlay_type) { + if (!xwin->pixmap) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Back buffer not configured for Blt\n")); + return GF_BAD_PARAM; + } + dst_dr = xwin->pixmap; + } + XvPutImage(xwin->display, xvport, dst_dr, xwin->the_gc, overlay, + src->x, src->y, src->w, src->h, + dest->x, dest->y, dest->w, dest->h); + + return GF_OK; +} +#endif + + +/*Flush video */ +GF_Err X11_Flush(struct _video_out *vout, GF_Window * dest) +{ + Window cur_wnd; + /* + * write backbuffer to screen + */ + X11VID (); + + if (!xWindow->is_init) return GF_OK; + cur_wnd = xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd; + + if (xWindow->output_3d) { +#ifdef GPAC_HAS_OPENGL + XSync(xWindow->display, False); + glFlush(); + glXSwapBuffers(xWindow->display, cur_wnd); +#endif + return GF_OK; + } + +#ifdef GPAC_HAS_X11_SHM + if (xWindow->pixmap) { + XClearWindow(xWindow->display, cur_wnd); + XSync(xWindow->display, False); + } else if (xWindow->use_shared_memory) { + XSync(xWindow->display, False); + XShmPutImage (xWindow->display, cur_wnd, xWindow->the_gc, xWindow->surface, + 0, 0, dest->x, dest->y, dest->w, dest->h, True); + } else +#endif + { + XSync(xWindow->display, False); + //XRaiseWindow(xWindow->display, xWindow->wnd); + XPutImage (xWindow->display, cur_wnd, xWindow->the_gc, xWindow->surface, + 0, 0, dest->x, dest->y, dest->w, dest->h); + } + return GF_OK; +} + +typedef struct +{ + u32 x11_key; + u32 gf_key; + u32 gf_flags; +} X11KeyToGPAC; + +static X11KeyToGPAC X11Keys[] = +{ + {XK_BackSpace, GF_KEY_BACKSPACE, 0}, + {XK_Tab, GF_KEY_TAB, 0}, +// {XK_Linefeed, GF_KEY_LINEFEED, 0}, + {XK_Clear, GF_KEY_CLEAR, 0}, + {XK_KP_Enter, GF_KEY_ENTER, GF_KEY_EXT_NUMPAD}, + {XK_Return, GF_KEY_ENTER, 0}, + {XK_Pause, GF_KEY_PAUSE, 0}, + {XK_Caps_Lock, GF_KEY_CAPSLOCK, 0}, + {XK_Scroll_Lock, GF_KEY_SCROLL, 0}, + {XK_Escape, GF_KEY_ESCAPE, 0}, + {XK_Delete, GF_KEY_DEL, 0}, + {XK_Kanji, GF_KEY_KANJIMODE, 0}, + {XK_Katakana, GF_KEY_JAPANESEKATAKANA, 0}, + {XK_Romaji, GF_KEY_JAPANESEROMAJI, 0}, + {XK_Hiragana, GF_KEY_JAPANESEHIRAGANA, 0}, + {XK_Kana_Lock, GF_KEY_KANAMODE, 0}, + {XK_Home, GF_KEY_HOME, 0}, + {XK_Left, GF_KEY_LEFT, 0}, + {XK_Up, GF_KEY_UP, 0}, + {XK_Right, GF_KEY_RIGHT, 0}, + {XK_Down, GF_KEY_DOWN, 0}, + {XK_Page_Up, GF_KEY_PAGEUP, 0}, + {XK_Page_Down, GF_KEY_PAGEDOWN, 0}, + {XK_End, GF_KEY_END, 0}, + //{XK_Begin, GF_KEY_BEGIN, 0}, + {XK_Select, GF_KEY_SELECT, 0}, + {XK_Print, GF_KEY_PRINTSCREEN, 0}, + {XK_Execute, GF_KEY_EXECUTE, 0}, + {XK_Insert, GF_KEY_INSERT, 0}, + {XK_Undo, GF_KEY_UNDO, 0}, + //{XK_Redo, GF_KEY_BEGIN, 0}, + //{XK_Menu, GF_KEY_BEGIN, 0}, + {XK_Find, GF_KEY_FIND, 0}, + {XK_Cancel, GF_KEY_CANCEL, 0}, + {XK_Help, GF_KEY_HELP, 0}, + //{XK_Break, GF_KEY_BREAK, 0}, + //{XK_Mode_switch, GF_KEY_BEGIN, 0}, + {XK_Num_Lock, GF_KEY_NUMLOCK, 0}, + {XK_F1, GF_KEY_F1, 0}, + {XK_F2, GF_KEY_F2, 0}, + {XK_F3, GF_KEY_F3, 0}, + {XK_F4, GF_KEY_F4, 0}, + {XK_F5, GF_KEY_F5, 0}, + {XK_F6, GF_KEY_F6, 0}, + {XK_F7, GF_KEY_F7, 0}, + {XK_F8, GF_KEY_F8, 0}, + {XK_F9, GF_KEY_F9, 0}, + {XK_F10, GF_KEY_F10, 0}, + {XK_F11, GF_KEY_F11, 0}, + {XK_F12, GF_KEY_F12, 0}, + {XK_F13, GF_KEY_F13, 0}, + {XK_F14, GF_KEY_F14, 0}, + {XK_F15, GF_KEY_F15, 0}, + {XK_F16, GF_KEY_F16, 0}, + {XK_F17, GF_KEY_F17, 0}, + {XK_F18, GF_KEY_F18, 0}, + {XK_F19, GF_KEY_F19, 0}, + {XK_F20, GF_KEY_F20, 0}, + {XK_F21, GF_KEY_F21, 0}, + {XK_F22, GF_KEY_F22, 0}, + {XK_F23, GF_KEY_F23, 0}, + {XK_F24, GF_KEY_F24, 0}, + {XK_KP_Delete, GF_KEY_COMMA, GF_KEY_EXT_NUMPAD}, + {XK_KP_Decimal, GF_KEY_COMMA, GF_KEY_EXT_NUMPAD}, + {XK_KP_Insert, GF_KEY_0, GF_KEY_EXT_NUMPAD}, + {XK_KP_0, GF_KEY_0, GF_KEY_EXT_NUMPAD}, + {XK_KP_End, GF_KEY_1, GF_KEY_EXT_NUMPAD}, + {XK_KP_1, GF_KEY_1, GF_KEY_EXT_NUMPAD}, + {XK_KP_Down, GF_KEY_2, GF_KEY_EXT_NUMPAD}, + {XK_KP_2, GF_KEY_2, GF_KEY_EXT_NUMPAD}, + {XK_KP_Page_Down, GF_KEY_3, GF_KEY_EXT_NUMPAD}, + {XK_KP_3, GF_KEY_3, GF_KEY_EXT_NUMPAD}, + {XK_KP_Left, GF_KEY_4, GF_KEY_EXT_NUMPAD}, + {XK_KP_4, GF_KEY_4, GF_KEY_EXT_NUMPAD}, + {XK_KP_Begin, GF_KEY_5, GF_KEY_EXT_NUMPAD}, + {XK_KP_5, GF_KEY_5, GF_KEY_EXT_NUMPAD}, + {XK_KP_Right, GF_KEY_6, GF_KEY_EXT_NUMPAD}, + {XK_KP_6, GF_KEY_6, GF_KEY_EXT_NUMPAD}, + {XK_KP_Home, GF_KEY_7, GF_KEY_EXT_NUMPAD}, + {XK_KP_7, GF_KEY_7, GF_KEY_EXT_NUMPAD}, + {XK_KP_Up, GF_KEY_8, GF_KEY_EXT_NUMPAD}, + {XK_KP_8, GF_KEY_8, GF_KEY_EXT_NUMPAD}, + {XK_KP_Page_Up, GF_KEY_9, GF_KEY_EXT_NUMPAD}, + {XK_KP_9, GF_KEY_9, GF_KEY_EXT_NUMPAD}, + {XK_KP_Add, GF_KEY_PLUS, GF_KEY_EXT_NUMPAD}, + {XK_KP_Subtract, GF_KEY_HYPHEN, GF_KEY_EXT_NUMPAD}, + {XK_KP_Multiply, GF_KEY_STAR, GF_KEY_EXT_NUMPAD}, + {XK_KP_Divide, GF_KEY_SLASH, GF_KEY_EXT_NUMPAD}, + {XK_Shift_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Shift_L, GF_KEY_SHIFT, 0}, + {XK_Control_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Control_L, GF_KEY_CONTROL, 0}, + {XK_Alt_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Alt_L, GF_KEY_ALT, 0}, + {XK_Super_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Super_L, GF_KEY_WIN, 0}, + {XK_Menu, GF_KEY_META, 0}, +#ifdef XK_XKB_KEYS + {XK_ISO_Level3_Shift, GF_KEY_ALTGRAPH, 0}, +#endif + {'!', GF_KEY_EXCLAMATION, 0}, + {'"', GF_KEY_QUOTATION, 0}, + {'#', GF_KEY_NUMBER, 0}, + {'$', GF_KEY_DOLLAR, 0}, + {'&', GF_KEY_AMPERSAND, 0}, + {'\'', GF_KEY_APOSTROPHE, 0}, + {'(', GF_KEY_LEFTPARENTHESIS, 0}, + {')', GF_KEY_RIGHTPARENTHESIS, 0}, + {',', GF_KEY_COMMA, 0}, + {':', GF_KEY_COLON, 0}, + {';', GF_KEY_SEMICOLON, 0}, + {'<', GF_KEY_LESSTHAN, 0}, + {'>', GF_KEY_GREATERTHAN, 0}, + {'?', GF_KEY_QUESTION, 0}, + {'@', GF_KEY_AT, 0}, + {'[', GF_KEY_LEFTSQUAREBRACKET, 0}, + {']', GF_KEY_RIGHTSQUAREBRACKET, 0}, + {'\\', GF_KEY_BACKSLASH, 0}, + {'_', GF_KEY_UNDERSCORE, 0}, + {'`', GF_KEY_GRAVEACCENT, 0}, + {' ', GF_KEY_SPACE, 0}, + {'/', GF_KEY_SLASH, 0}, + {'*', GF_KEY_STAR, 0}, + {'-', GF_KEY_HYPHEN, 0}, + {'+', GF_KEY_PLUS, 0}, + {'=', GF_KEY_EQUALS, 0}, + {'^', GF_KEY_CIRCUM, 0}, + {'{', GF_KEY_LEFTCURLYBRACKET, 0}, + {'}', GF_KEY_RIGHTCURLYBRACKET, 0}, + {'|', GF_KEY_PIPE, 0}, +}; + +static u32 num_x11_keys = sizeof(X11Keys) / sizeof(X11KeyToGPAC); + +/* + * Translate X_Key to GF_Key + */ +//===================================== + +static void x11_translate_key(u32 X11Key, GF_EventKey *evt) +{ + u32 i; + + evt->flags = 0; + evt->hw_code = X11Key & 0xFF; + for (i=0; i<num_x11_keys; i++) { + if (X11Key == X11Keys[i].x11_key) { + evt->key_code = X11Keys[i].gf_key; + evt->flags = X11Keys[i].gf_flags; + return; + } + } + + if ((X11Key>='0') && (X11Key<='9')) + evt->key_code = GF_KEY_0 + X11Key - '0'; + else if ((X11Key>='A') && (X11Key<='Z')) + evt->key_code = GF_KEY_A + X11Key - 'A'; + /*DOM3: translate to A -> Z, not a -> z*/ + else if ((X11Key>='a') && (X11Key<='z')) { + evt->key_code = GF_KEY_A + X11Key - 'a'; + evt->hw_code = X11Key - 'a' + 'A'; + } else { + evt->key_code = GF_KEY_UNIDENTIFIED; + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[X11] Unrecognized key %X\n", X11Key)); + } +} + + + +/* taken from SDL + Ack! XPending() actually performs a blocking read if no events available +*/ +static int X11_Pending(Display *display) +{ + /* Flush the display connection and look to see if events are queued */ + XFlush(display); + if ( XEventsQueued(display, QueuedAlready) ) return(1); + + /* More drastic measures are required -- see if X is ready to talk */ + { + static struct timeval zero_time; /* static == 0 */ + int x11_fd; + fd_set fdset; + x11_fd = ConnectionNumber(display); + FD_ZERO(&fdset); + FD_SET(x11_fd, &fdset); + if ( select(x11_fd+1, &fdset, NULL, NULL, &zero_time) == 1 ) { + return(XPending(display)); + } + } + /* Oh well, nothing is ready .. */ + return(0); +} + +/* + * handle X11 events + * here we handle key, mouse, repaint and window sizing events + */ +static void X11_HandleEvents(GF_VideoOutput *vout) +{ + GF_Event evt; + Window the_window; + XComposeStatus state; + X11VID(); + unsigned char keybuf[32]; + XEvent xevent; + if (!xWindow->display) return; + the_window = xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd; + XSync(xWindow->display, False); + + while (X11_Pending(xWindow->display)) { + XNextEvent(xWindow->display, &xevent); + if (xevent.xany.window!=the_window) continue; + switch (xevent.type) { + /* + * X11 window resized event + * must inform GPAC + */ + case ConfigureNotify: + if ((unsigned int) xevent.xconfigure.width != xWindow->w_width + || (unsigned int) xevent.xconfigure.height != xWindow->w_height) + { + evt.type = GF_EVENT_SIZE; + xWindow->w_width = evt.size.width = xevent.xconfigure.width; + xWindow->w_height = evt.size.height = xevent.xconfigure.height; + vout->on_event(vout->evt_cbk_hdl, &evt); + } else { + evt.type = GF_EVENT_MOVE; + evt.move.x = xevent.xconfigure.x; + evt.move.y = xevent.xconfigure.y; + vout->on_event(vout->evt_cbk_hdl, &evt); + } + break; + /* + * Windows need repaint + */ + case Expose: + if (xevent.xexpose.count > 0) break; + evt.type = GF_EVENT_REFRESH; + vout->on_event (vout->evt_cbk_hdl, &evt); + break; + + /* Have we been requested to quit (or another client message?) */ + case ClientMessage: + if ( (xevent.xclient.format == 32) && (xevent.xclient.data.l[0] == xWindow->WM_DELETE_WINDOW) ) { + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + vout->on_event(vout->evt_cbk_hdl, &evt); + } + break; + + case KeyPress: + case KeyRelease: + x11_translate_key(XKeycodeToKeysym (xWindow->display, xevent.xkey.keycode, 0), &evt.key); + evt.type = (xevent.type ==KeyPress) ? GF_EVENT_KEYDOWN : GF_EVENT_KEYUP; + vout->on_event (vout->evt_cbk_hdl, &evt); + + if (xevent.type ==KeyPress) { + s32 len; + len = XLookupString (&xevent.xkey, (char *) keybuf, sizeof(keybuf), NULL, &state); + if ((len>0) && (len<5)) { + utf8_to_ucs4 (& evt.character.unicode_char, len, keybuf); + evt.type = GF_EVENT_TEXTINPUT; + vout->on_event (vout->evt_cbk_hdl, &evt); + } + } + + if (evt.key.key_code==GF_KEY_CONTROL) xWindow->ctrl_down = (xevent.type==KeyPress) ? 1 : 0; + else if (evt.key.key_code==GF_KEY_ALT) xWindow->alt_down = (xevent.type==KeyPress) ? 1 : 0; + else if (evt.key.key_code==GF_KEY_META) xWindow->meta_down = (xevent.type==KeyPress) ? 1 : 0; + + if ((evt.type==GF_EVENT_KEYUP) && (evt.key.key_code==GF_KEY_V) && xWindow->ctrl_down) { + int sformat; + unsigned long nb_bytes, overflow; + unsigned char *data; + Window owner; + Atom selection, stype; + Atom clipb_atom = XInternAtom(xWindow->display, "CLIPBOARD", 0); + if (clipb_atom == None) break; + owner = XGetSelectionOwner(xWindow->display, clipb_atom); + if ((owner == None) || (owner == the_window)) { + owner = DefaultRootWindow(xWindow->display); + selection = XA_CUT_BUFFER0; + } else { + owner = the_window; + selection = XInternAtom(xWindow->display, "GPAC_TEXT_SEL", False); + XConvertSelection(xWindow->display, clipb_atom, XA_STRING, selection, owner, CurrentTime); + } + + + if (XGetWindowProperty(xWindow->display, owner, selection, 0, INT_MAX/4, False, AnyPropertyType, &stype, &sformat, &nb_bytes, &overflow, &data) == Success) { + if (stype == XA_STRING) { + char *text = gf_malloc(sizeof(char)*(nb_bytes+1)); + if (text) { + memcpy(text, data, nb_bytes); + text[nb_bytes] = 0; + evt.type = GF_EVENT_PASTE_TEXT; + evt.clipboard.text = text; + vout->on_event(vout->evt_cbk_hdl, &evt); + gf_free(text); + } + } + XFree(data); + } + } + else if ((evt.type==GF_EVENT_KEYUP) && (evt.key.key_code==GF_KEY_C) && xWindow->ctrl_down) { + Atom clipb_atom = XInternAtom(xWindow->display, "CLIPBOARD", 0); + evt.type = GF_EVENT_COPY_TEXT; + if (vout->on_event(vout->evt_cbk_hdl, &evt)==GF_TRUE) { + if (evt.clipboard.text) { + XChangeProperty(xWindow->display, DefaultRootWindow(xWindow->display), XA_CUT_BUFFER0, XA_STRING, 8, PropModeReplace, (const unsigned char *)evt.clipboard.text, strlen(evt.clipboard.text)); + gf_free(evt.clipboard.text); + } + + if ((clipb_atom != None) && XGetSelectionOwner(xWindow->display, clipb_atom) != the_window) { + XSetSelectionOwner(xWindow->display, clipb_atom, the_window, CurrentTime); + } + if (XGetSelectionOwner(xWindow->display, XA_PRIMARY) != the_window) { + XSetSelectionOwner(xWindow->display, XA_PRIMARY, the_window, CurrentTime); + } + } + } + break; + + case ButtonPress: + if (!xWindow->fullscreen && !xWindow->has_focus) { + xWindow->has_focus = 1; + XSetInputFocus(xWindow->display, xWindow->wnd, RevertToParent, CurrentTime); + } + case ButtonRelease: + // last_mouse_move = xevent.xbutton.time; + evt.mouse.x = xevent.xbutton.x; + evt.mouse.y = xevent.xbutton.y; + evt.type = (xevent.type == ButtonRelease) ? GF_EVENT_MOUSEUP : GF_EVENT_MOUSEDOWN; + + switch (xevent.xbutton.button) { + case Button1: + evt.mouse.button = GF_MOUSE_LEFT; + vout->on_event (vout->evt_cbk_hdl, &evt); + break; + case Button2: + evt.mouse.button = GF_MOUSE_MIDDLE; + vout->on_event (vout->evt_cbk_hdl, &evt); + break; + case Button3: + evt.mouse.button = GF_MOUSE_RIGHT; + vout->on_event (vout->evt_cbk_hdl, &evt); + break; + case Button4: + evt.type = GF_EVENT_MOUSEWHEEL; + evt.mouse.wheel_pos = FIX_ONE; + vout->on_event(vout->evt_cbk_hdl, &evt); + break; + case Button5: + evt.type = GF_EVENT_MOUSEWHEEL; + evt.mouse.wheel_pos = -FIX_ONE; + vout->on_event(vout->evt_cbk_hdl, &evt); + break; + } + if (!xWindow->fullscreen && (xevent.type==ButtonPress) ) + XSetInputFocus(xWindow->display, xWindow->wnd, RevertToNone, CurrentTime); + break; + + case MotionNotify: + evt.type = GF_EVENT_MOUSEMOVE; + evt.mouse.x = xevent.xmotion.x; + evt.mouse.y = xevent.xmotion.y; + vout->on_event (vout->evt_cbk_hdl, &evt); + break; + case PropertyNotify: + break; + case MapNotify: + break; + case CirculateNotify: + break; + case UnmapNotify: + break; + case ReparentNotify: + break; + case FocusOut: + if (!xWindow->fullscreen) xWindow->has_focus = 0; + break; + case FocusIn: + if (!xWindow->fullscreen) xWindow->has_focus = 1; + break; + + case DestroyNotify: + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUIT; + vout->on_event(vout->evt_cbk_hdl, &evt); + break; + + default: + break; + } + } + +} + + + +#ifdef GPAC_HAS_OPENGL +static GF_Err X11_SetupGL(GF_VideoOutput *vout) +{ + GF_Event evt; + XWindow *xWin = (XWindow *)vout->opaque; + + if (!xWin->glx_visualinfo) return GF_IO_ERR; + memset(&evt, 0, sizeof(GF_Event)); + + if (!xWin->glx_context) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[X11] Setting up GL for display %d\n", xWin->display)); + XSync(xWin->display, False); + xWin->glx_context = glXCreateContext(xWin->display,xWin->glx_visualinfo, NULL, True); + XSync(xWin->display, False); + if (!xWin->glx_context) return GF_IO_ERR; + + evt.setup.hw_reset = 1; + } + if ( ! glXMakeCurrent(xWin->display, xWin->fullscreen ? xWin->full_wnd : xWin->wnd, xWin->glx_context) ) return GF_IO_ERR; + +#ifdef GPAC_HAS_OPENGL + if (gf_opts_get_bool("core", "disable-vsync")) { + my_glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress( (const GLubyte*)"glXSwapIntervalEXT"); + if (my_glXSwapIntervalEXT != NULL) { + my_glXSwapIntervalEXT(xWin->display, xWin->wnd, 0); + } else { + my_glXSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress( (const GLubyte*)"glXSwapIntervalMESA"); + if (my_glXSwapIntervalMESA != NULL ) { + my_glXSwapIntervalMESA(0); + } else { + my_glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress( (const GLubyte*)"glXSwapIntervalSGI"); + if (my_glXSwapIntervalSGI != NULL ) { + my_glXSwapIntervalSGI(0); + } + } + } + } +#endif + XSync(xWin->display, False); + + evt.type = GF_EVENT_VIDEO_SETUP; + vout->on_event (vout->evt_cbk_hdl,&evt); + xWin->is_init = 1; + return GF_OK; +} + +static void X11_ReleaseGL(XWindow *xWin) +{ + XSync(xWin->display, False); + if (xWin->glx_context) { + glXMakeCurrent(xWin->display, None, NULL); + glXDestroyContext(xWin->display, xWin->glx_context); + xWin->glx_context = NULL; + } + xWin->is_init = 0; + XSync(xWin->display, False); +} + +#endif + + +static void X11_ReleaseBackBuffer (GF_VideoOutput * vout) +{ + X11VID (); + if (xWindow->x_data) { + gf_free(xWindow->x_data); + xWindow->x_data = NULL; + if (xWindow->surface) xWindow->surface->data = NULL; + } +#ifdef GPAC_HAS_X11_SHM + if (xWindow->shmseginfo) XShmDetach (xWindow->display, xWindow->shmseginfo); + if (xWindow->pixmap) { + XFreePixmap(xWindow->display, xWindow->pixmap); + xWindow->pixmap = 0L; + xWindow->pwidth = xWindow->pheight = 0; + } else { + if (xWindow->surface) XDestroyImage(xWindow->surface); + xWindow->surface = NULL; + } + if (xWindow->shmseginfo) { + if (xWindow->shmseginfo->shmaddr) shmdt(xWindow->shmseginfo->shmaddr); + if (xWindow->shmseginfo->shmid >= 0) + shmctl (xWindow->shmseginfo->shmid, IPC_RMID, NULL); + gf_free(xWindow->shmseginfo); + xWindow->shmseginfo = NULL; + } +#endif + if (xWindow->surface) { + XFree(xWindow->surface); + xWindow->surface = NULL; + } + xWindow->is_init = 0; +#ifdef GPAC_HAS_X11_XV + X11_DestroyOverlay(xWindow); +#endif + +} + +/* + * init backbuffer + */ +GF_Err X11_InitBackBuffer (GF_VideoOutput * vout, u32 VideoWidth, u32 VideoHeight) +{ +#ifdef GPAC_HAS_X11_SHM + Window cur_wnd; +#endif + u32 size; + VideoWidth = VideoWidth > 32 ? VideoWidth : 32; + VideoWidth = VideoWidth < 4096 ? VideoWidth : 4096; + VideoHeight = VideoHeight > 32 ? VideoHeight : 32; + VideoHeight = VideoHeight > 4096 ? 4096 : VideoHeight; + + X11VID (); + if (!xWindow || !VideoWidth || !VideoHeight) + return GF_BAD_PARAM; + + X11_ReleaseBackBuffer(vout); + /*WATCHOUT seems we need even width when using shared memory extensions*/ + if (xWindow->use_shared_memory && (VideoWidth%2)) + VideoWidth++; + + size = VideoWidth * VideoHeight * xWindow->bpp; + +#ifdef GPAC_HAS_X11_SHM + cur_wnd = xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd; + /*if we're using YUV blit to offscreen, we must use a pixmap*/ + if (vout->hw_caps & GF_VIDEO_HW_HAS_YUV) { + GF_SAFEALLOC(xWindow->shmseginfo, XShmSegmentInfo); + xWindow->shmseginfo->shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0776); + xWindow->shmseginfo->shmaddr = shmat(xWindow->shmseginfo->shmid, 0, 0); + xWindow->shmseginfo->readOnly = False; + if (!XShmAttach (xWindow->display, xWindow->shmseginfo)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Failed to attach shared memory!\n")); + } + xWindow->pixmap = XShmCreatePixmap(xWindow->display, cur_wnd, + (unsigned char *) xWindow->shmseginfo->shmaddr, xWindow->shmseginfo, + VideoWidth, VideoHeight, xWindow->depth); + memset((unsigned char *) xWindow->shmseginfo->shmaddr, 0, sizeof(char)*size); + XSetWindowBackgroundPixmap (xWindow->display, cur_wnd, xWindow->pixmap); + xWindow->pwidth = VideoWidth; + xWindow->pheight = VideoHeight; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[X11] Using X11 Pixmap %08x\n", (u32)xWindow->pixmap)); + } else if (xWindow->use_shared_memory) { + GF_SAFEALLOC(xWindow->shmseginfo, XShmSegmentInfo); + xWindow->surface = XShmCreateImage (xWindow->display, xWindow->visual, + xWindow->depth, ZPixmap, NULL, xWindow->shmseginfo, + VideoWidth, VideoHeight); + xWindow->shmseginfo->shmid = shmget(IPC_PRIVATE, xWindow->surface->bytes_per_line * xWindow->surface->height, + IPC_CREAT | 0777); + + xWindow->surface->data = xWindow->shmseginfo->shmaddr = shmat(xWindow->shmseginfo->shmid, NULL, 0); + xWindow->shmseginfo->readOnly = False; + XShmAttach (xWindow->display, xWindow->shmseginfo); + } else +#endif + { + xWindow->x_data = (char *) gf_malloc(sizeof(char)*size); + xWindow->surface = XCreateImage (xWindow->display, xWindow->visual, + xWindow->depth, ZPixmap, + 0, + xWindow->x_data, + VideoWidth, VideoHeight, + xWindow->bpp*8, xWindow->bpp*VideoWidth); + if (!xWindow->surface) { + return GF_IO_ERR; + } + + } + xWindow->is_init = 1; + return GF_OK; +} + +/* + * resize buffer: note we don't resize window here + */ +GF_Err X11_ResizeBackBuffer (struct _video_out *vout, u32 newWidth, u32 newHeight) +{ + X11VID (); + u32 w = xWindow->surface ? xWindow->surface->width : xWindow->pwidth; + u32 h = xWindow->surface ? xWindow->surface->height : xWindow->pheight; + if (!xWindow->is_init || (newWidth != w) || (newHeight != h)) { + if ((newWidth >= 32) && (newHeight >= 32)) + return X11_InitBackBuffer (vout, newWidth, newHeight); + } + return GF_OK; +} + +GF_Err X11_ProcessEvent (struct _video_out * vout, GF_Event * evt) +{ + X11VID (); + + X11_SetupWindow(vout); + if (!xWindow->display) return GF_IO_ERR; + + if (evt) { + switch (evt->type) { + case GF_EVENT_SET_CURSOR: + break; + case GF_EVENT_SET_CAPTION: + if (!xWindow->par_wnd && xWindow->wnd && evt->caption.caption) { + XStoreName(xWindow->display, xWindow->wnd, ""); + } + break; + case GF_EVENT_SHOWHIDE: + break; + case GF_EVENT_MOVE: + if (xWindow->fullscreen) return GF_OK; + + if (evt->move.relative == 2) { + } + else if (evt->move.relative) { + s32 x, y; + Window child; + x = y = 0; + XTranslateCoordinates(xWindow->display, xWindow->wnd, RootWindowOfScreen (xWindow->screenptr), 0, 0, &x, &y, &child ); + XMoveWindow(xWindow->display, xWindow->wnd, x + evt->move.x, y + evt->move.y); + } else { + XMoveWindow(xWindow->display, xWindow->wnd, evt->move.x, evt->move.y); + } + break; + case GF_EVENT_SIZE: + /*if owning the window and not in fullscreen, resize it (initial scene size)*/ + xWindow->w_width = evt->size.width; + xWindow->w_height = evt->size.height; + + if (!xWindow->fullscreen) { + if (xWindow->par_wnd) { + XWindowAttributes pwa; + XGetWindowAttributes(xWindow->display, xWindow->par_wnd, &pwa); + XMoveResizeWindow(xWindow->display, xWindow->wnd, pwa.x, pwa.y, evt->size.width, evt->size.height); + if (!xWindow->no_select_input) XSetInputFocus(xWindow->display, xWindow->wnd, RevertToNone, CurrentTime); + } else { + XResizeWindow (xWindow->display, xWindow->wnd, evt->size.width, evt->size.height); + } + } + break; + case GF_EVENT_VIDEO_SETUP: + if (evt->setup.use_opengl) { +#ifdef GPAC_HAS_OPENGL + xWindow->output_3d = GF_TRUE; + return X11_SetupGL(vout); +#else + return GF_NOT_SUPPORTED; +#endif + } else { + xWindow->output_3d = GF_FALSE; + return X11_ResizeBackBuffer(vout, evt->setup.width, evt->setup.height); + } + break; + case GF_EVENT_SET_GL: + if (!xWindow->output_3d) return GF_OK; + +#ifdef GPAC_HAS_OPENGL + if ( ! glXMakeCurrent(xWindow->display, xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd, xWindow->glx_context) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Cannot make context current\n")); + return GF_IO_ERR; + } +#else + return GF_NOT_SUPPORTED; +#endif + break; + } + } else { + X11_HandleEvents(vout); + } + return GF_OK; + +} + +/* switch from/to full screen mode */ +GF_Err X11_SetFullScreen (struct _video_out * vout, u32 bFullScreenOn, u32 * screen_width, u32 * screen_height) +{ + X11VID (); + xWindow->fullscreen = bFullScreenOn; +#ifdef GPAC_HAS_OPENGL +// if (xWindow->output_3d) X11_ReleaseGL(xWindow); +#endif + + if (bFullScreenOn) { + xWindow->store_width = *screen_width; + xWindow->store_height = *screen_height; + + xWindow->w_width = vout->max_screen_width; + xWindow->w_height = vout->max_screen_height; + + XFreeGC (xWindow->display, xWindow->the_gc); + xWindow->the_gc = XCreateGC (xWindow->display, xWindow->full_wnd, 0, NULL); + + XMoveResizeWindow (xWindow->display, + (Window) xWindow->full_wnd, 0, 0, + vout->max_screen_width, + vout->max_screen_height); + *screen_width = xWindow->w_width; + *screen_height = xWindow->w_height; + XUnmapWindow (xWindow->display, xWindow->wnd); + XMapWindow (xWindow->display, xWindow->full_wnd); + XSetInputFocus(xWindow->display, xWindow->full_wnd, RevertToNone, CurrentTime); + XRaiseWindow(xWindow->display, xWindow->full_wnd); + XGrabKeyboard(xWindow->display, xWindow->full_wnd, True, GrabModeAsync, GrabModeAsync, CurrentTime); + + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SIZE; + evt.size.width = xWindow->w_width; + evt.size.height = xWindow->w_height; + vout->on_event(vout->evt_cbk_hdl, &evt); + } else { + *screen_width = xWindow->store_width; + *screen_height = xWindow->store_height; + XFreeGC (xWindow->display, xWindow->the_gc); + xWindow->the_gc = XCreateGC (xWindow->display, xWindow->wnd, 0, NULL); + XUnmapWindow (xWindow->display, xWindow->full_wnd); + XMapWindow (xWindow->display, xWindow->wnd); + XUngrabKeyboard(xWindow->display, CurrentTime); + /*looks like this makes osmozilla crash*/ + //if (xWindow->par_wnd) XSetInputFocus(xWindow->display, xWindow->wnd, RevertToNone, CurrentTime); + + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SIZE; + evt.size.width = xWindow->w_width; + evt.size.height = xWindow->w_height; + vout->on_event(vout->evt_cbk_hdl, &evt); + + /*backbuffer resize will be done right after this is called */ + } +#ifdef GPAC_HAS_OPENGL + if (xWindow->output_3d) X11_SetupGL(vout); +#endif + return GF_OK; +} + +/* + * lock video mem + */ +GF_Err X11_LockBackBuffer(struct _video_out * vout, GF_VideoSurface * vi, u32 do_lock) +{ + X11VID (); + + if (do_lock) { + if (!vi) return GF_BAD_PARAM; + memset(vi, 0, sizeof(GF_VideoSurface)); + if (xWindow->surface) { + vi->width = xWindow->surface->width; + vi->height = xWindow->surface->height; + vi->pitch_x = xWindow->bpp; + vi->pitch_y = xWindow->surface->width*xWindow->bpp; + vi->pixel_format = xWindow->pixel_format; + vi->video_buffer = xWindow->surface->data; + } else { +#ifdef GPAC_HAS_X11_SHM + vi->width = xWindow->pwidth; + vi->height = xWindow->pheight; + vi->pitch_x = xWindow->bpp; + vi->pitch_y = xWindow->pwidth*xWindow->bpp; + vi->pixel_format = xWindow->pixel_format; + vi->video_buffer = (unsigned char *) xWindow->shmseginfo->shmaddr; +#endif + } + vi->is_hardware_memory = (xWindow->use_shared_memory) ? 1 : 0; + return GF_OK; + } else { + return GF_OK; + } +} + + +static XErrorHandler old_handler = NULL; +static int selectinput_err = 0; +static int X11_BadAccess_ByPass(Display * display, + XErrorEvent * event) +{ + char msg[60]; + if (!display || !event) return 0; + + if (event->error_code == BadAccess) + { + selectinput_err = 1; + return 0; + } + if (old_handler != NULL) + old_handler(display, event); + else + { + XGetErrorText(display, event->error_code, (char *) &msg, 60); + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Error %s\n",msg)); + } + return 0; +} + + +static void X11_XScreenSaverState(XWindow *xwin, Bool turn_on) +{ + if (turn_on) { + if (!xwin->ss_t) return; + XSetScreenSaver(xwin->display, xwin->ss_t, xwin->ss_i, xwin->ss_b, xwin->ss_e); + } else { + /* Save state information */ + XGetScreenSaver(xwin->display, &xwin->ss_t, &xwin->ss_i, &xwin->ss_b, &xwin->ss_e); + if (xwin->ss_t) + XSetScreenSaver(xwin->display, 0, xwin->ss_i, xwin->ss_b, xwin->ss_e); + } +} + +/* + * Setup X11 wnd System + */ +void +X11_SetupWindow (GF_VideoOutput * vout) +{ + X11VID (); +#if defined(GPAC_HAS_X11_SHM) || defined(GPAC_HAS_X11_XV) || defined(GPAC_HAS_OPENGL) + const char *sOpt; +#endif + + Bool autorepeat, supported; + + if (xWindow->setup_done) return; + xWindow->setup_done = 1; + + XInitThreads(); + + xWindow->display = XOpenDisplay (NULL); + if (!xWindow->display) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Failed to open X11 display %d\n", errno)); + return; + } + xWindow->screennum = DefaultScreen (xWindow->display); + xWindow->screenptr = DefaultScreenOfDisplay (xWindow->display); + xWindow->visual = DefaultVisualOfScreen (xWindow->screenptr); + xWindow->depth = DefaultDepth (xWindow->display, xWindow->screennum); + + { + Float screenWidth = (Float)XWidthOfScreen(xWindow->screenptr); + Float screenWidthIn = (Float)XWidthMMOfScreen(xWindow->screenptr) / 25.4f; + Float screenHeight = (Float)XHeightOfScreen(xWindow->screenptr); + Float screenHeightIn = (Float)XHeightMMOfScreen(xWindow->screenptr) / 25.4f; + vout->dpi_x = (u32)(screenWidth / screenWidthIn); + vout->dpi_y = (u32)(screenHeight / screenHeightIn); + } + + switch (xWindow->depth) { + case 8: + xWindow->pixel_format = GF_PIXEL_GREYSCALE; + break; + case 16: + xWindow->pixel_format = GF_PIXEL_RGB_565; + break; + case 24: +#ifdef GPAC_BIG_ENDIAN + if (xWindow->visual->red_mask==0x00FF0000) + xWindow->pixel_format = GF_PIXEL_RGB; + else + xWindow->pixel_format = GF_PIXEL_BGR; +#else + if (xWindow->visual->red_mask==0x00FF0000) + xWindow->pixel_format = GF_PIXEL_BGR; + else + xWindow->pixel_format = GF_PIXEL_RGB; +#endif + break; + default: + xWindow->pixel_format = GF_PIXEL_GREYSCALE; + break; + } + xWindow->bpp = xWindow->depth / 8; + xWindow->bpp = xWindow->bpp == 3 ? 4 : xWindow->bpp; + + xWindow->screennum=0; + vout->max_screen_width = DisplayWidth(xWindow->display, xWindow->screennum); + vout->max_screen_height = DisplayHeight(xWindow->display, xWindow->screennum); + vout->max_screen_bpp = 8; + + /* + * Full screen wnd + */ + xWindow->full_wnd = XCreateWindow (xWindow->display, + RootWindowOfScreen (xWindow->screenptr), + 0, 0, + vout->max_screen_width, + vout->max_screen_height, 0, + xWindow->depth, InputOutput, + xWindow->visual, 0, NULL); + + XSelectInput(xWindow->display, xWindow->full_wnd, + FocusChangeMask | ExposureMask | PointerMotionMask | ButtonReleaseMask | ButtonPressMask | KeyPressMask | KeyReleaseMask); + + if (!xWindow->par_wnd) { + xWindow->w_width = 320; + xWindow->w_height = 240; + xWindow->wnd = XCreateWindow (xWindow->display, + RootWindowOfScreen(xWindow->screenptr), 0, 0, + xWindow->w_width, xWindow->w_height, 0, + xWindow->depth, InputOutput, + xWindow->visual, 0, NULL); + } else { + XWindowAttributes pwa; + XGetWindowAttributes(xWindow->display, xWindow->par_wnd, &pwa); + xWindow->w_width = pwa.width; + xWindow->w_height = pwa.height; + xWindow->wnd = XCreateWindow (xWindow->display, xWindow->par_wnd, pwa.x, pwa.y, + xWindow->w_width, xWindow->w_height, 0, + xWindow->depth, InputOutput, + xWindow->visual, 0, NULL); + } + + if (!(xWindow->init_flags & GF_TERM_INIT_HIDE)) { + XMapWindow (xWindow->display, (Window) xWindow->wnd); + } + + XSync(xWindow->display, False); + XUnmapWindow (xWindow->display, (Window) xWindow->wnd); + XSync(xWindow->display, False); + old_handler = XSetErrorHandler(X11_BadAccess_ByPass); + selectinput_err = 0; + XSelectInput(xWindow->display, xWindow->wnd, + FocusChangeMask | StructureNotifyMask | PropertyChangeMask | ExposureMask | + PointerMotionMask | ButtonReleaseMask | ButtonPressMask | + KeyPressMask | KeyReleaseMask); + XSync(xWindow->display, False); + XSetErrorHandler(old_handler); + if (selectinput_err) { + XSelectInput(xWindow->display, xWindow->wnd, + StructureNotifyMask | PropertyChangeMask | ExposureMask | + KeyPressMask | KeyReleaseMask); + + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Cannot select input focus\n")); + } + XSync(xWindow->display, False); + if (!(xWindow->init_flags & GF_TERM_INIT_HIDE)) { + XMapWindow (xWindow->display, (Window) xWindow->wnd); + } + XSizeHints *Hints = XAllocSizeHints (); + Hints->flags = PSize | PMinSize; + Hints->min_width = 64; + Hints->min_height = 64; + Hints->max_height = 4096; + Hints->max_width = 4096; + if (!xWindow->par_wnd) { + XSetWMNormalHints (xWindow->display, xWindow->wnd, Hints); + XStoreName (xWindow->display, xWindow->wnd, "GPAC X11 Output"); + } + Hints->x = 0; + Hints->y = 0; + Hints->flags |= USPosition; + XSetWMNormalHints (xWindow->display, xWindow->full_wnd, Hints); + + { + XClassHint hint; + hint.res_name = "gpac"; + hint.res_class = "gpac"; + XSetClassHint(xWindow->display, xWindow->wnd, &hint); + } + + autorepeat = 1; + XkbSetDetectableAutoRepeat(xWindow->display, autorepeat, &supported); + + + if (xWindow->init_flags & GF_TERM_WINDOW_NO_DECORATION) { +#define PROP_MOTIF_WM_HINTS_ELEMENTS 5 +#define MWM_HINTS_DECORATIONS (1L << 1) + struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; + } hints = {2, 0, 0, 0, 0}; + + hints.flags = MWM_HINTS_DECORATIONS; + hints.decorations = 0; + + XChangeProperty(xWindow->display, xWindow->wnd, XInternAtom(xWindow->display,"_MOTIF_WM_HINTS", False), XInternAtom(xWindow->display, "_MOTIF_WM_HINTS", False), 32, PropModeReplace, (unsigned char *)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS); + + } + + xWindow->the_gc = XCreateGC (xWindow->display, xWindow->wnd, 0, NULL); + xWindow->use_shared_memory = 0; + +#ifdef GPAC_HAS_X11_SHM + sOpt = gf_opts_get_key("core", "hwvmem"); + if (!sOpt || strcmp(sOpt, "no")) { + int XShmMajor, XShmMinor; + Bool XShmPixmaps; + if (XShmQueryVersion(xWindow->display, &XShmMajor, &XShmMinor, &XShmPixmaps)) { + xWindow->use_shared_memory = 1; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Using X11 Shared Memory\n")); + if ((XShmPixmaps==True) && (XShmPixmapFormat(xWindow->display)==ZPixmap)) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] X11 Shared Memory Pixmaps available\n")); + } + } + } + +#endif + +#ifdef GPAC_HAS_X11_XV + sOpt = gf_opts_get_key("core", "no-colorkey"); + if (sOpt && !strcmp(sOpt, "yes")) { + xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 0); + } else { + xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 1); + if (xWindow->xvport<0) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Hardware has no color keying\n")); + vout->overlay_color_key = 0; + xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 0); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Hardware uses color key %08x\n", vout->overlay_color_key)); + } + } + if (xWindow->xvport>=0) { + XvUngrabPort(xWindow->display, xWindow->xvport, CurrentTime ); + xWindow->xvport = -1; + vout->yuv_pixel_format = X11_GetPixelFormat(xWindow->xv_pf_format); + vout->Blit = X11_Blit; + vout->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Using XV YUV Overlays\n")); + +#ifdef GPAC_HAS_X11_SHM + /*if user asked for YUV->RGB on offscreen, do it (it may crash the system)*/ + if (gf_opts_get_bool("core", "offscreen-yuv")) { + vout->hw_caps |= GF_VIDEO_HW_HAS_YUV; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Using XV Offscreen YUV2RGB acceleration\n")); + } +#endif + } +#endif + + XSetWindowAttributes xsw; + xsw.border_pixel = WhitePixel (xWindow->display, xWindow->screennum); + xsw.background_pixel = BlackPixel (xWindow->display, xWindow->screennum); + xsw.win_gravity = NorthWestGravity; + XChangeWindowAttributes (xWindow->display, xWindow->wnd, CWBackPixel | CWWinGravity, &xsw); + + xsw.override_redirect = True; + XChangeWindowAttributes(xWindow->display, xWindow->full_wnd, + CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWWinGravity, &xsw); + + if (!xWindow->par_wnd) { + xWindow->WM_DELETE_WINDOW = XInternAtom (xWindow->display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(xWindow->display, xWindow->wnd, &xWindow->WM_DELETE_WINDOW, 1); + } + + + { + XEvent ev; + long mask; + + memset (&ev, 0, sizeof (ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = RootWindowOfScreen (xWindow->screenptr); + ev.xclient.message_type = XInternAtom (xWindow->display, "KWM_KEEP_ON_TOP", False); + ev.xclient.format = 32; + ev.xclient.data.l[0] = xWindow->full_wnd; + ev.xclient.data.l[1] = CurrentTime; + mask = SubstructureRedirectMask; + XSendEvent (xWindow->display,RootWindowOfScreen (xWindow->screenptr), False, + mask, &ev); + } + + /*openGL setup*/ +#ifdef GPAC_HAS_OPENGL + { + int attribs[64]; + int i, nb_bits, nb_depth_bits; + + sOpt = gf_opts_get_key("core", "gl-bits-comp"); + /* Most outputs are 24/32 bits these days, use 8 bits per channel instead of 5, works better on MacOS X */ + nb_bits = sOpt ? atoi(sOpt) : 8; + if (!sOpt) { + gf_opts_set_key("core", "gl-bits-comp", "8"); + } +retry_8bpp: + i=0; + if (nb_bits>8) { + attribs[i++] = GLX_DRAWABLE_TYPE; + attribs[i++] = GLX_WINDOW_BIT; + attribs[i++] = GLX_RENDER_TYPE; + attribs[i++] = GLX_RGBA_BIT; + } else { + attribs[i++] = GLX_RGBA; + } + attribs[i++] = GLX_RED_SIZE; + attribs[i++] = nb_bits; + attribs[i++] = GLX_GREEN_SIZE; + attribs[i++] = nb_bits; + attribs[i++] = GLX_BLUE_SIZE; + attribs[i++] = nb_bits; + if (nb_bits==10) { + attribs[i++] = GLX_ALPHA_SIZE; + attribs[i++] = 2; + } + + sOpt = gf_opts_get_key("core", "gl-bits-depth"); + nb_depth_bits = sOpt ? atoi(sOpt) : 16; + if (!sOpt) { + gf_opts_set_key("core", "gl-bits-depth", "16"); + } + if (nb_bits) { + attribs[i++] = GLX_DEPTH_SIZE; + attribs[i++] = nb_depth_bits; + } + sOpt = gf_opts_get_key("core", "gl-doublebuf"); + if (!sOpt) { + gf_opts_set_key("core", "gl-doublebuf", "yes"); + } + if (!sOpt || !strcmp(sOpt, "yes")) { + attribs[i++] = GLX_DOUBLEBUFFER; + attribs[i++] = True; + } + attribs[i++] = None; + + if (nb_bits>8) { + int fbcount=0; + GLXFBConfig *fb=NULL; + typedef GLXFBConfig * (* FnGlXChooseFBConfigProc)( Display *, int, int const *,int * ); + typedef XVisualInfo * (* FnGlXGetVisualFromFBConfigProc)( Display *, GLXFBConfig ); + typedef int (* FnGlXGetFBConfigAttrib) (Display * dpy, GLXFBConfig config, int attribute, int * value); + + + FnGlXChooseFBConfigProc my_glXChooseFBConfig = (FnGlXChooseFBConfigProc) glXGetProcAddress((GLubyte*) "glXChooseFBConfig"); + FnGlXGetVisualFromFBConfigProc my_glXGetVisualFromFBConfig = (FnGlXGetVisualFromFBConfigProc)glXGetProcAddress((GLubyte*) "glXGetVisualFromFBConfig"); + FnGlXGetFBConfigAttrib my_glXGetFBConfigAttrib = (FnGlXGetFBConfigAttrib)glXGetProcAddress((GLubyte*) "glXGetFBConfigAttrib"); + + if (my_glXChooseFBConfig && my_glXGetVisualFromFBConfig) { + fb = my_glXChooseFBConfig(xWindow->display, xWindow->screennum, attribs, &fbcount); + } + + if (fbcount==0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Failed to choose GLX config for 10 bit depth - retrying with 8 bit depth\n")); + nb_bits = 8; + goto retry_8bpp; + } + if (my_glXGetVisualFromFBConfig) + xWindow->glx_visualinfo = my_glXGetVisualFromFBConfig(xWindow->display, fb[0]); + + if (my_glXGetFBConfigAttrib && fb) { + int r, g, b; + my_glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_RED_SIZE, &r); + my_glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_GREEN_SIZE, &g); + my_glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_BLUE_SIZE, &b); + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[GLX] Configured display asked %d bits got r:%d g:%d b:%d bits\n", nb_bits, r, g, b)); + } + } else { + xWindow->glx_visualinfo = glXChooseVisual(xWindow->display, xWindow->screennum, attribs); + } + vout->max_screen_bpp = nb_bits; + + if (!xWindow->glx_visualinfo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Error selecting GL display\n")); + } + } + + xWindow->gl_wnd = XCreateWindow (xWindow->display, RootWindowOfScreen (xWindow->screenptr), + 0, + 0, + 200, + 200, 0, + xWindow->depth, InputOutput, + xWindow->visual, 0, NULL); + + XSync(xWindow->display, False); + XUnmapWindow(xWindow->display, (Window) xWindow->gl_wnd); + XSync(xWindow->display, False); + + sOpt = gf_opts_get_key("core", "gl-offscreen"); + if (!sOpt) + gf_opts_set_key("core", "gl-offscreen", "Pixmap"); + if (sOpt && !strcmp(sOpt, "Window")) { + xWindow->offscreen_type = 1; + } else if (sOpt && !strcmp(sOpt, "VisibleWindow")) { + xWindow->offscreen_type = 2; + XSetWMNormalHints (xWindow->display, xWindow->gl_wnd, Hints); + } else if (sOpt && !strcmp(sOpt, "Pixmap")) { + xWindow->offscreen_type = 0; + } else { + xWindow->offscreen_type = 0; + } +#endif + + + /*turn off xscreensaver*/ + X11_XScreenSaverState(xWindow, 0); + + XFree (Hints); +} + +GF_Err X11_Setup(struct _video_out *vout, void *os_handle, void *os_display, u32 flags) +{ + X11VID (); + /*assign window if any, NEVER display*/ + xWindow->par_wnd = (Window) os_handle; + xWindow->init_flags = flags; + + //window already setup and this restup asks for visible, show window + if (xWindow->wnd) { + if (!(xWindow->init_flags & GF_TERM_INIT_HIDE)) { + XMapWindow (xWindow->display, (Window) xWindow->wnd); + } + } + + /*OSMOZILLA HACK*/ + if (os_display) xWindow->no_select_input = 1; + + /*the rest is done THROUGH THE MAIN RENDERER TRHEAD!!*/ + return GF_OK; +} + +/* Shutdown X11 */ +void X11_Shutdown (struct _video_out *vout) +{ + X11VID (); + if (! xWindow->display) return; + + X11_ReleaseBackBuffer (vout); + +#ifdef GPAC_HAS_OPENGL + X11_ReleaseGL(xWindow); + if (xWindow->glx_visualinfo) + XFree(xWindow->glx_visualinfo); + xWindow->glx_visualinfo = NULL; +#endif + XFreeGC (xWindow->display, xWindow->the_gc); + XUnmapWindow (xWindow->display, (Window) xWindow->wnd); + XDestroyWindow (xWindow->display, (Window) xWindow->wnd); + XDestroyWindow (xWindow->display, (Window) xWindow->full_wnd); +#ifdef GPAC_HAS_OPENGL + if (xWindow->gl_offscreen) glXDestroyGLXPixmap(xWindow->display, xWindow->gl_offscreen); + if (xWindow->gl_pixmap) XFreePixmap(xWindow->display, xWindow->gl_pixmap); + XUnmapWindow (xWindow->display, (Window) xWindow->gl_wnd); + XDestroyWindow (xWindow->display, (Window) xWindow->gl_wnd); +#endif + + /*restore xscreen saver*/ + X11_XScreenSaverState(xWindow, 1); + + XCloseDisplay (xWindow->display); + gf_free(xWindow); + vout->opaque = NULL; +} + + + + +void *NewX11VideoOutput () +{ + GF_VideoOutput *driv; + XWindow *xWindow; + GF_SAFEALLOC(driv, GF_VideoOutput); + if (!driv) return NULL; + GF_SAFEALLOC(xWindow, XWindow); + if (!xWindow) { + gf_free(driv); + return NULL; + } + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "X11 Video Output", "gpac distribution") + + driv->opaque = xWindow; + + driv->Flush = X11_Flush; + driv->SetFullScreen = X11_SetFullScreen; + driv->Setup = X11_Setup; + driv->Shutdown = X11_Shutdown; + driv->LockBackBuffer = X11_LockBackBuffer; + driv->ProcessEvent = X11_ProcessEvent; + driv->hw_caps = GF_VIDEO_HW_OPENGL; + /*fixme - needs a better detection scheme*/ + driv->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA; + + if (gf_sys_is_test_mode()) { + GF_Event evt; + x11_translate_key(XK_BackSpace, &evt.key); + X11_BadAccess_ByPass(NULL, NULL); + } + return (void *) driv; + +} + + +void +DeleteX11VideoOutput (GF_VideoOutput * vout) +{ + if (vout->opaque) gf_free(vout->opaque); + gf_free(vout); +} + +/* + * interface query + */ +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + 0 + }; + return si; +} + + +/* + * interface create + */ +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface (u32 InterfaceType) +{ + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) + return (GF_BaseInterface *) NewX11VideoOutput (); + return NULL; +} + + +/* + * interface destroy + */ +GPAC_MODULE_EXPORT +void ShutdownInterface (GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) + { + case GF_VIDEO_OUTPUT_INTERFACE: + DeleteX11VideoOutput ((GF_VideoOutput *)ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( x11_out ) diff --git a/modules/x11_out/x11_out.h b/modules/x11_out/x11_out.h new file mode 100644 index 0000000..dfde0f8 --- /dev/null +++ b/modules/x11_out/x11_out.h @@ -0,0 +1,134 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2020 + * All rights reserved + * + * This file is part of GPAC / X11 video output module + * + * 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 _X11_OUT_H +#define _X11_OUT_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#include <gpac/modules/video_out.h> +#include <gpac/thread.h> +#include <gpac/list.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> + +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_TINYGL) +#define GPAC_HAS_OPENGL +#endif + +#ifdef GPAC_HAS_OPENGL +#include <GL/glx.h> +#endif + +#ifdef GPAC_HAS_X11_SHM +#include <X11/extensions/XShm.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#endif + +#ifdef GPAC_HAS_X11_XV +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> +#endif + +#if defined(ENABLE_JOYSTICK) || defined(ENABLE_JOYSTICK_NO_CURSOR) +#include <linux/joystick.h> +#include <unistd.h> +#include <fcntl.h> +#endif + +#define X11VID() XWindow *xWindow = (XWindow *)vout->opaque; + +#define RGB555(r,g,b) (((r&248)<<7) + ((g&248)<<2) + (b>>3)) +#define RGB565(r,g,b) (((r&248)<<8) + ((g&252)<<3) + (b>>3)) + +typedef struct +{ + Window par_wnd; //main window handler passed to module, NULL otherwise + Bool setup_done, no_select_input; //setup is done + Display *display; //required by all X11 method, provide by XOpenDisplay, Mozilla wnd ... + Window wnd; //window handler created by module + Window full_wnd; //full screen + Screen *screenptr; //X11 stuff + int screennum; //... + Visual *visual; //... + GC the_gc; //graphics context + XImage *surface; //main drawing image: software mode + Pixmap pixmap; + u32 pwidth, pheight; + u32 init_flags; + Atom WM_DELETE_WINDOW; //window deletion + + Bool use_shared_memory; // + /*screensaver state*/ + int ss_t, ss_b, ss_i, ss_e; + +#ifdef GPAC_HAS_X11_SHM + XShmSegmentInfo *shmseginfo; +#endif + + /*YUV overlay*/ +#ifdef GPAC_HAS_X11_XV + int xvport; + u32 xv_pf_format; + XvImage *overlay; +#endif + + char *x_data; + + Bool is_init, fullscreen, has_focus; + Bool ctrl_down, alt_down, meta_down; + + /*backbuffer size before entering fullscreen mode (used for restore) */ + u32 store_width, store_height; + + u32 w_width, w_height; + u32 depth, bpp, pixel_format; + Bool output_3d; + +#ifdef GPAC_HAS_OPENGL + XVisualInfo *glx_visualinfo; + GLXContext glx_context; + Pixmap gl_pixmap; + GLXPixmap gl_offscreen; + Window gl_wnd; + u32 offscreen_type; +#endif +#if defined(ENABLE_JOYSTICK) || defined(ENABLE_JOYSTICK_NO_CURSOR) + /*joystick device file descriptor*/ + s32 prev_x, prev_y, fd; +#endif +} XWindow; + +void StretchBits (void *dst, u32 dst_bpp, u32 dst_w, u32 dst_h, u32 dst_pitch, + void *src, u32 src_bpp, u32 src_w, u32 src_h, u32 src_pitch, Bool FlipIt); + + +#endif /* _X11_OUT_H */ diff --git a/packagers/osx/GPAC.app/Contents/Info.plist b/packagers/osx/GPAC.app/Contents/Info.plist new file mode 100644 index 0000000..c1371d9 --- /dev/null +++ b/packagers/osx/GPAC.app/Contents/Info.plist @@ -0,0 +1,577 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>bt</string> + <string>xmt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 BIFS Text</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wrl</string> + <string>x3d</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_model.icns</string> + <key>CFBundleTypeName</key> + <string>Web3D VRML/X3D File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>svg</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>W3C SVG File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>saf</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 SAF/LASeR File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>flv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Flash Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>pls</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>Shoutcast playlist</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m3u</string> + <string>m3u8</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>Playlist file</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>aiff</string> + <string>aif</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>AIFF file</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>sdp</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>Session Description Protocol File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wav</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>WAVE Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>vob</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>DVD Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>a52</string> + <string>ac3</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>Digital Audio</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>aac</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>AAC file</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ogm</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Ogg MPEG-4 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ogg</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>audio/ogg</string> + </array> + <key>CFBundleTypeName</key> + <string>Ogg Vorbis File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ogv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>video/ogg</string> + </array> + <key>CFBundleTypeName</key> + <string>Ogg Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>avi</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>AVI container</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mov</string> + <string>moov</string> + <string>qt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>video/quicktime</string> + </array> + <key>CFBundleTypeName</key> + <string>Apple QuickTime container</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>asf</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Advanced Streaming Format</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wma</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>Windows Media Audio</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wmv</string> + <string>wm</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Windows Media Video</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mpg</string> + <string>mpeg</string> + <string>mpeg1</string> + <string>mpeg2</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>multiplexed MPEG-1/2</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m1v</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-1 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m2a</string> + <string>mp1</string> + <string>mp2</string> + <string>mp3</string> + <string>mpa</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m2p</string> + <string>ps</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-2 Program Stream</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ts</string> + <string>m2ts</string> + <string>mts</string> + <string>mt2s</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>video/mp2t</string> + </array> + <key>CFBundleTypeName</key> + <string>MPEG-2 Transport Stream</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m2v</string> + <string>mpv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-2 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m4i</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 Application</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mp4</string> + <string>mpeg4</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m4v</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m4a</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>3gp</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>3GPP File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mka</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>Matroska Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mkv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Matroska Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>webm</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>WebM Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>srt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_subs.icns</string> + <key>CFBundleTypeName</key> + <string>Subrip Subtitle File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSTypeIsPackage</key> + <false/> + <key>NSPersistentStoreTypeKey</key> + <string>Binary</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ttxt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_subs.icns</string> + <key>CFBundleTypeName</key> + <string>3GPP Subtitle File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSTypeIsPackage</key> + <false/> + <key>NSPersistentStoreTypeKey</key> + <string>Binary</string> + </dict> + </array> + <key>CFBundleExecutable</key> + <string>Osmo4</string> + <key>CFBundleGetInfoString</key> + <string>Copyright © 2003-2020 Telecom Paris</string> + <key>CFBundleIconFile</key> + <string>osmo.icns</string> + <key>CFBundleIdentifier</key> + <string>com.enst.gpac</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>Osmo4</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> +<!-- Please do not remove the comment in the next line + It allows mkdmg.sh to perform a Version remplacement --> + <string>9.9.9</string><!-- VERSION_REV_REPLACE --> + <key>CFBundleSignature</key> + <string>gpac</string> + <key>CFBundleURLTypes</key> + <array> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>http url</string> + <key>CFBundleURLSchemes</key> + <array> + <string>http</string> + </array> + </dict> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>Multimedia Stream URL</string> + <key>CFBundleURLSchemes</key> + <array> + <string>mms</string> + </array> + </dict> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>RTSP</string> + <key>CFBundleURLSchemes</key> + <array> + <string>rtsp</string> + </array> + </dict> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>udp url</string> + <key>CFBundleURLSchemes</key> + <array> + <string>udp</string> + </array> + </dict> + </array> + <key>CFBundleVersion</key> +<!-- Please do not remove the comment in the next line + It allows mkdmg.sh to perform a Version remplacement --> + <string>9999</string><!-- BUILD_REV_REPLACE --> + <key>LSMinimumSystemVersion</key> + <string>10.5.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>SUEnableAutomaticChecks</key> + <string>NO</string> + <key>SUEnableSystemProfiling</key> + <string>YES</string> +</dict> +</plist> diff --git a/packagers/osx/GPAC.app/Contents/PkgInfo b/packagers/osx/GPAC.app/Contents/PkgInfo new file mode 100644 index 0000000..dd0975d --- /dev/null +++ b/packagers/osx/GPAC.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPLgpac \ No newline at end of file diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo.icns new file mode 100644 index 0000000000000000000000000000000000000000..da7226f64f8939dfd5fddf847699e6f2ba13250b GIT binary patch literal 147235 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 J|-(K2J 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 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_audio.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_audio.icns new file mode 100644 index 0000000000000000000000000000000000000000..d4a1d32fa20b9156aff490e73895d4c71408962f GIT binary patch literal 209406 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 F|Jj+&$WH(O literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_generic.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_generic.icns new file mode 100644 index 0000000000000000000000000000000000000000..6d0417b4509c2ef92bb938aaa4c9a1573772f653 GIT binary patch literal 190916 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%0D|XRYL}7ZOds<PDXwav3 z(TxdjHyFaT4s$z(JmcIE2^W~jPKD_ZJrxy(Kw(Q&uUk49=M)=ksb@I)VXp$X9r&hX zT%xUR4%9VR^aUFAWJR6-e1ll8&FXn%$wW-;eV2)2X%~ag>&G98zY~1;ppEc4G~x2J zhiJakvc`P_Sph}36gZfJhXxlq*a)DV3Q0Q$@g>e>5v`X|%=tZxjah;tnrPuNmakV< zZ?KntA+=2LY`KNB0#&h-vj?w3Z5`Cqa<XUy*w{XN-wz4&Fr#Ar6n45<c{>tgP|g<z z*B8xq3(L78*8M*_3_wGTE&}YEV$Q<HT9sVeZYSKq8!RLJ4pXi4eBwUa{M@&7;p;{* ztoip5PDpb1<1Jo46)G_5$<j8fN`R1g^Xe4Pc)<xjGi9Y^qwqdx7}f~0R-xSxBZs!# z_5DMWTb0y9V7&eb23jEynnvzlh%m`dq;0TIpPJdNL$NVA2PUo|o~iz{v>P38Kdo1- z6`^WD#${^8*}Smy0ica{cxMYc9L}LG<E2LET5w{_xXdg6es>B`mAw#)u3?#m_QjtA zfHb6K)B`cTonMR(nA&Y-)K$o7oA&X^w<OWTOd!*8+X(U|nY!TIu{=C>j>7u>G7n$Q zC$-$NOu=Wi_f79*j(!Zf-B;??hf!Oqtx2pbSwiZ<*l4!nL!b8I!n!w92UaL;iP#}y zT@Dc3jNy9bwFiS-dM6-$L790nUnwCS>1^i%JYk?6Tp`QyNO7X2OAwwsisX}`Jz?!r zvM|_npiE|(mWgb7l}s-VD+o9K0ce!<@1BD>cGoiq9;*>Bdj3==GT5Q{6@7tx^dXoB zoBso83Rl(&(S=}8V9=%Vck6<zTzTnfn1xvhJiW@~i&6jpT>k@NLA<O*t;dRQEC5n4 zhQ==@CtEQZKj%8h?5`_djx1-x>=`Rh4V=dqMm$;~Tb#;PTJy!s1fnj-aicJEHMoQv zUoF*`W^zngidPM|5|<~q6KBvD5`t6E8QOC%&dX4{UZLP-eW#2|O_tH_g!{{w@}PR> zOmRrEC#s?AcG23M-K8;m(!jP9e(I0^J@H;wh%t~X=g06Is$ZB2-t|1YSM+pHJm<N~ z!#e4SX3M8Nsd@SgE{Clf75`e5BRsEDNV|V<(7}!#i#2N8pWz<8J!^yr%W6VTTGPJ( z!7pgMiP~yV;XTGLmh7j9Wd%YvVI#wrqj?lcI$E4amK~RITFV&q`qfFnHiTWvUm$j> zrJl6ruDIyS@*wzZ-B*C<bf++lHtzOpLtSJHUl$WtL(0I*TqZmwNaKxs^kcjr-w$0n zz56sTBu+Jfz%%}2H(;^Q0{0`P6vI2Ng^e{sBm2ZTNGVb$<XdT3=7{UcXaiE$hhxQz zIMYKUL;7o4sv5y_TdQ@l*k0alh`&f0nT)o*%H@)^6@_h~5vjzgv{`5sG+d|#O@L}) zl;(=i`PwiTb>XK<y^s5%=$xlw2h!^^EqLAfGh;$~?5Iqq;OPK1OQeuA<$TShXS+vt zITWjL;Y%YBbD}u=;1nFL6P_XnTvCcxZ$fa;3|T?;EpfkX;gT6G)CNL9fB|qz(34YA zpLXDBra;kwkiBdxJ&o0pomCD-sn%K`7azT~Glx!eJME4)K>fwYIbj5CbDg|(n?f3= z8mr&~)0{TWN;vjkgr45hlwcf{*Qm8pbS3ZA?mzybl~LPFr3$_WI6e9|leM(vB6pdg zwvUcIZ0(du#KYsEf~RvcFn?+%UgTqP`5nnIvH5BZMrU;U6?@)>n}-t>b?&LP;vPxX z3+wzWpN|O8PEqU<I&=@MdkoKALtEYGn*IR^Zt5E9*UDlNt*P{y-?uS4hc29D4?Lc= zXMiGQF0sH@6-X=S{I`K`6`#jg?7-T1xwpA+W(%O_&SF@%7zUop0HpPlP^5?|BBi*s z&%%t$mMCnQr4wX;wM;vTOf*)L!X|a}2RK#_0p}~`P+k2$r*O<|!F7w@MFf97-*+@y z9^d0(lq59<1cqXB8$r3HA$`3kF&#bJe>vW``(#6@bBP4}OcmK8fu=i{qB|aCWg=&X z-3_sKmCWM`EXG<!>V#|yrrTdHY7pp@I932rK(4>XOic1nk>q#zgP@$y<}WCV4$_Ng zb@hM9ljf|?s|4?TXpN;;@LQ?bLbFuo+*2E;q61~mv5oiVsqgLYS_W~%bil@UGJ60_ z41MlqWRc@;P_m4)MxC(X4iLLvn{~!IhSRDwSu+jO97$B|DP*cs^gTfpWWaG`Wc+a- z`)>M1In2}n14mN0GP-Dl8CI;>Pb;Lf`>z5Xgt<(c)@SGIMW-7+sN_>H9OTBROd_dB z+*eqG{e)^p8KkcKsk0bsx|GChsxF<vv1oaDo`{V+yVH~t|6?Zk#T&Q`%Hnmjwtw+b z)6HYe8k-h`7kCx&mi2l9ZRiJr0`_vCfsT4*Pm-pSCRg6q-`Mi)x*7fRfW%q*g8;)@ z3EdR6#}9vY8_led`I0EZv5yddy_wgLa}}21kwTjuB~=%b78)cglJBGXyG66ib5&fW zJ?FzcTHnEg$m7SfV{VdUSmgCcwujSp`P>N=Iz&fPYp&u9rD^+5csRLF@Ie73_?b<s zc0OErXQt2n@VKEA40-o>cwL;N<dzQ~h2?ug{ShApPM>x}olD*wC)7JTMf+x+4aPB8 zQm!cw_PHN?5?h`8=4DH1)Bj_psEj^1U7B_jdKL-%m25u|<yg7!s*uXz7mdJS<CVuc zyW(|z;DdqX6Nk;O+<~-k&=EPd1>EZF3diMN(9-+mlRE8fJxPnU(X8<%(d2I5OR@E0 zwW?}T5H{Ok&ym)%D_<?;tH>trNwhF%jAMw6Pg*fO9Yf1V!L_CUYL?Nr7+6rQ&_pxi zL~dZQD_R@)uC<4~1!HTX4B3FwOKf$!YplaiG}7BJUoa&VTdM{L@`&9jafH*T#y0oC z-<N9L8mM;n8iYbtnNNqXJfM5jKB*!>CH>jiRj9}NbT`wy89uHLvzPdkF~XzzGDrUb z6PseltFMo2=vBgTa|X>*&C7kojSr=74my7z=}k`id{1QdiN(^?{CtLxf*W5bu>Hyh zf~~!=$-S&naQTnPQv}`vQS$6DK5R;cY;&9$urLpbCQl1nr_U}bTJozIfcQF9AhdOT zBTiRu%w-MfauGJi(w0wMzy1MrOO~9;c75`WFb<<UFyg_A%tV=2hcv_A$HwCyVpD^J z3_5bPeGkARF0Hb0eh+z-<X82CoDgMZ7d=uw?bG(u05Bh)r?xtd?86Iu9<UuEr;KY_ zjB-n*l8VaABw;w$=b=x<APx?ispd+VGptEF-6SC6-SnjWXXcLzqWJUDn>%78ebP7n zh}%EFSsREcVF{8~?t{o!D!CQ=f3Yl1Qm?OF(SY?72Kk5EDyhJybWjK(wvYgcY6WPb zE+feT?2KZ|a)X6QQsIb2_`mKv`9cC%N0vwqM}7?WuI<_DBfY$B5EimrK$x!1ASveF zRql!QboT1w65ef`R4t5mQCd#w@UAFO;PQ}WeHH8(VCmmu5#T>3MDK^t3*qle;qNR1 z@cJ+KeHi>b>G*rg0Q^3p{vS%e520U&(IE8fP43KMdt2Y(d*u<n$Kc{qxjL4nHI;^K z-d+)o;NwK2@LUwRJXB(k)ApW#2BcPbdl$nnOK%~y8vhfUo>#2EAnu$sC3UtT$=|X5 z`N+Y-18Bjiwd>-DO=|S7P?1b&f3jJD<)Wt7Cc8~}N1bPy;W|(rFASY9{;kxVZ$k$O zM~rB`wf4LpYdhG>;EcXn!`^3oG3I3D$E3+8!j%1}bV#P#T7*5Vfcw~b9h;>KOz=X- zyGn6^64*6V-FAC1YxqkHoI*C1KcPbHr?C&V;_r~1nQ|uq$0*Jmy%g_RFHSMAGoI5A zgl~Sv`Fz0Ah;9gCjAM|6mpf6*#5Qxk_E8fQ!R27Fwp{F`@Z~cw4)BHa*o-kG7{}wT zEv)0dP=s~UuD}`$k#ZMR{}+8bHc)GG)dZOXD6GsDj_|xR4DY%GyNXU}2`kP4{G5D@ z&1WX)V&swgmxM*UGuSDzrK)iEezaaA1iD2CKx6<?4gxU-GK1WOLH#kY#K=R3PSl(O zMGNAwr0xGg6G54#jBUcoFL^I;x74cK>Bbr{xsLaTN^TebNbGmMmkyS)sB?V97N5Na z-BJ!!K+STfboC{Umz3EJ%7h<mNg#&L^!*eU%AV47wtc=<n4Rg#-reR>Fo5kC!6_~6 z=)VK24(KmI3|e4|B1u=@?VwxkW4&ASa`L*1PR7CcTu;WPHo?%0M+Hgn6%I@SeyL2z zWsX(_LvO@C9*hOfI*o6imU@6SgOrM~Tq)W3&{8t6STunF=ntxunb_rqh%RLd<a@-} zEKCoXr&@%(B8o-FWX3fwh8f`23j2iEbom?ToNO5iz|iU$krcw_d(ZXhEs(IX_hr;E zt(!5l@XKfFbb>9P(sqBFZoKQ+reLX2Oa5lvB_cClX`+Mv8h$aho>{rRyw-Ch03(QK z%<^Ct^(E!&1f**RczRO=_xV;A8N%do(+j2T$RBS`xnU@lXMWbcDI7j6L2mfbpa8uu z!li&kJH)|m^{KCSoL*Hsj{AJ{RN?aw9;)LP#9j7%X1F!n(<M2)5zPdjk}KKR`B*GL zliHy1jf&1F^30VboP(>B8f3PkbqkJ|J-GC;Q&XcCH{iL`!;NLk<SV&<U>Tf2=DG=G zAl3(*A&*z7*qT-ab9LI15AQ&X*b9g>)E<>7&&FT2Z!#9n^h+D20<;rU;XK<x`G)1> z<V7h3J{k)h4}u+eOTRJUc&lE$kGO!EHnrAsba7<p#pMZ1mZm0qdQnd0>=mawFdt-T z^y3J6N7jh98tghdcQVGfm#A{*@BT=&Q>wg2Cg-W&XJGB4sw|DC#KbNudU2owm{2ov zLs-Y+{Q-OX)-6aywrGdvh3{avzjDeC_24WShhK8cfP5y6x#~Az{fT)Dz|_)gC}g7e z5aYVjQ**&xhKi;FctEj3+c2v|$LV&|L(R%@M04G@a-3_Z%f@Us_L>3Xay79eT~2A4 z{LgQHY?k*!V2k@ca(+Hz>$%XuE@5(H`ON*W-MKCVKj@W|j(2W;1;?hoeLCKqd?P>7 zHGO$nMQm7)#qftxhT=7L_NtDVPEa1l!D`J)5A0VPJJ1&gSzV~-^q}Ao!8GR;PVX2n zQ!ZH<FU?vM1LwE|9>0~n8j}O+ZG=OPkWa;$8&H#;0KfN>KaGyj^ivaieHmn8gO!uL z{wLA57uTu;Hf;pPs=k6wR7GC(I?~_}<s}OYsv9dTD2F?fW(g>lWWByEZHuVH0l6;> zr`8(O?T?>`PtaSZ@EB?Z1SF&#`d!mLY3a^t5^KRVI6;6ZmFySsWjb>;D!w6Rx>1i= z>C&pJ^~wJt^wzdR1p9=PG2WNhf7Qz2w6$%7fE2!(bwQr>DEIUq6Gm~!1j)u$ziWcT zd_pHDvsg*+Oc7MS`+qyg_KJmdc8g1=1`sm#mvVe*Fx22hMck#YfhXVx*M{d;VALvx zOC?>Q1_+MBP5v2-Hxb>Q4O~|O7awTsa_3c5?2OrUwxg5x7N1}FL|v;IN!2LlyxZA2 zwi&9~BmWGrJK3en?xyuNT$>|f>6X__=X02T!L9>Dsgfi-62$dbQ`_kX26ac=2x=zM z%fZ74D-*YfIV-TUUR998j|hH|DVhwMWPuyo*$kDDbOKQqmn%amL{hdFIZ{VzC{*jo zSxilp#u+?%?V#53K&}gyC#<#gt*jrP8fs`xz5&FM79C&+zqXZ)F8nn!T%Dk`rP_74 znEiJcwH%_M`~60-Txs_~HOwr10KuuYW3T)ki*PWQpt_%EW$t)l=^MZ-o1?u@I@QHC zuWc1RGC-C?6)8EKCx~RJ8DA<QG?`1=`U!5m<Lc;7=Kl1teTb*rMM!I?`c4LeumUbV z3HKy!$`_Frf7?vcahSq=uA0pmH&{<u7=iME5j=+?Y{aePV>B3qqY|ea;2u~!W#dLv z5eCD^Cax+t4(YUD$&sJbk%M^0MF9O3o=85mQP8k`;Z-y9gKrDP7OVvx%Qp8mP+k;p zl9q=kB10H?EyXx@q%nta?Z8sP$IBu;oM$ZAy-K2&v|Qb{mZcYYKSSLQIeF!Pcm+rF zA`XrU!N*3{Nj=qkXGM?05t_f}JDsRL&D4}B?%Y@;yG69yv;lb@%dDW0)llSj2PpX> zAvslyK5eU?o>OifvXb9I(yKF4c=h;kSny1>+J*9P;q}h)$1e2lYwTgdMK_>?JppSe zEV;xx+kqUT;_d!tC)17cy_K7yHJ=F%B0&6LzjgRG>m|$gROw~}UFfx_tMCXBigMuV zmY~e~>|TaOr`4Z}rEbIjQiZB0^dlh%4mlR-quM;dRk-XX7@Zh<a~EJrs=UWNd?4ZI zMOdygO9O5pCGe4(ov9K>oOZdB4ipW^G%<W+_TY5$1sBgUnT^o6Y;_+s9maNa#k~ot zaP#`3A&>tZw*c@Jw`g{tV|{#*i>X%qynn>jT;n%Au=AxkSTa%IMvv1~528~E{~tOH zlQm>1Cq7d>oeSFOzg9`hrM;x0^0-W=*8M^SUU^j(+U%mm_8sye^s0VldM`1{A+MdI zCVcaAT#=(2XHQ=YyR^WJcB^ixzo>RdxXoHk)0$~ZRX#>~ym$hVfSz?rd|3^3(7;Yb zx)S|}aSbWk0hisimzqoo3ELBiaNVwiDfx6$&yU*2^|BjdG#ekZt$G+m0lk<Y@i<!# zVvh94q*(h>+*eimR>MTt^n~!VH>__;T@FVvWWqhF_gY`42g2?w!wgn3e6;o{Q3`YE z3#VVrw_E-^4RMYFa`>wqA#q@x0z&>{sVt1xZS<n&2tAoW$uk4Pitg)1;yC_wLz}!p z?$6GI7msz}!=J?SS*pgV^S~E;fO0fb{xwHJ60fExj%Uj#^f~ZVHDhOJu^R3$cKz^i zk=g$A*TgBh++%m!fc>yJv{~Y!r&7-IG+g*R8_a?-+Q90OQ6;~BA8y`&gSAtoh#E#? z%;?aDZep>Xys9`i?TPH6gQiUVpw)W6GAt_Op^{AnE?9AyL2^GnEQH6wF-vsiq|ZI+ z<Z!mKMEut1>?!eyn*`=lJ3k6__b)#qrh*UJ%_K0b)@H*~+{|hH#$X1A1cQienLm^` z+1ayr)<x;$HvcSElEq6Fbit}rp^z7?|6yL%<q2A`IXywfl{@)L_SW*&%S`>W9QpUh zze;@*dWRZxUl#YQ2kZCZZYcEN16>yRZ*C0l^nHMFA%r6DXC;_^s#rZy5pX?;&OI~E z_Z-97iK|qXD)YpuPJ-^dN%~wtFg#%zH&~ppBlvo3?o+z&sSN7{_2(SZqH#{#KI0fF zHP^|5^;$2au%kE19!wHwWabGD)l5zhTwl4E>A?Agg;kHYEYBJTD^ooB3eoteN%ljr z7Wh+RC@^7jg!LIPcj4xPC=7xpSKAR>w9zs+$b!@>hYI(%rM4!`C*Ui6CJ8ZqIiI21 z3UUFkLd7OZfGs<YoHXuF`Khh6RcFtyRJV;>0uOO9nan2n7Snh^TM@zF=#u@S;8=WZ zDAT+jl^sN5mmLA{N<a&?j?)mqKmpmo?dEh@1w{XIw{=&SwKi;l0aoTci}hEllDj4X zXl;7{Ofw|wqj%w?3w+aU9N^vY3-Cb=WY(nsUVijLD?uhkr3kG0h5k7(SvZ|D@Bt~& zimiXf$)1dr31g^Sf>@pK|9*JEhtEW<gK%2>Os%R*+WLC@ze%5@5pF>5Y_73VA<s@W zXj8v@e@cDBw~Y+LNvW;n4KPw6T0|5#mYaa#`+73{y%T=kf<JFUSbpA-zi&q0x1tZ* z(Dom<qp#c12kq!1_V4KOu3+9D2Ef`?N`><TmcV5>OXtqz?4k8n#r(gZro>;2!E*m+ z+a%&cIRKOjL1QGIcIrIgH&Tt8EUr`&FlxD5zZqOFdG%M_=^4Fn>Vp^@xGS3pk}*_Z zlYO1ilJhC4iZrkisG<d_U@7l3^C6!vIw+3%-<gb998)lY=#}R&2iWQjwbEht?VOU5 zvNXZ8P)e{V-x?#<FDgldyv4QM`PnsPL=<ch$j@-NW)-36w<q~OuUB{Aq~rRBKB1<C zDIu))5^K;pwN;fZT#m4xFiX1dNn?3ChGFxo30I08K-^3;63dKoZO`aw*|F4;#(9bx zgJf7pUCFOCyrrSCjA)|ep;tQ|?5F8b`p&EBLn?2{+<-j~V^Fg?N?ikN;L^uFJuY-9 zCsbQz4utoUzqgL+A3pB+7Wb@z=*;N!cbg_<SNi`jE~WquLAA{@meZ2=qBXs1uI>*l zI(h~}mX=uz)b;RFRGEeEZx#yBojBl*j}PiV)|RJXGcGKh`dE)*sh99w{SWyqE}w(w z@Uk7IAb3X5Bq10$$C84p)8P+UWD~}Dy?4ie19!eWbjtUyxc)+{WaRYC-YkS!_-skO zIAccH<~f?#f^zJqfpJ$|{)a3Agb`P;S%qnfZ;>5(p1VtcU~L~Q*3HO0y%3)CLfA}4 z(TyXeiLejcYEiVDkVtW+DZHDGcf{p2+GCte3Ogwph%G~d2=GBgrDLy3y11)5AKl2> zSK5S>eei?saHSPOku8yPFfSv#Wn8;<DQwKW08eQ|HEu#7UP4NVL5DYrjGfBfOJWg9 zaJCu7Zbm{+$&CAWm#`f|{!{I>YWsT}e?l?5n~oem5;ytRepdNvk3zr+rFEk!<<{mp z2aL@Sj}>ePB{13fYNkg*^!i}WJUc`dm%FbMuFSGtRk$`TZYx5d7fa|+=-1;m8>{Z8 z-~HtbIhgJ{R6;o&=v)@ZE@ix^$N~}dk%0TiE&I&jx{J3ID0k~Iw~lE2?C#%gg%H+V zQ3JTYO$X1SNhc~q=e>;=M|qKQK(ENB=W+wphCOy``N>eie{d&YRlL=y{8NwOh*flF zo5)Y_X@*5>bf~+I4gFoPE60BhT7sY{^Dog<L$rd;McMx>Hx&2CGA?|-R(LiY#MkIW zNdJRBRlg2WF}_33k#aVVd|3NtZ?5frgyiv@(dg@lmrH)W^%3TV%SWAn-A5IKKuMj} zj3HMn5>=|ES~89AgTZig8}O4Pdmt-naK`_1<71^5@DM)y0X<O*)i;nYHI1pIr@_UR zS9_ft;G#-uCx<nunSq5Jn<2}_&OqOAbJ5w!fxUHiP&i`m`8RW8aRy=`X8?O#hQQ?E z+A+%Pw^OMGGN1Y-R0h2>6ep#=>uGuy!U8G^8P8FjIpmikfzFx}OkFG~(6im?#~ar! zoz|$mCI|i^r#-Tth9cSpiZx!T3P?Hjq}TK`{Fe&Sg9L?Di}V%`iM&K6lbGXcI4H^h zB&L(JIy_JHv|q|vCGK}BmlMqPiN8x|j@1~GH3gsZ<7%rC&VLD`{?)sh%<5;C1~s^B zmI$@DUg%?dB#$#Y3&SmnbbjvsdQz2RD?JMj<DK?#bWN~uhU4g@fc)`P$>gtoX_QfY zMpQcTg7A=<LBDGU9m5e24M9kB(KMmkPVe#ilQdEYez&}M*cg>)K<{ywYJJX2?^}Mt zlLMAELNJM?v1rYZIo64z%~mvXc+``Wui^Z)>Fn8;_y2NSz{FI9b-+e!f`*O8SsNIv zz3Wha^%P}L({|(DpTr3UiS|;xiNJIxXcfOrTA>BE3&juGRMEi%{*UqbgQF`UDUW5~ zEN;11(4RaLWBSXrB(XyGmGUE);&3#bTKviOu|Nr=v?AC|@z<KYHrQS^YlUx6@za-> zcoWp%i*b$j;#94dR-*wgCvSbOmbq(pZ`Sa5-#kd>x1@kE+wxZr7Vf|981C07H0)&a zt8>4{wEu0l-U@!Si0P<E9LFIbthZc~O+B;ZfJ87$Q|W5)bNUgbL7(%cyQlzhXW`jO z^A41-{K{he4)PyaMpvif^3A4ts@L&5@^Tg<5B!q)A}F67RiB@RET#xJK}Ke%*5pYC z81`-;Aki67FpB8*U?meJeCJxT@Ib{u1e#VZ)oX?iI-Gi+B<g11^C@Q$$auj<aq;_J z7?Q9J?B%B?oP0j{W1fM40F`K~oe^4G1(NKi>5xU3yS4XB2&jcr`DAF908KJ9z*6^E zNXe!5jN?jL4xruI*#I+Ux_IS<DBfMpy%L>_tsDd4LmlZ|YYG#1IFcW8+Y%Q_Oa(2q zt`ceb0%-4wzPz&VY;0NH)>_TzL3W_Jtv5*GSG$w3Vn?)ik#kyk`d!ihs{Ns=@Ba7M z9diF>TQS&oZf-G{V1T2Oi4*?0FC!c>dPuqI;=_$&$x;NeSbj~RxSYacUKo&X{j;T1 zL*aHa$gbq5i6t&eRreH^pg{MvIVYilaM>`OF>(?rUYYUOrf61&*j+snzJdQB@Fp7j zL3QBo$O$UegCcg4Py(+$IfysT$}{m~ar<K*i+fbFo_RLIXSz##pf<FA+Yo(<O#C&P zpUPFAH~a<8QBOtY@|1H>a%E0G=VQWf@~h*hE*J~MWrQ&_wUL&*XrWz>#_ywN2A%4) znrz(@1_)UJ2pGYRa4M+0ADCMoU1kfm`5Y8TML9{!mX=%ORjJ~`iF4ti#(IlznXxId zxTP5I=<sdeYDF+b_?=B4(CXX^ci__BlrVU}X<@Cz0E7+U?S>}J=BmeIC}83MS(c6_ z5unlOtQMGwi=YQrrRCIG2@*Edu3fXv$syPx8rbDVu@hs<P`|;xQ;~q#e&V>J34A@$ z;)nr*W<*VeY<Ptn1wM7S^|x{sKAH?0v&m<>Zs0qn?90rPcf$7T?X0oS4+%&0Hmobh zuAKY}Y<qmmT2Jrh=0vF?C<dIaLc0o1G!IE$S(9d7ENoNVEtSy)%r`1H8&E+Y2a#QH zWs5XZ?7F>sX4i&`@3X_}K_G*%Gy-Cftt7Es4%XO&M1rb#MPJ=P8KaLQGmR0KQ(#ry z7RM#>Vp=a&*1+6+kvpNyBvOJ3rix%l@A~g03p~f?AafJA?In~f76q_m{;2Nxs<Oge z`x;0Kcy3eIT3koMw@Bqpw-{=k)`jYbKUE4DU1j%tUu~=ZX~iUsiYt+c&Gavlx!37f znitz^HvORD8=UfB7VI(z2tL?B6Fq&k*cxn8=m%=^W`baz_egF;v3)$Y_PCM58+E|C zf;tcF|6~&oZ_mcP$jO-wlXzfYTwTEYwww^I1h<@8{EGq__<sq|b4Go<y@#(A#qz7H zCTSi`zf+G|5b6v!){E_E_@JlP2jYLW_m|IctJG|Y8TG_;S`~V3ihe%Ae?SG=<>qq* zwijLG?GT^c2n28P>EPtXE)7PS^bt~wHT?<gK%ZQ0?j0?kiSJ1naj=H`*8+5hBl~@8 z%xiPDpLP?xVMx{AHJ%Sz;uiBL)8b~=3G0Kc8hZMFjg__r4k^GQXy8#44qdwIN3MzY z`p*k_#YgLbqbm{-dh)44Kau%Rs8f>F>g<4^?fPICT}G*uLMV*_spBWn{FsJ>sIM=+ z^di~au2%er1s(w3C&NlF!^jGn99FO}wLAMHd3|^V8QWXKm}4r<Le*fO^gmupY|uPV zR>+tVajvpez5T5`cXwn>7jK*r;`5IwC&4TFQOW!AfHt*xrO**ExuBzwxSJ8RWU-8V zZ0{F^_8UC~bu^5Z-|>5zt#c`7Mf7d^7Ba21uWW(;eWqh0@#JLz<I?!7^?$FW$EmA4 zpx^pHM1I1p(*F5L**R~T&&1fnz@F?wHSTyiQEg!w>ORFMf3E5{f#ltzI(4yP6lLj> zn+R`9d9+DrrWi*~t%D#MVe|52yWpCJ=eSVV_I(9v0&0L&^o69>Y%c6wyQf%TG&VZ3 zHOf^n%a6D72FFU0cx1}9n$0_7Yg+h3BxE`n<tbEQgMgMgS2r@t8Pq(YMfiRcJeUV| zJTSK6Jk%<aa@lThG?W~3!zaA!#V!pK_=?xp!9K;wl0Jb$kVVL^^e|~OAq=s`?gxc7 zS^CsfEMW6sE+s-;l@o7@8N`Q*OpfMaWm^{4+b%w~l|TYT04SG!=bBMRw6JvfRic2^ zL||9X=A5>csk>Vu4AWdRCK3N=PH#a^svbTTdBL>Cii{$@oH^xX<<`i{Z+e0-h{w8? zV-nNsi@uRzyu(s|y@UNK?R|rgxos&}ZAp1o_q-Q^EVxj2#EPa5Pc97cgpbpNc1OXR zHAC%7?b57=iD~3UK`PAuZ2un!L$`8k;>;MPm-sgTuCKd28kOB%(HMtF&mnV?m+|O< z1q_My;XUoNrmiCZMajsoQ42F}%WZuclZ}=`C1|z>(-r)ft~(IdKs!+ERg|x;ky85B zsNq3if3xBN+5Td^m5P|Pi>wkLEg$2O{mk;L<YQ|IS9@t1f^oFCF%A^Ij3&~B960#A zg^Gqs!4K%WwOlG$g$1ynz3=K5I8=FS*_6WhfBP9|xbF-73bWccdV@s(j(Jy>V$4|8 zhd6ofSUR;KsM%GfL(x`S!btfd6M42Z-0_I})AHYFutZFHm!;>wDxU4Nz!^3Zw{Zj> zS4b6AyQ<<O{@DmkIv7NnPLX{w_N0)7GaPQ|MjM+NECd`zNKK5`h4J&-n_{7vnl=(u z6r}i^7eQAd(w-)$8vPNsgtp*D!+<fqi*9A1Z)Tv@kJv}~N|Jugg0wcT=U4Y{O6ccO z_2Jwd!W_45fN=0a`KUYY9hX99OS>v`y%i!S{U*bMn%~6_0#fvap>iRNi?#l+5Q~vE z6S&$K`(%AjGrMa1t<Cw#S*udRdr#{t35h_0xP06SqEF#X45e2Er|#X6rDmI9MOJrq z@h+ZtZi}2H3s2DD1fY?ONR$v7im5(8^>ozVUht^V?j(0L?r3Y9l3NUa&xA${?U{fs zJxA@8#1#I!S{78?{nnxw$0XFQdvs(|<@AwSAlPVQCE?19(guF7FMim3<3SZ3dlPx& z!a5~tvYcgb2HcVIzV(inq1cK6Z1IX($)0M>VKv9DdFbnmkoCl4c1<i<&1Ly{Y}@_J z4m*So7o68syJvk=Woz@4|7TQH&crMu2`qad;Os>IO0|BRxN>^fjJ1k51LaoBGyUP( zRp5An-6{dm*?A>RupaCj9bfl*o9c#h-j<~8${8$9H_g+S9WAOwx{cADV9d2~wV5+d zc)LVY`Q_u{+CaFZn~#~-FA~C`cHc*b{ttVzDwg40-h%1+$OXn>fIM{PGV~|LHbq&W z$fV^k*8R+{gU;u}z_k6}`5pa^UdgNybTz*>IT4v8?$gp7EU@NCS@y%HcMN$PidP#? zf3}U)$pra8mcM!V?&_s(<mu*foaCT$pc8}u@5LJDra1gQf+r_23!4D0ckf8N;AIqQ zv6JC(hT(>+$MT~5l2edSyZ<a>d+gDOSiq+!JgI@M6f~5bb>q+o44C0FSg<`nhu9uE zw=t(*GF>WToTNtJ55w2)$2U{NhjG*kPFPWOtle-b_Fos9e?d$f)Zg86(KW^mNGdk% zKFjdz2#HYz6Fr08t2O<D#MPFd7h&01_DlYU){H#+X->;opo>ALhKXrHi2Z9hY4`0k z)X$l3s60QmawbDoEmoQ0q9}g)aBlY=TE+w1s;4!pcs+SU2;yflbp;q0<%dWxk%bkB z48pz>t(?&q+djJ&h>I;^0W8*Veuy<J#KqP)Nbma8)<H-^iSLI_W!9kLmQpJ?47K3B zYqNw&BsOLxdL-Z(AZa5{K#AyPrmZX;xqs|^&Rdn<2RGKWTR^oFac$?+Qf98-LK6v> zU_nU!LD{mp{UF_bVir)^193zBqnvZ*@-3fquoZ}L>;i~BP}|USDfe@95~DtstU?Lh z68gW*S61FF9WmIX;OtUEU;fWT+DjDgqW*YA5$r;_=)q<~U^rWIcX7>j#xKzzo9m|J zV>g{qjG~$%{JcDJyBWe!4RR^DlhH7>;p9OCEXsNs2VWxV-TZ7$0Et6}JCYU}pLlnS zF1_7{RVSrweE5Xoq1X{L&MtR<-_(Y~i#zp&5Ay{;-3YzFkx`kFSJ%~<wwEXBo&FeV zF!e2k%f#eZ<dfH6VI{6iOKt^k+$ws+HkD{FtNIGgBNYi`=5Bp<ET`Xs(E-#szo0B_ zad|SEoE&IPAF_1ETMAALhq?`K7&O<(1zIfC*KL;@$ejK;N}ZMRUno=W{>o8#pvEdF zDB*OB-llhuu2~W^xNKGl*1&|mqR*3TA;qoTyXv^VG<<D=BuKQigik4(7+m?iX2D!E z{NB8*!^?-1<I~SovaNr7fiB2q%b)Ak%47kEGg(}-OdaR<@C+%6Z(~Qc#HIMZfs57I z_2BsDR%K?!n^tKS{U<8QI0Yz9@R34rCUX!A`1-XucfCH7a%xm9wI*i$FE|^Cy{z6{ z0sZv4mM0z^U|c&eSL^(i3@F8EH^kW+U1+V=#MK{?W9Z*0Y>;&6gz2q8Mmv?aYQmfq z2*N9~M9l!lhBw&TE4-xvx|>4UConw2{V1*m_qM^g2b0OOSXbzQjXyG7GK~o86?g7t ziUN8ZZ=Vg_x~`qpNCwy%i^R6_=PKUi7xVt9D_`>b?&8^nB=!T4Q-<lxGBj;55h6av z2)Y|r2!a{E_>^1dchfwVRG;xZ6q`~Ha`;I)lSRSs>aU7$yp)m23mPQXO1(Zk!CffJ z@Sgy#A*yYbn@fZERrdZE35o8~*K;%M%g!Ila?V8zr9o>rwi4|UnX`k*S7L>A#ji<N zyX=XOC{>me?$|8Fm#?})+1UR(>&u7(g!<<2px@gsFIcoB@2&XiotMAungLFNiJFzj zQ9dPNeOShxuXiEI@Fd?`Ak#XF#4nx^Q$Lz~sgWqKc<u^g7hf>}>w?@sL~_b7_$~e+ zyn1RA3XSElTsUUa<zBw@+t9h}wb3vNvwLjWIKNpx0u%N`BRPW;_PEHd!%9Skf)LCX zijdwvyCWs|>_r25R+!ijWGqsCr++WoYF=PYy?H3F9tfT|gQCMt{m2%lC;)@oFo5J^ zlm;g);PR#b#Qw=Nv^mu~&fIEq-0p#!$^LV$@b)}-ifX!8Dr0T0Z}gRNedw3z5f-^k zB04RO4|w+NB81&9Z{7VcI@IphQwljhrNL+1`H1~FT?R7??fuuIe`TuIw4C*;##DWu zbd>D|q}-WdmD|Hd)9IK>1Ik0`8zIe9?jFn42s&6e;^>#<9@FOXB{l%!7)gi_Abxmv z9H|_^q?i2`>mL(4CoI=VwKR~GEb6P`f5LcD)+JP!#g{Hkv+-Z54Db9TBCf_3Q@g1w zl8QroXnVdvN@mKAI6gh64)!(B;LQbv$GhHJGYm!p7`C?SlMr#^hOV)>UAw-`3hOY} z7Tu1v`z(3&FmWA&`eqeg--2g@Wx<Fjb2376meYDLx1WwgerP{#Gxmd~(S16f1hjL@ zt$Il_>I7NZ-ieKRt@2{n@nc)lU;$5Zaq8wP@Iyv3eja$l5BIn0r!N0FVUUd|)YRn2 zy})wANhtA~d7+bc*XO2~ce|^G3mxx|FDuG(xpeM(o5ew+w)?{4Bx+s^A+QEcgoC-Q z1xJCqcBS<e3s{|=Ky<5Z=-(w8Rlu<;scxUqd>j@o%3jHLR%|4s8P7LD`U@V&u<*yA zB<I5oYaF{*g?Xa2Yk9SdBL0Uv6S!F8ddg~_GGxgOo42Psu+c$YBY)b<YlBsHB5h={ z3ZmY|SjPlKBSkIlO)jie#6e9=1R|J;;2<=j$ONSpBNQjK#CI!pW^Sb|YZtIia|E@f z_XoBG$i3kKUp^sO_{_uGIGcm2qlrgbTdgTV9Es`sS94QXUXJ-PbXwZnMSt^odF%#z zOF#C@H}%o{kT_!;Zt3sX`f=88%ehi>STBgN99%Hdxn<y+@ppF7VG@m8Tm0Sea`c$h z>47frw<2Q-(nt5MFzoEO)FyUfb$3?(ei*^R<qF27(tx8^hj@n!!K^M{ALV&lgX?w7 z(rS?6%CTM5ay0CpzE`^({M9z~Jj&i!(meIMBLnHz(RRgj^wVL|GPaX_**!Z5QZx$h zyB*d;&F2_9{SC2SHy6E-V4;%=w!hE|=_F{%_amNV6W_x>;?eT_y|sDG7<+ky$HPnN zu*JhEYF&ZG`*r6W3q3f@!O43zd^A(W#dR78;MTYA9nY}f<xLMWk>k(vyHWQFotf<3 z-L>Au@eE4n(PQr_eOUrci2BiAs^0J6)<(4fgj?LE@jAXJZ+Tozte1UfXuV(hcG~;V zt;vo*%7x>!@-`qz)c&xYHlmJdnS@uxYX|u6gxGr<p`A3Ikl=yH#Ud|Rv~<M|wv(Dt z8MUi4udO=d`Qq+(L-m^fVmTtx<LsKYpX?nl806Ci^X%_QBnz>h2=X4eiX&=T4}{7F z_C+GP>81iLmI`+PR?5zkcBkkZUq8bSIhUYhN7A?ouT~0qSD!r~T^i>`g{@C&E~V+1 zMp&3fu*}1H3AGJ5Ru1{@5U$lvEw3W9i8sTcEzT0u*1gzvj<HH~Q}0}ikzK>y<VB<P zKhNX%bf^3}1pXWC{5l`U@abRpbVd9(Gx%?oKZiv3+q>VxYHzo4fbjEvQLsK?GN<2Z zVq@LIx*Z+%aUcbQ;_(=HU}YHId>I|<?!6Ah6;WJV;?NA;*AE9>05hYOI)+tDC||wn zD#4M$**_~;%Je@Su6Lv`FcT5^eHhSr_1z&=%y<%bV{?1Tu_w#Ytld-h4}C;xCU4|V zpti9FeS<i7S`0z?#~Q&H=2{z-V{31<nB5d$TcbFx_mT_r+QfKW(Dv&u8ux?kHb*!0 z6(uU;W(b1`Q$=Kvj%&UyLGs<Kc_^Dk<J>E1|6u$YvOfvr#*W3*oUNCE4Qy1Peh_~1 zzrlE>_5LY5kPu0ShxRQnP?|AXP<F#U`ipuYG>@e4#g+HGk`0Be?GyVnl4a%Lv<*Rt z45)hmp-v9*clBOV!&MRI66CWIV2!;0Radz-?{xF*yCfY8$A;tWaHFpjN_b*qKL2|s zm=WWg6{b47ft{6Q56+w^f5~3PSQg!Z<)$eYK)hC>Cy%-}tuGmIM*ma3Oy8Nlh&@w+ z{Ll*veF?=E?vV`H5V_S7uQ;m7SF8Jvp%D0ldc(KHRqAH=-e?0VU+KSsI(bHTMl$*} zer|Ip?BIom6n8}wIm9%ZydJ5IaSlCMqP6wnP2brNwB;hrZ9ve8)JyQHht}Y#3RHj? zFfm3`ZIcBweZ&J23n^-4WTDdhB2}Ub{5+c50UcMXB{TlHv*4DT>_Be5?TrRH3@;RH z2&+K^f&El|DvR|GJ(20>+2p-5?%}Pff7@6v)8B*Tmf%uEsE>`R{N!)2RUZBY7a^Sr zl~z}_<5}4Ncy->N@w0wC2hKKRa`X1zi|?%mwGPY&Cnwi6pM|GQ$l03xO@3&BypW%T zerKKk2A<o6U*z=8W?kY9^J{)x-Cb^04Vo#v0$?q0s;DFnBMu@LPIEYLb)g?-CvYt( zPr9uQr^nJ~5F3f3s>BR4laDDXw%7=(<#j>%H(C}|dN#E%)fZX)w-EDUNJg+Ya1n2< zc)&B8MM@y(Wy8nwI>(^lQc(XcH0k={?!95<^cnEUzP}$(EAmL^LvY{m>T`|4R-pEt zd!p~8St;4igd_h*q>W!a{CnSCQoUG%$Q~z;gzn~aGVJhqBw5(tnKr3KLE7dQ#o^A0 z9*}xqHq6tR{eM3PoSN#dK_`H;bo`9sYuSKJhF!Y}zKtb7!N3EOZB2}3#^zQ{2WR?6 z7z(8|Rf_Cx3Mg=)=tlCQ3gH%Q8!IjpUpKy?KeV2*7cx6^?@Cm1Mb&8RBf5FDyYa1? zCa3U9Q*s64X7|Ro*No)K*{qvoB|fGo_ZLltea;EW0%s!7X4*+9PFgcQz-=7aB7;aQ zZ~GaqKBhyAdBpS-os3zvk^uqWnjGl{AcSROO5K{6e|_Fl{vFlPqC$Ah#^eCJqZm*B zQrU!pUcJ<KF5b?C%(u}&Gp4E5A?Tv>U~?0Tm)Ap6v2@xb1m{)v4z!qr=MKK!NeFGU z_@Wxg89Hif=>>zkl`bxmOlN0~F^}8XT3Hzt7MamT!X_FY9`l4aF||Xmxt8NWxEOL< zctK(5m@cb2rRE}?6sz-xkrt>JPF&+VvX6VTi8M|bNLS!54i!6<uU*U}MWy+4A!D7+ zJw%K~<DR|l$lOs0@c$v-rihl$AiqinuSU3fGS$G7!;R8-1(44~VyIhOOY*%FkQOJ( zMK^5d`WF~8CS`^ffdU4s)<f)53$yk9w3NAh;jTJ1^h$aQ(Zd7ax58KkzC_7AYo+P+ z9X(-27|S7^0SfP<RPg8RVKPT+14>bY3cj6^Xsv>i)a}S-wiQffuBVBjHQ7SkcB`YB zYr{qDUM*+6&L*Kl)4V92yhup4wX_x8W{xyjIc$b?sSAJ%rOX|Q3SieU`Ma$Xb46od z(jM5QgKO|*3(%0h|1lH?gtvo^@y_h-u^y`tg*c{&^Nk2ak%gf({YV6#r5FXwHVI{L zmT1()5jaDQ*PD|}J;MhCcK&hlF*c((@jv#V+aZ6d9sx00kEg9ez|oyi5t>V@*7s3! zXg{@D%Z1(?)G<?3ivzJ{vOUU7)2I2U{kh)0Zdd0(rR<dDdiwBw{_&Lx27%E>_E!;( zSVumz^h9y@Ag!}`@Z8u8FQy_w0cvxcE|)LAo*$}EpH5Boz^0AbtU{F+-fYlBo9;Qm z%5Dm?zRv$;ahBo0xtuw+|2x`eX!ss`Cyy2I7GO07k?kPS0BvT&+7FhmlN||Uhy4eX z%Z6`fFhzj2jF7srFe2~K=%%Y=*NJMr2u%Iy80N`~+h%A<Jwz4FAFb)#bR>PU<5lVy ziz(~8!p!t#581p`PW^@vj<yK=>q2FC{d;lRiLk_!B2*o1u8gpg>WNmUD&FA6DDz;h z{rN3j%C|Owsz(H=^!q5sby&0$tKD~ExwbF3z$d&)#U8Iw+X^wjG2(&VG7A+d-G0B& zfL&RlJ(60j3(lZv<~e8tm9n&E2X31}p^t;e1U4*q&U#hFUjz(7Mc}pqU(3SS%^y$g zI@*Mm3Y6c4v)XzCAxG31)%spZGmjUb$1MK-yPLs|i#%<HEvk$|zwYd|qo$e8?I^Ve z-t^GxXcWx1M<-596uRwpm%oS^m`#N8^#3C#SG2(k*yUYPjJc%#8bhX$ad`!)-8`dB z5U=br<7&a)M6o3IPSIJsQ3bR`7B<LL8KbAg-@2UXApv-qs8W*0g*wLlI(8yxw}B9_ z1EI6$z^~xB5*LZMMyo)0sK^jQ#`_%OB?|9b72-{whny}87su=KI3S$(D%FfA{u&Iq zPnB%K7;_}P(^+=hG{hCiei04T73USyznBgXyfYzZFOTV&$mtOIk2SI;KPKXbJ#yc9 z!`Yq#(e;5&U{SudisnlLBB?C7Zap*C6gWSL?Zowtp2ojWX2xwbdof!O+XeF@67p45 zJTWG+eC`kMTaHEGGD*p~(@~7nkoGl;y1qD(Jr$!9)Yq73AvOaKHK5&(=B&${sFba- zzmH%M@w?2KT+dR*bSL2YOp83XFz%oOqG>YVG#;DW+ApE@2faFm>)$IL#T3jvg#|0Y zh)LNgIHjdVIv56BqmFB?#Xo9r<f~RZZgyX>X5a%n7v_eQ)?Oc@q%OJsEr<Fa{Q|Vo zde1L7p&oMNo8}VDC^>=8RsmeD<-Vy{5qSq4Daf0<g@%=>z=Eef`R5VFd;83W^!3mu zC~!w)%%HoxQ=TOv0K!58h-}Kl^V-ti>)0;DjJ3UMcj5C|I!LI@jYemdGL=o(olhpC z7Yv<FvaqUnAVk@uQYj%G(HAglzQ7DD0f>*(?u4Pm42SE7-<tRsc&+>;A!acE>ndm9 z+K<-(r*7^=1{UG?W}?hq*EQ)uL&z8Azy;iTb9DGJ;a(CM^$-=Yhx^eaH50vwl6+xw z!(n-S<WJcS0mnLWyBz8m-8qPht8(-jAUX{+bs{pZ-eWkpbc!|(V~3SK)ug*-;!^tk zK&C04K+|u5JLbo|h3o)}2M9n7By>xnxgz_@K)RFKfs`AH$~;z=-pjk^#kQrs>s13* zcsV@9Cp+qt>^z$`uZcjtMU3gtSt5>rdjDJM9FQnvvbFFbt_TG7FZlYy>wuhdDmg)} zQdMrWGPu)M=t64wrPRJxlo2Jdii!r=x1%pLNYE)EJggI7`mz9v<pJ)$`Tp9Jjs?SD zB-j^|PXTs3Z4g>YQuI%T(j3$Xf})ecJdr{}9i>OsX0d*Y2{rswf}rSLEj_hoIN?57 zb0l@hxw1K$+4LerD_vM->@X3o`c&n9R2r`!0-W5+W9byvn@RjIvuEh{6r4c$r^Y|q z^g9LXP!sVQtw=r8T~iZDZi70LY)<h2-p$VR(XGR<2tQ5_F=K*l_2K%@&+yUI+8)Or zqpnRWQ0=S#bU4`%m#m)D%a^0ThhL9Ov8h6C3qNTiezF53(qY@6Qme_DB9`#(OUrnT zmBw{!5pq*AN4p&dC2PkGZlCh?(ph8Uik$EqBX^HH=I3Ot?GDU;+>1X)e{NdH&ChVe zZd!0%8ki-k`3uE*zdFURb(&bd5a#K<6ty=0FWqyDZ(_`W#^BGf`j`4l0Wj}X&1#sM z6ZEq_x9HCnv>B*{of>!o!|QCMy0;v=9LFt*B{eDG$+wOZ5X2GSm+$2zd}>U;OBdsR zjkh7obfU<SolBlE^Aihh8SychA(S-vG_JvZ23d2_AB+atAy>ZX!n1Cz8<Qwe1Sa5I zvV6!ps&rq$N&DQk!`gA8lM*pjWrA1uxgz2>-N;5aZZ%0=oATPc!ymRXfXqFx66HW< z9MmA^QnrWV1buc-o4#42dsuJg*W-<KNDH;WkIT~z*3Z^$ze{~G86lO)8nzZ-A;6AX z)^&eIyZdTH9L{P|=5+HgRRrbmVvkh@eaC^I3}drxsU+vm1DhfyX!YD=W^jtT6E*43 zwQKpJHR)-X{t7RI4fw}b{*J_rKx1P?mUQ=2!Ga+%XBAYVO5(w{zoFe;J9;1Ybq&?C zS;-arrt`BwWO)|Jx?0W?t)NxcIsH{pBJJ@W8o-vX-f9Zj;IsGJQ%2#k&=<(p+?S}! z0u(Skr%gu?@3O&+lth^B62%Nh%TGU^asYwpcyJ$Q`;k&V^#>-t4yv~u2O{P~UF2d? z=9vxeKcEClG7&1MfNCyr6`_jtNVh|r8qq|YLHM=b{t9Gd4k9e29J4O58>7}viK4d3 zUCC!HW0NKuh91hD@8rj*g3D@Wr#B%rtaNDmJ-kh(ZKh(3F^4x|C0hZlAA;!?+$vP+ z_y$;WX-hMp-OHcGk*pake5~h$4hkP*Hw?P=1s8dxgeo*n-;TWa)jgoFh0CUl6n)RV zwV}VG%B&1}1UJnTE(YfFBz&L{>}M-yGrZIJ{*%<atRwT6nwXJ;n6skuk8b@i1n!w8 zF%3D0DBp$oE(vOW{peFk4J8PQV%cBsfrP>jW0(6eICNYlP7F`Dg`!%IM+~Oz4|tSq zWH<V~>8$aS){B!ol|kQ0etacuahRjAZbOk(p!BIF*uLt&W|vGQB(p-k(d#cXl8RkC z%cLL_waHg~b*Owfx!GiB$7?&&SXF>Z=}fDq#MjFZP9h9itLGJ&spt8~gr7$D$ui%E z!x=9&9}@YrVo(6F8UVk;9L94K1<<9mV=%Kwsz!vyyFYfwJQNxnJri6csugXH9M%6x z=zA(sSm{H>*pbLr3%-?5`8EpkR_;M>OW0w-WRtTk?Uo{DiOkN~jWx}nbE$uV^F>I> z_K@q?6{>JJ$bSd)Xq>%(Qi^X#w$96a1Fh$fK?X!6$ODKLaa4;-#{KrV)*nB#DtQ3> zBIigTkT!bG+u#(>AQA5zO5PDV(R#UNwfwho7OTaeQ09ry%}B9E2D{HJwTH=Ol_KJi z7`5OGMc{CIl_{}-WQ}sqjd^)iYoNQ}317Y&wbzZ^or#NfFDjnV5u>uBJKP_RT@CcM zm`xnmk%65vvGE;8*-g!t@yK{jS6rxhqhp(%)>B8cnxuw&ik!L`+pTt$w52tkOb_lZ z`4)#~d-Cg!@6i29LUV<~I4yAmaZ04?$Z2G_%l{}Yu%K~}GT|!5`1Hhre1ZwLoQ@77 z;D9EK7dquJfkCfmHI|kB&gbYiH=73%WA(~z0!*?mu>M=A?bDyGdgLEV*Dr1|Sl`%D zTz9-c*d7p$Ef^}$46#T1X2Rr*T!|p|q)PJ15T2_aUVf~z-dHYouv}S+PpH%{)9Kp8 z%uwE>`D}qPEq;c)*NZVqm5yNBR9gsG=Nes5?1ZYuv}V`yOYC(;(~8Xp^gbZB_r2|A zj+1ko*ii#sNx;dXMj&L0=r_1%>O$M7#39S_j^_28`}|`PN{DD2a~}0k>nvJabKwHs zj0*U&G@P7F-?_rfhpeFQW=Gx%?Y-P>r+U8f@<7JkV6so96i+_qrtc5}EV7e<*kI2n zN1dkr&BB!}!+bMTdLmcW_5(|C7ma7D{v5gvh-@j}I;6xcAM<Z<PFL)mYWoKO#^PwR z0(1!_8Pyy9vJMPUcqTC<4>PT{jcL~Rf^>s4+Re2<fxx*bkAYNgeWRYyv>e>D?UYOo z-hT=(h!hzJPJ>;dzN%F)qz!;FSI3oX%$5F#K>0`5qjUVAL=CedwXOVXh)Ii+ZeOU7 zsjs&sK)X5?i}w1F?VhZ3fjjJw{1*kmVXWepxPMk#;|~71CL~ygJbwmI%WyM2)gERR zO^jU{p1D?S#)j<p_EFJmIc9%XgZN}YOf~h$JFeNX1F9gU3bo?xwU9__Q*0u;#aHl7 z-S+0o8Kzl(bZ*0w?HGori?9|v)BRRP!(bAO4Z}^EfP4=Pd`KYHlPsVq+cm10s%Krr zC$4s<ji)$S&h=?x*c1loW4Mf(hn~c4VSk3yR!71#b5jF$9MF#N+cnfK4GtcE`9`OI zzrGWj*3KfDwxa+&t8N|SyBiTY@sX!TLvCE?Z2VA1=E6id{TAOoQyLKR<yI7PAeywC z<f%*~G!HLTy$2gTDVZDXpvLfA=kpP!OdhFe-=duJ`$b*G{5&sefI<B!gcn%a!GZS4 zPVj4>S}+yo4wr2R$EU!8&`fxKijjrKSk>ZEagwQAa|pj>C?zL{A4n~xuy7Sx&fNK* z_*wR=m#-Wq0$&o{4X0QcNAMn*C8dsgvK2$@pp@Vs;@ZtZ5-l>p&wNO9J+n~ReEqiC zrR!b~Su%>io80%vxvCg!+;xD2pO1qmSlE)}S&drry_cqi%|p6innAF-Z1O;m?|Vvx zq&*AvSr`X!tA$GNvq2|TAObuta5MIt^wQ3gABX&xI@nnc<ufmjtfg^jb#4c7HVca@ zb+7pF>A=48rUWOuF%gO9@8*bWJx*acKcDE@02a=*$1y;W{{b#LQe}NnYqA1|1oiE> zZz32#hP#!({y6F|68Y?zYuldmH1ioG$T3X$0<9@{N?4=R0#OP&zn|U^RNSTNf{Q4d z2`WUb2QTS7%@X~xY>>>7w8mP3G|F}~Iq<Cx$NG8gIA&l5B5pj-b$ROIVbVZM9OXK* zFq^Xa!q|}GBeJe%>84JgQ|Z`iI8O51R@kCw#56&Za8AK?ulfopo9|p@bWc{curw<q z@;>BWA5NPGUHx*eO@tInzn(ZW-HDqKz+yf+(lFkYMDL3_=gCsQK~y=x2eFmB^bbq| zSupycGe?+BwPNgcIPv5N_#v#zDl{vK0qc2Vo%u8`g>^`WN8LhKm_%7ofdRfVp#}{~ zK|m=wE>?EUg!K}#06{>$zvY9)#jtGXL;oe$HsrWF5+v<~aM5vU8}0pqgzEDEt#kGT zQe6y9CQ{Y-1Ov@5rrx|pE%e+;1pY?qv6utp^hoANn&M4Hs`o1EBaj2cqpdFrmFO%H z^!OT=y%WQ*JM7HDy%^BZHJbzfcVilVd*Y;C&<(+m-LYrXolG3azZ0bWE`YDx?+Has z;0&$NYWYxA^aIPPnsH-gA#Skun?Xx>MU_P$RtkEi;+CDWn4n;UMHm73@$JedUru?r z12ab`*F#v?@`9XPLFUaQKYrH&OL|TUV_RPmm1;1&f58bAGy9w7o;>#JFk7>+xT&s8 z-(E?D6WTD1xN6S~)qw22h`qh58x8$?{!9(GnV}*Q0}cRc_i0VWG(S+94h!<O9zck~ zH~=Wjg8hbJ$wvP|&+2$AW_eK;|1tO>+7?!?0@H_+A#D&A-*U!m-oM<dox<eF$TiEK z%uoQw+wWFAJa!-g4-o215tE<{<3bSlQkk55ogEHq3m+Hq<3ytBi=I!lDmqO9V~)5y zNS+LiMVzO9$Jcf|3Uub!r>h~$3Tpl?q8;_0+;zl}2!Hf+|6G&v#8?r;lo*Cg{2&Dj zr}h$aEZV7UuR+wyakGHV1xn)lbC`-i&7f6JGdi<HTfu#tUO^4{m!b89xNP8UR;Si^ zi3^y;gYo7^i83iPFf}T!M`sr8e(`&kB<{|=0)&1jg;D(SCEn?2Mo#@uxdUo^5tq&r zDP66K6aNEos%fz1q4YCV5w!rYWE^FD^z5K5oTx&E@$llSIwF=NvwpTBVOdfc!EJvW zh4}e-6XHPN!v90-uv-|v+ot@#V(8D}gmb-`GLd7PfCYbfpXxBKZ~+&uR@yyp7X<I_ z_j(pNELo-&R9-rK9jWc#k}SFJFs5l^CB~}?!ttLw+4J`YzOI8O&hGlU3H`pYf49&c z`nSJ+t?B(;aCg<xPwnVy^(g+XfG5x2i|SwFK6(WC{S|lB)b#o2U$<-jH`w#``esj` zwEFq`Px`wz->aw3)wg%gNBvzaey*Hvp1Z=ndME1N4f?t#zIR`%bH7)lck1nL^=)?j zTW5Uz?B~zfuiLl(RGs>|627j5Z>ePXLx?NDn6)SW6a2-7N>a;XHNTOqQyU;t4`jbC zdwq_JIhvBecFOo37h<EgLgbY>&z?ub6P27$2aAbN@tp)>3L@yq{3;mq8<h<E9<Ka3 zWMxCyP66f1dBMyGyGeB1@blpgv4&F=sz=zy$&l@6S_?#YvXeiBB|%0+uJHprgV4zJ z`~opikk{WxoL6s8;>~Ugy;}D|hrS`Q;G4mpk88S`s)-!g+VgwZ5dfwy573(rv#7Vr zBkM|;47qmQZ9LU>Az$XvRYfAP*o3OXNP)WCqqn<Q79B1}%F-xG$b<mSN+c_cY*=DN zL+Pjj{Eg@oF9?E5CF{*Trej|o@FSAxhu+?Bn9dw}EQwobAKvel)-eAgF7u5#T3X_7 z(234vAS^TKX5AF}W!ZVmlm9f3Kz44fSlfzDI#w%XlvKdSb2nsFT4v5C{8rka{pFr$ zqxNl!k<gAp+Dot3-6fqU({XBNQU5+HgjmhQMIsE)rfmQe8?I91^%GK)fPuLD$Q{*P z8QC#xk_9`-X*4ltKEaEn@9KA`VgaEe%d4~Q<Rb4Cp^rnT#aQ391bBf_l}LFNebYvn zCgkpe4&+yn1~j~WLGgBwARr7FO1AvL?J6DuxgW9BkC<aPoIM3I!r?_yG7e7!LoOI@ zXE|%C_hZb?h|W?@LHK^tV2nO6C(&RWzDvEOwzzhMc^v?JBiQZr$$^3S+6kk;7S`VO zH2u3`dA|&Z7%##AEeL8851QA94A-x0Qr)&;cc%Nyx$bKkhGYhg!z4k(q(mIMr?H_d z#dl0FFb^bwN;;A|F^xmlaFLrrTKIR%MK*cdN7PUh-W^gw(qw@xt2TsuuK2#?&~qEx z=dcyieMLS~n>v+FeJXmwDUvcvjl!M+JPd5QVPG~ub_LE*ww+WKy$%@_&R<|)o0sTi zg8dx}>2OR2uMPG-78*Tw+ZDGi<0TCfxSA%u5HUu*d_?vza8R3~NHs6HzGq+sqM+~d zz#*s~+Dg`E(jY8)+GCAZsVB6Jm<C3#AC|&?LTxc<EXd<5>W`LvhgI!edsWUb8AYSV zkOAHuL4RlQqm=4?DNiO^ehF8n%78c}kHKA%mxrXzOnyLR$;bmT^K9PVEMZ?@UApy* zqoCdnl%Zy*Rd^j(Wog}gw|gs$Ro?(lL~eB#R?Yjm$u8GQx`X}nV;lPXqj!jSiKZe| z%;7bW1{)l?dV&*mOboZo_gMVKcv^MnQwnOSehQr`2GZ>C1B1boDB~=CJsITL&)G$g z*BO`IW>8Pc<#bNFaDfdAUzcX1rwTeMbTXAd5OH)9bRrF~QNwI;yN&J~yY$pq71P|Y zumq{=-qK_{4FMCQaW?(_?D!7VI;Iydk8nL80_p}0c&F|e;sM1aRqZ`&rzl_si-^0w zbF2&ZZ9Hff4eR>tYDiv)BuNWGz3l-h=ay}OJw#6}!3O(K!oNlgn>&TAsq1IZUS8z) zC%!CVl8>)7HRe$LdTO*2+_Vw=bvF`ge)DtH@;i)n$mbinsD^ZneqQ(pD<)rnzn!5E zaDF?YTqZx^+yPtqoSK^8s)~{+lwQ+(HEsyiQ?F;}B}QhWJN`hr%z-Kg=<Atza_@QE z-iOoqy0<zXpejybHHrxXuNm1bPh}QoPN}5Lpz7!3?0MB;9ZjmV>-hdW#O_V!{wJ0~ z_TwT5TDkVZUAJW%I&G=5t4-YE$M8brs!~My9p}>suz=1ux{w<N-e0@S;+20-<gTDg zQo+mXHZuwCNpr5`=dR|tCLoyoYCj4VieRTpuHaPWbrY0}$yDX?Ln5Arh|9Ejj5%ai zxHL@)7_6=A+Kmhg>NSLe?!@OH`e8`eZp_*G$&P{nD<W!wXtgl0I{r=!)^+|w>FRJ7 zw4cO++skT`&Wc$~E+QL5?W3~!b98DYUcnVHVu6KY`L^FbUuwc#l-LlXP@UYgx;Iel zP2j})vAQ|Pl$s0&nM=M5v%HBmeo0A+wIk0Dkh%P=GPH1an1K&Td=Hx_9Od62swkz@ zP^z!MxgZihnB@A>JzdtkWn;V{O-gn(`4CzQLNlI_ghF)Ny0KZZMuc1`Kr~)*|7be@ zV;2baiC?;~Vh`Pq&2P{5k~MO@N^NLcn&?p;OO?TZV9dccTK0>y^rXjXs;vAwBBcQR zt(}g;n@xVc*jrW$sCy1@emLTQ)geuR87u)TP!0tBQv}V&`#dQ=s|AedstH}iW4X4| z+#9p;T$|c=+bQD&XwuxX@sC}ynI}q|tOBaCClDUbt4Alh21AVV&>WY$o?jy!$P_Ne z-^D+j7!H1h6t*1JF*FkdG8yOP?`$HMve~@+wY!pl$raV63|X=jF;;aX5Cwn5oD`U@ zjNPM;JwPjUt~nJDGOT^9+Ve&`e!oNIT87)mbXcQ=hC}giH>IkQ9%9yB7yN+@zUn+p zH*lLI4MAQ^3=WT^Kk2LmZgU(jK>$6lS2m=(^rno?hv>|Q_bPLs5Duisu-OsAD9n%n z8R><W6}9~`vYxTf#{p;L7nQ_nQ&82P_Zemr0%%Yot2+6(MvL%0-T@l7&FC~NsgSS_ z9hF+O7EMYwgOWKy-sXL9c-7oDfo><v$PB{nZh4GJZnr(U*QL$)-9+4C`72}yC2+@8 zu%vh**EGw7Hox@e#Ul}H?^$GjEtUdDvF&32B`FonkRB|i)Y}8&dGG*cZ(ucGaHGz; zfTcZ2IS$yo4??m=Zy2SKH8j#_-VmRgL$=11O5UABLlMugD)9d^3uc5Q=q&ocqJSYi z4QS)(?@c5@be|;8mWxT~Xj8~vV7zU$hULfcUBh1N#eLvMG|+w(G+LE>zigBJ?TeZ- zCURte4m`xL8bdiR5O#QcCkKGO@Zvf*tPw_L6Fz)aKfFFGD~tg=jmL)-(&zc?s@JU~ z5!NAw*wYGkidbITmBH8|i=6851KLN|&+uFPkxr&jO%O&aNM#5kh;Wkt;heZ$An5Q^ zVp~!_;CMZny#ElM`X4R{c0?@w+VB;`U6%EAXMf*w8deB@s`rge@prY6@!KsA34?<) z{=Ri2tp|f9EP!H1-Vz<Vz$TDPzHj;Zt@q|$D#D+G@y;%On=Z@d?hMWp2We22?_86r zB#ZJi-YT~T=tRVt29MK2VLj6MMBZqGel>s$fP?0t!rMhD@9s@SQ|WezON=Hq%8uGC zH!idE4o%0r{I*^MOFGWL8g`bRsfCxXhaP2sZXC2Kra1!Kn7;K=_(rSoM1Yb3<|XOF za}Ujk9A2|cgTfXFJj6=m+vkdN7y{7HDby?2@TxG>OQqOL@oH;NI2#Knn$V-}`g+I@ z`$v$hOw0|4FWf%O10`+rDaJ}y9vy?GJKNwrC-7sAS+w`T4H3j7bT0d!W|n<UMaj=A z1!qm&taaS|!$8BQs>{ChTNCS1m!)pzPx-I@H3$cFRz#Dv0;m!?stL=H24`Bpz^I9{ zjB@>8?i=FK$f%4U=NiBd5Lpv{N0D;3Er616r7RTF%sPJm0roP%anc878)1v8WMp1` z9{7(C&su}C`q9kdhVd?Sf%?y}zqveB^Ji_kojc5f&hQj4!VuSTbXBQDF2hqyC<`Vb zRBLA>=XdN$z0DytLgoSp^J;4`^>N^uzI0a0C>okQoFqsUQMkA;vJgHnV`sAjq)ray zxO62rGF{UMN1`E)A``{l$oP5+X(-a4v?ZFE$2$KF1!qF$2N=r$v(tHL>D#|>;pYfM zp`TJWmQ1ezrq|P!xpn_|rQW|)4-g1GFN!Gz{q2UG(-5*0^fU*O__3uT*QLkUCDaCz z(zl^^9%eNz1V=X<eQqwh{z<%{%G(kS2GEPzS)61*4$^|D3b3|WGGH-RkH)tR`KiF| zV&q@*L1a;M^(k;tjSSjNfX%be8urCB!mq{-rNaW{lNedxlMvfcHG2K2E-|~fh}3R` zz~D#Q_y1W*k#1=5a_$o)2)itua40{bf^q2<@OULZqRW_`t87tF(ws(*`+?_=!Of_0 zTN|fwq3GdT1%$~J<RW9*NS7m8%d2(f-R(sgMfFvbkj84g76OifcV`fW{SW48JiY{8 zPt#K8*Nri{c}X?w_=zI~M~Nq~AG_dN*Y=ODUX=Iezo>K%&1ZWD$F(SmFks#|Z-K}b z&{UIYNP)JCO9_L5d>@JKf?9ttYsGYvoT&Itr4XctTOvxT>YZ%d!6kaR4?^Er9=gsF zt)b~&LUZHmI@=ncq)&aAwy2GTpY};u{RcDfO$F_IKzo50yEudpn8}EDYkG~{kLVy| z*W_&<s?k%T=5Z!ABL5Z*0@bY^fPK%5vBv=8f=pva2SyDMnW(B-o62di%)IES3l9eI z{H-jdx-EV&SDWPq8BcyzEwOA-=jVfTjv2IGPhfEdT6rre?%Jn)m<#q!ae%M3QGqOP z=0zJns@aWtozAK5w@qD2*Puldg2uGT2W9Vh^ZbFOB{}a?ikFY9`j+4%ae6CKbUm_b zm84(LmSq~V`MJ6}+~ol6m%5%koM^7pMp`kxz1bY)I>aioM1w#iY@S8Sd^iAWi$r&) z*=No?8?84_#9q4%HQSm_4`~Z{e|G~)>v8C}sLV{pz0zpG7prj;gfQJf*ohdgo<8T8 z;Q$#kOJjSP7jBmiUX`2Z08u5U%%>q%#5Ly+WKYEO$ko9K$${gsYqUZ)aIf()k;h9i z7G5A{^wy*b>4^=>Auuwm!{J>&q9qmKW_x@Lj|9!&gGTrXWKK~xG**`{Z>ICAa?5^p zD>^BajW*-F;Y2x-ZZ>o-{Id6uL@M2ksdZ%mGUFm=_2c^z=7kKWkHDv@_@@|gL*E0Q z>@>Bi9KOLdMz+<BOr>-a7I;F-S;G2Sx@Z;VJT_$^5k<N1_{ZeWnrtm*7w$Q$C^Eq; zLp*cim*z$mS|l|kNm(=Q^p3)R4IXZQZSY^ulz{isIFa}qfb=ntm`J4qGwCg#aS?ij zZ#@|s{!^>j>{ngI+GIE6n`31V`QoA0v@fANi_c7Gz%ohAq?izLf50sA@5-!n02jWq z^Pt23JIpQ!@C!qIbQ_$ahfIh?tkk7Ef~UP6rf_wv9f$y2N2!1Wb~C3u7cgM;M#<-y zL6LVg&gOK-qG4F690c!kZA@XTcq$Z6KDTc%rKx9dGtFg2U{dpZ?pf=Wbq)HkNKi;# zgGdIw9k{KG4v+`dc6c9?6jtr!V%u#OQd3jsR+Y9VcRc1h+Am0JD)eN85$H|{n{BUK zOzT71r_A&*iwen7lhdw(vqq@s)p^@d#6z<wi*0h^rbm}KJ|dvAsMy)Ca+JD>6uQqH z5$*u%k!J%96uA%rUBdXqLO~}6;k6A9&@d3Yqs%AlUHBT)GkiGGLPQBGMi9-mS;t&_ zW7F3FGc>hJ5gTDAaN)YL^d~wSv?Sm0)EOLy9ui$H`CAYEZXZ-oRm|>DzD|v2)1W*b zYHCVOq$qwQ|257e3mfRTI36sxG;(~B!@cTd4x4tINkj=RyyVso91-!W#3GA1`$`~- z6a&BEPS>CaypIIRly)K^d#(ZT?!=a-qI+bc(MYs;4WX2PWQ<A2ZD0K)Up_)<g}&2J z6=Z3XsYl22%8r{Syhqrh{=u(=_NVr9U^T+K9v+Gb)I$rT>Or*>0Hi%zvN{RlTQ!B} zf~=m-W7(e_a7Pxy^Sw|`c+5b!fw|Sm959b;UM2iq7}F*r7B3$>LNbBXp}uB;IgmrG zGdi6r=Q*dwH%27!$QLI=bAazi<knXG@2r1J^a<Bok!E*@6^>kr(+i-u)O3g{lSCIr zS_MXjQhFA=*d}D_Bi`ftA~MZeT_FleCxCUI&!fRK3IeXfWE|4#p-K0%Hp-S#LQ*oN z`dumF49Av+7~VKA&ja4(K4ARt56AdZ>U>J;za|4Rl>mRyYzlx@VEe=k+;Y?3y!rGV zlENK0br4<Y1O*OU6-$Qs>=~-H^eKI_)NP~vxe>=9>&z{bCc_A-uq$gjEgj*R{e|Yx z7tz)s$TRTvwK|GwCG=SbROBENmRsC9jkMe%Sy8x8DR3(gk<){ER!l~TP#54Gepe0; z^4klJVDVIV3=b;m(wsT>(gm^BP}FZbQB$QpbDuLZ+n6EB(M>JgDkH&VQ2DD6rl3hx zEiFQ0FtfP|LTVoG1ki9WKYxtMzb#N7^qhbdjgB$c%E{A?%*!VosVAsjH&1y%zu^aX zdsT6+qyq}frHC-GB@3Fd0UjQW=gJE(_(LYe-2x&u{jy>yU{I^r%n~#6Fl*n-?^@+M z?7wXcHStm@X{bt<vhHot6PMB_G&-Fj@N(%>(rDrNb+4_K4?T&k;TOPzvn+b?P#gJD zxNESC$j7CjbJFvTgI0u+>^^3xyG~p+t#%W&&3?spe!~d|RN7wnEXlrf=Uzk_Q}4W% zLaF7;^qg6YqaNV;lXXz+dh2?4&7j?6@m2ijUS#m^L;FE+3t?&Y62Cl^z<z;RKUuHN z;^1OZ-AU-LdzI#cygSW!#%xgtC36Mg|6##ISP{^RWmULY@Q&FIx}ps2FzXbUM0>$? z)b@1fF7+9TSRHg98|$MHaJvMv4g>^Ekc6-ydapMIapaJj5Ke)p`7fJmnfN$rin*Jw zNN^5|6w~tE?MMEgg#<auS24J1`U=CFZhE%Kh4smz{B$naA#fYv4O&!KD;{x=g*$$< z*W@1CSlaj3U|@Q<&_Kx{^B%^a?&%ZKq;Qh@27RJLk)<Ms*ou&M=T9{PYls}mIF>`P zohbxs<R(U_JuOJ>AES9D`h>d$LWdUAZ95eIA1gE}rg2--;c~yqBHeg$Pw2CT5VgDN z2*{`HP>GhFaT4m=N@tpdYs_Cr{&||ObSqNF3nbIe9pD7H{}HRWBIZPpz;PzvhOeW9 zXeyMs;L7nb6e%vgUf&##Qw($^gty?x>5N%%t&A;ojH(u9u_;vpOG}Vn^y^#FYCe&m zpCSnTt^Xr+14?+EDtiN)H*Rr*l(a4qFlVP9S=yXNEjy{t_A%bj#F|K16VXC#Q8HWc zmHpifzrc|a$AlxK@)C%-o8ilWVyEG-r8N>JyzVm{WW$&tiDQv$@CXT2U9c4w)6kL$ zfAz7U|7*{SC-+ovk4d+SFjcosTbsny!@jw%Ve3j1>FqGTqISfJoHe@2%Dre<lTC2b zlMBWgYg$Geo;E?dLhlX^cK)Snw+BwlRHQ&3c#Np|P`!^HWNt-Fb?`7P&y!j`hya(T zAin}0-wJM&JkW|xwHRx4#1vrlq~#6-`n|E<wT)tR`xK+VE-+?<MU0`UI@Cjjl!J^| z_EGohG?%T^mTu>?o9tJ1cPTZtLWCcAzD7Ed>Y6yzlXm2xb%K`Sjj*@u8`@MjagRmA z2Ir4xuRW`0IT1@3#`DokJ?Af+gW-8IxqU4EWAeY~qhV(lu^*ugc}l^Teo5k83d+hp zgY^iJga}zuCM{D&lLUSNM6QaijYkkp4S1|4jF2TvUCNUlH_HL5NLlj_J_^L*Pcyy{ zP;qsh``pp3$%Y$+@t<iGi)eu9@D|)Fo1UR871+~lXo&5Stk2T3jHVM{a=9WzUXbeS zv86A@1V4^r?R)JfJKfUm@GVsh{iqT{MZLSZ`eWUkR?3C#4P^fiX#JOoS9i0jUCuWj z=CFxVmNi6eF7Ha4&5J|Or6V?Tho7{=fxS5W=CJithn$4q={KdGdSeD;a)8sMWF|xh zAP@WeqxmwOHw39mNnE+m8fN$Ln%<L_@*0i=+xO&zIwpJ)Bo#5d_xJ7l1v3O>nbjq{ zNR)8aca0NwN=UGv`o}P8MKKVmp)W_}8eO&yY2WVHikxy+hm}Hzrz##cx~-2^5vpy; zB6sr2y(%GOdh=Vh!;x0c4)bPdbO7f#AXoe?#4Qh-9Z_W#^qd?RE0efxy|n@RTZ?Sp z1OqHG+FLjhfQM<M&bZ4k_s4u2ZwFC%NyisTbTOJBnDj{f0TB>1nmyE-;+|w!C8oUl zW%j-Zl_|<6{GB_^jG@2b@YM%vxn^k<@ewF8GJ-8B7ySCLN;Fbduv5nGM3RjN%fDm% zIZ|9QW_r*Xw2e@*z*o|`m<2|ZI7-orlrjFWkz3dD7A<ONe=9K*L9JF;Fj1od;1PVo zXRu!P02f-S?EIO={vXjdhFFuI@05qGprW;0SFfFA8>B?Ym<TbWqlJ@}4A2Hm3{<v; z=Bd4~X+Mu1z4i)uda&`OC&U9tl|PmH8M|KyIA7T3Stb=p2Xs^fa)<CqR)1)LjV{?O z%;RKHU$R0;3SlR4^5A#Fc`Fe|t(Yz>$r*@Q{=3elCTAWHyN^u-uD?Xqr3seL1{ww6 zgf3h=C7RwYMK_TOwgwbcpR`qZ*gcL#3>4ykyGQ{rCj_F@r3&x;4$T!W3hi89)_iuN zuZrp?i2$X-${RgreyAK?`LIoP5U{t6BSSq^INv)Ey-K6uZi<I3+^`W%0;^%oj^6|V zXf#GwKu|tHF>hj13@scGq<$7W2^+s>nJgtUPq40~5q=phzq*WTr91TNgLbuBQpe)> z3%3d*Cf+k4WQ5=VLv8t<0G&hQ2IJ(%{!Ns9JyV)&^++kxHV2`)JhP^+b%Y}7&_xNC z!&aBfCrTeWDMBvEL~oHqOE&r%Qz%{jsT<7LOP|oi&RtF37r)6gD*Hl^+AXrt4oQQP z2&Rf27G&-Ztj?SKYV8R*(r&`*2#E7a$$zLKB?87GUDXCP>25+-E~tI2&#+xb<`Wxr zs~nL{q}7&`c8h!G0vZO>qK`k2D+Z@E|2ll$)k4N`_~TVtk7>t;Uh^?8Ig#30#-<JN zdm>#P(82i$juF!3cpU(IvcS_d+eBw(8)E)cVT6}(0e3(_o)lirBwU6wN!3E@z6khn zUX<i?%0BG<NZcBRcm9dpxT2=1VbHB$%gMXHU;>{Yqhp0KR6fCRNJJ?G3R%G|0KPsT zI%iA^3hk!}W}~f*z?IYm;7U=sP8mqq)w&j;!9LGRr0AWd1OYQ?abn8<Qy!RA)aKc> zab%ocSV1!#WY%UZk2rH_3Q<}KUDjU#5klVS5ODuDrr!PU4{t!ulSxg<js6|-p*8rL z7(yFq`+c&}`GxebWCY4tD~`Oz8yFZWAt1C#Jh-9+o?E#^M<Vc7mnP=e;x9yi<BBe; zMv_-C?-d<hcQd}fw4ynSASb}MW09wEft(IF&%0;ouepQ)Wh$~9M{gqNHQ7!^edoUW zlK4<9a6PKw!+OgMM$>JipYgqU_VR=;D<3(4QAgDzJ=Rl*y$XBSP7g_Uzed5sH;?OB z{d4I#x!2%2j;01&<X3q^y%7z0oEEVddt<uco`tqmi|^leMsl^GD(QE!HU)UNzTD0P z3IHD)rrBBHoVqv)GJ0d!LKSv}SB1JH=TK4=s{5$Fk!D4qE#rf!&e_z$oWk3!%sAVS z!Zdf&!u4<Dr$OQOhJ-dnSb(?1MgojJk<G~j3vgcQc>vNX4P^1lydW;xDMTGvqZ#nH zifBjsTMEi5k*F4Lq~%l3?6}V0&+mc_$j;^_CRA|*u$viTc59}p0Uahc9`lQ453Fr+ znhcGG+^*ROOcp|8EEbwl{+>cC9x|GESSB*chsT&jMDMiUyI-5N)vs{K_OcTzzEx3V zs%Ab=mOb$v6P!Xk%O}Wdn{L6Jv#Hqq1IER+1rQMd_mEsnOomwj9tMjTelLm8qzp<= zE<rGPX|3I<MJi|g-?}j9I<9|j_bugF@-WmXVIgQVzpm(1RLZXbru!772f2(wc_YqF zvJsYRA^XA{?}S-ZW?B89<>~swF?FnhJB0BD*-k@-kI1<EooJwOji6ZoDM>4dUxvQI zwh;q99|hJz9IuS(P^?*fgbePC(I*Kb7P(16`7oF~`4*VQU+>M9h>&LAa<E7aIY_LY z!KUP2K$_ui@TsZ$UxwRQD9eP`1{Yo4WuMM;shwh4<Xu6xy3_bBkI*6(Uf|8Ct7l7$ z3igx<^v&0d<-4w4^d`JpVUFiUK%|1B>2)%Qs239Zc6l9ftD14P+oh0FIQAVbY-Aqr z3!$&*GI%-0YAm%7u$7|MtQ#FluCOBsMwdcEqU<4WumqqhilgIwv0knMsKxd30_s5G zJ|KKPx6dx;ltH$#_f=VFH6lzL7;Qb@mY#=RY5p+c)F=T&a#II3^-B$mURS=za@SdW zz`+X$pzgTXW})a{#4!`S6S?tH!aY3a@IC)cr|fPwwkBu~{^kM;@gd=c`Cl~-hlahU zC;O1xU{XJjd1{BtH{PxPbm?taB<t%0ozmvY^(t!h%O8Fl!#E52_6CxZ?%|N_b&V=Z z0t&q~Eh>`$%|L>)(`$pU_?V@e3z&7p?peNujpz!YrKoVVI>$3jBk>Wj95!Zinxn82 z;YtMvL8A+H{2t4{;83wmmEJagbf+RNaUuu4a8hZGc0XoCeWEK1l(k2CbJVl>QwroA zP<e$Ix3#Hj`U0^4ZE_?CVr?fN{w#6dCxGCfeYEy|yk2xX>Fnp}N5SpS4OY{BQ5}Kt zq+gzP&7Y#m0SugBdQnVHLEHzqh~|6$Spf<x)jOC&EB`@@yKInQWisfd17wFgGVRo@ zgU}>DgWRsyUEO1P#L0U0a3;o-sbQJ%7(jyePpH_Dhfj>+O#5l4Ya<AMHaDwhlgzx` zJH3fG)j#C&)SLF?8hc^=mqet><*Rm?yw^o1jo(5O;qEu%!bs`o8pZ(Ybr(NGTwI19 z5D57?idIboX}u=xxH`{p1Wcbs7#Z&#o{q|W>mY-ue~PxDQN}si7`Hy~vrHEYpdF&w zM&`|s?2e!E*O^4pRnTMh9S0<uA?h}yT-rP+$yUSTNCK$NvhFl2vr~hZP2`W82+eGx zsI-Or0Td+TDS}uj5y?5LA}XS$_^fBDiRaR*)a?g|EB<dmHANy@4<aob!5cu?jSwZ1 zvv1diug;_({!zP@(V2<2C-6=}gfBm7$yXaUGa{eN>ygM}ghQNxq~YdXwucTnlQ>fQ zLyWMw+rPCpDZvjDgVVOAvgPa;*HGx5hs`u14X=!!xJ$Ipkgm@#4JB4Ec|M!_ybQ2l z3egzS=S6mf|3`z<e)Q8NR&fW%vpcNwYtqHFR4h=M<?kg%WQ`ckHY4DDglYyP?%xwt z&0XPs3{d=c>ZGZR*!_{jZ!jYjXT{ntFDmr>cz-J>DBt#p@!$wY*)Xmz`+v`<@dni; z4uV_rYb}iY>r(VKy{`M*==Z3rX4@h@-fT-f=Kb=5qbOYdB`Zr%!M(KYP(eEw{g-~3 z{OZ`kW_?y$hAB%oU9`t)emVkT;}U%p2{n4_W#(j}UpO^n^MP+Ia0r`?LXPPfMB|{K z<FIADoE3b%LhHD<JP_UNOHg)dymoakXm`|vmlruGq_}M}Uq`K#`vu1pDE1F5qa)a3 zOT7ki)%J<SHNxv0zB+*4I1b$1)h8(Al4R~rf$dM4k<Vc=_DUr9y<sPVS01-W+`Z#+ zJPX7b>;<DbaN|Q)H*8igumH$KIV5l<5AtCuBtAcYc<!Og?R_dgYOGEP@LU+xBnGj> zjId-C3>S<dVOlX=8Lk0+X3d)-D`hTt^Y1|lHwyt^C?k(H^}?@sTT7V<^0vPmfEA>$ zU)Mf2pPiHz6I^NOLm0c%?a-mf3IN?Iceodb&9cL}i^gY&t}&B3ly1}_6EJ*&ASyzw zUILO~jP%(pE7PF}gcW-uAYiz5{?12m)Kmfe^}^OSE4o9VhDr5_088cbrizq`@V)@^ zVlX_O$l8o~B=yNnORt1~)40Yt2qP<!3ns6#oyYzrmHs9TILmNnPup30K%)V3r#D@u zv|PkKI9;tk`ZgnSjkz=--(x{~5Lnu_g2Nzu{}z~TN9gToVk`P~Bw*)R32F6<>HkK8 zr=41eV^l>2iTG32Rx0JpSc!~)Wu{3lV{zvXvPv>fLnu4sSC=Lx3JK}kDM(kP<@qL$ zg@EEud8TuOZPI$*jj~Ag6G!;;0k!BqX~^4?v})`i?K`7xF5Fvfi9-9@3%rYy9shYx z(H@9=_CK_G82R5vYhCGMU>Pb=6x@~_nZ61ToTJ=2(}Ej@=$9)Ozoc~O<VW%>{vxjZ zq;*meS-B=8TEY>zKIHsEEH<{<X91k;32?-~Xy2Ohf>1qAR-)=or2()aNl`VY{UN^j ze-nl>v5fOt_Hnl*UKt6LnO^jU;zBb!?Yac-=E+=8NG(&-mK*2Y(}Ko}rTBl{CjH5# z)H`R<fJnG{A*XPY&uA9+?G_Ji8vRHd0Gb0_wmE2JlydP<6mkE5E8W@(PPH?#%6a1e zJK$%_V~kL;c_7>~jJggoicCMjX;pIhJ)H1Fwm%&6Uu9CGXTcZTRgQi3Jvnu7&C>Sx zK=T-zTtH)PWPA?%fZWEAtY^|Q%`Tqy^9Wsi7I`61i(C60IZwb$_(uCs<xF4Tv!rK4 z10O!^59AZ?Vb5iO^FvQjS<~?Jfy&jSF<ay^+2zv)M<(alCK`lndsdhDoQpVPguzwL zRNO*5qHB;mu7ChQSaRFx$#UPYaJ6$g&C#d;ec85|_{Ir`Rq5qf!vS95j9?Uh^-JqU z5|2cw5foteV7U@iW*9A=N%%Th*Q?VjX3Vm)u0?R!t^aylba<p@O5S6G&`Q>B?_~`` z(US}BF2_sK3>Q-c@B;}`?OC=icL}I)2V+gxV6@F|jPfT0Y8UGMo5OEdf=@kBg`%{0 zdsddfr*EzyWjVW5Y5paw@gO<o?qR~)sA&3ZbH5}e@6WU&7egsBB^@OGSkTHs{#CHA z{%fa7jQXe}42Z<#4BRj8;eX|U*tLj;9&IUVnoT>>b7R7_AsvmL36ILktK}$CHOVJ_ z5Uk?SYA=xDIOmp+>3I|@IR83~_?E(Q?Ki+(j;?N!_^#FNFc5pb1BG~0#4YSZfANEq zcO!C9_*e~{@}FLNyFUU>R(rlcwt%ROb6n!N`efB4)(}oG$DV@d2ID`xtsUJuha=%$ z^1LargmQG4f~Ngjr^Xj~jj9Bqejmq`AK{3|f#{L8hN$eq4BY|;uzRSLlNw2jpHGFo z;$h2HcU8F2H-4eeP``BI)6FsS7y=aw48IVcg*3Ppt)8tN@pGf-N21&1E}pUj`d}** z9V)vLRUk`o^fac@u8~mdoz)~w4AW}2HSX-V_^h(~FIY(QnG%GRQnqETT+Df5&@C94 zVDg%iNC{Nli&)~ocb%-%I1Y}*;*Hp;I8VVS%F8m?*G1!ns1#H6&Mi}zm@9X#7T5%m z#0koQ`3Qi5U*>*ajI|K;a~pavwxsKJ1abNvBxc?9q{=Owz$JZ^-GOW>y`xNnd7UjB zSMEX!bPA45GBe*j*Z`EGu<ut&5G4BM>>762dlw;U@T!9|U4mH!?}UvpAdBncpqO6w zZ68JyYgNyGLzR;;+QusX7<;;pmL_@p-vJgyFZhjA*jWR*7Oa3C<=QwcXh>;kdY8(T z2~+@cis;XuWg|1TIZL!OA{N#qP9Up*{Tg+zsL+5P<0;$#(<bltR*dn;k|kZYhn;5! zk^(L4PkYcTa<o<bNML9jc6$K*QkYm`g9Qts{kaTun5KxS<S8rqI&Y`hu<sVI%{=U* z!k*yo_Ka4%f{zv3%aHD8#0MRwa=NZqQOlMJqE|tkyQm0s5)m)VlKD^ZKpJLfpO!CX z?>eEU_$aTMlLgnKbYbzEN~Qr>h?Q7)uThk@N?f%~I_k1TRE%5Td)U;}^FhcX+cBLf zl`ZdY1c5Gr)0Fu_1um6Ac-f+Z&XzkkxL!H#K#(uifwP_BVH$V7-Yin^^1u%_Y(~1L zYvZQaYKf*1{nk6~zsnu7*|tF7_7WNQVA8pmUW!4J8Cc6e(W?SM^KU~ThTWa3zh#+4 zti@@tz79l^Mp|>EM015kV%ix+x9%kkweh*WQsNbuwxIDkR{oe>6*S}JR^_U#Ix3+i zlOrT{MG~?r8!U9t^|=bqx(NJy5)Eb>2t(!qBI+Dlz^w3@lJle{p@oAI5i!56HFqxx zlj=_%)&ch&?S*Cy?jhR;AcFTXvM}B-NMMNj;fZ=<s8$5e=R#ECSsNd(x9Z{z0d$kc zVDX|-HdDv(4!mIge$dJ1X~#z7XjdB}fLjg?q96TZGj7f-Dn5Qf$kEQNI@#cdXegu9 zBMEFX=zP@QVN*QN6K+LIqc(}5qJcvn3<TrU0qTZ4{<ISPuWqjK5ERQ5Cw8F#Ya{R9 zteMC_p8=pf{##Z=5`2sE*z(e2{BVGA7X$=M0y9U7zg(3Od4u~mzzv$s371;D{+HE? zAJwGpakA6^wE@kH(2jTVNR?$V^jlF;GyY4){&P7a063S$4LR|6H!$o-Pu42Z^>bcE zZ{j%V3HPALjgu5Jvwc12laFohsU<YNz2)Y<GXH~JI^U99OiV1LF=zBKQ=Ed(=f2gQ zt=-cw;UOpBuqAl+T*jJ_iwdJ?f3u;FJIxQdP_PIhQPwl&l$5~u&z;SOGV--&@KOep z!6EkW%*_pc2H8*>6$AK!P-q)zPMtoPcrS$okB~Klr|8*Mkr#m|RT^y@7`UA!G{+Gc zJK5MfnUxHzpL(a8^zGm)q+QY$gK^${KAMagMZhC2hu2hv()VXsjGh*<vh|rMeIqki z-fAUucAQ{UxmQI1q5N^)s=P0)jziWiIBt3MSc(06KVp{6kb#UYVpNy_XGN6vwQRng zJATZPt*5DZKuca7^&*v^tDBARClL%aSu;kG*c2rCZFW~&dmR!avQShfG}=Ae3qTiG z`Amv5c(q*p7ieiRl({PL<I=WRPDtrl44*~&ZN*QHEKjgWxpRAF5qKV!>%b~s%eE8K z_HEq0sQRPi$7{2jow$b^jE&pHuEQ(Ysi>3ji2qPOOq?povPKV&ht>LIK)SB75teIv z4YNWmE;DG2=DX#f|1W6J=tb`mB@wi?tVK8qHeHbzqQcKz#Dq!xjdh@c5tmOmuHL6e zs*wlNe1NcANRILB<sK)4#r4I4=6c+h96Ifood79C=AONsx!vom6&%|xdexJq42b8r zRV=2c82PtKbI4H(cC(f>ixCPWG#il}j!D3)ZPLwSjqt-RCd>*`H*9b)H_Z++B}cpJ zYmojpV`RHPsgEl32rW2~IUQVGaSL}Xu(|=8MGzeb?V({G<!|WCiJOVlH33RlAK6*% zq<GY!j#Pyi=ZdYDrl01&t8dZl%?Z6+l~z5QykostPle(;mv$|Oka=e84Yg>o=+^<u zQI-ZYt*ECs9$;FgHtebfLLH4~tK<*YvxqBP`)_=BEwgLbX@%yg+~Zo^`=-~;(31)& zB%vWu*SafPQ4$=Ar#!gn0KNt#^w3P_pKkdtCZ_g3^E=(^_Arc%HCc8XIf<dt6_FF2 zi(vp6cm<kLFqzlMYex+z7)3G&4Qi2~Id95sNoGpMchxc80%$?K$+r+XvCbb0A?8VR z9OS5^-mKHp$We&%s{HmGZ$FRzi0Y7We&aixve*vwgviCG!d!%7JM{)H5~0#xyv&zS z2a?f1m{1JY04U+zet&d=$3J#ghk>hdgRll#w1gARj&HOZV8=6u&E@YS8*=7$!B+<^ z^Y3iG8Qg3iendQxl<xmPlE-e#Z(8_dhzb*e>yJbATO-+>_^B6{tuT#gN|9sBnf?2v zq2wOSRH<LDs07FrwS^L;31evFTfKOj*nT{Vyw+HI@xAnisqXT$QMLMv*qkyI`7|c6 z0yu^XgZbXxO!GxGjExORK6wYf3B(oTOIWXT8aHh6h}2Q>9-1Tl9=$bRU_fb328Zq{ z=`%aNx$raoRqwO<Nny%&yXbC#MftA1H@H%Eipx$vubv6#R*TktgCW$J?k(f&te3BC znGzFHzO;F}>vs_b*=G19v**@Z|3aNVp#=3X%My(<3Oy&G0zr#zXIH%~#pX%RJ$|F~ zMzZD0(gSblbA6RzB<;q?qj{NdzI0%Eb<Vd4))ONjdYL1<!hlAFvW(^$tVp$LUwe!* ziT&{?vDWk~Twc!e@b&hPnJu;~R+`b6t323}GuA=WZYH|^{S?25A|yDVZSb+f@-ISU zK96+b{+KV5)i4-$uGG|he9n;g)0QBiG=S~;y(Z4CE+sL9>DeW7f;o`h3pB9i0mUbN z$x)vRDBzHWzBH}6d#|p|6T2}`*%S=hx*GIji<8LsjBT+|><NXor;jByURVHQT#a`B zWFeJmE_$VxqSGU@^;453l{l6cNJku`cflhOj_^PXphyWOZd71>IiLmf>$wVnxPA{W zAeK+q9{NcEPd%of(GX(hFcSPPQZ}Mu*4i68f4pIVXj8Uwt9O(%Y*Lbo*1sy`2ZId% z73$zy4;+0({ox(c@e6!)D~w_0G4C{rJRur<1yk+1Q2lrG2HI!qn2FR$z=S$L*bmuP zfx_WLttbs)F$hzC@FEib5MrZ$xD-j<^7F@j*DJ2lc158!b;_Gf9(js@$<*9klu+iX zf;tK!0Hb9S0+X=!z*E*5%t|XLiZRN?s5QNVQTvevFCM&s+&J%<NJ=5l{ReQ@2PeAF zszgvk>|1>ajaWb|w4LsTz(f*vF;qCB?iSo72)6<)?)Uo?>0$7G;1HdbJ<$lLfO$I? zG}C9^Jm@ehiQ@_VFdlM?S_pBnu&g8NHizu^lj@A8oMPSknl4OKMG+*0AfOo&{MIAQ z5~+sDsG^iw5Bt0Nd3Vb`%3q_gH?#mAo=n@S)rz?y!r|iN;AM{J38tytu1d_atPZcy zE!-C1*>Q99*#@x$l6i|pcsqjAz<F^ASE<6gNjUM|fAJOREdp^GMTVVV@ur08QAkbw zJc+VYXH<jn<0Q=3ojWp>NRR7kXy}3u1@?ScnTMkeH$qgPXh~E{d;ss&f%xqa5dmC> zGE>U@?>r*5%+I)xvKLLNTI#Gn(GzxQP%qSc;=yfPhfpjcNj}Oo@=i5!P)_mNJ_u6Z z<Hes=&G&(5(v6`ns~o))8KwUb)A*OcLOoLS<^@X2?OqC}Rny_IWpOPSX+s?+Set?r z_anAKJD^i`s#Lvo+fDY{R{(DCb;Xjzdw!*f7$S4d)^Eo5&Y8gcY->meLi3RQv+R_2 zB3V_(`J@-F-2_t7sQz-GXuY^0sbv3hx&jD-4gGqZRoEn^A62^#bH-x3L;#%_kKYkB zE}vM!=15N)&jrqmTZUGztDXRsbA;A&$@7tHG*;QS(zK~tl*y=-{<h?ZYmWVp*3w=v z--CNieEeqMZ1o>)MuzJcB*VBoLDnDnM(;MlZ&Am(;)kZ*;QsPzp@^qKcNqLIc~!_P zC<FCkC~fv`CA6rH?G8kj3h<j#(*w5lq_K%2Zdgm}ni-ZHYBg*BSg5Meo=D{7`n!l+ z1ky$GA=WlkEE9u5zWNv+_Jrshdw&*OFv|;Ta+N9{r6yO2`)e=<EG>TEWKodLhB1VX zxQ&3lfGX7;|3Q`en(Y=2HUU*-c4A91r`dB=V*1CG0*2cw?yn@wl->$7ivp)<jy9~+ zNEsYtd(<PgDuuxZX>!22;oxecqGg`7s@u$4;D@q;mGuvXoOB6GB^nh64$ZG)et8Nt zFlVNFZS);I(<zt`tNS6ZeOa<k+PI%+9L0NvfO-YlU^Ps`$-R^)C9HkzWorJ#B>O+g zbfv$KjDtsJ+@2)cA)W}a)e^4nBh<Lb)F?kInDu2x8I!ofVJRC^saXUPxxC;qL>m1a zX#@U0^mrzld-lk$=CkAtTH`@v*)q8nlw?`EO@<a4zE92M-7xUY44(f*sfSFajRsYx zE@7#3m*ZGY<ZE(6PRhKWB$LD9IS!K*>x<xeEMBzRi5#Y1t+Sr0^%p#-Q^swyjy2+2 z?LV0;bjX3L5{lHR3aGe|xOR$=&j;w@vy}tB-aeEW@ZMyBGb)n^f%Ss{s721MQX0ig zE{M@sOBbddwpzlo`sE09O#R7XG)Sp2vxJFp{|7p*%GVSXQEyCk`_OO&@^5;(hfIZM zYyz?Z&Z@*DrRbn{6JW^MQIWZHZJV<&SP+{pL3AT^(9@R36HO^2DE~_9S4v!Dh)fH> z$(49)2WknJZSE`{wt+(bdN~pn;@OEsF%A5p4yrzw%xo4rs3Q3sz;Sw88cPlRyzW9H z+6x3lDi*szz;JsF+#dLaF7Uh0l{o~~VVsXXVP|%OE_u*9^oin^kN#@4K(H7`k2|pw z4P`oz000000007r1H(Q@!$_JRPj%mNNpEo`#=4p<`~ge>bVjWbhcOgnr^1)BSMNzE z-eol2J(ZU<Ipu*-LkllN2inH}0P4s9l<|y6cGNql%Wva%^V;8s<uBf?(oH4Yv`IsV z?HOK4Nr87K<%E_N<<YlUB&e`GAcr7P`U@PgDg`)L!DPI${!DMd=UUKQ`);|gM)=*x zk2%j7@e(El<$}R2N+CZL^nwB+gqI|bwv?)$Y%z}mQ817sO*lSu%2c{1V5C2zReUhX zc}yN|<ruBIc7w@8h6AdZb_*-pjnb0CV$t(JBCRw6Bu(26$+1L(dp0Fq1HL6?qKNB6 zLwx`&=Wjy(jb>b^qzB}vx$t`t(?aBuXd{mDcKLU=ko&|Np&hJ>!Gc|KoTOU9haw6G zyfu^Kft}YEru+?q0ZFt(x=V&fb5d;jHNIbT`PL=_I1bqMzSBdG(F#WsF=dAKJ+(ye zy7-Qr&n=LL8kDAIT3n4Pw09@$#^K1DEJh;~69~~?^L&<%w@<^%*(eUO(=DQmJzgix zWU1FmU_00$P!S(u`r1e>vqQ%;l1U_zNhFdbQA2J?2<nN^J=7yW#qo(kggl-0j{9{s zr4dfD&ph<Qtm)5PKp{V@+s+aQo+hgJp`FhJwVP!nTnhPqaK>+qS3^DU3QSH-g`WR( zqz&D|4`)1HyxdS7C%2z98*QH^*?8_*$*2tT_6g8oi!Kud6;^?tvdL1OlE1T9?G*OP zT)@p!;*-9>upal|VV4FHY3_BhS6^LdY!hFV^P}=G>nO1TPbmB`Soba;x;p^+VYIiO z#ORRbc44tA9Lb;`G&(;p*?^hrNfe%i{X#<n2*{OBMeDLQ0#%eGq30uz=`Wi<8{j&k zMGWRo<gBhRt03^7Ob6=Qr8b_4keSJl<RU#M76{HRTj$BU@?o9vS0IjrCE$M*y>h!d zi5|`aFY6h)C_IU(CAv%+t`X1@lI!Fn^GfHbY<n-y7$OFKeI5e56mVaDB&Jc7q8!Oy zEataosm|}&v{Ln4*(0Tvz&0`cBm>%!CxKiY!G4=7f0;yfQK2t@74~b*F&`K2w`DY& zyp*Pj2%^4Rn&XL7OCrh9PDjrMy;rhkM^B`_ZBCaQNpg&nc5o-yX?9LtvF4Ddx##?a zb1*2`#k#?Ls|L9S{sL52h?QYWL;haQDWo?JefMzCB5<esx#NGvyuq^BscYjpmw_u1 zV=p~oRq_|A>Jvw9oQ0=^QeH-UeN;)+egYqW<XJZ4W}WC1&1v<KEj0Az`+KWu12)Q- zE;Zck=j$O^mxPEAJB`*XooJd|8zReG^PgF*_oh>G@WZ4~qAou}R`>*;VZ81uObyta z=aVPcqyguIhY;I-|8Q(JXlf@l_DYm&%Iv<;UeylH*88=mL9YKG0yDu|N)k7AoRe}; z!D~YI<S@BMMU|MBC#1LLYoT<RU&+2n`9Dq3sE~)eZEj|&#TxqqckIl(3&0`d7KWD= zEc<VQBlwcFjr_nL_^l_?{}tVWU*Pm7Pp|^*hckh+R1tSX5Z3=%O6t<ip`Hv2ln?do zv&A%`g7^sI@yYp@?A3IoXBcQly5TmAX0exO45kb2DzCnU5lN|lChWbXwN8iQKm(-R zUX96UFSC$qxWXG2$Zf(q1I3+O64>PI+(Q($l~>!9Or9NI!_Ax-POzNn^mFQ`=BK|- zvN$#F(ZZ-<t}LZSeF~}?b*^r+$zy*v?2q~ukmK$8Nly7}5vNKfRV<j44@QdvLMF6k z-fQsw(_^*vGIR`5`y%p|5J)J6F*Mzq5_@LTDD^(EOLBhkv-&3)_AVXAs6R*ysarC1 zdQ%YrqW-}auk3)3frO~Co{B6c2B$o<9TZKI-rkykB0#D09it4m&n_p#Sjgz@N76fN z^<HnQNZX0TztwxsL4D7wlM?p~Cp9*@=o=@Y2~~Zi(Ako@h&1=Yh9JV(6H8{BCFajr zDRYIUri18e2#a;qI6ojWQyVo><@s7Zk6PaYBwg6G5|>h7b6pGL!LH(DXC6?xy}7vM zta0!FdcdWS{MV}A(pXK^5qQvPUq01knD6C#4h!mJX|U*)`BE-KpEjqU-!(6*WILuI zx7?$@;hqk|fB0Z#7nhnsb53&+%{^Gs6puOX-QD3s!`SDaAjXMg5b=wvo#Z@}KfFds zBu#~{4wdk5yJIMQRXm6uwj>7S_G8*Hnki*{#SL54^G1uUH(>@z00?$5%^U_PvE?nk zU3%=|*%C{|<nq&ru^uh@j@3IPZ#@xBE1vy%kU>DiHX>py{^SahkU94vnRFq_N5e60 z0~EO*K`;T=zime*nqe*S{1gFrJnIYOA*sAu+bqO@J|qAsK-Ry+pA>r*7qb=7LrgVm z1te+5c>t{M^OmB@KPZv;_jND%QvruQ)Dm8{D)@BlnMqQFz7%Ofd=x;~Q67BJjyL-` zpt;}b!9}l2H1Fe)NK7ePZTynsqY>N$G3LnQ?W$VW$ALLPOr)h*7<~R#!7OwnLgZpZ zUztS+QHVb};C6p=Hlp(EJ3X6NLf=L9X`gj>j}Jx*{rN)=`nvWL$0$re#fW7v^0lIJ za~ZleVqzuY-zg=c__T>GVu3Dl*-69Jd`0a7-Vqf|s=dNJ@_jS|?&9&NggfR{uJpVt zIE;x?P`CaX^FEXAo(~-X3kW`B?rn5C4fv<IGZP1JOyEn!VT0l-Nyz_i@^TUYo5BOO zJlR#Tot1mJE+9@)-J=ZXu{*43X4DD~l5pUjyJeC52d=v>c6nKuXWE?|IwFG=elt#5 z09eu{@h>J(offevYn}N1ZDk{exF5NjQ{DH7;wuumy02a5WCSX$wy-~Pp!*lyBaZEf zC(MF%Y2KIkQt$>XiAtvI0!Tns$ZJ3xUB?|?-o0_eub)Oq`kLnmtyn~jVNq-<poMy3 zO$1-$1XUc@D;m7kIVVFjC{HFK+6bEovKSqcJU5j~)tA%{6D|9z%gBUA>`f}0(@9SI zayH?r9TYAaA>&1sXnx+wWyiqxCV5C7$HVQjKixyX$u^7cBY=29dW5lwrq-za8U_G( z8al5rcEaSX!-Wr`l@dyD|0v9y7nF;ki8K<M6y$pBBXyjYLy2QVJs<^rWTWMEk}U7s zM;rA83zz<_L=5S=E>hsHd)<+%U#PI!IWj7J4$J0tJ8S7R0El<Kslg@uhr+_88CvDb zNG8+<B!{<}YNvj<9~QQmFzjeNQ?l@;*(J2>Uu7rs2AhC2GjVv8hD#~GnWNEdn8(s! zxO?&@bbkwJ<qknO5fQ7&Ac7#vIRkde4}MT~QV+WAEHW$f=@hCnt^@C&!W7iTiy(^< zR}DY>j-$Fblcr0wSy2C0?i~9%FKbR0cUl0K&K)3J_DI7L?!~RotlJ<q&};j^!9^f` zS}YC6Pi#b%9w&Z~@-`S_Wfz%(zR$|N*ZH~$9uOcbKl8{B!XuW%1jrfM`21}dEW*fZ z%5(IG%2oN!vsOFQ?%R&o?|vUJ2~IcN?8Y>K12zRc48%r9P0{P*iD=d+_&lkfn0DlH z(EhyATxi#<kqu}SAKQiLhkz3*Ai+QFfwGgcc!RovQbTL=;?%6*&VCl2AR=KL(q1Og z>tJR)1*(zoV*Q2A)kadEz<X2_cw`WWfI?2C0>dxR+YNw=D@NN@U&~wUF~TRJiEf7H z4)$7e3grx^@OcXn%~N821k%l8i;Q_Pz=sZ~k)6XP-^4>S<bph1CbK?#<cvteN8PL@ zsDLD<J-Pi~6S?$x10rmb)&%$$L)vDiYRa0>k}#Tz^=cGNZ2<d*5mcd`0fqycIlr>* zeR>6=Kk^&IYhKHZi@rP5g>Djt<@1^$=$B}I!&;!kdp|5Vxjookgtx{hB}HAj1&sOm z8f48PYIddkIjEEvae1T##dSxJ5K;5tH{<Urkd(g>WNoag%(4aFFBUPO;=wHpG&y~s z=N^V7>49dCe!6uvZ_OuW#?&Tnas(zP8TQ(O12wNO&9RV(P+4F+5>Lcr9bZ!+j=n4% zd^>v^xG)=M&RjBMp0h#q>h`7eA0KLobrR@kbPnW+pI9c++`Umfaqs)F{B*t#4JAms zCCg1H_qt9otI`e`(&o5<elWcPPTn1kE?IMm#M9N~ox#Vd)l#IJxzx`I7{H`f`TdXT z2~_R5TCYg`wz+<rvT-8Whr*NXGJZ9zWGobw4S`75u|x+_66t`)%+^&Yj89AsU0#c} z_vF-FFTvqX4dnzzVntE+{1Iw%{^j!I9<BXHe$yr0MB*v!M(YTcX{Yd@@!GYDW38fG zPl7Lg#bq6X3^%9oMNrV+<fqa!S2xr1%?FuNS~lO3%B(<LajTb3+6aA{CWA2dAyIDy z<zbVf(hymPVRO&n5PA@RMcIN#a%<p063FToq}btf@>&0FB`xR@<D3(cU)SakfeV{Q zBCQfsl=J#^uWA~p88)C!L|jDtbu~S2wQ)QfDLi>9D^$OPHwZWI3k5fbz@1v3Bn?qk z&QEdtJF!<;LQ{k}Mk%PX+G1e&B-_lPy6c2F>S9AV^@eSv+nyo3R5AOh9^Z!7UUD8f zHvl=Rids(KC<J9C%+neT_wEj<PdC238er?IEL!;CU5H!l_!e(bPR}f7D=L-NYdo7C zgAV9zML&{<bz*dY&~||J1{EOZ4|B`nlWp<a175_Hw4lw_6@zG^>A?+SvO5FKj3Bg^ zZstNu(BKGMy@`A{&xqWk!uM&i#wb!{Mbj6hdxQ2LAZrP=>l<`-ijLEXB0u|?(-@_1 zSQE?%S<+ys=K~9jE3QO%E<1CctZ*pOFc9#89v=5RD)$r{>AjoI+m2}uD$(wY%DrvV z_Xr6*Q}1_q2wF@oyol}L#-Gj!zb}Q+Vt{;Lz52sPVn=w`j@Uffxd>0kCdjV{8{N;W z<9ApXzmm`|TuTma6g)N!&7%H&_<z>X$hnjo>ZX|ZusI4Ag0Oqn!S^BCLxV^Frg2Ua zFBL9(138Vjf?j~}c!ACbrqxek@>xjcq@^a+G@CjmZ&c4GZq}8E%~LxPij(scM|{5% z-33dtuK=6F$G^i%CVE12B@t-SfEvMN2aI;4hJKJueURs-XkMxJkmh;ElVVh&_(Wq& z%Ik)kSG0V{=92m}=rh{ZW%>EvXhyf`8rV0!k|`TKMJ?s(ET{gpd^V$`Ojjs<md-{7 zO|uFIjX>p(+I&c%V@mg%k+V4Tm_Isn*Za3#qrnUdQ1UJNVwz7ykHTp0><8lb(U<Dw z8Do09DZa+J#qs;jg#++dOS#$DnQiCvKIIDBri?B)IMtj@uB~{t|6=|(j#&8xSeNb< zm0aB27t%YPfG4&)9-V-z_LzT)EBAOz(`$nEmQk$#1xx!&9fW7p6g!xiB^gN_>6M47 zyEpZQ6_v|kzbD+{0uyi2mz^l<6SVoXq>!=Pr)PP`B*{>XsAk1f1?aJJ4sVpt^~sB> zf!k@p_8mhz3ns;`xB(eBwG*NPOfaYHkdzXQ??6$bkc}|;%zN)nUTG;cvrq!qx5iB! zYz^<1_frJ+$sH;wCzL01C`?eZ>M2vku*TJteoE!?WeRBf$I1Bo-4qwjt=O2{2^REJ zEQV+$DdK~xEJ=v-G=$0c4_G82ZTj(Xu7!0y3F=mEPg3o+2ipWEw@_%ys>IF`oan+$ zlaQqQM8-Oi%SfDvfn!Xe4qsH(+da;O#_EG91MoM$V^(V^4t9mY*Pw(6^9Hx1Q^(oi z)`89c9Go<{frJK-XffI7)$C6!$-8}?HDPH88NE-2zWj>uTuii8^DoNDElwDrp@W{0 z*=kfoRApkB03+<?8_va^ng^!S_6diS6=7p-<P!0lA))^tMPqe8qG#D%QAT>sro$#7 zJGd?AY_)6Q(O>OHT|c*FS^~Z8!kv@jlId7#6i#GYwV#<_A>O#-m6slB>Y5|{dGT7p zp}(2*ssvZAT2jpUoNFPYK@+Y-py>sP(J68(nr;t$iZ&*=RZwbih=ddTD%uf*N1UUK zNJ%}Pn#m{Qz{A<t-Ay(Ba=S{2=)S5Yl;qHjlk8wu%JJp2&sq)HQp-WSGdyioPA)Hw z8bH<kY(h_Y1MiFE7Y4*05+SF>uE!nDwcXl!Q`5=LQlGbrq{1XXaMFFz%zttKw^ks7 zgE@tc?LMuZuATx>It1Xv!BVnYFyQEJ#esQ4;i-NPMbX9OFU+W;5O)zIYt(oGD=!6h z{!g=Ei+8j0N@eZCO(`1+2=`Iyi~i9aX!s2<X$zNpBxO^zioQxa(37_#zeeeL(;?{F z;1g_#8&U?wOnJ+R?$T38#xju{NU%##Sf0m|g}_$<7RI;gDPLKViaiH~<H{2epehaO z^lR8ywMZeXjYZ&iGH{K^S-MI*AeLzhi`%>j65a;?HzA2pv`6--$s|@Wix@dhD&~iD zp7E`M-~V7<R;nT8c1J4tV>noB)*S^$gRKEzU3jFPQwnadPbPo-?$xXwyt(Xqa2sE| zRgk3hk)sftDJ%4!C8Hf*u;g7&SPthynNrBvI;X%9M$MOWDFM&J@TqSRRX^V`5P^E0 ze|{QqDr@gVv~!%5UokN!njWC^cxOr8{Y`V~#t|l-L549cAgg<wa*-W2O>g~%3>R+n zox9`Hvy`OS+6~t=x;gOOK9&@@|7dMx`(lkxZ7Dzc&-{a?Osvr$2S;zULDQoFH=`b6 zb%iVnU{uyxI!P|Sn~CT~>PE$+4wVY-y5BVVXBAYM6m~GG)2bvqEN%|Hp~|hD6Kf>) zzfH2e8>Cmn)17eSbeO&ykKg@fFp^_xWT2(;vqLHR@1#?CX-ojyVwbGTaw>boUCG}d zm)rFAi9tJB()gga!5BwJM<avXfCb`WM;i9f`n?^nhH?8ExJ<UkAIVu%lawF*YSa0R zm<W?CP?OYkGEn-TMgEOkK<A+b-=@}za6YAEnRtz6E9Qea!-x@Bf+lBm-z{bsfH;Jr z+0@5lB7nhocyW8EKowV+#w=m7>b%MiiAA_uaT>y-cAY`|C$v!)Wfx2E7st)MNh3go zLwmAxq&!5cO=sKwO<7PJqC*C%f~y@zX#)Zvy5<=fU(<6c2LZC3qd`lp-stmv_<6FG zL9d%p{y=s<o7?2lZCuy7B-zByk4pB3dyj16seM@L4mKB>zyw~<t(R$T<7|9)q*wu< zBzEUdn&hla)&V9DljTj!LO2=@3c0V-dhix*Z1J2)X=Sk?q1gUn0w0;Rdg`7`LG&In z!ni`YGsJ|YrWmNFq`(RFCcj|Ru{UTkK!B(3rjShTbU_H|?@%w4!Y@QNwc{rLW~JHs ziV;Ea(qOXKFp5-%xH<l2d;<kdAeQ|L)er8@n~Fa`(MC%~Rh)51HkzCErwrZ-PNcAt zoY3*;c`#jfO{!uNpg(*o?JzE%vR#b7bQ{-01`RE6Mi>QDcy#q|TWGC5JLcB>57nDj zmb-4z9LG<_Ir8^Gg@e=l;<ORs0&<tysfV@izx|GF(7jMLxI&4l4)%%r+)B~X_5*>S z`n+^AWwdGQf%%U4A1{P``SfpyU}ea>+&B%5=FwMQvR4EaJT*+{d)Mi+m098<@3`fy zXA>}?b<mzD(RCJPkE&A4><G=ph8O-p%i&EUUJECMZFRZvebhe)wJ6i8tRWR>k$Pzu z`BIcJcs!Acu++@DWTw5epBl>Rsj)j=xUL()eau+0t6ihgMdyk@m2=nXZVCP5Q1TTd z<w(KBREN#K^vxG=9O8C*DRee)qn-vj!Z5JiWrnmNID#$vG`)e0R+AP1QdvRL-o_n= zj*^VGb{W*))FvXkd1_8-k>4bid#b|m?DB=TR@`Ojrc0+0#^-+etM(6|Psc)x1Jru{ z^|L>3K^v<&XlorrGRsx?8(|U*ME?`-KUz@E<C8g-=@vOwbC&#tS|PP_nxA=e7qIPd z!!4%|EOL7(N!Lx!8GcD3OWhmIyG&6&q%cJy0QHk1Gs~UCow(|af%sS)-jTPZ=tQlr z)y7P%6a!w3Xxt#b&}w8f#f%rb+{C8wo8dPO<l4!Yi~h*4Hc#|C_4={vvhDe^YLt&j zj0;7L@0FbQ-Ei+nQGZFd31if86dtv`ywHyA8ln%6o)l3b)gfnBpAzK>s6j3qSI1@h zmsDOaGwvq)t=Nvl3~+!3aa17HoYi0&`UxrF<|OP@miOmEcIG9a78G9GWnJkWuNE&$ zk6YRJ+&kDy8gg66-(Rd^ldI)bU`}rba&I04Ot{u?P_j%T1VKlF!4@GL1$Fe;R|#Nj zs&P`TIikLTWA;VkW0S<xP1I>*Slc`5pw`tyb{k|qjSQGafp)Zb!x-uA$4+%!%A><n z<*usd+nuj9!~7tD%ii1qb=3vkayEpl|3uJ)nEfpB|9_!#sukQ!y=W+u>!@vILiow* z%ee_<5gIvgnl=$>5#&5@%w^|1hR3^xZQ>|;s~FX~J%P^#ff06uQaPIF6?-Vxj)~zR zT|JKdxlRe3jboqKN+ibkxum<VahadyTwj8Dmb(qpQ7)C<1~6$Shz^gMY<)6k;zq%} zP?&RGjIbx-SioS|f?&$QV>$t2F8hHmWXi85k#;3PXNArjEIposeRlIIA6_raa)>s? z=5F_dV7)BA-|8Eu6^IWF_Lu-Er`aN+`a#;MujRpSzl(z}ADKq|Lqq&}6e`5`?T%bh z42i@HVpP7D7%3%Z`y@qn>iV?iNW9cyJkqo9d0vYgzVlIxS@l1xVKt;c?!yP`(a$NM zJOHJ%)i$6*LDw;1PsV>WI(C6Jo*G`75d_hk02(eZlK!D?ecsh<B!9}UWaH#2wSP7c zmFRC3i5#Yf*1DW5g2<4hJ)nZR=W+;s0fJ@Hu;!x-;V}1~hXy2SksPKgD|$=#@=uM# z3AnCm%Fx{YuZK=z+%`(A$gr)G7UMIQiqE0tD#A;`e<|WiwX<w7WnQ1Sn2I;diqE|T z7XusY-TSR(St23@Mvjvp340)Fk_L{p4j5)>tOga|^m%?Airjf=?CY3;xX|OHXvGZ@ zFyF<r7}(KjJHS0E+!4272Pib<)9uY&(X*CLysFThvAUct)(|<mJ5%4vTih!rl%IJW zKVvt?8=(D?SNI{o4fD(78Df#%mJN=8T)HM4B>|6ns0xf$P4QYMZcI6$txZiLeJ zq!oiXT5UeUX$%>x6rr#GF`Wyjjc5#C_0MFp>io6ZPAJ!2C+{92QwKRMtPUJ1`KkOK zJvg0I#YK_E&PZ}%VbX^FZ2Rj}fSrQa+VPW3Hu*aeU;E8l&gf;o)0)a)BL|cw8?jwk zw`J@b3V{OxZ$9lqJw3Ia(hRDjL5W8yYr?-MlWHh<Pyt24=49%oB({<z5_qeeg9^iF zHFy2Bo|4n@BhUu=<N-Kq2aQDw4zchs2M_mQ;g5%oNY%W5NmVMRgIH+LnG3{PF=p<F z%Q3s>K?gCuiXlney~{%015tl=KEaFQO_s^wt5}pS&rG$ysdSlZJ7vp$JljxER_V@6 z1EG8(f7Eb&%>_h9hqAv;mnuEaMRY#u$WSCPi>02XoN7}c_0?P|(l>&DRiO)bDFr4w zuc1EjZ^emn4?SJAE6bpSBg!`}eo!0cQUPDX6V`CJhZ%Hl<2&9pk3O)vep>Ue<_sXP zx(ewpPDmi_0jk28l@$_~IE<`CTgaagsW3vWJk~L1hDID{%{adO-YvZ$Jzlc(OZvzq zx8{~63Jbkr#hidl5T4Fm=X#FV@8U19w!i;*2|jl8RQWAf!NEd9*v%CGX&y2Zx$9ur z@jO|BQ|pU83*^XG*0IuZbfjSvfE2A>F8oj4+%%<4nTHv3m?~iu5ym&BC&F-G23_fT zdP+LM>Q{+e5Ru1Oi7X`$w*H=(H70|HqD1$PX2+%)DXOC6xYx5A%WEEVF(*T<+I<FR zpWsHI>U@;PFSCe}6XHq@RWy3Nlib|ii%ZBokeA3En1%Ddt}X;wH`hQnz=hPj5%4IL zB5h+tI(?nXES$$w96XCG{1+%}bqN`bi^G-eofiE1XivDSUi2wgRyUWw-tP^}ibki< zdMLl1cbMgpfXopOitHiwxmeA#+XbUv8qsTj<W{9~I$}rJQ_u|rAJdFK_Xh;g(~(k0 z?onp)<7nx}WgjlJZIMeua7@vi>12PA0|KfSQ-Qq*4#KZ8_ec;u%DM`2w~FrHYk0h8 zejiI*n2HImWUAB9fD<Ryd;tvaR|o>2+ONC{PBlgHy0#7Cy2b8%R7|&$U8!~dL1p~K zZjzQ$xCAtj`^jAg9V=hG=*V+alyD3jvJFh!DB$`6y-Zy<CQVo++t9+9GAY&@%<vlR z?sACJc+^6kcg!lEsLjgfp+Y_uEAFP?#K>$jVFTP*jPh*YOsTtE;Z{np4;ldCrV*7Q zoLxEIRO>Z|uN`V&MxozG#+jLgj`9R>Q$qJ^a9xMfLqLwhakfJ+(ghn1tc<U6jg)+E ztJ%K&w&BStoArTSg7pYEZ0%bc*hSK@l@Qm=2*V^+&PaAwMZx5?q@f34ele&u_z|0! z2|hI`hETsD4$dLSBQ3JK;x{S=B3m$xk*y1v-W^uG__)d(z{t*M;xno2D&dx>6B_NK zYp~&L2rH7L@E;9~O|MBGglTGxImA>;WJRdv_~d}5$>dTQ*!W!C)`nueFy%tj)fOtB zLRBccz5jh&hqyey9<_qheisDo5}-9lg46h9-(<g`__B*z`n&RCkvS4c%;`ELucQyX zxhI{DR1OWuL=#dTIF=Ln-kx(@b<JBS*(<MDLyJ7Anp8B#q>T*`2x@%k&GF`V27BK8 z=FclOe(ajl+t#fmmY&rBza$-z#RfPI88j%ocXFK@BkhW7kY4fN4hdd=aN&*Qq5ch7 z5sWS3&O2W;Jg02P^c4osGfA|9A9*+vE<pYxSyF|8Q=X`C8A5P4{csIxD)(M;SUd}~ zOg>K;<zc21X_GQgc!Q|OgSWe+R|`P?iTX4Iurs;}d{0eD3c<<h-#T=eGHsLdOT&r; zf77|e6|c}M9hd#aXh4ifwIy!^C(flNBw}4q)Yw60UrAY`>yp%&5)}5ZVC9OSsh9Ek zYi>YhLzqK8*k=esLmq&iNe*lxQjf6io$KuAOb;oB@!b&YZSP1kHrgbI)^V`%{T}#l z3Zp0^Bbr8{27n0O!9mjC-u<t~{lS}t6OI~FTGiCLDw_iAyDA|qs#J|N%Q2Nq8G<BY zk$*nFK|*jWKDhMzAekKprlr==^GG=tx7EHDuI1THXaM?!SX`OEE4cdZP9c*(V-3Ux z9`?(HLcldQst78UX2*`D@dOlFChh-DxUCA<c1{v{1zi{p;$VW1w>4Lqga|B9uWJ*r zd;Y$gy&w75^*x~Gv`m}GY=867x6Wm-t%d2zqI*of(D5{0%zDm<;d?$&aMS*(c;$CR zF5<n>3V5?I)!mLg#i2i@&HcxO4Qfm`%ZzeiuY@=8415*m>J)s&hK&M*@tj8U|8l9g z=km8(IWr%n38o(@8jq^<I0l#a&xgq%AUQS3j5~e!#1u*51d$N};o=9{DO*)-Jj6ow z(D||(v}RIbL6CTX`HdH&Iea<{DZJv<4K6;~Fw&&xt0o0MAr;b?m}~*!^(Dut3YUp0 zVT%#5j=E9A*J2RzW_Idi!GiaHPtY~2`WzKIXvE*)d??E3(mnJ6baj&)3S`REPz@*c zo73cw7%BuAY{~Q>J=@!+t+2SDWd<3aOIH9~j?gg&&RqY0E@k$och6QVTL@WUj}wS( zC;S6vrf4IUNqE(pYwii#YQzv%Q-lnf>mOi#A0;|zNf;r4d}%XY4-tCHm8>Z<(c`!W zf?H{vGlkXrrZ<^d&1?#dmSlZcZElXO4o$>N1N8IEHYA+6Pq1UH6Nf1J;m-i-zH)Zw zc?u2UQ|A5D8Y2zfKVoexRryJo!R^M>PHA#7$*QpFu?zwKXp3S^vrUJU78;!!7;rF4 z!8i87wx$N&3uJAwX<9Au*{0v@g>s*$_KE*1%7t?rg5dkN;uG&wfk~Ajn$D%5h&a{= z>RVuLa&{B=GqhDfF-{4t*h?9LmnR9y`PO^Yb)Uny$N+c2FSPqqA-4dnxCiKUM5XQU zkM-uZRi&zROULo3S~oAg-lLUY9g`4?ssy#pXXu0Likltnk-quV&uJ0cxjISNzMjX; z2Zn%WFv$wc>(Bi~(Rln}Iyx`XqsYWM9e6K$Ynl@01mN>W`m?1RsOpT~JF;?*o^`Ri zbXP%1E`<)Br1qE27j$hNn4kCWZVcgJi+0B~QhbOpxG%<oGsc(H#qd7>GmdOO796WC zCqz*lv8C=)7VbHRNtU=1M}!yJ<j6UpJ+7-8O`sWguJW&ZDK|)G@ffuS+N68&|9M>t zU(!$HO?^qEM)7hpm&G~q`d*@g3ZM25!a6V3`@e2C<ajc5DR%~e=nu$DRPUul^aXaA zSg^`^(&##!ulqggjB=^Pg=mlUvx^F1B;M<S7@9tLI=tt6-Q36j97<8T^vzA!f^(=_ z0MrtpmYgf+nMh_Ip4tA(rTMx>^dmNYFe;^OeT_=;Bt`G{iyFa9#H~Vx+mn^F3##x- zM;1++@1bD+Fp_Wv?2)(gfQiI<oV>CbXknE}^c|`e{h(WlldB5owR^b&WS0S7xJ5f$ z5K7G!qOYzM&Q=WdQGNm4iXImJ^AhB3)T(`=o4ss6d-IpQ+>Vb|*DeYJREa85`z5eV zs{G$#m7UDcc4d%IIbM*^4mJkpOS7eue&%f8Z5GXqJfI=If4D}fPB>>4IhNGuGAIZ} zdpBE+#^hMc)h*{jE|64*d2yBNeOOVrPjh9B)OfE!Q2G&f$jW41KvNVa2g>-2yP8Yv zx2Eom#&lymaJhWq)Jciyxfx6`Su-ZnFrX7806N5ELgLb^8B7(6{fns4B~i#vnMfdf zT?2<9F#jM#gPRsAcJfTAK0S=XG3C|DdRfFAVm_L16v9K)bMB*&@0RP4GfRykag4AQ z`r%9C_Sqr-Xx|7%kI4Z=znN=ZCNGqYH217C9Sl?jn3Po`IT}uiTlZ>t2`%p9>~!&# zONqUi5>vnp)F?Cpv5S6k%EA^i)$5Y<&Ic-D*&O6h6)zw<(O*L&MtA7{X^6+wk^%kH z;|9uQM|rI1T?t>>Wsgwo4E09@!4djvjoW=Oxz*64@5Y<hRwk8<a^)O!Oo)8Luq_6e zh`ChfEQ9x<Du-7THan$*Bt;J=SS+Lx2}ha_B;WZeD4;*TY{@)2x7?t9TfTTe9<Os1 zJUlI0>9HMcG}Rj|%M34#0O+^;Vz1X`?f)E(YRVoX2f|M|k^d4D3<xT*hs_i3mOi<V zQDwjW7w|8`SNMG>--pq!!%_Hse!IF|)xQt0U*zl5?T0&a`8xwUM|?h#Uk{{nw7d)O z)&3t|H{td2?MLDCe(y~F9ry71RsK$pPU+n4kNG-3J4<{%yWbD5leD}F_-(I;)jfPZ zr(X?!<mv?PR9_EB{!W{JpQqE|)HLp``8t0-8p-hbi+nz$PSJq)j5+lEM8KfV3Jz0= zZdn~$UW0}8Vv$-`7SfDUR}<v4wpS5a*Q|m@mgDi9n-X}9!uc{{O-@mJZypWcc`cXY zOj7U{D}egR=biAx*04W=n5Scr%pw}e9HeK&>&DhA$)biJz7zn_=Vy~t)t*#|lQg*} z<{FP<)7w6LP~?)?18uJ9*DM;uP7q#ELFX=JE*VLwRKvl{<J<;vB3%O#6${b=tK=r> zBL$QF)W@PQhk&ryF%)0j5|3G1#o-SAoXz~aUMeS6>wNTVU@AAJLElssEX*Hb<5q^+ z#ZbK2Ss**^l5RIMie8U`P1Czhx`tx%plR#v@<QpFt2Q5g8Ltyi)LE>t?x~(z`uw>U zSi7723Z~@>^WTy2BrK=7!fKzFWF{i?y4c?GM$CcE@AaCj0SoSoXf22%Pc80`_9$o? z&xU~(yw+yWIpn;M`Iz)tKZUL!TWH|W^RKQT=8>=v)z&38Mw*;(?`mMlczo2Q-P1I# zkkK8S&duJ<ym^UZd{3K>p4jFX4WB#-g1q*IUoem0>>907#a%psKl%M!iCuTshwKpX zDXLo7e@~CW^=~;6GklIGf9l!+qwSpNFBo*$i-!?0?&MJv3&7tuhpn&jS^m6{;v?fn z-qyp7;m)HUW$s^)UHS*q!SQW2cpCJMe`ZzQE42|4Ha{NzK65}tmq%XUFI=^knsouF zZp|G1aQ@+MCo%+`Z86%gsju$TuA6rBBbh}&_zOa;&<$3(Qki?-@*i6J<W$EGV^3~0 z5IRWj0aWV=+xHZR=8+&-bIFfmH*2$mW`?`d!zR0yQHJCr_*;HNMhEf3MBdox8xhlO zL41$rjN8sa)?buFxRmxJiK?t-T|FBm$K6Em4y*pPyoWj=w|<Cli4}O?yhWWpD}P~p zMC1<i1mTIpV|BaUZL^a_TlwDWJ=i`AqE`m0flakfASPq$lWvpn_<0!K|8^+UP8{=Q zQ7uC40-ZS8U^Q!Q*A93%e)uua$@ODcmA>}uO|!XYYykx+zfxTo(OiB#@Y~p=1#2;i zv5b|)O;YJxV)0s#i%J7UOd4TzkD#p<KEjI2=(*KFH;)P+?y{OD<+}w6#6jUUr8&`2 zvFoQs;HWZqB-{Ry)GHrp{>sK;VbFosX1A+7^l^hYH`3dUI~~wH^08_<SGG$Sdj_+4 zAza$n@LgfTwDCuh*D5!nUBr1lVmRZL;{SAQD(EohafyTBs1J*WFRNYs4_>XDLLfYn zG2$_4z{a$>=`U3x*tQ*RZ~l+RLYcH{o*UQoVM|neszD<eEzRHtC&9s2daIlOkwy^e zr=7=5Qha_Y$AUi<S}Sly?f*ywFI=E12Ron*&szZqXn-vc6tDJ!)GjEstY~1}r##yP zoqhvHgzhZv{*(a1Ygub*U8RxWP(k~2$(N53)*EVfFJTW$PF5`0*n%r<aN=c|ETBS0 zuoILlALkr_vjB7B1w`4BNsuR0czuBvqn&~<(~>hPQ4dZ>^fiP5SGCNk#YeL|UYu}# z9~h=huVkt>4b&NkAQo9p;y4As_~HdNUmhpVi>w9zT`|c{gm-Fx=6Gh5P;HA%RI5LW zVs|I=3BI0oxpgTrV!&Bl8m_U;JkMiWQB@e4!UON8=8(U?z%1rm;#b!OPs)z2`{+p$ zAdjhxqZHsk>!02OWp&m$(5mIm+iBKX(S+?Bfbx<haZn?NsJcxhgzF9jV^pw^kFUDL zp~|B-=SQwz<hFe2qDr!iog^UCO^srU{g5Z>|9&5x*~H#PlZ_xU^{|9=Y~(XMaH*ow z7XGQnY|rO9blvImE9p<#=)$%KcAY`Z?<_QAr5$E<1$b2Gt&E?u*hahlv47Sj0DVsm zEDc04Iyn)MSvwNWIyjq2Wj<rKF~n+0<|P20FKSE=vaN*)#o^wcX}w^r!yS-K?20Q# zSfKf4d-6i3AZ10=N0ZuSdRm5}@DLPBNqDQ2tewN0kig6LH^TZrIGunimZn^_ad?7O zD~hjmb0`~cBks$QxOfFS<KdQrw_eF_7TD7kwa`VI;QH^G|5P;2j9o<TQ6O$IqLpdk zid^Y50h>u_^>twuckh9iC924Ksk4&>;ewY3VS{$&d6?NCTq@TM)ys;SFw^qgBod+I zj{8<7zQXlR8z{REl&|Vf>K#$`2eg<YlZft{%sf)mFx199Sx^Vsg6eCMvC3j{Y0Ah6 zN73@fFkze>V~hj;6#=WRA^B@8F!st#3_kS6wBMp!hG}ZUC?-;u4@10;Fq6Fvz|-LL z{g6blGQ~x%uSjsDA;N72XGFH7UCH)78~IM+P1I8MN0|Y|aR@?;I2F-#@kjRpe1v6= zI82ed-!4Y}Tm0NG$}u$oBW|Uj<lxO`sL&{X=ETZGed~_`-XObkr8e=X*mos~zUujK zG&onC3?YIk+HhNszKl2<-s$mbUR*H-w_H=XgUMY+Nt%T13>JuPL-Q$474ebPXS9-l zq6%CdoIO(oA4fUG0bJF{{vONdGYLO!U!-(+ev-&mm#XapjPA3#6JTcEvqn35w#?u8 zHw`y51d*lR2{FMbm)P2{!;HDwnII(>^viy5l1S|a)Bw=y^Q};QeIV8{DO=8}rMT0L zVyTn4j7Wid)^9j`QU0~u_TIsR_UY*pu@mvJgG2CK5z0d|{|Nha*C=aFP3;dWtk*Vs z$MC*0rK77y$7THiJ6dd;_w_K=udJL;byL(<xg)`+?`&~xJ<2#GsNy<OG1r=u-)nnL z|9VH1>*-pZF*i?&L<vTSaWfMHl4)-aECmW>=lr%BvgH7=W{Eg$S{d;VLgd)H6nT%< zDlMcA8!GwmLm&v=Lt@+!ChMbnA4L+5$$#W)0z?zl;eAK51#f)`tE(ZvLN=bY><S9C z4?B19`cw^D>L89Iax)Std|hUNN^#~z?8D)R$Mcns+o|qw(^nr^5KseHL0ey3z^N7f z{s9}LPqwsUCFL{si2;}}vo&gvwezVI6<?nmaas-pBIzgZoG7C3krtcgr8tDc6+{sa zXEnG0Nem?9J|~=L$F(CH>;W@ys~mh+)XS5Hwa{=9sH*9MT5V~+af)HtP?<x%8}%5z z-AU<fjoh6Dd#`GCFS=~1tAe!#y`OmKCliassi(18*R>#a$Fa5B8rz(U(tA$WG^PM> zziE4<4UhoD3EXL_^;;xT1T2n08@gKoFL)~c!310-f*`gc^SSd=7a_$4-aOgW&$)+U zMcnx2aHFQx9p*BA07dJg%^>qmI1(f+ZMG|yTm~(td%DJqS}GgW1{ca>f<~)U`Pq9K zdNj#|rJ*P}v7t8|X&j31>ML=`<qBu$h{4OUsII*g3e1SV&hRz&0h&;&HskDj>HTHb zD6WlQUAN~vw%k@Gs>5Q;o5f5q<Kry8CyLMq8FQ+=-a$YBcv*v6L0uRuLYaZ%G3X|= zwNWC(v1r05|5@P&VU_#EQmQqy+|OnhQuP-h#x?^N(GR@`D)!mvOKm8Mfb+TcQmOAP zmOYLMaM#4KR~%}1XHLAGr=x(hc|VCE(*4nhEy8Yk{bnzdaisUT>UcKU46yo{H{U_c z*N7bXi(r%rknl;jNuYUnN7iQ%b@SrvJ3F(%S@j2Cc%Cd3PqVnouoTW<X@|kljS%{* z<|UsuCgfFi-Wf#@HWd6Aan2LnD>O>PQ3=AC`+UI6&*21MO9tiqO!7OPK45M~d_N^~ z{}LXW<HqQ3%>-uiZpx=hTdaB~)r{^zgebILA5m-*gD3HDqxuLVz|lnWIpw3L6`i!J z$lHpM4N+UC&l1&<6Y3{liSr)92N>X(vZhe#>u}Z4N$d?X;Z8<4w0~bII_5Zq?&bHV zKz!^Q$ilUpOA9*^*V6Glg$+(^<1aNq?SQ>^>?NIP*}=?k2BLk!k)1~$iI+9t{}Wf1 z>jeeF{jUe!nfL>2DO}&*83|ur+|dIt4lxb;@2cH|YKTb1X_A@w`Bw`AP|w;mxW}1B zC}xl&QJCEmM$5;SK8qT724JZL?GRQAlP>co^4(_hAY8x-P+~4uUb`B*M2DTm!R3t| zuao1d8+kU?q1Vv8Y@u_7knfa@#%d;fFYGw+ucyLE)mfHF8GDEE<s1{M<$yYX#l?fD z+|EcQ5l;B^jJ`|6mNPmY@(V5`1^4&NiStXUBkyS_&AaN?+){0!g?VE%t3kN~#9;Gm ze@XmtuoE3nL4=63@YmVYiHJHyb#8|@0UYq8Tkpg(1hrFBLRBk%NE&mWi-%G@&|!Np zn_PUTXybecWfO<%B`(mmv%=3y;OudlVI7A>UU4Fo1p@p>KjT36StYmOt^Xk7dc~k; zkh*ehWMx5Fx`hPuc+Y7#W5uYFWd~h#eygYsK&gOOLk(y2_+;&G{p`LNCCaBH?l}KZ zJf!mK8geozCjr?G8k+uEIJA%_I9*ZevZKD0R}xBU5gq{G+a$WmESA{HBoEeEjxbbl zI4ieI!a&au<Jm;P5qL2+q$ylx7VQh)#F}^Bx-;#Hnal~T=EJsKdi11EeRuO;sWxWg ztRPKV_M53Z7T8D?QHP%4>cVf)wT796<?J*JvltcJ&nNXND^7CF#bj`$lUY{6#eegl z^pDy8#L|cv-cykjPU|KfC%g_CzT)OYMerZ2UOW-Cpo#gJ$1|;sjUZ>&`h6QpgWZrj z>)h3G-HvR+TDAgr7jx7WD(*Yfh@t3N@L>H-w%u9(S%R4AR|u>;umbDXoRmCV@-#Z+ zh+UlGN;Bute(nSkjutP(v23iWNEqg-YrM;%69TcCet(+|=o3p-&6M$VR9hDp{E2?G zr^ks-Xf{V9Z>8}r`2=Rj`qSB(G17UO59+l$RQf{lFQioSWLuKrLo59y1)as}1hZ?9 zx>w8DT&xsY#+aekmD9}Al)(thKFz}<w9;dm>eh?j#p9>Y4?X^2aD5Wa<O#B_ju&cM zD3sN2Rtx;>WRn%d+5jsJe7)sI955mFGA*94vRLm6D6}%m#objMduAh>oDvh{spC+y z=%XG^%OsTKI+Ln-yd>&K_eftKhI9|v((Ph<_zk+#Y!flllc5^IT_&E%3ZDhRwg!eR z<z#M7Ry^KoqgvI))iOB~-fAic-jrp-$B(I(Cy|SwBA6{9*{-k99uU-lDtkS1tR9oU ziT6yiSkuXUu0-`)nDl@pK6b-Y6_9`%8DakdO1sS00XeQooJ*~}^~pYd&werFazz>D z12V^M-@^sv`{E`Mq08JG^iX@LR}RLbasFCI;t6AzR!gP#z&!Q}mP1)wMcVUsjQxEj z2MIL2)<vNyGL2S~VXGGc*+w=L)bqwsGsj|dS+Q_ogadoHh`72Yv{M!@<9xg6;#iVk zXLPm8)@+1v-|(idg2?`|gBAlUifM~Y66wfas-iLFqf<6LYSu38UlmnBnLqjd1lCZ3 z`63{eI_Z|Wb_WR&FY(w=#8;A<Vo*4I)0v4i3!17TAbOD6;8m}h1GQx{62Foa7E!%X z#F=km27Kg^o0?%A_eiVtk|9o;oel`PUYH0tj;v4EE+l^K6x$560u!Y@i|#AG>m()` zc)nO9n6chuj9{i+9D;ZWDpoqNsR4_}@hMugAs=d_cZ@d9V>le+^BD~w+`91QuI~f$ zY>9v<ww<S72-2q_`|F2_ndf^<MZo@T^cKJJVLO*^T)Y&jG7={z&MNK6TT$R94BZIK z_pD(tk}WT6wWPUqV>Sf5pAsLjAX(nUj9xoE4Da>RtV95?6jRB1gR(gmEL<j9uFMCI zBSmT%aep=`yrdq3d(3$j1N0}PeW@i{#WO22`h5lW8qj46BsW@1IK8Nem5;kPaYZhw zrL*LVdAl<bIBUfKT2Ga~DVuo<oDKprL8TS#1r4OApVC=y8Z~eB)t&Lu&a%H%EHwDQ z$Y82N$#lF<o|I=xV2P3*&-~>tnQ{C>c}-J8B^3dx&Of=NWccp-@o`-hS5r$i3s=`t zu}NyYJ#*aMD!m2q>@i0ip0FW7q+P|F-dWp+k5lsEHm^C@rjTOt{lV(JN%sek1)(_k zV=DPxH5viYwJEM%l`_&9YFm~S;*Jz(T;lE5F20i?KR8T4O79fY2Xzew-QnFc4ar0g z*XOi43m3R$dBY>Y_5hWbP_#6jRk8hcEbzF8bmPaUx!#@IQIwB#@8PP!#tV2mNz`w% zcZdg186z<uM{Vdg-0Y)7;{ZcuRyXLe-2t*uIo0tV<qflAqx%Sl{D~F4fOPg!X~D6B z)b&-_9ANwX1F?3TGzWj|FUChlJnUC1#g)y9rGuJW#^#Ue&?MF|hDo)rQZ7xJgSYUJ z8QGEOY32?jSg<!Tn>Q<W4UB6!remxjMSL1jICNPZfGokg$#x%#<7|!>hr=%#)kF`+ zXr{hSY`a|bOCgo`Rz7;Uop@R*>WrQ+qUsU4jyqUD^=Y(*7<6r<-<|HJZvjAK8sbjA z*M9Np>2wbI*Rs+zRPDE?Aik0IAb72SfUGsHWI!jZDFr!3STx@c4h=Y46U5`fs!Eh@ z)U+%sO}z>_lJcM{n(^oAB4i+fe~*UWA1;xoiO~Rr<QuX^V1?#~t$iO?5)B8*_h)hk zFvQHZAmv=@OcbhJwb(I}Pd0-UWqXRH<A|g4Q@e6o(&H6gnWwrCXPFu`6kXY7Z$$u* zDbn<`j08q7{LIfx%yhhceos<5@Gpi^9|hr)h%mGf)2<uE-QVr2(cg5;s}srcR|5YK zId|Dd-jqz3QnMks2D{)gW1W7TFIq%}+XeHI2JqTfk2r$%>)V&(lANho(I(h*K$?>( z8lkp{h)2c~D+1Ph@OxneaS~~(emc(lo|HWthmhU5*W~Q>bzp~F{i@+rQV7y*eJT6G z4m>yQ!bF%KQuq3Bwg=uYIh}d#V}DSc4T(2r=Tt5B_=R6L9wr)Hq`Y&XWk&S(HV}Jh zT~l@5EpHdDmV){_`j5G&i_|V;&FoI5DrmlR<e9D%nv=84PWHY;xPTzaf;4NE2h@39 z1+mA!!g;jjd`phQQ<x_ByFxJ6+WJ1_wct(68@r_B&?S;2)}f51n<*rpJ0W2bf-TK@ zDYjN0br_Q8SAy-2_1MP+p}(!T<CMv~{>~!gn!q8|S1qHl7#k@d?r#)s!$i(E5Lq?j zV0lej1TK<?sdDaxGL7&wJyix6p#4>jp8%EyW+(B6FH_|0qdHH6Lt?7%YFWO6k<2)h zSc)-Llg$iN9QAX>^f_;XT1JUH^E&g>^G5*WXIR)AC4W!j*m#2AVEz6tS^-hZ>iwEN z{|p|tfkWe(!zSp+G#9R;b!U@U2wRLkSUljWL9g%L%cq4gYG*x}bTTxB1FR)oN9f$T zYN{Wm0|;@6O);lW{Qq+%fX-YV>?F`2mGt?Z+94xus@)j%njwY4!P|ubz%d|8AMd-l zM{7~oO+w8c>Wm!DY5AFB;t5?oEsNT__k0>`kho&8lRw--d<R;^hSYUhlsGZWIYif# ze)%Svp$T+J+1q8!o<<(L$(Y%Gs!Q`M2Xl3OL{^B-qA=#<i#sP-Ij=N%kB@T?e$IJz zgNeFA6|dit7Ii}b6$_`3GvhA>&m?h*q5E$jzLY*_7vbP$&|)jl@Ke{ZSR2Q_OVWnn zyr69z)eP#-6Y7?6bzcai5U!j^#owRQ79Ygf1z*iYD>0q6=E|io<#Rx~gFC-0SG&v! zbyvE~V~sT5cP&*lV<}7?u04;$@MtuqwA$r9)j3E`V$TC*Bh<AtXkQq|h8w*w>9Xjk zRARuQBBsi>s~G8O=JTYUNTXU02&e*92OtC=n|!n4y#AUk?5u+^d6rSF%W<Zz)MQ=p z36+eJ-1t9p^Ypcc&UI`^Nn^-`Ox7ABgtf^DVGeoUlrnuISb&b7a?b(nk#Q8S?LHvX zys{l2^ko`D)j;X2=-;u@>I|cfs)G3RA=aZosp9m>!HlTg-yG~py@i0I0D$7N;@ph! zv$q0or6!EB9Y*v>r#iTb<Pa^Bu2V@LKhKhw(um+BFmH&t6|VlBhhYruZ)GK|8_@$G zbm;_>E9B<}hnAvdZMCdR6?qTWrghB@iS(y`(l_Qzu1&aLtV@R}ZiE$^`XBu&0E(~B zy|Pp$DFWZaRV(&2?;iQ(iGgkfdvn^VFPHAX9D=*T4hS3;a_i+ivv%LMjw5#*GbFka zu%{Hsk%!5OkB}A`Bf;mgUDQ9sXJp_i0^K>3d3nzi0&Pz9M{;2oKQ9-?-dLk48%<d_ zg9jf%Z6J8}jkr8)pd!Qw6l<LXAeP8fVc8a5%Y$F{qurt+Z;aaR^DI6Z9WF$I;mvTH z8rS0=XM|4e>BV>B-FYL+Mb}FpME_swv04NlREKW(7cbPf&BX9Q`I?tIfsuJqF6v*^ zh1NSh<odAcs98DT!{SRD$kDFikpOk9c*M--8lqG(-q7R^I=TgjvN0IMqN<g9*jf8} z_=*2$v);HC2wwtjzaFw`wpz$DI2thasnihksw$z_eK|BU?g%~ZcIoCkKh7c<vB<&) z|8MZL;0jF>Zr~eXzyBUEy73fzw#sN@?xS%CIH~fB<szqQ<YMbdbZ98CtzdSy#u(9{ z8gN<CZ@uuQXBjx_mu0VtN%cVl6m0y|;_LrR>E-1>6#Vn0V5h|iM*j%}WWo-Et>Z3R zM1*=li(fFFzj)oAePRY&vEInqEcGsphLF{=i88`UJ=C5~`EQ%Ek3>_V1elWdFE!r{ z4o0y5a%UdE@J$b$BEzhGO_E~lcq&)dotbTE0ah>em^owe!<BaMNDddqa0SGfw1Fm5 zcV#x56zpMUZtiwM(7?t}P|e9wWH9$9EY2J`Gd4##?25R)9@oeaLhUHHgMYUuDs$}* zWbxJ%T9&_4=LCS`di+{`=gKi!TEVpAsI^iU?F~mkZD_@A1ApqEQLFfg*umrRjXgA> z0TmYT<h?d(`kSduJvb6KI3E>EEG?`<?m@P7<M3gUB1X>~b#yLm(xurPrbIxl9@)-C zR6@B6y!l3BxD)T3Qz2ir3z`B&Txvk!IM*}$@G6xF$^1C**|<CkxC|T&g!0Mc3SNcK zqvApBGvNA7lNNLL(r$l-`Pa3j(hji=w2m>dyb>Cy0;+xd&Cg26zwEg$A4}}uK0>ED zHxj|%rb=t(Jmx0n56a;NLuFa397*%BRL+i(wS$awFCAreZ1nCEM2|<Muve{~cwpmp z(k?F*g@x%U-#=jc7Zr4hXp&dOEr*ABpMrE!icV}s5ZF)KfSuo!Z@gW?(8k`X6g~vt z%iJfdzrlsvMAmvC>jpkF-^gdt8KUXU2@X6#5e<@7oL~JkorQ^CB0Eu;GqM|b0VBGT zd{`@*!~rV4fx;K)_adt9UnA`>oX;G+nJ&TRn3@-skl^s0lImSbl=%}Qs!-sh&7{)! zTuf+?^X6_SUn1e%2&b?DjHQkYVlNrT0=3;k2}~ToFs-vig7&R=p&*eiLFnoam94A? zM~q<0?aboL(i>Om+wE};r>3AKL@RY5p@n1|&J2zCZ4e^5J<B%eUOJxi_lhKA98>H3 zUA`DZrc6#l)Coc~gLu@OFdv6_VAoJ$0ZbFIh3?3<ql(QMT}jS!gOl>abq?H0&5L|{ zJ`1$6kC06d<YPF2Gz5kMPzk7^VB|h&*-gXlFFKp*fgL;7lO6=ZOQ<}-1dL<e#2V3G zR$TKrahcP0Lbh^2&~-16zAAN4F8ZrRGqNtH`wgt6yfQ1hdYHc;rP=JO#j5wAAE`NB z$*n@?zwz(kspclYC3FL&E)3|qgX#0z>y<I%)*bFAst+}zc=%0IA;ZbBvL7HYtM+Eo zTlC$mBK2~m(KxJzDm13+v`IVpd2=$S(NbTx_}{%yQhiVq2y^S$fSlrJvzoVXyj4~< z;tMMwU4x81yjQsgo&f}!9RQVwV^xU+GdWEq=5>0mr)o*YhlQr?fQ1#ZhpT;tVIl8I z)xhuHgsqz6@uvi>d%eh+>MoB6gbmOK?PQ6teYc0cltrvO=?CP^mBgdb2f1-1goJJr zyb9!8XKpwY>S<5_^L5?LyO8l^GUK;dUnjzkQ1lmHL24n2ES(ADiFxfpKv*(j95{5Q zw@^6pOamO7UyfNUtTWuUEF^F<Y9A_f08LTUn9^qz=jE#HC2vf&B;w&xov1Y~GE?s2 zED0tYwaEv~&`p`N3^#?IILQ&b6VM;gW6*PdD_!e%hAIWVp2Md#S-&;E$Vg)tktae2 z_~?T8G6PK&ouNpqa_E{7kHBcNa6kq!f*Sf)%)xu=qp&GF<iHktCt(WGa&`GP!?`YB zk;iypFO*!-R%~?zpnpVmLzBLJlBhE!l~)Unf3Zb6tA4EWVmY#+0>YEza}%Lpo74xh z3){G~*#PaU#sp;3s@14H|0<u@9b^H-y<9az0R!`5(^oFf$?|lk@hoR%A8@5ScD>7$ zo%laN^P6$zN|Rm&r4*E9!+LvP@LH>|NEJIPxZ%N909HV$zh^(J$nRV+j(kvyU~z)+ z3D0ASGwE3TCj6YLi7IV<>6xv~`(-`=X8nqU4=YK}M1yyMFw<%A(O>8{;2iht{=HH6 zKCa|7Zl5p5rJn$(;a^Ihpn2~e#&<stt$clc-B<T8h<&HIksLJ-u@%UYSN%RjG}XF) zKR|69)JB5?BMOXGS+otPb#L2=tsWaD&`|FWdYg?jJ}(^b_dU);A0j}W44HQ;)Iw_L zdsm}aH_G&Va{$+MvxwvqCjwU~;Y~6#)4WgVT92ydyQ)WvrYKkej0EOX=fK{6qx^^D zdA);$3ziE?wd6czQ1`A#p(K2gbrGH@D@k1UYNi86Ws_<d%8mStG++n4>z^>0`w!0B zgBf0=Bvo|@s6i!dcYF$uLQ!NNUaFoEjuO|{T^%;w5+ynPDQwPRU1|3E#-!Te>aC*i zwTz$GudmcSaztL1!>tg3B~6qm_B%6H|8-S%Z?)sVd=dzJ^leGE?I`pjkZa|4_v=+1 zKg<f!41^}?MNUrHpK!pfC1q5hzsU2ia2wY*q2tM<LAs_Bd4<{cNZW<gtYSwt`@0}S zp{oW$Tet{%1LB0yu|{JsR+&RVkPpKXT&7kU9P(s9U3lS%DM;{Caz$KOD3;}k6*8{q z>{J~fBbQ4eY+aGj_VO-Trj3aE4<fSvXJXD>>~-;x_<OI^4zz|e=+)Hy!lwyBRMKm< z7--waBdJxj(EJwYH-FHNP7n!O-Gm)A?bUugfznC5w{t$tiXEM>_Tw119ijREQLRIw zOwbAo&K8Zl4q_!@zVPamI4EX9!~0BbesWzb*W_l(pLF5DzUA1bdfme6ejYI{o+>V6 z1o&pf$hM&$H{!ijYOv%QeEYbRH16o9tVI#x!>vrr6CKkdvuMj@(CW*VunQCK!noV2 zuOVw-dS`n$V&gHiN-^SC72a}jsf4<$^fD7!?{EP7Rc8FsxlN}tP2L|J+%y!zI#q+x zcz!PN)m{3BiX@7rL3VsjLDThQ5NzDcBi0JLN1w1rS3iWY>sJXfT30*EYds^Pz!abW z=NaHVCjz<n*qX#L%@=E~@&_NL^UYOj;=rT1Fu8{Q`2kF8P6j2#BVErQA)8%LkmaGN zQPW4TWAig1lwrln*UQh0^%103U7Gs7==K%NLg%hHHLHtp5r9XeOu8Cgb0=H;DJYHK zg3DDnmlAQye?_F<1pL9Y;4QM-V8dWIQ4>MTFMCql37TY5bz44Gioa`YdqDfssjcmO zXS?v3$jeRa5T!1iYx4w8#gH6Z0yHd^U<pqyz{;pJU&n{!sjOr!2wumrzyoaK{hsRQ z_94IkL*iH<pweX_YUU@#<xl@~5W(6nbOdT#ceO&0e_zx(RG}MnEDa3aW#hNo58A6Z zUQYrY&Q!$?yFf~Uw2)-&OH13Yi1NO`d6q+=5+Dlq$e*s&f18qC5ZxM*<1~t86WC(8 zk$FXHYzzZ9jm{%KTZS?OwS!-ejSul59+K3^O8`>2FmZYab7PWde(?x0djfnnXtjyj zJ~l3oXLMvnjR^A>G1ZLfGrmGeIGQ5l!EvjvO~Rs#yrAx<1ApKVa8YH*cc&xRu-zlV z`+HO}2n2^qK>P-CG{|arLHW9J@fD`GE(0%@9Z=S3;Y5E61UiR^IsjHq{YIsgY!5xs zR{19c$H}m{wc}=C>NQ29y^7DbQ4Ve6ut{AUv<$2V!<VJ&oe%F^dMfU=*A3`FAYXT@ zC}JTIm$kV%7hoj;m0KRG0s_J(PnnbPkoy&6H`C756d0SiMzbfMA=zW@M>A}A7LCYL zsFBva+2oe3ojE&0^J-|6P&Jt)ZI5<<4BiRn?WE_Le}Mt&h(yM<z`Ef#;%&M*2P1)1 z$9!erN&R#}J<EV{9rBuYi02*XR&3LMz><Lhhub`^0grSE!VEEl?@Z+?#Z}&G?>Xf7 z3?21Lq@5LsFw3D#V%4)WDXNCod%w=Zf@4x>2a*+0kVTSv+#VlECdy|*9NKm8F3GN? zY1-x!Vyd%M%VZLrk8vd$#OPIXT!LZ}J!*M&-%st6TyVI+$IHwOFDDSjjH3;Rue}ri z_ZL+Q($|FApy{YN>8u}z>tfdSX1^=XQT}Tj&^R0hTw89u{Bf>+B~a5d8gW^Qz<{@J z3-iPI^a?QaXSAc9@vz$J0O}Lx=aJr7CFm%%<BSMv#sg-ctc7IKq)xu-7k_Be8Q30g zF?%>mcfxq^I>h_D(#+$MoNUjf9(nK>?RdRzwd>Oyuk!t>*VVsT`nn4Js$aLK`|74I zsr&Zz5B~>KbLv&s)VKS3exFi>_V2Il=puado?o?J_Vo+@2U8c?wS8R&PoG^!&!w-o zp{@IR9KNQVeN7*?plS2czi(0hThv7P>6~x1bNF;^zi&mqwOjafO+Kq4`l|2S(H{N1 z7~fNNzNeqt&_(qq{@#c`Z$)?1l=wr4E5MoJ$;Y5-O4mid)4{4BsA<BLh`nr>FnI2Q z&W0rU!CkRU@Yy1coZJBr@v(MqXGf6-9<(_-A~&m*-tl4_QdQWj<XW8TdTqo*b-9NK z2e{|&`rl-0L@;KP5AUYsE-n@uCR4gZrI{cgjUZ>*d5oy)se!Cvr&r{TrV>>i@+fUl zRm$*_TqcjL&o6kfAU`!UR0(g_)9E9)2pARQ3P>WChH89N-fu+<$N~DbH5OARx1l-l zXFPGiUeyGkNL5x=M!){EsJv7EYe?*QutlKyH+(<eFYTNTo^KdS6|r*L!pKIH5|@}K zaE<Ihf5u|=-$rn>hf87bBGDi%Vl?kFDkq`$;jZ$rvlo5#=ILZ^Z)a&QtFryTn+?c# z==K|Ebk&=ca_?~3P;Ep@LUK~UHPB#U;2D>aOND3^6(fi45)B`6XN91_2=u*swchy~ z+*XAinPr%mB_NN3{&6s`&h?mSKA|)Dg$6s8eZn|mjp~@$R!Z7WzOEPYAZX}coec13 zO4SL|8A=*(_Z%cM6#k@=z111^#cQ*eaSOL!Wx|7-xqe&}U*18@sD8kOZ81O>u&e(2 zuuswl=FV-19+Z4HRb6CuZ;m@{fngc+FHH>x3ao5lgMS|1QFnVy6pAl~^pHe-)(Es$ zReVp&vXnOFu^Y4@ryL4KHMd|YFJ2d7QpI!$7Su*ccfJR_!P8J-e|c<HwO2mWzPru^ zt={qdolS3s<fv<j!2SKRv|lmwydP#HIFX3G8qYTxN0NW;(T3j_OY8vR@G0uQV^5Hj zKJ`56ufQ;~3qVesIHi$@Z(?u`F)!F;%r;CTA||!kipt##A^$}C#AG`L(u}AO-u?^g zz1RjBrNF{t_>rr5n1}&2q<Zqg`~%*mN$A9fY1(!_VgW78gyL_-P%M0V&5#M6o^j(a zcV-J}(-`xC1)6S!Txpz_VT=9kE@TPn+|&{P#R5vsd&4xCY$3Ex<?n=*O|c(c0AWY$ z)!4U3xhfTOtfnZZZm?K}!BHLj{)c^*W_fK^lgZ4kGT4`p<m5+pS<fbVDk1XjGMJ&f z>%=e|^)_6MSz40ivY)C*ZEAkD$rz>imqi@VJckq67WSsLOTz`y%}zyVu=~>sTA`wJ z$fH;PM6N4*;lIU*WYiX~G8^(uyPC8Fkya6bStc-#m{BbpZC1Rj<ZIE`-JT&5J90~D z4`|)@Z1_bW7%-g*Ydc);fJ4#r74|tuDCc1@$W(I#`ctc<Kle!enANC=6k%f<)O#4q z`|k%wXPNu@PrLl#I()XYI|*0{%na>hr)OWIf(-6gU9ylm;DA*kbrTs0nvy?0aX)h_ z!}?!BS~XQJ!URC4Xq0`DqbV4S(iAB-A8L<#Q{u87(H7kZ5RHnA;GP&oVX&<36k{yu zck=_*E_rVssu2Ux6aCm}(Q;TPk|$}Yb8@5QlY7@HK@U_#UhUcivVuVN`t<_mt8ka| zwHu_ySrBF%b?`6eNcs04$lXf7$p2bzBkvcnY4@|ZI4|pPE@O4ez{oGR@b-`gB4ktY zBo?;8U#ylb3PoRuO1&}r2MBU6N7u9<4kWeBdnbJ{SH9eThDHT&OhQA~X>R7Xb~Lxu z!Wbeo@JC7vz$GVpr5%y4B|UPQPKRUjz<M6kgF4OBQ0`21UoRivCboG)XcQd}b;%z; zXwrxmMiQoubc|g$9R3sAxDpXA5kMdMkYtPkDrqG%hI!iMnVxom0A>DzK6!!5wcK={ z<YjIO!+CsKG3oSv_%mUU>4{2S;T{wSGdRaf7toAjr>e&F$HexiXv(t<q9~?exN!=v zM#zv7=;Up^&DItiNDN`68OOH<HI#`=h#_D1?|pp9)68R<Ua=4O#qBk{@2mzB*n~tF z<vB`4qQJ9Q4a9pBpP$XpB%Pnx4ihILgZbtCR^$T=GL*24{yGK2jO{pct$`DyDweqE z>7?^Sj2%;&pb)7|-Uc%WJaM2wlD2mh;9gy+>&!6~ENk*l=JVzgeu;eH82b4k#40Fe zKZL{kBA^GXt6G@cugUKMjm_hHbDN5JM_;mE+yVk;0z?QHXdAAAJ@PdXKNUn55&-Z< zv8{ct<1=C>?P|{5@dY9&Z(`wvThB&*gdRRs>XV#IIAo!KnU+w|-q_F9g<ABkkVZFr z60Jm~j&{z?F5cP&RQ`~|eJc3%E-c+gz?nO8rw>o?5i&x*yLFDbcVaN@fg6Kwe5T6= zMlXHyh>J8R1v#+EHi2`J1H3oxxxwOwp@_Ik7G&31GVBqT>>Vr_Tujo%^MpEnJ(PUF zn8GQ4;fUTApcf+Us^Yr@&>zthFn^RboWgv0MoV#{gd{;RM+2c!92AErRSsw^Y0*yS zyuV5M4N@r+Kh5zS2(8W~$B`a~bD=Vx*YtSK?HDAgt{0)3vnAz^4lwhgmkF&=SQVkQ zHJGTCNh6q{qQ$F9&SBl4RYd}>*$i5tH64motzO7EtWt2~-ltij94tv{K-qh-=R!Hv zw=7bt4TSw8_)`QToP?b6eH19px#lJ-9_xy~Mqm?cm@)VG7TQv@?BMV&&-l%LD3mP$ zH6)RD%8|8^K#3hI0td6c!&}UCi)0u9*{NCCLH}a&qrLKl!fW-srr;~A+f}Cq_vGfH zAwoJ=9qf9WGVWtrbn^TrXzPatrhK(B)}g>vy%TscD&Cm*ZXe&Ca4_h}J!OLLP$b#E zwU79QPT{b@Bd4V(KVBYFfQ~!v!L0qmi4t?@WI@I@&lly&WDZ&*EsUzm9_M-gEjhf& zLTd7l%I@mO?D46<S#nl4mkSHmHpg-#2#0;e{v2`j9>macjQLSCI#7oA^0;Onq=NA4 z>csW?k3-(aO<ZKt%0jvR70h{tJYZ&?aD2Sa-C8PFUqGr#(cmCD#$$dtj+VeH`xxHd z$>TGCJkrxZTihfVG!0zO#{;3G7z6@ydRpSU&z6SMvcPg+_Q0PLc-p<wj4l|Z(0N6c z+KxK-tb5ZE%dWSIzw)kQ8nWd1eSzY0zOrMTu^i?6Gt0zAvTe-<-?oE|iwdsZu>mrY z3OOPfPg0G00@=5VQB4mC;T$}q_dMCuH3WwO<3JgMG+GyC?CbXj<=F?tM4vx#q^-p? z2voODv4t2<@>mYWEwK((6R<VsZCqb?NuG(j%F}UxDIcT2QDD8ghU!1v3KQG;5v0PC z6?@$Gn55g7&JWLh+<Vd>a)Wy@9<1yalRhXHh+YlaM$V-(OvY|CLkI+#yFNp%fIpiw z)fWpPtvjj8d#-P~f=7lu3jbxWxruk}gmh!^2r4nM^B$_bxIIw0nFQQxwO{@R_c6(c zTo6qLHbO}r4a0DVD~S)z36h?o2RJK{nYbVqO4Xdfw$lg!h7s(JAv7aWdMXrhbVc#C z=i{cQG8NJ=h|gS|M%TIZcc5-P`V!scjkW|7yY$?S0sab<YPX;@EVy)i`R^wMUcT1z z+eo~Ng_|nWdVBQy&!c^n-(6+mNCUdQ8$6{i@KUhQseNRLw4#&+N62}^%nZ=!>z=uq zDn?dwU9Ic_cjOL5w&;)F%)sjM=(Kg%p<XE`YL?Qr>^8JoaS0%R_B{&D4BM`D4`m}b zU{e&`pFPPR{_o@qo=RinBk!}__S#Pl>-2seph`KIz)NG19T^TuvD>3IWK(TmPRVQ$ z-I<n8_($`BvoZxpIZVIC%|c-oUf?q-%bpCl9knBOVM3bs1Dw)DnUNPY+23icYP9T( z|0ETvD`|P3Xi}VTMQ;BR62wwai&S_12VRRN`i|<}FW;SGp)<xWFQ_CQ+cmU3Pe3vs zr5fG)?m%26#hu7?&qb+3Hq6APM<%(_P~x<5*um%{DjxH;Ga>BDO;LPC1iOQJBnYiT z?vvySlSADJV38O;8$VW9h6}2<S$^i`ky_Pjc#Ir1leSeYwBdAT+7l0blCclq%tYU3 z<=q`)M%H{0wog<uUx5ox%Ivg_%xu@vM!J@kTuFUhm}tgHW)s#(nbFOg1_509wwR9- z`T1XpE9*fLMU8^2BRnY+47)fz7`|NU3G^I6p`|2TAZlp&zS?-}WBQvfe}oy<4|7Xa zJ&r170hvL&I+<*tPArd_+t?zeBAYPDaW9H!h=jw1WUKz1I)G80?{E(m5(U<AU&P|v z;TtD87^Fzg6v+;FeDiOjGn%U5xMRs%B>T*(m%C@IGMt=2ru3MiLad@&4O??5+zB^V zWf%t}sV$wbomXO(@5Pa>#XgU=5lqaKl^<#$W6~E9{RV#7%5YJNL=LbXr@j=lFghF| zfcf&254<k9-6xKVET^Ck5F|3D5XC3^awO3CQYSTflm(U*5%eh5v1X1PwJ{NvSd_Fd zQ=Ujq)gpb_W-w;3f5j$bIh6LoW*Ed*6XweqPKI%)OGt4Rya^Fc$@^0!;<EaIUnz5{ zqTc$-5Fl(Z8>2;mrsbR1@M25DwgN9<AzOp7;b~|ZiQ)l8!orbV`xG`FYouOOvVCZ| zwNTZMn!CWVLlyrS7;1nF_Ue^uTErB={#YUdAgSm07D|hk`>(?oDTtW}K^ey)El?_f z5Ce+3B+hh^N{JI04Dbw-4e5?2{4cUG-G3HSDKO!qAA`MU`{wIbX=gwg5n8<#viJ1b zv$%XsZVD%$gs(fhk9Y;L<Cn55f*DMiXbMGL5BDuT#f8=;s6d9N*UH&Q2^J+%9akws z%JukI(0QoQn4sm|R`dO>cfL|;M3kfh%yt7LL2Y2JGnsw}Y3QxWiy{SBDO$vx(!CqV zSBl@gRD!Otnc#y@17!kgzyMWmmKZ$mw1oiShekz6y`mkIJm3mPU*M8xB=8NHqi$15 z5x)VaomX_XB`-rf&vkDlnT)jqFJJIbG5I(|7bXyKBa0!IHu|no*SLzDGIj+Q2pazy zWdsUv1E&P^zgHlIvi2rcdQhY-SX9&c;9S7bJLkvqe0OVvOl~{i7uLV=z3(mChYAN^ zbGXOHVwkd6ayXl1vDTS+`Ks4|+-cqg@-aB}S=d^^vVLsdy8Z^%=Mp#M!-JKl&tjo_ zIVv750M@ppd^+uQ*jQDaD)g@Y6DE`icbo;2B=fWC)5gz$@1eo<A9RjInD9=BTdD;~ z!5z2(QId>-CKl85`8t4rUWi`O3I?Qmp4&$n4sq(a1aL(g+Ir5HQEJwA*b%}X#-XEG zVcw!OgMaEQ_lw0R@SeHv4o0f>Rk@&~xv4OmR&_c2mq*`M6TFTM9j>+48vx(v<1BR+ z1<N)yH?2(K$m3GF`of|ndS&QN51ztTlIS=dxLClDUKlm(<B}(jyNbpCBWL^VBRK4k zAGI$pR@fir<>tQ%2tFs$C=8eHmWlxMaK{pNdD}WX5#IALv97TSg(V!($@FsjvhUS7 zd}aF=LtnBuLgoR{Gb-63&i147ATh@8+^&od&$(R&j)EcW$04KxR0NUPu}FK~GlwBQ z(<4!M+er_vI4*!=M>uAWJw&XwL7Xy=G2k#Lp)(9&Iv;+Vt>9i!ZZx3J$VTgE){UO` zbfw-no5d%oAz%=9krVCW`lI0!kMpS0ix`3yJ;&W@yJ}iJPGcm=%?$|q{JnF2lC8<< z^c&4;K`QO+OR5=4KdXKbfA2`9`02I?bQ>45vgbJTIP!2}3d=Wve@7(UV2MMK-G3@q zNxaO;d#VLzrTv?53L+F3kCFN(rZbAx5TW7bt<C0l+U-vbMQ6L)B6qMqP4`ts*Yc54 zlfdpLy*Fd*u3Z5;j7~*<>NduN$TzPtAnmt9;iQBvHBuk4z{p_qjG|*I-~z||KeS}O zKSVbZ-tZrH)SlaZBNI1ASK)U0#^&fi4XIH%&))=RSot}l0RSLvWm|Jt>k=q#X&?}j zh$V`d#*svN^OXH!m)f0xM_k%MVAIh`Y`~}<Y0J_jtLX)NX-E9JET+=SeCzhd6%mvv zjB6>An5T_Hbn9|g9v6Ms8Wy9uB9eZT#kWvC<Ll0T-6PJz2EW}D+bZij#s(e{GsdZL zJmN>_s)z*pVV)uA4A*`CCm0U32_Jh!-9QF|)*Y!2F~HCStR1U}GAz76a&CjB;)2z} zwJAB^^qaX+F^wY(i;UTmW}k$sJd8BkD#9r4fwG9t4{!X9BHEgZ%Tq&B;drp`4W`vh zMBImS^Y>THzaJTQ`FJcMdekC`cOSG$S{Ud~ex?B&#UjcQwVkgUCS~6gkoTVw|0zJA z+5Dw4kjJ$1!|+5@ziG(1JcAf!1*viir9u6EyZl>!c7{$zgB0G=x^3vr-A+S?g?BHu z{d0GT5GshpC;kq-5rt*!3Z&)ah3yz})-}pBnu1{0{@O4)pnGM(pi~wb!?s!`oop6N zHUT7!@PaYn5k2xQ*B<|RWOhpzuH?rR%g0tUYID!@rAaRdwaH6RPYQft@D`?<$RApm zdV4F6#l(k5(c4MgxkDP2GIu`|gZ+H|%vLHGA2UlQxPk-IOwh`~UnMsh9*Y};MBo-3 zGGSY?^oD_>^Iomvol#+Zt%yNT#7Y!l8}4Z->vJsHlfv5`7hi;Y0Co1Xb`Q$Jk(QPy z#cJRflUe={Gj|Ss%u$lewTu$<$%+>GC2=^OvT3y39LVvuDa0Nm*=ADd8K|TX5of|) zFXLzL?7B?Y`Y_bF?d=*m42V?EL>72cQd6tvSw-MPWY5&KgB%vK!Y$TrHtG)*^x@D1 zePjlh5T*W8Lx3itFtkdim{YXO=0L>$he&_<-Owef!`S#S_mvAHo>NLD0GMKHxA2^+ zErp2>FJ=gR=W2ki5t#<S)!2Cu_MOPAFRv{w)Xwfnlp9V_ioK<_+muYNf#o)*H;FsJ z<`xv{7EvkkiF7E}Q(Zx5cxv6b!`iac05bSm@ACqJ8^0FP?Z;r;UDM<zTS}?{=qpz0 zw3uXEuOb|mPghQKXcb|LAvC+F6ab1wWg$Ypeo4jU@?vabQ+5mE0I;{0=iUo_3T&eq z1<sJQ<0-oDK3v}XB>CW#mz$`;3aJgGkmVlQmUA{-K<GGcI+CDooKDx*NbDgZ$+J<% zJQ-moUcCfD$GEb;K<V(C5{HffUTeAPpoF{q%FZtj5!dS&H?o1B0pGb*>D^<YbdCf@ zbGoqhYgrb5d(G-a@XDI$HF_RdNI7zD-8+ot_T(CSVf~YV8QuDVsJip}&Qa^&6;Mdq zk1qD^gx%1~V2#`!xB_2a!4xDjM%`)Q;?)*Uq9mNq%VaMJlsAjgRIt<?D^ePNYws-# z?9vYzM-`Isl$7xB3-T5wAQH$AsDTk^nad>Kse}-~NgYIjtG0};d}A6SskYT9fd6W~ zBqlXWgLG;ovPoKS<TQ`exaXF~qbHs6sK$eZ7>|ac;Yf1fL-a*c$M;)3v(U$+G!192 z95HxBlIsU1bmc{Zc-P=kOQ2CoTXze}cMKFE?K;O9Gj+e9ws`Vpt<oyy2gu_L^+Vd> zT;7EHVP#U{vQYY;prU0E{wC#QdzSJ^1Kp)k6yE=RIAx9`;k89Nx0aKrGvgBtO((|g z&I=Yy{E00Wpmbdq#kbkG0}gXu@ve4vcSF;{T?@p9gdoVqSE^O0tRC!PUo94sNEysO zaL$wDv_k<IQC&f(Q!-S!FPmDqWFmn%xr<VOa!F=;5qidJ3IWB~1dj^h6Zsq?E_DQ7 z{asw0V7zcxi5-*LW>vmpR_6CV`<dml%pMWY>#4h$Gfvx99Ai#6^@e%r8C>uXWM4`W zOdo`?;l~RkPbKIEAyeIhYxnS?!>ilcLTRkeuuR{Aci){3A;G+UU0?*5DraVEiT`Lz z4qd!ki}vl91!xwOJ|A=)3vm(ckpW|cSBtoaw6}3y4>ptz-cfR6H|?A1IC4?A=zeyo zkG&<lp#Dz7aJlj$!uUe~Pe3ZUAO|w_$@^*u6bxWy<x0F;m2_OWFwa-j8FZkDm^6{p z-q^rEEl8N|g(spI)&XLH+HUea7t=0r?5w7AHFuFaOe|N<4C9NUwCWQPnREWcOA=K* zLYcl}nQV6VjuRB(Q|x|EBnzNu24KdtR^vq$UjFEr+ymmzhN^}Mb+3mlxkR^W?{+25 zprL)I?ESM@Jk<YCE3k`fBBD%i&~R(Dxvcw!l6lZ{>;<TDJs%#=c-o-W*iCf4U2E`C zdbA0fL-KGe$CwOSxTDh8lzP^}kFiD6+W<hgG9E?d_lLh&Cwp4XbPlmTY@fdR|9aJ~ z)2U;;s0Pbg!g2L~<7y!R#tXScd;c2M^clb`C2vnLl_^QEwo*jx9AOmFf&1qEAj8h3 zkp}T50p{N^<}MlV$Ppmk5oCyd5HmCIIc3wh>197_ZOn{)_uq5s25C>LQz9H{?t}XD zHxb2j*=Q?ZpMHb+pC8>F(01^!4QuPwVf3m!gwQpN07~Zrt=tJL-V9#^@+V&OILz`g zHe?q-QopgKx7=}UuU+|ysic)n3R~9^&Sl@OqfK5X2feT3Xnpx<8$#6HqcTwSvyVlB zn?Mc*Dn|p#+{Sj5PjZYOc)ZuQ`f*W|gji>0MZd{i^_66|rzjH9omNwMZP8<mV+s(- z<3puFO$t7qSwmXzLI}+3xl9in9%WBe?(Ch7c5P^{N@z%t4O>6aRxQWkY2^ja1Iq1X zH|sZqL!2j`FWuK_6-oao?}hHI3Z?*msx$_L(Ab>zA@P<miA{NUF<3|W*r66G*1jGH zr>fx78wIULznrdPC;+iRw4TDoTc?Q{rA%Q^LPY$8O2iEDG*_txW*>zcLlcnRb7A4e zG=wMXjp;~kL!e6?x6uGzQl&E=YM|dB3)KryB#8qwJv;KY9$hMcL_Ky>R}?DB;w48i z;8L+by$3Yjgxg>D_gP`IsB*<+RhA(b)IK`XJN%uYI%E|-!q+L?<l^G77Eirn<(_xx zvhg4o^+h??TwZSFGsLXcs7IUNQGlc+Q4!b^_0maF>_z-|uXq~Kh@9i(<br~{iSa?l z?-!-Pez=V{GP=Ytp4x1`_ep9-_XBd)T9>SEJyW?UBVKX`Cw)T+SGu&(s#&GD>Q5=e zhspE9PZzHrN>5&`;zxTG!*OW0Ky?G`y7!>uj_$Ri%GOqVZrYfq@@eUx3PO00PMj#6 z+$M`Mm(HC7{3P0+-nsO4nBBWHBWfj(^{Hwxz*p|ns>4qfH*^{Dby=dGCw%#*etG3V znkNl{s-WGH&-YZBcD7>c628cuURd?H@(=dg4>W3=K5%+nj>+;-&%ngbe`B-K{mO-c zO|ygktKWLQw5N~*QO`V}i720roYN|~;(l#|17Yuoaf@N0n%ib5538gqyQI}f_B8>? zHObJ_gg$`wuS}ZOGJXJeJAr*8?*$7}{{SXNUDf@alE(d6!=w4^e$1)2C%`-1kQNZG zj)g4r(!_NUSb;jV!Jn<1HhhA>nb1HSQ?T>kdU099rGgoo_g1}JBoYO{>?cB;==DRb zAkXIe1`VY51Iv!q5Mnat*y*dKhL|SJ;;Vh+1=3zU`ZYt`X&xeVkmK=uspS6pqMhum zycYn-i^;=a4fWX!`-nQ`;JD1%aWM$v61XG>nMlbTj?tWsPX>5v|6F2BH}HCj9(wdC zzfp#mh#juR%>D`q&bf~YgW=+HJLpbX%E7iP$?xakQ@&0(5ozG}u3INj<hT|S%x8C3 zF#c~~r~x*O;;J9K)<VYLx+XppM|zCup?t;xcHmT9fz$<@i<g4bIPqF#rbjjLq&6iP zI*n=Q@PHKZF%_0J`L$QY*BGFQ5gIY*DcXQZXQBfMG*!5fh>@c%(TQN~4asn4<0b=5 za;_3D8jZ?2!O`*+JpVHFSj#Z@Qld`=C#}1&QX+HrmtHs7QWbU|+AuR@@~HCZGQLqU z6As>3FdHF4E;A7B7S-0y9pQdbQw)V>ZCtD4e?IIiOvq4g+>UPS9Vzu`I^#xg$I{$0 zesNK`uNA3Imy5YSon}kul)0;{^R=DDS?qJaiIubPFfC7SCi&(oX)C~4|3t<2f)9@5 zkkwOH#DK6E;|~JIM9q&}yY*0gFLQWm80PASwhMs&bdSV$67OL^Z)wVk0=il+*p=;g z2itAON&sKn9gcrDBPbm2U8gHl#miN(S#``@l43_*gkiICl;{^J?V{4|9t0fyyx5>H zv^F`20oFe4tJna`L$c)%xVCVY@2dtTyPWDZu!B!{shQIzDtO2s%(`B|oxfh8rNc<q z27mD!Mc-k3hTZmq`ac7IxLB!zGUI+#Y_3O~+vo6M1ZJ)e<vx=S9;tjZ6N1$%uTz4_ z?EA^&0M^C_B<b#8fzi>t<N6%aNtQCqou251inVM><&PCcJ}KB~{?5*(Q*S-oB!}zg ztu=&sTGQztq=)&e{v#NWh2&p>elLuU@&#hk)?V-Wz&74eMWb&G2BvTL%bGdo?1&v^ z!^d1#t`9yhlB6B8l&Z#KE6tmWy8zTFizS*#Li@gAVj=<MPGrmY?9%wze23B*%;CH7 z7^g1b{>yoc^WB%GYO@pmI~n1_ayFA(<WW1{IuioyOJ&6CQWp$)EZ8P$S8I`cz&Y7A zcaQ*<X1E^wmR;4bG_js7vOnUIoopYDCH$p!Or5_OA4*1>3yeJ`QiW)*^lJM-$*Jxl zgT3}(8~TOXukq|FS_C(tnteQNgzeFu9gbl__ONzsoQ~D$ztIRDX;At<ame5uPHyqu z(hnB6)t{i0eXqrfTfftEsGf`Z(g_x9s&qT_c1P8|-&Ox=eTQ@ep=&Z!$&!U736uU9 zV`m<o;zBH~*vB<~Vwa+g9^!4EuA?2RTa_v;u0E&$yX}o-Rn#LDB%y^E<*Q=yQ>msQ zui^oHwDN9Ca)OmSu|Hru#-0hRZy8uC{d(y5<rHM$6XtcUo9|C5Wh{8Uf_m2CMl80{ zUPJXmlY~#|5UY3c3A#B-G+#8oeR4C3MnuSv!ySrYgqizYNUZUVDfGpuKBWd~T@&g3 zv*h%C%)1f6#kG)jhN+Jx3oGPuhuLO09J|!4g~&Zo)*T|{iWnzAL$IZCzqD<Zk^P9x zg?y^n2ACvcIC`^W(~VFQ1)#@d9E%@MXF?#+z*yUS9K=PmCoJ&Oj~D!7`il!R^SHrf zC500zI9SWjp2BwsgXFL~CRE6N&#^+Q?+gO^WJ8dw1Tl1_)H)=nBtLozf789p(Gi`! zXFQ|jrieb-Aa5Pxfk96B3*9U!+J^H{f7s?;H}ik3AQ214#TmK!fVlX<8Q1P!aRn16 zgy6~>@BU^bD?nrd*;`@@@d*5sHb^zEW`|iP!8!M=ruv^NAL_M3!O||BZB*EpTnYML z|A0xmwgn9HBGMKgIt&lZ*K9W@mY*OsA)`KSb3yleGKba#2$a3yEmOLq_N+qzO~<a_ z)Qp4ff=bx_Ct`>H1dE^lFKt$n;FI9>bo4f9jZq7{R~K%*K0u{3nfMyB&9bB>ULB0% zmItxJ%HjZrw4<U$A~XeknH#V=S1x35&7!dWb2?RE@hx~2l}L=myENJ_Ip<;~7JN|* zQHbm%-3VB_u+H^(5O_ax>3-~O!Wtfc4qp}W#y*q0KshxG4DaY%P+FkrnB*iJJsJD6 zNYXUpPtUYm`tW$VoX=6vWYY(-L})(*tp`B=Ud86<7IX9KPM(>8!-JS{^-wh~QDrh1 z&+KGf-PQ&|hIIX2T~^F_)he-39k%xCL8?Ym+LD)91!8wuh^o%H3zrr!_4Fy5z!sW5 z9l;5jtsc-{W1)+<U_(!0@Pxslf=IpS=uf&Nt&RtNieqFFAiEhHFI<Lc#E1~q#b!M| z#@<>YJ}M46O+la019lvgHgeHSk{ojP`o^aI)-mYuPRjrTp<~yT3vIjdKzeuQ#XWX0 z1;ZaqFd{~jEuCRBm^KhU=zS+zP{AdHWI~`f$qpA_i~kR>b{fC2$E|{U{;-6&g{aZi zEqxA?cKX5cVUtZ?C`zcPI7gL!Mt%+K!M##hX-%*BQ1K2!G%+y_1I9PomBY=HcqBmK zPjmI8ef6Jcu!E1}`=zEY|3=?7Vdp<BRIZ1b)CxTmoYo?c{$u`!1)%YnfV~GmG1T4F z-zdNJvsvR$1QRq57d`%6@;T9U{794gAbWttXv=6vtv8xcUWjx!-B>i3l5GVp{bB%g zzObdKUt{vBFZ=j#J6AdVJK8K#_7C<zs#hCV%-Mr`bTeYD#uk|&>vdDyP0T_U7Na_` z)b*ftN?=_(vvuv@c_H{tHf&o`_j#0Q``w&d?{&>EK4<>@)1V&?bGnF-y1NACPBwZb zH>x<X%QRmVdO!CDnSYI$>g$+sL)+YLPV61VtWLE~T&>Ot|9JY};EdqvPgH$+r|Oy1 zK+GHSqwrn&D~>-iCOnm6nvL;K2MdO8tUq0pM{yzo@jnrxxZm-y$ZMc8Yb|NzP?k1y z#VjKThB`@rMK_yEuGbH<eo8nper7PWjxxd!1{O}epoYe~a(Iiw(>r5d768^JIEO*s z$mPH-N1MoLWn-^R3`}>x;}36ghL9N#{50S3Z=rQUv3j&cRbDRbThskhj=4<k<(A4W zW^4`$>f%%;?aMjpg4{L4d(eTpnB4(o@0o`uv{fa|m&ML={t>y5HmX@ZhPIUQhSh8? zY&XV5LiN=Vo)+R6V3o)Q8evZ_#h@((a0=c_vr&6SXGCpjI$4R33%kYZ-iBS`o$A7# zX|tzu_tkbz@~|HH=4LFP-ODsC7Q^TS%%a|=cGVQyxnWPY!m8qF)xikCCz2=v-z`oy z(j?TmbmM1^#o3egm&dZL+``4sPT4S02zq<K@wJ>h{<5%WqOnDxvBwVc2JRxQ!=evL zC0S@&{IVvl5_(WXr>RVDlkcdz7(na9xiD7&@L!(aYdl${$ZTC!Kxe?rhhqO|ZGf^2 zN^Cm^z*;6Y7Z|Y982J?1JmuVbutIkxjb;XdB|Py!bbC0Pjfb5*oksExoB(*0&Mv_9 z!Eo2BwHQ?^-6XLA(_wOJA)Uku<*M0cU(efgP5bE9X;_Vljh;kWV63xZ)$TO6b0reS z_&G_|ez%ZBO*%Iq?fa{B#wgBlOP5Nt8Wz~cXhN*WB=kIjuWxh-|3M#WQx0tq4l#HI zf|)1+|6Q=(70D`2)^*=RuvU~ZNTQBhyFO#N69K<HaZ?3-PUATkb%LruU0%;$FjuTV z@ML@mK*li;pOrb!-$P1CWc1gWsKDO9t^*(y0F4kNM5DT9)v>(<>%t>eWqi80)f~n% z*qCM5RBvZ$y=^1@@Fz?p67D3K_N<&GRWwX^KkD=8`2#N(XT(rS>R_#|@Nm$8vH)H! z$Vp61qIp&phv0?Ue^IUhHm3|hC&eA#xft*?WS-N(f>yJ#sJ&D#2J5py(kII=i_)^X z&X(MgQ(DOtblt!2$Pss^01ae1#q*DmRVm(tKKGZ``fx4)m9Ii6zxkTUGGax-=$ZmO zV;w12%oT=$yO&`2tp{Qzl{aXckSv(l#UWR37g!dY1%j6teEI=qqx*%cYfJMARBt~o zmOu=0_5;~P4HI%&I?%~PqLM8|>aUmrzRKGjMmYTfNMZ#K6qnWzIuc$n7o?S?&DX`# z9iF#~xmDoVziaF}5+Fzzv`&-H_r%4@ZjDlU^rrQiJ@*WotQmjW;#rR!5+NQ6Fw|5; zP3=Hs(t`1W&9Z_~^iy_;pTu3`2(Ljk7Q63fC0=>3i_D3a57;%wHxGX4z9lARZ=C5b znW&W#kXPr>qt89IMEXF*1u3@?H-5*@jN}@<zkV!%bu4YDC$t3fA>N;>$Uds~KX5-A zZg(2RmLB?XFAA=f>87et>dlSmhZa0P&9pXplh2nozVSMIxwP;d^;oHwBuOnZj8Vj6 z%Ax288`fM6F{Oat-^l3?l@PvsA7v|v%PIwnO&omM(o!~*Lmrm*;m}1_d04Ivxhxt+ z`7LW`=}@~<y1G!cAm#SV_fA#XfdJteAP&wJ*wTw(7BSs;k5YK5U1-PklZSt(X<P-R zD(Af5Dtj>UOE?K4)z`tWttpSilTn^o<knx+60uQ?YeUp61PlEQR9h1&LLQ>DMtSSz zi+x2Had$<ZP>nSOXXpPOWTVpg18x$oIl2>n@mH|SQE6YZP^Uvc!J}l11D!hcXHV-O z5(nI)kmO}8kRNBg=rz$e@egy!d$!a{zGjRnxQ3%kL2KT84)(?pBuZxC)K*21;z1B} z9j>9fT+TyGDhY`|-Bg(tjK+k7{=H<;{ttCP;ya@VcVsmSULV~6zl@ofj*T1ueTEBW ztjM02O2W+b^u+sMT_SY1)@z~R)K*4?CbkY1cuJPVlBUaA793RPDB1VjnqyJ7qmNjx zk985|T&h_R@BK6yL`?uQ8L3%a$J)1n%{*wQD}@~(0Qd8Mjn12+Dv<%!SPiF-ti|El zZO#IiTCHFLr0r6XMAl4KN#33^OoIE;$;LdD$T$B5>o2A?*<V;(x4{W?A%jO^?-woP zDwgD-2_ZZimUrHJEoGD!8=?wlsMUPUjj&*K>~rwOg$=+w3V7H&9!>zbvRon_NeChv zYb+}*gQ-Jd<&ubCuB)9aRz&vNf&(?LFwL=$h)`K;D7L|}P>=-mhU1AlcZl4&ry^WO z;8A$`XEieBo4Hj8N=b%GyLjeBIJrrlZLUa{i@2k7w}otCJymn+{UPFh)Rn8ht>`$f z)llpCiA?YNvHaPG;25J|mtQ{v4>#q#Z*w+-rsKc_2+L_hJ)YL$|1ob(r+oiWQr(hf z9~BLC{)daO1qlSb{}2i@>L5m{jz`~t5YMwr4e>P432|ylgdOm0EW6bEJ!<9t*v<0$ zO&H|UKKBp5Y*^qGW}4ZYoqiiaH7c)qH1-4f4~#Y`YlLAv8DhB0+1oO1{Wp!XFT~)G zmlt=hfnzlwiqeT?wNfWSfqtZ{yq_+Th^+(*_%-_g%HfMl>Zs`nA;3%ci<chQz4!rY z@#^J;0?Kw{fck^)C-(Ey*3lpu)4#5IQ6@Uc8Wec;xwRZ+T@vfe1u3lOLr<lw#^_Y< zmikbIo{Rj}8OXQaOru`aNlLH5qpqfwwxeqqFe9QIG9l>8cNIj);TJ*WqzZbXxHZ~1 z;y$>#{xxnH5aNkcgoxP7=6sFNDyai~%Y841vE_FHSF=;danv(!LSHL`@U++*bBjX+ z1}8YI$}>)f?=Kp-{t}ptoc4CB3PHi+K<@DOzEGPRO1yEOrl@uVv?COzo3#1<q@&vs z6YV5(X9(zXknBD|k@*mOGNsWjG^$`2lG5|$^F<075jDoV;fyHZ2P~!`+0iPk2fMTk zd8#19ShViDBTnSeAM<3R#?sC`=z~?dzi%W<Q@ryyh~g5+%O1z1-U@})6tYWDUv{_M zF5(vHpNnsM?i3Na&9`eVOojb0BJ_UL5i}>5a-&>(tp)%!3Dk-w8I!HW+-t`8q0NLt z>+={o9=jp&Sp1i_jFFsYK`wSW#(+6oJLToxETBHooxwtMm+VgQQe4y%iHR*yZy=2Q zebu*=<{>8N-%<LV8G-D;t-S0XwuVUFH%*QUK{7Et$Pl6H@{l&GjVy}SLU$h{in2=# z)`c5`N;FUtIns94M}+=tg_0x+V+=U6Ne0V~k3ZeHe+Jx_go>y)o4G6O7X>BF?DOf9 zHNDRPpfErpxzr~Sy_5^stXy}rlXpcCrS;K=yBg~<!cbv-Df~BmgMSaN8dmpoz4b*% z>Q0$XX4qWBRJ*;?7`851z@|iEeaL4~YxT2U+~-iO7KH4$B*=X`RO&i5&!y>`|4s#> zc3eEycoaSf0EpHp{!a<?)8tVy!6GP`LT)JdO^xQs_0&N#481NvQ=|{p%X_`asZCW; z8Hz^q8i#S@?QF`cGk7H;X`6)zUw$<?IrO0AKJJ|7q=P<VcFnONS;IJa{-}s{ZFL6B z*sprFAH+fT6y<u}Px?gLR}l9Fgo{^-uJ>=LgWtbCVJ`fJWl`n!Cy?K0?3&6G=jT%H zec%MIB!gUSPelT;{!U3OUJ4P=tJu{+gjFCpu{6>?<?SU71e{byU#x-{&hdk5`#3nO zKaMDtWAy<;k_~_6l=%*a?_Qfo_kFt?N!*g_T1;H$uWdI-E^9Ozs4aV-Kz+5MrO3Q8 z{SUzg5{U!o>+0HkbGvd5$91`86vaipD+UZ!Y<HOnU(fd34)w{4s)5_2GPkq_LcslZ zwbCO38Z232w-N%oM!tiBm;sWi^7$a(-f#`18;BLcnN~XUaWy4NeB-9K!pESlwSm4h znG6=NrV8B<APFO0^YTLInWY%!7hfTsc!+-V7FXI4y(h%eD@4DY>+c(7_-mMI6AknF zk}6lQ0nkI^6Nf7_$3p-k5qg|w2ywl|)gf1)<EHGd3##bSi(e18i6X_`6Stn`FY+dB zUNvEqCFNG}daK$F9uInWtBU%fVTtC)u*AI+p&}C~x<Cv)x_nggWn+ynsK2X1{&@kL zPG?6VKMLH}N9O1;Q;4sDk0r{67Pu3WfbuIv@1*ct52lglzWonVV0GL*rL#}nHe#Kt z(v=F|7D>zC;8wZ45(PBNk=e*YvPAwEMx(xVhOt_vI=7sIOE<Jm#Kn_VTv3Ex7pF(n zI~Ndhg?b;+VIRqcC%7?C{+7x87<|tw>SOt^Av_`W<;*A%yrvM8Ln8yc96f?hG2$mU zw)9xLC0cM3neic4jR#pR*XUFYV$i#9xpC%mv)nB36On9c_+VITxkzCgH+!zmuaUe* zRVy4|AMHeuIJBQGQV)9{=1}PGqj1mSt5YGIYZ<UriD9guFI#1z4U2pu6rRxO(NR{i z3-<!-v^&(V8uklUthWAS1aj{iMO3BH0<@IF-@Vr!D84xNk-5eu?ZZGjm>JOd0`&F& zVQ0LpnNd!zBEjKy9lc!=4>Mebi*Vk;F=9K8i#lg5j6>lBNLU_4hQ0^GDQYgJ3~>z0 z(R&<cyG|GRN~CV9Vy^*7^=#kdwxWcb?4`^-epcNlwVbKzvz*guXF``~Y3NmfSUpEt zyQu+uoG|e=w6ADWto9WO#TI;u>NcoIK7@Xsn)AWcAewf`cCCbG)zK!6heg;Z$n4IC z`A4e1KrUZwWFyA_Bf7?F;nDAoxWa_N>BL?BySs9?G!LGl8;{DTJXysRw+tYh`SCC7 zKMBTH_SiJ$os-}x&Eliz^82ltHYDSi)B>QmXb4!DqeOumF#9#RZSGHLIR&X27pN79 zF$L{#oS1VkrbqaJefr^&f?8)E07peAeCD#xL*9x#9xK@Uuaoddd&-EHbMqrXKH%uQ z*Ch1X^2dy)E)*v*-uH3f56eVocY((0#G2TH)n$en8=g?_8pnZT$Sroe!;o$ThZ0={ z7>R98phDu1Xv#MRi^7#2hd-j>$(LFG5Mqy5E{0BKygyVb6A^b0_?SkvG!e!S13@2n z*CD=!W|nW)CuhX34h*Po*S*40cQQ3=f^rJXQ&ehHEN3}23Q5}0msiSC41WP!T9*XR zzI~qL!SIn<z2{OlSN0mGIbS}DiQD?{;lRpAGI2ew(XiQ44(?3kY8%GAC65O0qzhf0 z^58q{;xUFY-BlY^atH&bT6x+G03UsT9|~C|S_+XY#nYZ)bFdP`gse($N=3?W$DNI) z`N(!79o58hop5&J6)VQL*kqH=2I@-wuy(x>g;x-+Yx5iVf%)FfG1l)QPy+eI%X(@L zyd^uwUX%q8GRx&sBYITBq4mS<zty+)sChXZW8<)=1aae#R<7IS<(O+>U3d;Y3)bn; zl^NNnC?6~uH~2B~pyUS=i<#7OZQZ^_{rSSm>Ms_gxnTp^AftYVunDA23>L-bZbi5) zmdt#bq6TBK)Bzs}wiDrTnC^V0xVYH1_Ea;L(C{Ry!A&EDTrzRtofwruw%toHp5IUj zHhpRLtE7v?%kpXru<ia$_ZE|RF)2mF%<$GVpKN_tXfl`V`|7}cnSj{#v8=WxEO3Dx zN2GP#V|rajtXqQlMS5N#bT1wM5<}}@UgYT$0LCfR92PUm_`Y|x2g&%lfRzLmFJH+u z#{}!c8R^jHhU&{ETDGQ6yVCvGx++r;Wd528l0{9BG{xbAdA6=tq5lfzsM7%1GZ<-m zqNTyC4H?-Hbx2+x6*+;E1_#8<JO`RGj}!^M@D+^<zY&MBP*l)u;2>x!4Z<m4_05H$ zmldZVGgdylos(w%&eH-Hyn<8~B!;rer6956go%_;u8`C0?L<y_TD79Vyu}hR2e1Q$ zgrrI;$Il2i5sH{dSHTDeNYMC^bMkjS-GrdW<k*-{9!h<Wrm5RQO>YMXq1&kpouP=2 zlD<s@e4Ck*X-AS*$U+2OKxGG{1~E)=-E9&hwvhV9@Y=J0o<LAC2$smp@ln5`Y#Yvc zv`N^Kh&y(X7CzdsLO_S;sKcS@v7o+Y#^x2Glc-nY2LEd4!Cg>TGAf60S)1d%BF{Hh z@vw-%v=NZfrUdw;`__;&>_wNB%nk=$bc0&p52i73_xBV%BnPr4AOmJZk!}kR!$zo^ z$#&6OQUhP4SD-z(QiKJ2@T$BlFbd#SGCr`J9z}BnuDJ&7(?`(~6-2KgV<hP!UeI*9 zCFn&(3Q&mldI=WMJ(8oGmV`6dHXdiaxSpjn#hCcb-WMVKs+4*xNW@9yC9$8N!?b;# z9+%VRaMg3isP1~SQ6SadZKWaNs}U<?)=EHr{=YiWd<4U4IT0_0=@cB=!5fXV$ZrJS z02&WXOn-8R8GyuhZnjY?To0daN51*BfINR|iwxxgvFxH~<&bO9PyGtt=d;g-C77m7 zfuMCw?{NwlaMZ$hv+f=Yh#}cqJ5mvY9f~1AterDdZ)mVRi|dX<SNPcw>$oCA#bnvg zED;YC_d?d=DI0GF47{FnWL*iN(X4a-R@Y|BD`+<8rCRs?55rSdoFXCzmSZ!j5$8lU zeX7Y<E#;hU<<XPSq-)q<Xf5DytP2wM>s406L%;X$UqrKHLCnSDS;H$8B>=Nvq{W&m z)CSBSleuUeh22N1>BGE>Z?{nxQL1>>?>9#BKfiPu!0B#2-Mf2ezV;0v`a&-}Ut>$T zf!;aLT`a1SayKxbQsvk{O}u|A;|mS-V{oQbKKA5s7lbO7Ob@-)pSPIIjt*@T!g(?} z!4`>50Q7dPK-0=xURu%q&TjRF1~(Qhp^TmNq)M`*NiI`O7b)n?A%R6Ka57+H-j<<% z?~ub+B^m#JSw?673KUgg0F*V*b4{*H0P}OSfc+LWc5w;-L}Gx{l(bXj^bpYgEVCiF zhjLY08xcT}Lg-c<q!U<`DlxCuS0u^-4>s`04)~evjHycNk5e2;x3pCf@;M<+xwL3O zR<;!_Wmjq-+ZW?Ms3P+1KuDpq)N$5S9%G&cZlfVl$V_1_-a?qjr~9{3&e=Wd#p3%t zh~~yH#XgnvtG%8!^d#u@@PI;(qX^`L9!J|7h7>m8jkqRhp_q-nP;dt1P+(jnjHh(! z&7An9v#$Zaz~(bKLf)r%3nhXNI}b_P=MX7(QOAR1o)tp2hlDYrPjW7|Yf?FBdJv?) zmn$)>5ifYM<#TxZ#o%YeY=(S`PhSIQl{3nxK;>&xg*#ZUHb?kpp&2VlPjFS8N<Cs) ziHIdySi7~4W6?;Z{MH_L_l%@(T{~#=E~#{ZmQ3r+`uO_THWq<0>&B=87`&nOlY+WS zFFkkz0;=um>1qW@q%Cs6G!Pv;@PcsD7F<a`ULzox>d89NMiAG(4+3H!Za!y@GR3Ky zcQCU)fvAp_mOTQ)B)hm9{d%;whH`jGFer5_X-1}WaEdqgZCx&ivb^Il9%*Q!nzbn2 zTBRqfVNn_qr{2)!nER%x+#u$C-B29~7sxu@{(b`^A81EWxAPkCnI|Qt)x=d5ELDhp z4i7dK)i27y7H7~$=`0F>kzh4rzUxg?>)@EOwe<H_@eWG_U+)IK2K5^YI(XJ<A}(B> znGg`_9wQZwlnHrR#Rj4Lq2o_zxZtPwzkwE=@j)j#5z7?M*(W5x&VE$w99EvnZdY;s zEp(DaIOtLqD`R(roC$GYl`sg5p=-{}X1bXX!6y?HXWHI0)6>JFzY&Ry8=8K!PdnyD zFwze@?^f4DG12KVK(Pp46t63FU9iy2TAnl#>HZmbdVaYia@SfuUxSbg>X7$sqZU+) zP)8S99Shkc_^z>Z@dn04p!A#1QQHQ49wH#{xzd5-!b_o+0a~H|9K2~b@x%_H6C@$} ziYU?HrJxMb(l^aOCQ6)K^LdR~sEmUgadW8Plnu<RiD+Qu4uk^v-B8Gf1vp+UL(>k1 zGog&?42-Hx#&B5`5m6cLd#d){)@5_^4#e=O(T3tVLs5Kx+eO>qr<xaP69aBfcI`X` zKN8ac<G4BT&NAZ3)Q*10>6!1rBjjkH?_VG5L9|`S8kNlNlaCC_Vt5*SR+Gb(V-9DP zgcpl(X}MMfB)Q|AHJn~~9-4gLVSvVfP8zqY_mB=P=Xb|^nmdk@Q`|L41Vrg62)?1g zqej5Z0rDu!0kgYYE|o(zwEp_{$}{5Zb;TvCr_o@gVNhbaM$DbTPd1OnYt9%g_jtX| z5f?-bF{Mw$Qo@hrdFyE*)b94^1~<dxI}Z9SuESBFt>?4&T>mpNZa7V^fleiQjwFL- zp6dQ*j``3pu@jFdRzBl1Stj2t;(i*ZuGAkNj-{>TeOg3Avmjep3356sR^0GH<fbIy z7gAkgaRAji4lVJ7W`k0;fRBrny*n%5n#NJtxHpEy2B~mIu5rt6t~FQ<e+j7;nQ0L^ z#maCQX%%BYI@?acU{^<f(mgL9&4}Yamcx@b6^V&I>BLedE!Udu<X#AXA!Y>!x%EE) zt@ro@I*cY93T2(8dUP^rxIk`g!K@P?ZBimc##%I*8$21JW6QPC;9)TA{Gy;j)DApF z*?_)N_E0eGq2eai1!WDC%<Z$d55~O{#-WGrD?J2(qNM1HsJ6W*$gKo40#R_pBa}6! z(^@X2D?Lj_Msdb{gv1*6-fxG6oNXd4UAi^)5D~T<5*j!{HSc{YyEeF<V|@ab%=z@` zE-~awt6tu0mYA8Ga%L-C<y+VpxNH3rr(j+6$i8GQ-@QDg)`PpfkKm|WX$An#(Y*^m z8ds2ntD7Yl!~@PIw;P=y4?ohkJjY~eW9?=<++ws!`s6eick?x?JqBs{1op&)>K--$ zlM0s;MlojhRMOub`5ragKzi|B(Z2`qVI27`Qh9q%_8nD-MEp;;J!TsE0(T4z7%FRy z(thJ-vB#?3&R<b1Fbj-HJ_&IUq>z}ZK)|ICP8~Vo!INm!2*+80Is5qEkn_{^oo|#D zht~tLbC4AF=8*u>+U3^(z@5StD+4{m{?Z0lOX|-PN*zj>I)5rMU|0PII=87Pqe<=4 z>BAe^H}aZ<Qdx7v!!Dv_3<PTZ{m;oTPWIx|r2USRL1iqV?}Z%~4XCfhlc!6HH{wW= zvLIX6$g`P#Q$g_%S!LI32@?#k8W<;G&gNM5o^=B-7~6Ku1K<Ang|GT=P;1K=_(Ut= ze5xa8K|q*ve8Nf_6pAov(bPrj3j0_mad_t#xt>VV;|lVoGT@ER9c^q?LTtnN{NiQD zTFov^Rr0>0|83Ns`*ae0M}N0xe{REHZkhiEs&Ct>SJh8*>Z$)jW<P38^=u6Kwtjs~ zJg=z#dv-^D-A2Bn`|8r)+qa*$STCzXx%Fs8_P6)i=RU18pHdI--rwQSeEOKUe`)XS z)86{8yXrgrx><c%h5K(W+o2!rzwfDs{jI;ZPk(Ap?boa7*}vMn_T;oY(Mo-#((mV3 zZAb1+L%@CL8Z?Pg9yY~0vFYRbz*to;K5YYNTbEj<PK0TX!0A!n?eu&C0@UVK!WxbD zfO#x{#B=Fw2EWjzpEZf;dE+2h5}j6I>;+Q*Qt+hm1sdb?mw&`q0{Nkfmn=UxSBMv; za;<?$Rb&VipH9H=DA+d+2_th<<5)C!wexqwRs||5>%n$kf%C=cO_f21?L%G~!QY+s z0O5?hK1YfS$be!MI)V#T?wtiF2v>gxkCRtjv`3@ZIHBr`uY8J`bDQ<l5x0XNqSZCF z3oh6jjn$1P)B)h5hm~_m((khv6Y?q1#2kd``BRgIWj6L>5(H@3Kk+vYD$Wk3EEH6o z)~*QT6Q+d1t;E{PDaVlBB&l(6HUj@_VNYKK*DARm=_*ASt`JV(BKBEXjA{l4Amni! zT`h=H4C$u%1a=5At~DlJ+=Q7y^$7-y+$sb@<e?m1av5Ncfjn5Bd$4>MMDNH2BZp0$ zRf##PAS2G@^(ZqqOzwT6_$4MQ>!B9AHe3$KjlE0f=*m*J_zJ0%WfCa;f3ariN&rya zE;7(Bh`b(-V5${$S(GWSwhu>K8py<_YeQ)I4M0GG1#|5o`fomS^ADgCZ8<ZSL`f|j zX#*FY9Fo3H#;A7OQ5s>W1mLs)#1?Q-_ELR7*^9c|aqmk;m0AREbsIrGx$@P((}#NX zb8oUaIMrh{Q;Df@@t-ADzQ*6dHkm%{fe88GGK4XjGZPP!uDrp?fZX_<WGt0#{;sy3 zH>lh@Lw3^1OVT$==`dY{K~NSCHF5I1l;(4O>Ns|D#+Cem+5)t^TJ5hqF)?6-!$m6! zd74s1V>b7l+;wtbm*)&(^~gnzOAk?W!DVZqrp59#API7#0?ABARbz8MyIuXyX=&3E zF@CEwHjvDn9NU0AcDR696DA=*sdb0f_d#t=WY)s%Y$Ryrnn7}<zgHQy3$B)2hRgmz z2P2`c@J4$4qAkqTXIIwG(YQy1;C7@m&BmJ%FLg55rQYyHDVljeg<W?tGHk|5e+Bw} zaw5tHkL}k%P$2K&OQ|4@lLCZ{wJ6*L>v-IGZpEoy<IaJUukcc)W-oS@Q&`CswPall z+7@r|I2}3WWtTvWHUg=BjHcTcXWYI0N(hwL99aRY*x^qBVIdIJ+jN3*Xv&Kr$LwkM z*>mg(Zs>%u+s?>>;Z_F<f%_R&$@saNv2>gHel&}o#NMdyNu^`hkHse#-Xz0=WyUy1 zvW%W%s>{|uOl!^R;RQ4((#cXo8|thk-zN3&b<#afGl|2ka*q4gsmyZ^7+H)o&Cqg; zM2ZW-Ex9}as-Y(9swd~!+D<W~aoSWoymQjJ#NJoeR3Bv~RH-JX22{}#@hs0kQD@%J zYejlFZomDY!LFTn*KYEuX3#qx28yNEDp&s&_!9f=T?~S?z7OXalbERWo0P2FTt2%T zGnnNaZjQ+z>6h{FYsxeX%d5JtmJPF?JrXpma^YnLv2;YsrL$T!ZBQ_gIb%WZ596N= zdUjxHbi_%%!~&772T$xpDe3OTcJlDOD#~#Fs{xAZH0=7=Xg0rNe>>h4%p$?HwcHSo zaz;|4j)S&{gVzVKhs#yG>VzNGncx}Wyn>)pEtIj~{1%@^{!*~lm7T!XT+E#w2Z^>$ zm<RB^;f)==WyGm)<bg}ysUHqBUw4q;5!wwPkbwys+Cm#f(#sol03AVV>>OG;Wy|U0 zYde$UpJd$o{P)xBC;pZW07yT%M&S(RCNEhL%E~%lz$ETi7JY?TSggBX?P>=(Wl6vZ zgC0znOd&B80;_2C;ibxE;29E~OL#b$P$JEWSdA*$i0U8s(Z(;6M*8Qxc+CcaN;qpH z>^!o6vfA{}(i0npCg<0s^foO*1*ckXW6ln!|36F2{=kM}#=S_F<NK#2`1siOngXX% z3KUQtymdd(;)Nv=T{)vPK{)CVPr6$<hi3EcO^FP46FGt4IO)vYfvl^vuUX2SinLIn zz_Z_KgWl7h;Ek9&E^bA$#R=IhqEcL$PjR%@)VhV&>>hSP%QNn+_OkcO3tLQ4qm2+P z!Ekgal|x7B;vn?)kHLgAbdH8Ndhxm8xgCE2GaUU&y^h}rq}uqrsFv)ClwdDqBm1}= zbN?z#3)MD;GF$&n^vM3cJV(5}Q;}42l2BMXM?r@@rJ<7AAUr)|9o#ywu}73S0HpE^ zAhSrbHVkEWycc(FE1>A@X*_{+&oU!T6l$`CjEfHNv5-;sVl}psAY(Om$#%@E8PDor zaR&WliV{fr7I9zC;>Nf(p?!wl%rJ2BM@|SK5RnHu%>HrNb3R-w_T?shIU@rnoH-t5 zKETKYoUfyLIt(p$mKU4xW-e*-ev^fj7u+g`Ey7fDj#0*;zk?!zZ}>0{fABXa#KXtQ zNvT*A=*_#sXWsN2So}S0Ij}h3#Ot{7k4Z|uWK;R-X?xN~f#fhu9^``mE1?GM(p+{v zH7?}3ZZI?iO!pOMW@eFjQ$Fqq7N~hbpw9RH^6T-gfonvIOtmr3{r5vWr*Ih+*w+YW zi^Te;%&H_#bj?|e;~UR}p?9WMwccMPev`N*2d(VyA0gq-kPE;yH6TaKWNu<k{X9lf zTVQ3ADeU4Pt1~OdHhpkTe;^p#2+qJ`3w<DRc;^)pP0%^5Mrz;!{k^eUpEa2Y&=5{t zf-q20F_A24&v1ChPQFw18=m);NcvdKOz`tgw2^M1fY!9}{mR%dQz%65U-E#TK=D)a zeIpuQfUbX4<jj_lAgGn;lz_QCo!`^eHehc!+L-`uKAdNz6)(U#Vf2&{r-ZEw*I$G# z_y*)SVKT8Z$nF=Jv9DK25r-cfx^kbZ4_RC7kh-13-s%{WR<nMBz@sw6eudb_w=(S~ zYfxo~0)sEIcAUPNXV-9{p+*gRf0O9kHAj1L%l(JHYqEe^xk_AlsWH;W3VRdgjmA62 zphbhx&&&6uh6-p)(1So~=i5m_fqJQsE6iwln=qn37qnhvOpcoqPHS6hct(NjI`mCt zJQ4H`Zk^>CR!y*cPf+nTISFdfVj^9B<7-Yi_=3I3Z9<MYx*`zwBw&5x72M5N>YMsa zppmOF+DU_;xp$BT`TatyIR?pEh;2yYxy%UAYVJ@J<Qmz%Nwsv6&;F1%Fp;Rpoa0NP zUpA@mf{T(ZFI&^xS1P}4=So6%cdrk7a7YK>&vKXf(*#FqXj?k!`gAqWj*nz$M3%e$ z#w2Xpg=_1v5{>NdXdieN(t9x4&-~y~3jH|W9BwgUu(3;G42M^2{oZ8u=BUPEm4<`2 zv~3+6A7#G{jSwaOfB%2~YwX^uMgwC(+oJy=ETFz(g;Wj!MVJI1g>Bc(^L*bo&GUTU z{wwWH{2p5n$&kJk0r%d*)krzzfny8d@mhhkR5AL;Of4>h8U>s48L=^T*rmXW{R-<B z8%r_o&x%@v<w!7)OWNJ9?Lo(ws&COT(XZeA&fzSANAfL_kD6SZIG2ReT&MG~PxOMt zqtf|Ti?UcjnEB%01@D>M$q!y(xU$8U606)e{R4L;WPfl#>CUog`M}825HM1ZA=E`! zhknGlF$QvZo*cyAH_h{W-#5+keBU?BwDAopCki^loy8~RjtVtSsvXzO^L*bo&GUTU zH|Ps8VB{{IP3Vu_3Ei^LS1gv<?JBF1ZUF3Z0yZVM7;H8h4Ti&Eu-I%tM`BhdDE7kn zPJzze)=2<l!33hdG_F0fbX+z}qg|_N1}v(Jv#eUpdKNaODy8`siA$sI6!AtfpEPny zxobJU<{Y`(YJ9b%2X&nDsviMEPzc-Znl`Y{ALeU=)W=vPeEf+YARkr+FhEc`Lj7_D z)|pbft_$1rczbzcY49<FW?>z=LA(&F2O}x+EhxQW9KYGIQ)9S0%9`Dt95$I93`@Pg zE@rt|#E}TSYTtPNBGMdP8-f1ll1uSvmcJOl1+e#L^!L5&Ek9;RZp$84QETTg6cXE$ ze-XYw?#APuj+>OW>8td3b0Y_WeFjj(5FP?py{b<pm)s^*FXIU-HXChYT$hptJQ!MU zqBi?W5BaKJyeC|YKFd0qgBJ(p5)hEVTcJ$DbK>J(>LeL|a;pNrCzC5NA-;I|%IHsR zK7YbCB<Yb7B*k9XU)Zk#?4D~4%d*#S*f}rbcJ&F<{zB*|YwseH0%Pl}UX?F9g4mEz zEB<~-s{WR8K2(qQr=9*7+PM}?s>UOm7y<#&GlC{n<e7N4G@{eqEa~6kOV!Yc${S%f zDHLu(dty~zzKge3*&UzaSj3{oI-91AzQQ+swA_6&#QrSM5Dm*;Gy?`k`81$=!?!GX zvv1dX&Q3p&W4)uwv8JaI@qx5!B4df5&8R{VMVLyiNxj0ExE9C&qvAXZuz$o|y;lN5 z!_;6mTCIASu$6f}hVco_NnW$rA0EviEu>*gAYQrcBe>n9&&0kkV<wmq|9!HMOcB<1 zs#MzpV5-T#tB*OO*{B3Yc}7zc-DQ0G!Tv9b<&Sm?X*x5j8Yx_7ILYjRLq_T?m^-Q~ zFB<q&jMF}NQuwwWP-qt~KUUT#M{KMY3e?pg+6h+v8CLb$0}CpY3TkX}k*T&7%+895 zMd>+Gfxi3jj~1V(ta%M{hR-y+*aTB5I1+^GOx`l#^5R+;()%Y2bvqg0@VuKh(RXs( zAyQ69vh`(Qb8_rxq!RR)T8BfTDXPFL2l~6gQ>E^nFmA-$(Mp-oS;R<8*5au9%(N)~ zN+`GGM{RHnkWkZ2U#2lhHn4t#Q?r{f2GZ4<t-5a@<X1wz(j>zJ71i{>$&xTMI^DJW zKyBf%6g%Co(h8AoY>Jjo7k%qMfMePYWqu$^x|z?PEV<4G*CCMV2`!FczMz}*@R$;$ z4D36*qx4@al?4g``yA@HCv*7y#PUO!^WpOX5cZ0VOK16QIWkZ~BSB<}#gwBKd5?6@ z;Iq#eoui}-$@mVkHS(<(He>bKas44&JXH*AGS9T@;MKCu^$jT#x-6!Ngsjv(?ZLEo z{6t0{Bw-EyDo<n~Sb!%NBB_V6-LNo*QNX}S2sb+252HRrw=b?iR3SFu<|B|oBd?2n zDS#}7NBnc(KDgBT_Ot@+mj*V4UPlj37Twye-yIVePynTMBEJihZs69u!(d<%lmiD~ zESLT!Ug5{-0rddb!ED|dxVRinNRuN`IkZ<v6&%F~WoW29xN_*vbA!ii{HICJfd+B6 z^R3FZR2>4uh&J;j*m64BF-rn|))<FC?_gUadjmv<oSq>52;gl_!^jXhHLeq7#4)!^ zyh6M_egV5{;wCyW<JU}&*Ds-4!z<h+AR!*p+QB7Ku1IEQl=s;JHl_Big!wDY-nk0C z1ujeFPg2>L@`d!DY}N<#*hzb7;4D4*6)UG(n1tvBTZaoG&r)$>oHbumnBw7)T@aci z3`n%!lPrqjaNu+)DzkAbRq!0lgk>bA+7pPj?+eoK5I}F64+S?MkiMzOu_vC1XkA;T zd8e|wmS5ZlnAWlPX!Ldn4!)9BX?D*ER1#{kNmKUTkk7j;#nJ@Obn0Z=h`FJm4}(x5 z2Y<p7$TCLCa`J_k3{`2^*;ES*{hXSJyWN@8j+xwj#ens?@7vjgf$u-hZ`_)rh;^*j zC-Ui8jL>XER5rxI2+b_Fh7ThQRNMgbM_F~qjeT$%@2WJYRzVOF1*=6YWgjurMq>wq z%<KSeeyt^T))p{FW(#+FoN_e6Q|WF4sL@t>L1?+#!U{S~{bb%#&mdsQK-UrLhcG|( zK_{mBN>lYlz|ZRkoA-)KsM?ER^v}f;wm_~!PQngSpyV^=c(a+0-}Q-6)WEI4XG5Y$ zVftgz*-P9}tsHzx-wOgA@w_jZMZ(@n(M%+3G@u~^)`&jn1N@2^;37_$B%krDxt#*a znP|mdWQn~$>9a{cP_cpkOZbW!^aRS<BbvP)r5&3eB@oR<AC&E}MLnw>=^2{&vP9dW z83@;YxK26anoU&ZZB(YSg%&cslN$Q^o|HQMe-8kIjfFk|qR<OEX8Pc~LjA;i^4<Q0 z_dw`tQ*SGvoA?WE)QD*p0pSeuY+xXI3>&Mb`}g#j1Q{DGKfRy*InSU1hM9d{Y%+q! zQj~)R*bme8tv|=$(<afA`<1iz34FH4ky7jNUq4|sey8fuws+{q>Fxq-@_OyH;#sak zRM;GTVyan>MSmAwpZ>iZ3r`KG56K4B<GLwe0Q`P@BX~_UM-h&LMCIn0j^YS5*`EaH zbaM&q=KA+2t>Y?RNyH{a#}az7=6wGLKW()KfZDWiPpxEzGhoAIy2~j~^j*}g1QMt_ z`tb^9@a}G>izYtrYWw^A;H4D1Sjb|ZDb<})$QVhoIVrDVN>-a)au2fc&A|T(mv6)8 zTs34o<omuST)P7ll);XFXj`udpsR;f;#*TCL-8MRSeBZeFYfk-OdfxgRQT+jM)Y32 z7L=-bNibTt!XxncaZfRq%I6Xp%A>wo)mzdhOW$kti(-SP1TNaM_S&|Y(mV)zQt-eo zA>{lP*Y*?j95S~*dnNG#Pb0I~Jr}ly&RvtuQYC&3rOl_tDyQ20^~9Q4KVL$AK~Sbl z6r*F0FyKTG!P9gN!-$;e7~mKxp4>{eytD5*Dx1L}S~DagD!h%PC(m*|t|8-ljRS7S z-<*V$^)CoL730^6)S%qTbLwZ>?n0S^y;I!>4eRT~`{fOY%49*eiF#jBb>GwOdsK6A zOGG{UoF578%RTT0qJ$|W)JcI&K2%fLQ7GR&M@(X3XiZ3T58n~xoN#I|gyL$fuwwE8 z>5Y>!?A&EJRBvmVcK=!6jODanZj#<}dS`JzIMN1THbXK7ScOzH{G}{^EGu<<DXBIZ zCcGXdj4?GrKm<yRqGx!lKcPrUam^NnB`>o&Qlfn6B0B{wr~@G*TC_pOU|-(tYXAv~ zx8s6e@}Ax0;F^$#zl*KoZQNI^n{pRC#LsihpO|U~337o}SR5wx4<{*KZJ=*YpT=zA zlaQl~O!TL^MCA7K68&vm6rulE^p<?Ri!a=3eG0SOH-9YszB;<O#Kp;>V*H9zCBmhn zpWO`*N4K<3F|p>tizH`~VjK6#d#Gj3h*r(tr3+G86MJ;cWi}?-oPx(VOhCWBY3`2% z=@JeC7nlk;>u7<rmfty04IC8UT%Nw-{LN+@w$08*BRzY4r^e`FR9<h%rD$7Orr$Zp zb4Z#a0Zd6Zf#dMuA}9=;`?O{U?!q<oq^@%^bvdQCasv25$)HjsaBDFcK7lsFBJB)G zj8`TE(IwA1n5tjwaE71M2x_w>qF^RiBJ;E0Ot#ED;z9^&!TqM3oley3ve>nYaitO* z0EJ*_T;djUOv59Wt9LrnZp1Y1a*z?G;6Ph_Rf^9_OC6LK8cpJNy#d!Awilp*=9NYf zD2atR(gUT9)txP1+2{~f$`IHoyetE9UEtnj>6OJQ%4Tm&csbI}<XDkC0KgA^{8aoD z^q3c9U-4^v8+moIz+%Fu0Tc6Y%M2+=3@1nV{qDXbcnQF)DI;Eu>g_$99yfv3TLR~= z0fE3G*@;<dE?k~bQn5+uwfRfLv~4;Y)pQI(Ps^RKb2_TV{ZayJBUZKzz#gt4xY^Qx zBi*>*c;5;=K|BLBof7aG;oyk&c-YzRfK%8+EsMn5&5QT>LdP$A6cs)G2D}WLyxS3S ze1T}{b1zg(+@$q4|7RhGq>SM%RM6l6<d%E<f;Kg)X$rP_7n+`b-~SXV5pTd82Mm=D ze;>Y++7@l9;pTZzYc8S;OmQTDyaM{$aArBHJZC1fC(-E6y0&;ta>|*AdVh|d++a)* zi)Nn*EO@9)5z)*Mga&oyN7$Zy{h2G&_L6wZ5>F9n&@xz2FohUY$<nx<;~hC|O+nYj zFaT<))t^vuo>fSMW9)Dsmf*R6UxbuloYxy{rv7$4^*S4hMbpw+PYmb8drNTG&_sSg zX7Ohe3tu0&zDi--M%FS4|0p(l|6i*xpMIoL9xV3)dKiE1!Y%te1pKow30Zc>sE?$M zF8<!u=h0qa9u662>HvaMdhp5rF=xwd`KJghjim~sev=D+UpJH)qR{QrY<aip?~{lf z7FQKvXOQ2jpAm``PpnpM+;{RZ&f!6Nmsm;MF5kejg>`BXXEukFO(Fv4I^$=-f*;VT zU*;$+CDkPEvTrOC3nkqY82LHZ7xSx0ZA>7zd+x>6@72h_rf$O5$|9z`UjxNY#tguS z+07y^uE7PMLPz=5ym5rFlo<p=PmP>O+;$oqA(A;%6EUETn03l_npwjtU^B+LKAlVl zq}(j5&Y*-#U%@-lyhE5zIemu9g9K4dz(%O@i}2(o*&N0dBS$Al>DI8dUM%FA>z2=t z5vZWAaGUe};8~xd=^w3l5z<tZiMk}paUALE1GE&kkFYigQt^T4VH|P|X8}d=F31IH zB@NNj*-5m4XBP;RriAkNW>h;y;~5K6w;bti{3BPcxX%#QE+x`unZ{q5I>4ncfvuQE zufzqH*Kh&e#X}39u(AtNF#TJA8h>`*;6mqp0i7IoGb|=vGwT{GH{$TG#5KaRs%>bI zw=B*6ocLd13?u<+Y7GU>VlKtCk0l^l%u>{QS}%zGf6^xsjMzdLhfMl5sghLhwb74S zkw|$ju2>o9zDhyX2q*SrTr?WyqorZ{5K4o^=#PE<1@Z~LS=OW)vx$a}!*1rKw1e4Z zKGfhhNA?w!-8mR=Zx!T**W1dWc>f7i9dwsI-halh-$&sv%faG<h7+E`rUQA-E1*?U zQY?;?IHekF6QR!7$hA;SQ8oAvW`#XcxlnFyl($sa0rE?vOKDD0NC);*ql}bQszj?v zH2TgW9;Xg)3I#omU!^pM1-ao@OM+mYM2m42Tkz)|FMNX=AoYX;-@&1-r8W)onY!Ck z0K<9=BmCiDEv6T(drfd2oIoz_t5d>cQ}{%)qsuxL<hUU)*UD?nIgp|9oVc;sPK6?* z_Ss*CF73*wyD^D-93?pOP&0LZ(iEN9^~J=fXT=sb$Z!C!rAPJ>u*fWAl4LP!x%<Rh zeEt?{v+ipC2}6)v19p`-pdorRk_~BPl1J4)Rd;|XqMIu;^Za=3Euhl^l&?7=QG#yt zXyja#aaJModu1z{t$*~-LenCm4D`gKj&(lJJpA04En7-$|26P^-yu)MQ@G-I=d!Ea zF4Q8<k_8#%P@Q`HhzXQWBR6sk@%)4Xz3U@_&F427+UplW+1Pq`s=KxG2-@6<)=UiG zCt1C&=Wp-<ko_?rA7P1*N^qKoA{G@-3DmXT9VF$wNN&WA91SeJg3MUhZA3{YTZD#( zR*R1DP5H>G)BR3IPAZy%K))qO?C@?tN?PITN+VOXBXGSu76lOhI`g-WNL!)gPsgHi zO3N*nXi3eMtg7}A!p{FkX{l<A5_;cUNW0c~UipptE{<H4k4MD##(+w$W;~`=WL{v^ zjGK$kpQDLTQ709~!xVLz8`Rwzz;m~1+%yQam`aSlpkRuII#p;pvUex0R$^>Qa>H5E zO%Hne=#Srdgf?JEAS5Im5cwe>5Fs;C;T*R%H<2A1Kw@JX$lX687Fu-oLE?1`(|V>T z>4(*-z4VBACV^J;oOFPtLzRZ*Ln23|%WB((KA~4ul#;|I;8n@XUsz|2Uetc&(a=7N zRvox1ij?fxf!Xox)1GGD@)ery-31HEw>?E-)~H%4;`K@lS#C$lod*lECjvl=J!#=l z12&o2p~0}r#;<n5g9b<UN$R^dHZEUJ;Z4Gr)`NL9JiQpGpN>8?+a1Fnj~cZtAG`-* zk6xsjEZbf<CbJH?0YM%W&wwz5JmNL=;S89?*GmG{-2%E6Z+{+eAgN}y6t|69X#B(m zMqh{rzr`8%N)v(kuLUEBn6Cq0H#(7wW~OG?aS@Pa;UnT|N-8~gB@`uEhNOI++guw6 zlwt3D6^$Gb&u<HY4$~xy{JOHT0W(hggF{3`2)$iV0(D~c*$#`Bw@1F3L-r!;Wpp26 zLx}E%Q=DMj_ZmryDqoef)jnxN2d@`O(%?A3Pbb;D>$&-DS97gnF~RX6q4#gmI!0#9 zWFWoimv$d_+jV(FX%k{u=7os7_kzI%2RjF$>bNb(R{57+Jk%@&)daw8$zO3cZkA|t zmSM4K&~IFoV%jChak}yu*?yrmgJJAmhUZh_yyv&$_X4N2F&Rk|qy|O|%?ptxA0_lD zfGv<kPw3k>b($7KT6?XTg=gTSjMy5hhR0(oDMEz{4R9dR&iH$$88}Av8s+#~u%2bb zYUe|<_lfpWGHkin?~6aqLRccF1y6lJfDts+kk68zVc1}+><;O&-p+daUgNscf%`lK zK4RBexCh(vQ3dBG1!=A$(Us-#T#OY`i%AX11x||4^#<C`*o-tDa<9p$FD*+y|6i^y zH&dfZTILP%(?2w3GNG%dRgU0MaB1Fh7>$%R+J_`sPs~ZIXFO_K)Z}=zu`ULp7wP~j z>jYe+l=dfa7C%3i;`2b=ZiCFw&`&du;Z)f%dOacR>`&t)&p}BdE+_Zar%QX!pI7_* zOf>fFNY5#)`|!G8*v3eq-M04I>u|97pj`>?z%YJNn6MDjpS<L9cgHPBylg+@)dkOa z5qL-5>;YYk9#dB9$<VK#Du$5+uhi0zBuju@a~%g8$CM~dKG#&x32sI}6%O(j4BXqL z{@k>#@w!&S);9dYo0Fr5Hc4WLPX84@eq+vMRt;vY^aXNAox<=kgulGv=a=h|??CFs zQ{0xM-!>F?d3Oz!ymxD|1;NG_d^eDS?>SICQgX}6sEK@rjeq2YTY4&@t{5y^2JOG* z&Fv}>xn^24nqdcFx`Cg1%pz)Gtejj#IVKsr&`&;RMNaH<DP6v?CydGM5%Rtw{emPo z&<k`GQ#q|v()1a1m!3lA2ZaJ>6~Aufby6z766!491l+!qcW!RZJ{K7x=X||;4Ds_~ z1>1+R-T=Wk4K~nKCqCUllj~Wxd=H(0h48L0@ubcysK6QV$MKPgI|r%e%91T?x5Q!E zM9oiVA&C_)t@c3R6u~2jQ)i6#_1G_D1lZ2k_2MO5IVOJUMth<F<Y?;|cCOFt1wNy~ z^pSJDwi#%2PT<?wnm#wSl7v~y0Jc8s#>~iM^to==yhNS8zXSB&Z|9$C_w355|8LWw zLJGRE6EON;xqxJD43w%+;~2dV!yGS3F5=)yxkhB8-)*Bs3H6b8cKj7Q+Tk&r*zB(s zXJTWA*758f>5B#E2Aje1D!PMlg+pdhGi*mrjd*?{ibkM*^|X8m%s(S~fLftFm`6LM zABFMh$a@y<vYV7@S!xZ!z5>n_3=IAuPyPcnDQOg^cL99Hr3^Ov+xvVOqw<*`qsvtP zUI2!!3<W(<*~IpSRg|#Y(D=JTtN=w)h3VwHh(MR7nrtyV-k)Qltfc+id7uJH!&AXb zZLoFd1<)UCHAxW_nT#UTA#tsbXhS0J$x0{LqeaQ?wd$l?+4tvS4C>>=$v6Ys-UU+& z1nqZ0F85Ikt(2Z?PWsP&5X&F+r6FeFwWMh6L6_S(aPi`1Ai<7pIiO573hP)^=A7i& zs=+Zo!n0!!K5~FSt`IWi=ZV=Qu<EvE84M{H?nuJ6Z|rFK0`toze;fXTJs^Wvp&~n| z>UW~a@j!DEKeN$GLXT2-&A}%BW_eap#s!QJ-@9#7!2DENVS4x|;|t{)uiqC1%VU|h znL$a!igv~OSgiN=>zsCG^>jl4&Nqyk5`US&pYM>;TDBgs@7yU_JT>%Om!;qsQ=g}o zyn&{`?IhTMViASwb>XfBe$b;14il3}*Ux?NIZdOb8;XwyGWL4HSe^hX^Pf{p+u>*B z4m=MGywMwlRQ^tN-%iDDKWrmr_{I`P^~DAB>=&#arl<$aR(6i%7a~<}0hnG&<H37e zGF~g%|19YrNxBPOUBN1;{V22f4OmVbiD_ZzsZ>YDxg;R6{TWShqN$-wuh69@H3)hu zzQspE&a%%#?{`)wgQ(eSAb<$0nN?o^j^R+JUooANu1}To&d;RG7&YSa_-gV|5b<49 zlYH0evBcmBc)57Ib=Vxq-;}d215mmu)aGyvA(M4jPpQGAbU3A$67i}BPXt+xN{2H| z0wIghhb=4<js1VVTuZ_dl@P+(j`1t}--M?J8;MyycY{eC1MPJ+L|(yiw#9h#8=$O4 zqPrH3i`0o+na0zMPf$-3q>bxuxQJ+H4jbIy$FfTsyDba>?4+6L?~G_~yT<~a?N5{R zv0$WlBG}j>I+QQ<j>07L;)0Gw9;{4Y^8R*tVG|clfHeE$(>XbdBg;O1DW&4}g#oQ7 zaZhD)OisVOf;YE9(0|lI9go>W5tIv3nOX3+s>ifEePYBmK7f#QuKzTMI7low3R#tR zmh(!qELE*_a>9B7j>9TNBkr%Ngx$%Vq|Nr3C1D^gU(0c^g@25KqI!$dDT1e<-fv`U zAY_Yu46RYQ4EVzap;-S!r{zj=p1O@vsrwR+mtV+$4Md4-!MQAXe?DhdO43<`0GC$; zrAK8Aw6+=Gxl8%CbNR4RMGTQQ_@N9S&sgJK9h?(<$?E(%_{C|@sH#ejAsLGH09)(b z*7JUGngeW2_);G-1oPh}daXK1qTMT0{njJaqj&**O~jWVc~dJTZ74X=wX(H%Vl)!` zA;2WGmusK_AQ|wuGy7H4=zut1LZUxh38v?zRGEm9KKlO!nCT2Np!e?6Igfc{icxBf z#A0uZ)jBZ|#&P|sKf#{R@r6oUrAwX<!RFarkIOK%K~LB(+bU2mah~mSn;^)j*%x$5 zs-DlCjy^Hq3|I9Z;_$%b?$k10z`EJCvMOKokE|5xHnx(N--!WOJ5!ZNh@%v>#H>6a z+5(831&mp(>}){ekV!3Tnj+4u^3LSe3?EmSloA+L!Ze}JR7X?g9OP=P49K|lCeKc2 z%lw&)_C0-64swA(PA=sfDyPbcoFp6cSpl@WO}PWY&I**O{~A`y20*+^F*>8n*%`lR zbWo*SRklwc3F-7xVZLx>A+01h$WU_W1tN1=z!=F}D?RR03e4nhyqg_`oRs&xL))N2 zlx1EhuSjeCX#EJ_-&`2I4)G1p`zvCVpURyw<W{a{PTx>sX=7bOWjMD0#jv5(XEkhl zQ3!Bmj@p^F+`_2WW1@la?W++@{QyrEf`0)I@n;+kufE6|#y}KdmgOOw@D;jzRnKf< zM5wR2_E+n{lUYo_nTISjP)G8l9B)WvIsS-PbyM@)6oS$QD(d1olQK*dou#yl@bzR0 zzu5P3$ki1)?N7>`+dAg^<OnV_C@5#KjE6|@D}3r~Y5`9p6`Q20Ed<62Ohk8$B|&0z z;2@-97x$5$gJokg^7>gI{~=>ey8suiIuRvX#^nbBi}Ai#!@0%weOU(z)f)KPO@i@b z;k^_m_D86FTYaQrKa&pis*$`SdI`k-L75^;u$VHQd&c<N>kK9sjz!6V)+Jf&VSsy< zXbW@!oc;LHic^&YDvH|xLNL+GKjQI+%`kqZ^Z-O_$k$h%S|}LK9G<YV1SHqSCe<*P ze{SSF?A2y~I_A{)Zi+1WV~5drG7_2fqHt3%=bvn9>dxzi>!b}t{+!Es-CZNe@|2hH zwCRJ@oc^dnB7Ur_rAZB@N~o6&a-VJ4Laf}cdDvyn#E3%aS+j#F!@O&g|7cb6jz9{L z^g4qzXl<0dJDCei)NiWb6Pw#-K%qh!$VJI_bieWfDsPrA>CdE=6=8ZQNd2<k_V?dZ zTxd|DAm4zbK^pe*CG?rtaX*jJ(Jeq?K>;gk!W?pZ?sgc+mLi_@J0&a%F^W!ND+zH2 zOVllv!WhMe8m?w7UKCEK@H}!L&um>@r1t?*fOmSKr)_bEgx{G&FAE=@M*V&E4~_sI z$UmU>{{Y>AtOtffOq0ipdh=}Q;q_gUswH_O?yse=o}YoVJ);fq;^ae$(cl2aKs9se z+_SO~j749INZ=NvcE~N_U6H@7g6$L^3)eDlb<VzBkc>HypN1($&s||HRC<fbs<$;P zu};32j*yWk_f2&W8)q!Dvc>VFN!yJ3kGmVWC6+iP6j{;=`$yTh%ky)je_%O}z*8vD zJT}nLQ%*tn7KJ#Lthw(_+91+Zyi`i%|3=rWE98hQaMqe2vVd_a$N7qZv1Z%W1+uXx z;z+s+=ipo5`gJQ*0k^^&gG&pahyw5^lKd1U(Ca^<rLp1`+g_z5#S{2zYItJ^^8tkX z7h{@EZ6OYsn+Mt)Xs7QLJL_84QA0zTr-_u~%UN)ry~8*i9I^ssWkZ7=F|1AKYs2P; z(=C(_NEu1`aQb)JH@D-J$1_>T-mP+cx2+(2rAKs3^bVR2M|!`zsUQZ;$~hfPtw3Iq zLmapG7f{&)ou(X>Zt;^0f{0exNOiCE?THXl+x<v5U|P3bOU<#6x+mmjQkdEGTUXhy zgeZA~;Gk|vk@24*)yku^Z$s~#A>*r-h0$uH%<X~0RkKfQ;MS0cNf@kWA;!8(NkqO! z0k&FCz19ABg~;^<UIq{crs^6RG$G6^dj_91T5!rDJ!-*{?eSQDO`RRU)erBi&rH6y zbA!U(>$jXy_KqSt)S~OXE}7a@71B#5EQ}`TwnUK6pdqi(-vwBaXIz&$icqrdu;x_R z8k3)~{PA2W$`u^XIAWkz$S0KbW041TY}Slq+^XVdWsTal>R@Rhw~~f70rOEhi#J;4 zbtKhHD}$%aG;};qnUb!@HcxO-DJzSpDzZLE!TrGxq}h-=%Jml?W%mRv^EoQ_od*Ct z>QT!9S4G|pLZZzl?0SX*u81M<4@wk`ZINc5`&S5+P_h6D`LO^uI%R+Fo!Moj+D}ZT zg?9=q?|UWz09b7{(d7bKNSHo*s$`A6i9l`d{vsT}d-KG+rc3xi1SksIIXS1#YE#|; z8(XJ8l(i>&>)nV4no^&!-1nx#tX{0WK$(}5%&YXAtJ-8JT4ZIrent34O`BSr$Cj!; z8GfiP0^i>}C{X^h%+IUj?w0f42NRc29L#4#k5Pgr#YDZAS)g=>51|5+)-q2glO>>O zDGe~G<=1}xK2_@9s!ru2cf5mOL1_!mg-gek(L6$UpS**5AMpSuJ__C4h&vV!Wj{7% zmlfNK^s^mGCHcukyBPTte&I$S=9NCbK(uq;Z)nn23lMg4^{^L>@gA=ZqbZWH%wFmA z>d5>%{UkB1otc{Il9Eb!WyUG7)u;LmU=|5A{ThA{aDw~222-eCVx}#~^hTnxIbn_t z*CjK{;Mv#3&kOaG0<?ON+{R<R@`k~DS#rPqh?p;5lw*Ek(^r%(x~H3++ckW#`9ls& zrmJL80@sZ;YiB9PUM)!tGJ~b3*mm_lAoEItUDU14r80Wq_N75F$jekPN%uz#%NN2; z+Svg0E9kWp)*9=rS8*V+55w)a^BYFoh1+Y+*+EY?b#Ql1#^MIkQYlBmQdL#q9Rcew zQjv^6kNjcz)V2K`VQF!w9*sfBn%~B$6E1*}ROlJp#Jk*<EM-R`^L1sOoVHLROd5oS z0jXC1PZT^L{#OOMN{jDiX@Xdnvdm~OksUf1(lxJ4YOsh(afz$8`jFVp&T?x&qF%%E zuDc@qONDyuMMGvlOK#0*J(^A@!N^;~sGCYoWmVAj|8&d$e(&2jJ+98DZFN&vMysVu m&J+o(RXq9&>e3uLNnSE!&EyFmhzqiMHYO=|zM@I25dYa{LwJAy literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_model.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_model.icns new file mode 100644 index 0000000000000000000000000000000000000000..18e61b3db349117c438408fe6bac9d7be2fabb95 GIT binary patch literal 236301 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 zΠ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%0DFgmiLCN&T)Li2JP735f4Q*`sj46C|6{ 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 CR&1F7 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_subs.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_subs.icns new file mode 100644 index 0000000000000000000000000000000000000000..3e8f02c94c1929d3877a6bd4bce1c38c8eabd3bf GIT binary patch literal 176846 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 )xmD%#LBw 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≪GwJil6xTPuu;#PcS8fUdi@OI; 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 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_video.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_video.icns new file mode 100644 index 0000000000000000000000000000000000000000..92316630eabc7f8f8a6e70337d7cb68dff8b190b GIT binary patch literal 221221 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%0AU@R&8=mz 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 Qbj%LG{@#%Zu=bt**@pI7?*IS* literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_widget.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_widget.icns new file mode 100644 index 0000000000000000000000000000000000000000..9f631118719736c5f6bf6cde67e71bf0eeae0ee3 GIT binary patch literal 227171 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 BN}~V( literal 0 HcmV?d00001 diff --git a/packagers/osx/SLA.r b/packagers/osx/SLA.r new file mode 100644 index 0000000..00977fe --- /dev/null +++ b/packagers/osx/SLA.r @@ -0,0 +1,446 @@ +data 'LPic' (5000) { + $"0002 0011 0003 0001 0000 0000 0002 0000" + $"0008 0003 0000 0001 0004 0000 0004 0005" + $"0000 000E 0006 0001 0005 0007 0000 0007" + $"0008 0000 0047 0009 0000 0034 000A 0001" + $"0035 000B 0001 0020 000C 0000 0011 000D" + $"0000 005B 0004 0000 0033 000F 0001 000C" + $"0010 0000 000B 000E 0000" +}; + + +data 'TEXT' (5002, "English") { + "Copyright (c) 2003-2020 Telecom Paris. All rights reserved.\n" + "For more information, visit <http://gpac.io>\n" + "\n" + "This software package includes many Multimedia Technologies. Some of these technologies may be covered by various patents hard to identify. These patents may or may not apply in your local jurisdiction.\n" + "\n" + "By installing this software, you acknowledge that you may have to pay royalty fees in order to legally use this software.\n" + "\n" + "DO NOT PROCEED with this installation if you do not understand or do not agree with these terms.\n" + "\n" + "Telecom Paris bears no liability for any infringing usage of this software, which is provided for educational or research purposes.\n" + "\n" + "This software is distributed under the GNU LESSER GENERAL PUBLIC LICENSE\n" + "See <http://www.gnu.org/licenses/lgpl-2.1.html> for more details\n" +}; + + +resource 'STR#' (5001, "German") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Deutsch", + /* [2] */ + "Akzeptieren", + /* [3] */ + "Ablehnen", + /* [4] */ + "Drucken", + /* [5] */ + "Sichern...", + /* [6] */ + "Klicken Sie auf ÒAkzeptierenÓ, wenn Sie mit den Bestimmungen des " + "Software-Lizenzvertrages einverstanden sind. Falls nicht, klicken " + "Sie bitte ÒAblehnenÓ an. Sie kšnnen die Software nur installieren, " + "wenn Sie ÒAkzeptierenÓ angeklickt haben.", + /* [7] */ + "Software-Lizenzvertrag", + /* [8] */ + "Dieser Text kann nicht gesichert werden. Diese Festplatte ist " + "mšglicherweise voll oder geschŸtzt oder der Ordner ist geschŸtzt.", + /* [9] */ + "Es kann nicht gedruckt werden. Bitte stellen Sie sicher, da§ ein " + "Drucker ausgewŠhlt ist." + } +}; + +resource 'STR#' (5002, "English") { + { /* array StringArray: 9 elements */ + /* [1] */ + "English", + /* [2] */ + "Agree", + /* [3] */ + "Disagree", + /* [4] */ + "Print", + /* [5] */ + "Save...", + /* [6] */ + "IMPORTANT - Read this License Agreement carefully before clicking on " + "the \"Agree\" button. By clicking on the \"Agree\" button, you agree " + "to be bound by the terms of the License Agreement.", + /* [7] */ + "Software License Agreement", + /* [8] */ + "This text cannot be saved. This disk may be full or locked, or the file " + "may be locked.", + /* [9] */ + "Unable to print. Make sure youÕve selected a printer." + } +}; + +resource 'STR#' (5003, "Spanish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Espa–ol", + /* [2] */ + "Aceptar", + /* [3] */ + "No aceptar", + /* [4] */ + "Imprimir", + /* [5] */ + "Guardar...", + /* [6] */ + "Si est‡ de acuerdo con los tŽrminos de esta licencia, pulse \"Aceptar\" " + "para instalar el software. En el supuesto de que no estŽ de acuerdo con " + "los tŽrminos de esta licencia, pulse \"No aceptar.\"", + /* [7] */ + "Licencia de Software", + /* [8] */ + "Este texto no se puede guardar. El disco puede estar lleno o bloqueado, " + "o el archivo puede estar bloqueado.", + /* [9] */ + "No se puede imprimir. Compruebe que ha seleccionado una impresora." + } +}; + +resource 'STR#' (5004, "French") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Franais", + /* [2] */ + "Accepter", + /* [3] */ + "Refuser", + /* [4] */ + "Imprimer", + /* [5] */ + "Enregistrer...", + /* [6] */ + "Si vous acceptez les termes de la prŽsente licence, cliquez sur " + "\"Accepter\" afin d'installer le logiciel. Si vous n'tes pas d'accord " + "avec les termes de la licence, cliquez sur \"Refuser\".", + /* [7] */ + "Contrat de licence de logiciel", + /* [8] */ + "Ce texte ne peut tre sauvegardŽ. Le disque est peut-tre saturŽ ou " + "verrouillŽ, ou bien le fichier est peut-tre verrouillŽ.", + /* [9] */ + "Impression impossible. Assurez-vous dÕavoir sŽlectionnŽ une imprimante." + } +}; + +resource 'STR#' (5005, "Italian") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Italiano", + /* [2] */ + "Accetto", + /* [3] */ + "Rifiuto", + /* [4] */ + "Stampa", + /* [5] */ + "Registra...", + /* [6] */ + "Se accetti le condizioni di questa licenza, fai clic su \"Accetto\" per " + "installare il software. Altrimenti fai clic su \"Rifiuto\".", + /* [7] */ + "Licenza Software", + /* [8] */ + "Non posso registrare il testo. Il disco potrebbe essere pieno o protetto " + "oppure il documento potrebbe essere protetto.", + /* [9] */ + "Non posso stampare. Assicurati di aver selezionato una stampante." + } +}; + +resource 'STR#' (5006, "Japanese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Japanese", + /* [2] */ + "“¯ˆÓ‚µ‚Ü‚·", + /* [3] */ + "“¯ˆÓ‚µ‚Ü‚¹‚ñ", + /* [4] */ + "ˆóü‚·‚é", + /* [5] */ + "•Û‘¶...", + /* [6] */ + "–{ƒ\\ƒtƒgƒEƒGƒAŽg—p‹–‘øŒ_–ñ‚ÌðŒ‚É“¯ˆÓ‚³" + "‚ê‚éê‡‚ɂ́Aƒ\\ƒtƒgƒEƒGƒA‚ðƒCƒ“ƒXƒg[ƒ‹" + "‚·‚邽‚߂Ɂu“¯ˆÓ‚µ‚Ü‚·v‚ð‰Ÿ‚µ‚Ä‚­‚¾‚³‚¢" + "B@“¯ˆÓ‚³‚ê‚È‚¢ê‡‚ɂ́Au“¯ˆÓ‚µ‚Ü‚¹‚ñ" + "v‚ð‰Ÿ‚µ‚Ä‚­‚¾‚³‚¢B", + /* [7] */ + "ƒ\\ƒtƒgƒEƒFƒAŽg—p‹–‘øŒ_–ñ", + /* [8] */ + "‚±‚̃eƒLƒXƒg‚́A•Û‘¶‚Å‚«‚Ü‚¹‚ñB‚±‚̃fƒB" + "ƒXƒN‚ɋ󂫂ª–³‚¢‚©ƒƒbƒN‚³‚ê‚Ä‚¢‚é‰Â”\\«" + "‚ª‚ ‚è‚Ü‚·B‚Ü‚½‚́A‚±‚̃tƒ@ƒCƒ‹‚ªƒƒbƒN" + "‚³‚ê‚Ä‚¢‚é‰Â”\\«‚ª‚ ‚è‚Ü‚·B", + /* [9] */ + "ˆóü‚Å‚«‚Ü‚¹‚ñBƒvƒŠƒ“ƒ^‚ª³‚µ‚­‘I‘ð‚³‚ê" + "‚Ä‚¢‚邱‚Æ‚ðŠm”F‚µ‚Ä‚­‚¾‚³‚¢B" + } +}; + +resource 'STR#' (5007, "Dutch") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Nederlands", + /* [2] */ + "Ja", + /* [3] */ + "Nee", + /* [4] */ + "Print", + /* [5] */ + "Bewaar...", + /* [6] */ + "Indien u akkoord gaat met de voorwaarden van deze licentie, kunt u op 'Ja' " + "klikken om de programmatuur te installeren. Indien u niet akkoord gaat, " + "klikt u op 'Nee'.", + /* [7] */ + "Softwarelicentie", + /* [8] */ + "De tekst kan niet worden bewaard. Het kan zijn dat uw schijf vol of " + "beveiligd is of dat het bestand beveiligd is.", + /* [9] */ + "Afdrukken niet mogelijk. Zorg dat er een printer is geselecteerd." + } +}; + +resource 'STR#' (5008, "Swedish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Svensk", + /* [2] */ + "GodkŠnns", + /* [3] */ + "Avbšjs", + /* [4] */ + "Skriv ut", + /* [5] */ + "Spara...", + /* [6] */ + "Om Du godkŠnner licensvillkoren klicka pŒ \"GodkŠnns\" fšr att installera " + "programprodukten. Om Du inte godkŠnner licensvillkoren, klicka pŒ \"Avbšjs\".", + /* [7] */ + "Licensavtal fšr Programprodukt", + /* [8] */ + "Den hŠr texten kan inte sparas eftersom antingen skivan Šr full eller skivan " + "och/eller dokumentet Šr lŒst.", + /* [9] */ + "Kan inte skriva ut. Kontrollera att du har valt en skrivare. " + } +}; + +resource 'STR#' (5009, "Brazilian Portuguese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Portugus, Brasil", + /* [2] */ + "Concordar", + /* [3] */ + "Discordar", + /* [4] */ + "Imprimir", + /* [5] */ + "Salvar...", + /* [6] */ + "Se est‡ de acordo com os termos desta licena, pressione \"Concordar\" para " + "instalar o software. Se n‹o est‡ de acordo, pressione \"Discordar\".", + /* [7] */ + "Licena de Uso de Software", + /* [8] */ + "Este texto n‹o pode ser salvo. O disco pode estar cheio ou\nbloqueado, ou o " + "arquivo pode estar bloqueado.", + /* [9] */ + "N‹o Ž poss’vel imprimir. Comprove que voc selecionou uma impressora." + } +}; + +resource 'STR#' (5010, "Simplified Chinese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Simplified Chinese", + /* [2] */ + "ͬÒâ", + /* [3] */ + "²»Í¬Òâ", + /* [4] */ + "´òÓ¡", + /* [5] */ + "´æ´¢¡­", + /* [6] */ + "Èç¹ûÄúͬÒâ±¾Ðí¿ÉЭÒéµÄÌõ¿î£¬Çë°´¡°Í¬Ò⡱" + "À´°²×°´ËÈí¼þ¡£Èç¹ûÄú²»Í¬Ò⣬Çë°´¡°²»Í¬Òâ" + "¡±¡£", + /* [7] */ + "Èí¼þÐí¿ÉЭÒé", + /* [8] */ + "²»ÄÜ´æ´¢Õâ¸öÎļþ¡£´ÅÅÌ¿ÉÄܱ»Ëø¶¨»òÒÑÂú£¬" + "Ò²ÐíÊÇÎļþ±»Ëø¶¨ÁË¡£", + /* [9] */ + "ÎÞ·¨´òÓ¡¡£ÇëÈ·¶¨ÄúÒÑÑ¡ÔñÁËһ̨´òÓ¡»ú¡£" + } +}; + +resource 'STR#' (5011, "Traditional Chinese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Traditional Chinese", + /* [2] */ + "¦P·N", + /* [3] */ + "¤£¦P·N", + /* [4] */ + "¦C¦L", + /* [5] */ + "Àx¦s¡K", + /* [6] */ + "¦pªG±z¦P·N¥»³\\¥iÃҸ̪º±ø´Ú¡A½Ð«ö¡§¦P·N¡¨" + "¥H¦w¸Ë³nÅé¡C¦pªG¤£¦P·N¡A½Ð«ö¡§¤£¦P·N¡¨¡C", + /* [7] */ + "³nÅé³\\¥i¨óij", + /* [8] */ + "¥»¤å¦rµLªkÀx¦s¡C³o­ÓºÏºÐ¥i¯à¤wº¡©Î¬OÂê©w" + "¡A©ÎÀɮפw¸gÂê©w¡C", + /* [9] */ + "µLªk¦C¦L¡C½Ð½T©w±z¤w¸g¿ï¾Ü¤F¦Lªí¾÷¡C" + } +}; + +resource 'STR#' (5012, "Danish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Dansk", + /* [2] */ + "Enig", + /* [3] */ + "Uenig", + /* [4] */ + "Udskriv", + /* [5] */ + "Arkiver...", + /* [6] */ + "Hvis du accepterer betingelserne i licensaftalen, skal du klikke pŒ ÒEnigÓ " + "for at installere softwaren. Klik pŒ ÒUenigÓ for at annullere installeringen.", + /* [7] */ + "Licensaftale for software", + /* [8] */ + "Teksten kan ikke arkiveres. Disken er evt. fuld eller lŒst, eller ogsŒ er " + "arkivet lŒst.", + /* [9] */ + "Kan ikke udskrive. S¿rg for, at der er valgt en printer." + } +}; + +resource 'STR#' (5013, "Finnish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Suomi", + /* [2] */ + "HyvŠksyn", + /* [3] */ + "En hyvŠksy", + /* [4] */ + "Tulosta", + /* [5] */ + "TallennaÉ", + /* [6] */ + "HyvŠksy lisenssisopimuksen ehdot osoittamalla ÕHyvŠksyÕ. Jos et hyvŠksy " + "sopimuksen ehtoja, osoita ÕEn hyvŠksyÕ.", + /* [7] */ + "Lisenssisopimus", + /* [8] */ + "TekstiŠ ei voida tallentaa. Levy voi olla tŠynnŠ tai lukittu.", + /* [9] */ + "TekstiŠ ei voida tulostaa. Varmista, ettŠ kirjoitin on valittu." + } +}; + +resource 'STR#' (5014, "French Canadian") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Franais canadien", + /* [2] */ + "Accepter", + /* [3] */ + "Refuser", + /* [4] */ + "Imprimer", + /* [5] */ + "Enregistrer...", + /* [6] */ + "Si vous acceptez les termes de la prŽsente licence, cliquez sur \"Accepter\" " + "afin d'installer le logiciel. Si vous n'tes pas d'accord avec les termes de " + "la licence, cliquez sur \"Refuser\".", + /* [7] */ + "Contrat de licence de logiciel", + /* [8] */ + "Ce texte ne peut tre sauvegardŽ. Le disque est peut-tre saturŽ ou verrouillŽ, " + "ou bien le fichier est peut-tre verrouillŽ.", + /* [9] */ + "Impression impossible. Assurez-vous dÕavoir sŽlectionnŽ une imprimante." + } +}; + +resource 'STR#' (5015, "Korean") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Korean", + /* [2] */ + "µ¿ÀÇ", + /* [3] */ + "µ¿ÀÇ ¾ÈÇÔ", + /* [4] */ + "ÇÁ¸°Æ®", + /* [5] */ + "ÀúÀå...", + /* [6] */ + "»ç¿ë °è¾à¼­ÀÇ ³»¿ë¿¡ µ¿ÀÇÇϸé, \"µ¿ÀÇ\" ´Ü" + "Ã߸¦ ´­·¯ ¼ÒÇÁÆ®¿þ¾î¸¦ ¼³Ä¡ÇϽʽÿÀ. µ¿À" + "ÇÇÏÁö ¾Ê´Â´Ù¸é, \"µ¿ÀÇ ¾ÈÇÔ\" ´ÜÃ߸¦ ´©¸£½" + "ʽÿÀ.", + /* [7] */ + "»ç¿ë °è¾à µ¿ÀǼ­", + /* [8] */ + "ÀÌ ÅØ½ºÆ®¸¦ ÀúÀåÇÒ ¼ö ¾ø½À´Ï´Ù. ÀÌ µð½ºÅ" + "©´Â ²Ë á°Å³ª Àá°Ü ÀÖ½À´Ï´Ù. ¶Ç´Â ÆÄÀÏÀÌ" + " Àá°Ü ÀÖÀ» ¼öµµ ÀÖ½À´Ï´Ù.", + /* [9] */ + "ÇÁ¸°Æ®ÇÒ ¼ö ¾ø½À´Ï´Ù. ÇÁ¸°Å͸¦ ¼±ÅÃÇß´ÂÁ" + "ö È®ÀÎÇϽʽÿÀ." + } +}; + +resource 'STR#' (5016, "Norwegian") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Norsk", + /* [2] */ + "Enig", + /* [3] */ + "Ikke enig", + /* [4] */ + "Skriv ut", + /* [5] */ + "Arkiver...", + /* [6] */ + "Hvis De er enig i bestemmelsene i denne lisensavtalen, klikker De pŒ " + "\"Enig\"-knappen for Œ installere programvaren. Hvis De ikke er enig, " + "klikker De pŒ \"Ikke enig\".", + /* [7] */ + "Programvarelisensavtale", + /* [8] */ + "Denne teksten kan ikke arkiveres. Disken kan v¾re full eller lŒst, " + "eller filen kan v¾re lŒst. ", + /* [9] */ + "Kan ikke skrive ut. Forsikre deg om at du har valgt en skriver. " + } +}; diff --git a/packagers/osx/distribution.xml b/packagers/osx/distribution.xml new file mode 100644 index 0000000..be148ad --- /dev/null +++ b/packagers/osx/distribution.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<installer-gui-script minSpecVersion="1"> + <pkg-ref id="com.enst.gpac"/> + <options customize="never" require-scripts="false"/> + <choices-outline> + <line choice="default"> + <line choice="com.enst.gpac"/> + </line> + </choices-outline> + <choice id="default"/> + <choice id="com.enst.gpac" visible="false"> + <pkg-ref id="com.enst.gpac"/> + </choice> + <pkg-ref id="com.enst.gpac" version="0.7.2" onConclusion="none">tmppkg.pkg</pkg-ref> + + <title>GPAC Framework rev + + + diff --git a/packagers/osx/res/background.png b/packagers/osx/res/background.png new file mode 100755 index 0000000000000000000000000000000000000000..b544eed9fd21e4a791ec1f7b94045c7edfeea321 GIT binary patch literal 98776 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 Nv^4b8%T%pG{~s+Kk=6hJ literal 0 HcmV?d00001 diff --git a/packagers/osx/res/preamble.txt b/packagers/osx/res/preamble.txt new file mode 100644 index 0000000..7a70226 --- /dev/null +++ b/packagers/osx/res/preamble.txt @@ -0,0 +1,8 @@ +This software package includes many Multimedia Technologies. Some of these technologies may be covered by various patents hard to identify. These patents may or may not apply in your local jurisdiction. + +By installing this software, you acknowledge that you may have to pay royalty fees in order to legally use this software. + +DO NOT PROCEED with this installation if you do not understand or do not agree with these terms. + +Telecom Paris bears no liability for any infringing usage of this software, which is provided for educational or research purposes. + diff --git a/packagers/osx/scripts/postinstall b/packagers/osx/scripts/postinstall new file mode 100755 index 0000000..d821457 --- /dev/null +++ b/packagers/osx/scripts/postinstall @@ -0,0 +1,14 @@ +#!/bin/sh + +inst_dir="$2/GPAC.app" +echo "inst_dir: $inst_dir" + +inpath=`grep "$inst_dir" /etc/paths` + +if [ -n "$inpath" ] ; then +echo "Already in path" +else +echo "$inst_dir/Contents/MacOS/" >> /etc/paths +fi + +exit 0 \ No newline at end of file diff --git a/packagers/win32_64/nsis/default.out b/packagers/win32_64/nsis/default.out new file mode 100644 index 0000000..e69de29 diff --git a/packagers/win32_64/nsis/gpac_installer.nsi b/packagers/win32_64/nsis/gpac_installer.nsi new file mode 100644 index 0000000..0be1d2b --- /dev/null +++ b/packagers/win32_64/nsis/gpac_installer.nsi @@ -0,0 +1,753 @@ +;-------------------------------- +;General +!define GPAC_VERSION 2.0 +!include default.out + +!define GPAC_ROOT ..\..\.. +!ifdef IS_WIN64 +!define GPAC_BIN ${GPAC_ROOT}\bin\x64\release +!define GPAC_EXTRA_LIB ${GPAC_ROOT}\extra_lib\lib\x64\release +InstallDir "$PROGRAMFILES64\GPAC" +!else +!define GPAC_BIN ${GPAC_ROOT}\bin\win32\release +!define GPAC_EXTRA_LIB ${GPAC_ROOT}\extra_lib\lib\win32\release +InstallDir "$PROGRAMFILES32\GPAC" +!endif + +InstallDirRegKey HKCU "SOFTWARE\GPAC" "InstallDir" + +RequestExecutionLevel highest +!include LogicLib.nsh + +Function .onInit +!ifdef IS_WIN64 +!include "x64.nsh" +${If} ${RunningX64} +${Else} + MessageBox MB_OK|MB_ICONSTOP "This installer is for 64bits operating systems only.$\n Please go to our website and get the 32 BITS installer." + Quit +${Endif} +!endif + +UserInfo::GetAccountType +pop $0 +FunctionEnd + +;-------------------------------- +;Include Modern UI + + !include "MUI2.nsh" + +WindowIcon on +Icon "${GPAC_ROOT}\share\doc\osmo4.ico" +UninstallIcon "${GPAC_ROOT}\share\doc\osmo4.ico" + +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +Var DIALOG +Var Label +Var Confirm +Var VSRedistSetupError + +;LangString PAGE_TITLE ${LANG_ENGLISH} "Title" +;LangString PAGE_SUBTITLE ${LANG_ENGLISH} "Subtitle" + +Function EnableNext + Pop $R1 + ${NSD_GetState} $Confirm $R1 + GetDlgItem $0 $HWNDPARENT 1 + ${If} $R1 == ${BST_CHECKED} + EnableWindow $0 1 + ${Else} + EnableWindow $0 0 + ${Endif} +FunctionEnd + +Function customPage + !insertmacro MUI_HEADER_TEXT "Patents and Royalties" "Please read carefully the following clause." + GetDlgItem $0 $HWNDPARENT 1 + EnableWindow $0 0 + nsDialogs::Create 1018 + Pop $DIALOG + + ${NSD_CreateLabel} 0 0 100% 120u "Multimedia technologies are often covered by various patents which are most of the time hard to identify. These patents may or may not apply in your local jurisdiction. By installing this software, you acknowledge that you may have to pay royaltee fees in order to legally use this software. Do not proceed with this setup if you do not understand or do not agree with these terms. In any case, the authors and/or distributors bears no liability for any infringing usage of this software, which is provided for educational or research purposes." + Pop $Label + + ${NSD_CreateCheckBox} 0 -30 100% 12u "I understand and accept the conditions" + Pop $Confirm + GetFunctionAddress $0 EnableNext + nsDialogs::OnClick $Confirm $0 + + + nsDialogs::Show +FunctionEnd + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_LICENSE "${GPAC_ROOT}\COPYING" + Page custom customPage + !insertmacro MUI_PAGE_COMPONENTS + !insertmacro MUI_PAGE_DIRECTORY + + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + + !insertmacro MUI_LANGUAGE "English" + +ComponentText "This will install the GPAC Framework on your computer. Select which optional things you want installed." +DirText "This will install the GPAC Framework on your computer. Choose a directory" + + +Function FctWriteRegStrAuth + ;local var + Push $0 + Push $R0 + Push $R1 + Push $R2 + Push $R3 + ;pop function arguments + Exch 5 + Pop $R3 + Exch 5 + Pop $R2 + Exch 5 + Pop $R1 + Exch 5 + Pop $R0 + + ;test if calling HKCR + StrCmp $R0 "HKCR" +1 +3 + WriteRegStr HKCR $R1 $R2 $R3 + goto lbl_end + + #has current user admin privileges? + userInfo::getAccountType + Pop $0 + StrCmp $0 "Admin" lbl_admin lbl_not_admin + + lbl_admin: + WriteRegStr HKLM $R1 $R2 $R3 + goto lbl_end + + lbl_not_admin: + WriteRegStr HKCU $R1 $R2 $R3 + + lbl_end: + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 + Pop $0 +FunctionEnd + +!macro WriteRegStrAuth HKREG SUBREG ENTRY VALUESTR + Push "${HKREG}" + Push "${SUBREG}" + Push "${ENTRY}" + Push "${VALUESTR}" + Call FctWriteRegStrAuth +!macroend + +!define WriteRegStrAuth "!insertmacro WriteRegStrAuth" + + +Function un.FctDeleteRegKeyAuth + ;local var + Push $0 + Push $R0 + Push $R1 + ;pop function arguments + Exch 3 + Pop $R1 + Exch 3 + Pop $R0 + + ;test if calling HKCR + StrCmp $R0 "HKCR" +1 +3 + DeleteRegKey HKCR $R1 + goto lbl_end + + #has current user admin privileges? + userInfo::getAccountType + Pop $0 + StrCmp $0 "Admin" lbl_admin lbl_not_admin + + lbl_admin: + DeleteRegKey HKLM $R1 + goto lbl_end + + lbl_not_admin: + DeleteRegKey HKCU $R1 + + lbl_end: + Pop $0 + Pop $R1 + Pop $R0 +FunctionEnd + +!macro DeleteRegKeyAuth HKREG SUBREG + Push "${HKREG}" + Push "${SUBREG}" + Call un.FctDeleteRegKeyAuth +!macroend + +!define DeleteRegKeyAuth "!insertmacro DeleteRegKeyAuth" + +Function InsertGDIPLUS + Push $R0 + Push $R1 + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $R0 "" 0 lbl_winnt + + ;NOT NT + ReadRegStr $R0 HKLM SOFTWARE\Microsoft\Windows\CurrentVersion VersionNumber + + StrCpy $R1 $R0 1 + ; win95, NOT SUPPORTED + StrCmp $R1 '4' 0 lbl_err_95 + StrCpy $R1 $R0 3 + StrCmp $R1 '4.0' lbl_err_95 + ;winME or 98 otherwise + StrCmp $R1 '4.9' lbl_add lbl_add + +lbl_err_nt: + MessageBox MB_OK "Microsoft GDI+ cannot be installed on NT 3 Systems" + Goto lbl_done + +lbl_err_95: + MessageBox MB_OK "Microsoft GDI+ cannot be installed on Windows 95 and older Systems" + Goto lbl_done + +lbl_winnt: + StrCpy $R1 $R0 1 + StrCmp $R1 '3' lbl_err_nt + StrCmp $R1 '4' lbl_add + StrCpy $R1 $R0 3 + StrCmp $R1 '5.0' lbl_add ;2000 + StrCmp $R1 '5.1' lbl_xp ;XP + StrCmp $R1 '5.2' lbl_done ;.NET server + +lbl_add: + +lbl_xp: + +lbl_done: +FunctionEnd + +;GPAC core +Section "GPAC Core" SecGPAC + SectionIn RO + SetOutPath $INSTDIR + + File /oname=ReadMe.txt "${GPAC_ROOT}\README.md" + File /oname=License.txt "${GPAC_ROOT}\COPYING" + File /oname=Changelog.txt "${GPAC_ROOT}\Changelog" + File "${GPAC_ROOT}\share\doc\osmo4.ico" + File "${GPAC_BIN}\libgpac.dll" + File "${GPAC_BIN}\libcryptoMD.dll" + File "${GPAC_BIN}\libsslMD.dll" + + File "${GPAC_BIN}\avcodec-*.dll" + File "${GPAC_BIN}\avdevice-*.dll" + File "${GPAC_BIN}\avfilter-*.dll" + File "${GPAC_BIN}\avformat-*.dll" + File "${GPAC_BIN}\avutil-*.dll" + File "${GPAC_BIN}\swresample-*.dll" + File "${GPAC_BIN}\swscale-*.dll" + File "${GPAC_BIN}\postproc-*.dll" + File "${GPAC_BIN}\libx264-*.dll" + File "${GPAC_BIN}\OpenSVCDecoder.dll" + + ;create default cache + SetOutPath $INSTDIR\cache + + SetOutPath $INSTDIR + + ${WriteRegStrAuth} HKCU "SOFTWARE\GPAC" "InstallDir" "$INSTDIR" + ${WriteRegStrAuth} HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\GPAC" "DisplayName" "GPAC (remove only)" + ${WriteRegStrAuth} HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\GPAC" "UninstallString" "$INSTDIR\uninstall.exe" + WriteUninstaller "uninstall.exe" + +SectionEnd + +;player install +Section "GPAC Player" SecOsmo4 + SectionIn 1 + + File "${GPAC_BIN}\MP4Client.exe" + + File "${GPAC_BIN}\gm_dx_hw.dll" + + ;copy shared res + SetOutPath $INSTDIR\share + File "${GPAC_ROOT}\share\default.cfg" + SetOutPath $INSTDIR\share\res + File /r /x .git ${GPAC_ROOT}\share\res\* + + ;copy GUI + SetOutPath $INSTDIR\share\gui + File "${GPAC_ROOT}\share\gui\gui.bt" + File "${GPAC_ROOT}\share\gui\gui.js" + File "${GPAC_ROOT}\share\gui\gwlib.js" +; File "${GPAC_ROOT}\share\gui\mpegu-core.js" + SetOutPath $INSTDIR\share\gui\icons + File /r /x .git ${GPAC_ROOT}\share\gui\icons\* + SetOutPath $INSTDIR\share\gui\extensions + File /r /x .git ${GPAC_ROOT}\share\gui\extensions\* + + ;copy scripts + SetOutPath $INSTDIR\share\scripts + File /r /x .git ${GPAC_ROOT}\share\scripts\* + + ;copy python + SetOutPath $INSTDIR\share\python + File /r /x .git ${GPAC_ROOT}\share\python\* + + ;copy shaders + SetOutPath $INSTDIR\share\shaders + File /r /x .git ${GPAC_ROOT}\share\shaders\* + + ;copy lang + SetOutPath $INSTDIR\share\lang + File /r /x .git ${GPAC_ROOT}\share\lang\* + + ;copy vis + SetOutPath $INSTDIR\share\vis + File /r /x .git ${GPAC_ROOT}\share\vis\* + + SetOutPath $INSTDIR +SectionEnd + +SubSection "GPAC Plugins" SecPlugins + + +Section "FreeType" SecFT + SectionIn 1 + File "${GPAC_BIN}\gm_ft_font.dll" +SectionEnd + +Section "Windows MME Audio" SecWAVE + SectionIn 1 + File "${GPAC_BIN}\gm_wav_out.dll" +SectionEnd + +Section "SDL" SecSDL + SectionIn 1 + File "${GPAC_BIN}\SDL.dll" + File "${GPAC_BIN}\gm_sdl_out.dll" +SectionEnd + +Section "OpenHEVC Decoder" SecOHEVC + SectionIn 1 + File "${GPAC_BIN}\openhevc-1.dll" + File "${GPAC_BIN}\gf_ohevcdec.dll" +SectionEnd + +Section "Validator" SecValidator + SectionIn 1 + File "${GPAC_BIN}\gm_validator.dll" +SectionEnd + +SubSectionEnd + + +Section "MP4Box" SecMP4B + SectionIn 1 + SetOutPath $INSTDIR + File "${GPAC_BIN}\MP4Box.exe" + Push $INSTDIR + Call AddToPath +SectionEnd + +Section "gpac" SecGPACBIN + SectionIn 1 + SetOutPath $INSTDIR + File "${GPAC_BIN}\gpac.exe" + Push $INSTDIR + Call AddToPath +SectionEnd + + +Section "GPAC SDK" SecSDK + SectionIn 1 + SetOutPath $INSTDIR\sdk\include + File /r ${GPAC_ROOT}\include\*.h + SetOutPath $INSTDIR\sdk\lib + File ${GPAC_BIN}\libgpac.lib +SectionEnd + +!if /FileExists "${GPAC_BIN}\GPAX.dll" +Section "GPAX" SecGPAX + SectionIn 1 + SetOutPath $INSTDIR + File "${GPAC_BIN}\GPAX.dll" + RegDLL "$INSTDIR\GPAX.dll" +SectionEnd +!endif + +;Section "Windows Runtime Libraries" SecMSVCRT +; SectionIn 1 +; File "..\Microsoft.VC100.CRT.manifest" +; File "..\Microsoft.VC100.MFC.manifest" +; File "${GPAC_BIN}\msvcr100.dll" +; File "${GPAC_BIN}\mfc100.dll" +;SectionEnd + +!ifdef IS_WIN64 +!define TARGET_ARCHITECTURE "x64" +!else +!define TARGET_ARCHITECTURE "x86" +!endif + +Section "VS2015 C++ re-distributable Package (${TARGET_ARCHITECTURE})" SEC_VCREDIST1 +DetailPrint "Running VS2015 re-distributable setup..." + SectionIn 1 + SetOutPath "$TEMP\vc2015" + File "${GPAC_ROOT}\packagers\win32_64\nsis\vc_redist.${TARGET_ARCHITECTURE}.exe" + ExecWait '"$TEMP\vc2015\vc_redist.${TARGET_ARCHITECTURE}.exe" /install /quiet /norestart' $VSRedistSetupError + RMDir /r "$TEMP\vc2015" + DetailPrint "Finished VS2015 re-distributable setup" + SetOutPath "$INSTDIR" +SectionEnd + + +SubSection "GPAC Shortcuts" + +Section "Add Start Menu Shortcuts" + SectionIn 1 + #has current user admin privileges? + userInfo::getAccountType + Pop $0 + StrCmp $0 "Admin" +1 +2 + SetShellVarContext all + CreateDirectory "$SMPROGRAMS\GPAC" + CreateShortCut "$SMPROGRAMS\GPAC\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 + CreateShortCut "$SMPROGRAMS\GPAC\Osmo4.lnk" "$INSTDIR\MP4Client.exe" "" + CreateShortCut "$SMPROGRAMS\GPAC\Osmo4 (with Console).lnk" "$INSTDIR\MP4Client.exe" "-guid" + CreateShortCut "$SMPROGRAMS\GPAC\Readme.lnk" "$INSTDIR\ReadMe.txt" + CreateShortCut "$SMPROGRAMS\GPAC\License.lnk" "$INSTDIR\License.txt" + CreateShortCut "$SMPROGRAMS\GPAC\History.lnk" "$INSTDIR\changelog.txt" + CreateShortCut "$SMPROGRAMS\GPAC\Configuration Info.lnk" "$INSTDIR\configuration.html" +SectionEnd + +SubSectionEnd + + + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecGPAC} "GPAC Core" + !insertmacro MUI_DESCRIPTION_TEXT ${SecOsmo4} "GPAC Player" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPlugins} "GPAC Plugins" + !insertmacro MUI_DESCRIPTION_TEXT ${SecFT} "FreeType font parsing" + !insertmacro MUI_DESCRIPTION_TEXT ${SecWAVE} "Windows MME Audio output support" + !insertmacro MUI_DESCRIPTION_TEXT ${SecOHEVC} "Support for HEVC decoding through OpenHEVC Decoder" +; !insertmacro MUI_DESCRIPTION_TEXT ${SecUPnP} "Support for UPnP based on Platinum" +; !insertmacro MUI_DESCRIPTION_TEXT ${SecMPEGU} "Support for W3C and MPEG-U Widgets" + !insertmacro MUI_DESCRIPTION_TEXT ${SecMP4B} "MP4Box command-line tool for MP4 file manipulation" + !insertmacro MUI_DESCRIPTION_TEXT ${SecGPACBIN} "gpac command-line tool for various multimedia operations" + !insertmacro MUI_DESCRIPTION_TEXT ${SecSDK} "GPAC SDK: headers and library files needed to develop modules for GPAC or appllication based on GPAC" + !insertmacro MUI_DESCRIPTION_TEXT ${SecSDL} "GPAC SDL support" + !insertmacro MUI_DESCRIPTION_TEXT ${SecValidator} "GPAC Test Validator" + +!insertmacro MUI_FUNCTION_DESCRIPTION_END + + +Function .onInstSuccess +; MessageBox MB_YESNO "GPAC Framework installation complete. Do you want to launch the player?" IDNO NoLaunch +; Exec $INSTDIR\Osmo4.exe +; NoLaunch: +FunctionEnd + + + + + +; uninstall stuff + +UninstallText "This will uninstall GPAC from your computer. Hit next to continue." + +; special uninstall section. +Section "Uninstall" + ; remove registry keys + ${DeleteRegKeyAuth} HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\GPAC" + ${DeleteRegKeyAuth} HKCU "SOFTWARE\GPAC" + ${DeleteRegKeyAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" + ${DeleteRegKeyAuth} HKCR GPAC\mp4\DefaultIcon + ${DeleteRegKeyAuth} HKCR GPAC\mp4\shell\open\command + ${DeleteRegKeyAuth} HKCR GPAC\mp4 + ${DeleteRegKeyAuth} HKCR .mp4 + ${DeleteRegKeyAuth} HKCR GPAC\3gp\DefaultIcon + ${DeleteRegKeyAuth} HKCR GPAC\3gp\shell\open\command + ${DeleteRegKeyAuth} HKCR GPAC\3gp + ${DeleteRegKeyAuth} HKCR .3gp + ${DeleteRegKeyAuth} HKCR GPAC\3g2\DefaultIcon + ${DeleteRegKeyAuth} HKCR GPAC\3g2\shell\open\command + ${DeleteRegKeyAuth} HKCR GPAC\3g2 + ${DeleteRegKeyAuth} HKCR .3g2 + ${DeleteRegKeyAuth} HKCR GPAC + + UnRegDLL "$INSTDIR\GPAX.dll" + RMDir /r $INSTDIR + Push $INSTDIR + Call un.RemoveFromPath + #has current user admin privileges? + userInfo::getAccountType + Pop $0 + StrCmp $0 "Admin" +1 +2 + SetShellVarContext all + Delete "$SMPROGRAMS\GPAC\*.*" + RMDir "$SMPROGRAMS\GPAC" + Delete "$QUICKLAUNCH\Osmo4.lnk" + Delete "$DESKTOP\Osmo4.lnk" + +SectionEnd + +;path modif functions +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 + +!ifndef WriteEnvStr_RegKey + !ifdef ALL_USERS + !define WriteEnvStr_RegKey \ + 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !else + !define WriteEnvStr_RegKey 'HKCU "Environment"' + !endif +!endif + +;-------------------------------------------------------------------- +; Path functions +; +; Based on example from: +; http://nsis.sourceforge.net/Path_Manipulation +; + + +!include "WinMessages.nsh" + +; Registry Entry for environment (NT4,2000,XP) +; All users: +;!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' +; Current user only: +!define Environ 'HKCU "Environment"' + + +; AddToPath - Appends dir to PATH +; (does not work on Win9x/ME) +; +; Usage: +; Push "dir" +; Call AddToPath + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + + ; NSIS ReadRegStr returns empty string on string overflow + ; Native calls are used here to check actual length of PATH + + ; $4 = RegOpenKey(HKEY_CURRENT_USER, "Environment", &$3) + System::Call "advapi32::RegOpenKey(i 0x80000001, t'Environment', *i.r3) i.r4" + IntCmp $4 0 0 done done + ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2)) + ; RegCloseKey($3) + System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4" + System::Call "advapi32::RegCloseKey(i $3)" + + ${If} $4 = 234 ; ERROR_MORE_DATA + DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}" /SD IDOK + Goto done + ${EndIf} + + ${If} $4 <> 0 ; NO_ERROR + ${If} $4 <> 2 ; ERROR_FILE_NOT_FOUND + DetailPrint "AddToPath: unexpected error code $4" + Goto done + ${EndIf} + StrCpy $1 "" + ${EndIf} + + ; Check if already in PATH + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" 0 done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" 0 done + + ; Prevent NSIS string overflow + StrLen $2 $0 + StrLen $3 $1 + IntOp $2 $2 + $3 + IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";") + ${If} $2 > ${NSIS_MAX_STRLEN} + DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}." /SD IDOK + Goto done + ${EndIf} + + ; Append dir to PATH + DetailPrint "Add to PATH: $0" + StrCpy $2 $1 1 -1 + ${If} $2 == ";" + StrCpy $1 $1 -1 ; remove trailing ';' + ${EndIf} + ${If} $1 != "" ; no leading ';' + StrCpy $0 "$1;$0" + ${EndIf} + WriteRegExpandStr ${Environ} "PATH" $0 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Removes dir from PATH +; +; Usage: +; Push "dir" +; Call RemoveFromPath + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + ReadRegStr $1 ${Environ} "PATH" + StrCpy $5 $1 1 -1 + ${If} $5 != ";" + StrCpy $1 "$1;" ; ensure trailing ';' + ${EndIf} + Push $1 + Push "$0;" + Call un.StrStr + Pop $2 ; pos of our dir + StrCmp $2 "" done + + DetailPrint "Remove from PATH: $0" + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove + StrCpy $3 "$5$6" + StrCpy $5 $3 1 -1 + ${If} $5 == ";" + StrCpy $3 $3 -1 ; remove trailing ';' + ${EndIf} + WriteRegExpandStr ${Environ} "PATH" $3 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +########################################### +# Utility Functions # +########################################### + +; IsNT +; no input +; output, top of the stack = 1 if NT or 0 if not +; +; Usage: +; Call IsNT +; Pop $R0 +; ($R0 at this point is 1 or 0) + +!macro IsNT un +Function ${un}IsNT + Push $0 + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $0 "" 0 IsNT_yes + ; we are not NT. + Pop $0 + Push 0 + Return + + IsNT_yes: + ; NT!!! + Pop $0 + Push 1 +FunctionEnd +!macroend +!insertmacro IsNT "" +!insertmacro IsNT "un." + +; StrStr +; input, top of stack = string to search for +; top of stack-1 = string to search in +; output, top of stack (replaces with the portion of the string remaining) +; modifies no other variables. +; +; Usage: +; Push "this is a long ass string" +; Push "ass" +; Call StrStr +; Pop $R0 +; ($R0 at this point is "ass string") + +!macro StrStr un +Function ${un}StrStr +Exch $R1 ; st=haystack,old$R1, $R1=needle + Exch ; st=old$R1,haystack + Exch $R2 ; st=old$R1,old$R2, $R2=haystack + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=needle + ; $R2=haystack + ; $R3=len(needle) + ; $R4=cnt + ; $R5=tmp + loop: + StrCpy $R5 $R2 $R3 $R4 + StrCmp $R5 $R1 done + StrCmp $R5 "" done + IntOp $R4 $R4 + 1 + Goto loop +done: + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." diff --git a/packagers/win32_64/nsis/readme.txt b/packagers/win32_64/nsis/readme.txt new file mode 100644 index 0000000..379aa05 --- /dev/null +++ b/packagers/win32_64/nsis/readme.txt @@ -0,0 +1,13 @@ +GPAC: NSIS installer manual +========================== + +Recommended +----------- + +Please execute the "generate_installer.bat" file at the root GPAC directory. The revision and version will be retrieved. + + +Alternative (developers only) +----------------------------- + +Launching the NSIS installer the "bin\win32\release\nsis_install" will generate an unversion installer. The DATE/HOUR will be included in the installer file. Use it in case you're making some experimental developments. diff --git a/run_configure.sh b/run_configure.sh new file mode 100755 index 0000000..391d058 --- /dev/null +++ b/run_configure.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# calls ./configure and prints out build dependency for platform +# +# Shell-script based on code suplied in [1] +# [1] Guide du nouveau responsable Debian. Copyright © 1998-2002 Josip Rodin + +strace -f -o /tmp/log ./configure +echo "" +echo "" +echo "GPAC Build dependency for your platform" + for x in `dpkg -S $(grep open /tmp/log|\ + perl -pe 's!.* open\(\"([^\"]*).*!$1!' |\ + grep "^/"| sort | uniq|\ + grep -v "^\(/tmp\|/dev\|/proc\)" ) 2>/dev/null|\ + cut -f1 -d":"| sort | uniq`; \ + do \ + echo -n "$x (>=" `dpkg -s $x|grep ^Version|cut -f2 -d":"` "), "; \ + done +echo "" +echo "" diff --git a/share/default.cfg b/share/default.cfg new file mode 100644 index 0000000..49a3015 --- /dev/null +++ b/share/default.cfg @@ -0,0 +1,18 @@ +[gpac.alias] +-play=-i @{1} aout vout +-plist=flist:srcs=@{-:N} vout aout +-info=src=@{+1:N} inspect +-mplay=src=@{+1:N} aout vout +-bench=reframer:raw=av @ -o null -stats -i @{1} +-vbench=vout:!vsync -i @{1} -stats +-gui=-noprog compositor:player=gui -xopt +-mp4c=-i @{1} compositor:player=base +[gpac.aliasdoc] +-play=play file. EX: gpac -play source.mp4 +-plist=build seamless playlist of files. EX: gpac -plist s1.mp4 [... sN.mp4] +-info=inspect files. EX: gpac -info s1.mp4 [... sN.mp4] +-mplay=play input files in parallel. EX: gpac -mplay s1.mp4 [... sN.mp4] +-bench=check decoding speed of source. EX: gpac -bench source.mp4 +-vbench=check decoding and display speed of source. EX: gpac -vbench source.mp4 +-gui=launch GUI (see `gpac -gui -h` for GUI options and `gpac -h[x] compositor` for compositor options) +-mp4c=launch player without GUI (same as MP4Client) diff --git a/share/doc/CODING_STYLE b/share/doc/CODING_STYLE new file mode 100644 index 0000000..06b8fed --- /dev/null +++ b/share/doc/CODING_STYLE @@ -0,0 +1,137 @@ +GPAC coding style + +Introduction + coding styles only concern the GPAC core library (libgpac), and should be used for external filters and modules development. + + AStyle is a code beautifier tool. Code should be compliant with the following pattern provided by AStyle : + AStyle -r --indent=tab '*.c' '*.h' '*.cpp' '*.hpp' + +1 Exported symbols + + 1.0 Informative note + The GF_ or gf_ stands for "gpac framework" + + 1.1 typedef of structures - typedef of base types - typedef of functions + +All symbols defined within libgpac library and exported for application development purposes shall begin with GF_* +with the first character of "*" being in capital, regardless of their functionality. +For example, GF_Thread (structure), GF_Err (redefined type), GF_LineCap (enum typedef) + +All structure typedefs, whether defined (declaration exported through ehaders) or not (declaration intern to the GPAC core lib), must follow +this principle. +Furthermore, typedef of non-exported structure shall only apply to the structure itself, not a pointer to it. + typedef struct _my_gpac_thingy *GF_LPMYGPACSTUFF; /*this is FORBIDDEN*/ + typedef struct _my_gpac_thingy GF_MyGPACStuff; /*this is OK*/ +This will allow easy exporting of structure declaration if needed at some point in the development. + + + 1.2 constants + +All constants (whether #defines or enums) shall be in capital letters and begin with "GF_" and use "_" for word separation +For example, error code GF_BAD_PARAM. + +Enums names shall refer to the main module they are used in through a keyword right after the GF_ . +For example, + * enums referring to file format (isomedia) will be prefixed by GF_ISOM_ + * enums referring to MPEG-4 OD tools (odf) will be prefixed by GF_ODF_ + + 1.3 functions + +All exported functions and pointer to functions shall begin with "gf_". The name shall refer to the main module they are sued in +through a keyword right after the gf_ when reasonable (gf_isom_, gf_odf_, gf_filter_), or to the tool they refer to (gf_bs_, gf_url, ...) + +2 Miscalleanous + +All exported functions' names shall be in lower case exclusively. +All exported functions' parameters should be in lower case exclusively. +Exported structures may use case in any fashion as long as they respect the rules expressed in section 1 above. +Exported structures member should preferably all be lower case - this may not be feasible for scene graph nodes & like... + +All exported header files shall be in lower case +All source files within the gpac core shall be in lower case + +All comments should be C-like ones (/**/) not C++ ones (//) +All exported headers documentation should be written with doxygen syntax +All constructor-like functions should be of style gf_zzz_new +All destructor like functions should be of style gf_zzz_del + +3 Current structure of the GPAC repository + +This is a short overview of the gpac source repository. + +- *gpac/applications/* + various apps of GPAC, including MP4Box, MP4Client, gpac and other players for iOS and Android +- *gpac/bin/* + output path of build system +- *gpac/build/* + various build systems (MSVC, Android, XCode, ...) +- *gpac/debian/* + files for debian packaging +- *gpac/extra_lib/* + external lib directory used by different build systems +- *gpac/include/gpac/* + all exported files of the lib (high level APIs, full documentation available). +- *gpac/include/gpac/internal/* + all development files of the lib (low level access). +- *gpac/include/gpac/modules/* + all module APIs defined in GPAC. +- *gpac/packagers/* + installer scripts for Windows and OSX +- *gpac/modules/* + various modules of GPAC (video and audio output, font engine, sensors, ...) +- *gpac/share/doc* + doxygen for GPAC +- *gpac/share/doc/idl + doxygen for JS APIs developped in GPAC +- *gpac/share/doc/man* + man pages for GPAC +- *gpac/share/gui/* + BIFS+SVG based GUI used by client. +- *gpac/share/lang/* + Language files (under development) for GPAC options and help +- *gpac/share/res/* + various resource files used in installers +- *gpac/share/scripts/* + 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/* + interactive composition engine for 2D & 3D drawing - handles MPEG-4, X3D/VRML and SVG. +- *gpac/src/crypto/* + cryptographic tools (AES 128 CBC and CTR only) +- *gpac/src/evg/* + anti-aliased software rasterizer for 2D vector graphics +- *gpac/src/filter_core/* + filter engine of GPAC, in charge of filter graph resolution, filter scheduling, packets handling. +- *gpac/src/filters/* + filters defined in GPAC. This include encoders, decoders, av output, wrapper for GPAC's compositor, ISOBMF, RTP, M2TS muxers and demuxers, etc ... +- *gpac/src/ietf/* + small RTP/RTSP/SDP library, plus media packetizers. +- *gpac/src/isomedia/* + ISOBMFF (Iso Base Media File Format), features file reading/writing/editing, precise interleaving, hint track creation and movie fragments (read/write). Includes 3GPP/3GPP2 , AVC/SVC, HEVC/L-HEVC and JPEG2000 support. +- *gpac/src/jsmods/* + Various JavaScript modules +- *gpac/src/laser/* + MPEG-4 LAsER (Lightweight Application Scene Representation) +- *gpac/src/media_tools/* + media tools for authoring: ISMA & 3GPP tools, AV parsers, media importing and exporting, hinting ... +- *gpac/src/odf/* + MPEG-4 Object Descriptor Framework: encoding/decoding of all descriptors, OD codec and OCI codec +- *gpac/src/quickjs/* + QuickJS javascript engine sources +- *gpac/src/scene_manager/* + memory representation of the scene, importers (BT/XMT/SWF/QT), dumpers and encoding +- *gpac/src/scenegraph/* + scene Graph API (MPEG4/VRML/X3D/SVG) - BIFS/VRML/X3D nodes are generated using gpac/applications/generators/* +- *gpac/src/terminal/* + client application engine. This is a simple wrapper around the filter engine of GPAC. +- *gpac/src/utils/* + all generic objects used throughout the lib (list, bitstream, thread, mutex...). The OS specific files are prefixed os_* . +- *gpac/testsuite/* + tests suite for GPAC - this is a GIT submodulen repository URL is https://github.com/gpac/testsuite. + + diff --git a/share/doc/GPAC UPnP.doc b/share/doc/GPAC UPnP.doc new file mode 100644 index 0000000000000000000000000000000000000000..10ea0726385f1b5571ece395dd7a5fce640df8b0 GIT binary patch literal 81408 zcmeIb2VfM{_V_<3B%v(5gSdniB!pf>suTe!k&e`mY>Fh=kU|kqs!}X~3JM}1pcF+! zL>`EM3W5}oCW2H&P(V;DfJ*+KGi_%!SqSQTzxV#%EPOKe&fGco&OP^>TV{54-)BWm zEqc4`X~pZ)TnSKaX699bEc^Wl&o$zO73F2ZesD80GgEfI2rmKA+|U0=4P0EhRmrop zK%k;rDY{vdsQ48@seVd`qV&37Q3@(U9vSk;nZ0NBst0X<3Ivu^B33KPxFs6Bn%E9! ziDYILGWYok8wuXi0>OHm?a|^|_*PpziJDLpAb}tBo!M=r1y3uXDCKF!#_`GzD9Rhe zUn{AKx$Sm*DJ`A%c3wM05sR76Rz5k|hLdhzdqr8pi4z`Flp(}-ay~`O@-gbc$k#i8 zvESEL+>Z~2-VOW5kVmzFj`XaYK*Z+uI%YPXABEjdo#NPfoW+dGB=VOTL0{URVII`6jgb zV5NXxA;m$cKwy!;JIy6G{eQLw9*At!+L@M-lHyKHcgDI?ojoIyBOMM$J9nyUggZ6H z;gB6>iiBjRdPYK;Gsd0la;C?px-;V9ok{MPj6_#jgtJGyE7j$Ug7gt?=ZL6LX-;>n zGb1e_IZhSr)FnhX93v9aD9T zo$leTR5e9PV$>*tQOPmR5ebQj&gl54&{Wh5s=N2$f2deo8{^&K6R ztX>*)#6v&Mr|&uHZ;_T3P}+M%jrOuC(AbuGpxI#Ps0qM#+MM+a^mRhzTCW zrHpYVyMw#9gS)4=q7!1NS7$=3cCl#Dr6qI(2X~Je?&{gILz}c)oFs=u)15^P-E=Le z^{(#euIV98YMUC3aSj=!wgs)>r(qb0?4t_F%4}*`>p(TxLhFLDI zFO^`sIyG0d-Pjw9v~>N7y=1Bt+dVy%<{SK+s+*S1cz0TQG8O)Oa66hKZF`hcqEZ2( zLAz)#d6BrD;10<&Wmjr&ZADlLPsM;ygH7#k4;PyK5RH1IZ zS#+aG`pcTp#M(;rSG1yu`P(|tF6NFkV#)n)>7$$5jf2qI_spoo#3r?7d9{XAQqaAm z;&)Wm0R9N(&C|5SPrr zptafy#I(^m0FM*Q;#`a_LhCk6CzqO$oGhJRLNe|ynT~>sRvn3q8-^!n9y>PGounGE z!(Ld`4{!`7t z$tliPP3dr$L9*x)*|}AhRu8o8<{a%n<1y-m-OsSV#b_kEZstCxk93+v>*~1AMY={>u33QJR+h%+NK5i!Q`Hu~@lO=Nz*q*Lj7pmF0E>|yDhc@9(Jt*Bxb#&fKDz}X* z(UtDf)7s^TWXJG?)btF-$!ayYlWonXRZNUgAn|?SPNNp24{_$s(D%V>+W(54U5UrhP=z_ zU00fh@=85fnoo@Cr^20CYT7Xt<(Z>phus)-Q^je-$izh(E^CfFI-Qa8+j(t?FNE zK&I=~qBckA=;lht2kISX%t&Wg?V3ZPm`7B%Y1>Puro?V~Bf2x0#`WPG#e@3nJFq{j zN$Mq9>sy?3E^6bIed-lDjS}eNJF3V%Lh3}uoyIzY-Y}g})O5OYM0}LGoH4{D$Muzx z5wcPtQgJ`D^%NIX5`}h>YpSix=zyqLqsV4=maN%_wL9|^JVx*IjFIx~FXRq-Leu4S zh{UbQSgg8(fL3K``cyu~sWo!r5{#CpHCfG=)#-Vvr6u(2)Vctg@XY{cGhBXM!FcWwj7K;95OnQ#wMze^wfI-Ss2lt-MU23$~7xZ z&cFq*G{Zd4I8If>ieDmEt!>_JjG+%=v>}F9zh~)IX#K7c)VtP8FjZojQk}%%8M9dpF@!l6eh8nR*444I@!icfJm z9W9CKOS`x&eZQln43f0np6QNZ0@3=CuQokZyO$C13D}_8yj{as%44QXJIDJo?67OR zyZ1;e+ww{ln3&VD)W&%!u9$yg$+8Uoa#%9mIfuF8oKdmjKv=?(0b=!kXIZn1KyzAH zIq~0Ai#Zze=CGV;jGsLN5>GcF9hjq~r5kI4QEDwtPEqG;|DJl+9BxiW=6O4qqi)SJ z=si;oJ(RW%6q}K(t(|AESS{-oV%pS-I30QtUn?qlrzSld>z!iLte%VKwGOwe>lIn| zvt*p8chTA?MZG6LRm*C&+M`7|;}VABY1J;p+H*^9(T#&=(Du=aTmQ@bR4A-*AVCga}dyNTqJMKvsD-N1Jy+4&fm^qEpN@tlr za}xt&W|&k(}N}Ut1g%#!->mfq|U##1i%lGC9~?TVl`;FM9B9X2H$jFv{gSeYG^BX&5J}?LNTG8+VG*4>XgR6_>}CvaHAQR9DZ3+o$!k*s>JrPH`)!}i%3ik7 zo~C6q2(Nacw*_%*`bun9=C)yG>y$vaQf~)T&=mPb{_83BYHU<##~+NGex`NCX7B*OQqhK(}L2J zWYNX+lG;s6>q3wxp*oB+XFQZ6)NSh8&$(6qPk?+meJ_8`Yy0 zO1q+PT9Wswggi>6twmUK%C?fT&qh_io#bOT8oh?HT8&;svUV%$gIUeSNRY)k5>gr5 zc^>Dvb<5ZD_ucfp4Wh@W6l=Gxn~B_tN>)4kym>7n-R`X;+k{nL=((lVU7qLar9aNq zNp7w3R85$%N?|%`+IY4J%}J1PhPj|}N=;=UMm;l44BuF~)y`;@8kdoTdok-xuY9eH zgQWh=+b|YkD;k6Nah__0j?y@+V7bQprlbSdXfB z+giQsVog?mhL9c)+w9X>T^;9n3r=J0+@>q@GP7QRq?I01d!SFI9MX=|Zj|N5H2Xt? zcO=@ZKwAsZ^Rs4TpSakwBu6GLwu>>^lU=N_N@^WGn*F@jbvRukxlZ-&iJG_OLba@X zwW#VI4Oj-KT6_5`^32+?H||^IVy5(&-yN8QL(+QREzmE=?18j#mFP+usF}a4di;Cr zNm_?=KGxJO2Q%Aq0(VJuZ<-dJqCU8w-snq@N==uQ9j)QWay1eA zjrwjG$yyg@eQcs@vSyO3R?z$9cyOkTQ?hrvjMS`7*GQ`S+${tkjb4g@^vM~8cR>cO+R^Dn+}A>`kB2j ziBTWHPhuMtr>@mn8XwuXQ+(ticO!@2Iu9mqKC-YjUbRI#@`|rj?;WY9a9^0UF0ab( zLA8h^kbL@Gy!DFJ#&(Yt!n(_ACh6 zI&e9g6(98}M9&ak>fc&ukHtwl)U8i*z11z(B3EEO(yyn|XT|EWxHdXh9kbq!JwtQV zGaJo8b7fU7n-u58B?S{v8hm--y54Ds+gbWHj@z|-~R zRDuIHp*>hHwxK;8i$&0{*_ESO3Ch&#MS38;chhRuQ+=8;j0ufR5&9uR5>zltg*1 zUri`|qMn1O$==6xFz$z|>j}D2sWiPty5It+40RT5^-sDyIi#m#;A)JqjV^<6w46_c z8i~5Ipwi8ik~k_%8?BQ<#_DQ)TPs{nbLfQ;4e^+SHe@4_cHdlm96`VIAugUUj>9J0 zXwAG+;HxyA1!;?7v~dr`Q6~w;;9Qe$3eC3klt>ndMA4~oWnzlzQxh_9(bfW^JTnvZ z!3h0pE<7x&J}Rx<;nz+!2RgWJd`7a%#TQ|CAPMoQa^G89ztSo%Egm~kpK*@Dzmlie zx>J8Zg{Gw`CM}$2V7P&=mQYQ>llhu-DYKcOc8^KisqA*gaKm5y=94vvT46Mywj-mG zQn(SqDz5f;fH)W_Oh!s_ir%S_g}7#Yu|jnX5$@zR>N*!U|D4TC#}}sge0mn`!AHkv z2f|zo9;%5uGRAY&oHd+bS`udqXEh!tam7?~HgQ(de0NN>>a-+TN7fTnXI}0!1X)+h z)bGqVMV#LKN^2ul%~?}@NCAJa#4UbS zEyEI;>bct!tA&=-dd*Q!dOGX6imgQ2wdrDr&s=G984Za<`s zk)$HT)a{34k62>Pb5=Wi#szjc=KNL_lu)Bt9jTMCJf7ijj&+RH1}(fJ6sEZN7( zV>a5cP)g-+Na8^e7oRCb9nJO_QP?ai=z~)3lZrXv#xs#A_2*15In$Qfuy}6O*szO|@|Irc&q4={8c0 zp<&Zn>aM1SuhNp&@{X$Wrshp+S!L=4ReP}>a%+atT9=CS+~o3mMWq@U$&ghV2BnZM zLp7~s?x6Vg(`#juLoKPl=GCZIy&YSJwqc>&Gg4fsJ;>2$@S>b-wWrG#_0eKHpWCF4 zs9jqWZt7HRcb>D&Vwqzgi(}F%PaMfuGaa?3O=}#E|Lk{Ib94I}HNYDQNEzYit!t{Oe}=F&Hp&i!iESChXwH|tTittyI_h;M)I zM@q@~@dZaKN$vB7q=91Y-PkhJv$u4K_`2O^lD1XnFO=FHi+GL{EEzv9sF5em`I4rj zn%=B6GwiM`>E{YBTqXbf34@+;suOcC2zR~tqk(ldX6oAw>4FRknycM zI+Z9ePpB^Mc`@`~ny~0U6dr}qAbPdxPV^=^8v|p((9=o6m*F=Mo&9Fj!%sdVmwX=a zea>0yIkt%JbBZmPr{p+H<(I7N*CAG3CK4B;Dn8JUkTge> zldr0hzg-@Q)6k4~fGwqVj(IlkY6z(87ggC$msY~CuYBmN7?gy1P#^kQ%Nd!oi2m{t zEwSw!>-oOI_Uje%wC_=`W~rhKjS&e{O(~;x{AagQ@*FNr?I_t1{CqFJki-3zz~?hB zInEXJZ|S;`S~El<{c^{6$kPtX$T$NtHet|+JciXE1BxMWHha=I=4Yd~1F z5)J)I{TiSsrR7iA!-eH!E9J88DxY=NU0HY4KOCg^`zt_;I z=u>p~Ac)?4+rG`hZIR~E%To?*j)Y7f!byX zcRc>jBf2)m$JNt{{M;jUmlqo&4lMb^gjnag#B;;ECVF4_Jl@P)!eoI3cs0Po#v+z93hu7deSPvUuBfJkEKrLpyZJ+~mgig>Ixm-D|YKL5*H zukGqzW_k}wnXPGaC;6++*4QKSE>;Eg*_UTVm}REMs-SH~n9nkgH0Oab>oX)v*pkMa z=^1IV&MmW_Ga6;fnqH0=dmD@d>2me|_w_HjHhlPNgvEyk z^?yEB|Nlt;FS<7R?7Im|pS|>dK>ss>a_v9sV*j0w|3%j_CR+nC7TXLFjHUkD?Y}Yi zWODug-}nEbYh&CkW9^4v3G9Nu(gti}KwBgyg8+MPF9TKk9$69A`u|h8`u`{Ve~{?e zm{-YM>PLuR?)0~HpA&UwNn_Xct@J;I+ugbL{}0$dx|aEJOXv*U;Z^u=*MDuTspS0r zfA0Ekw(GyLP9nM&y*~yoz+zYeYv3L5E6N-Z3PEA$3|$}+(jfz$fR|t{yaAhF9~^?? z@HKo3=inwN{94Eim`gpK2}Mw7RVKWi(-QDY56?(o}>c=j9}`G+RSJloh~p3NP1?QHpL z?q%J_J1%EhkIT($GmaFAYfqn)?h}!M|7| zSFDi>e>d{aO8fRN6vuYXUy03*(b-*K)#otcvqAK_1Vp!MLG=3poPyJE3FH^`{)9{j<|pi`KwYQ@^`QYY zgD%h)9)^CRRgcG@Jn^KlxV?Dnk{h z1+}3qxFH25!b*4pwm>O<39vNW12)_LH%5N3nuN^}*pB$NFPpXcPG)_~Qq#YRTWCCGnE0e%zFPjNVo7oD-;I|+6cmCwzeTPqe zdh+8>PrkQe{tHh}czVAp&YaZDuIpz`CO1I*lc(iFsw(aG7iEC+X+Q+xc5!*iR_GjwOD3_;yGRkx3)=)=$v!P?F zenq#2PDP(5(Ua&+bYtkCKjF#n1T2NdWqEEJ2EtsJ2S?yntKa(3XT*21i`H|#wnqMn zG}}48GyQ*(?1c5=ZW~s8yE@9V^##(Dv=N{uBuyln9;39_P9Jy}`hn;)8WLbAWPs>* z35brD!#+3w)zNKDXb6qK(0@n5kuU(HEervpJ-kZzHTW0~gS3yYAhI0xSf1}0KuNe4 z+Co?84+9_p9)YPa4c3A56JHG zk~x!aQKi_MRHbRt*rfOcn@wWD+Biy5+K!Mq%RVGZwVM*LU~QymwP0v_$xjFC{wWR)~zDt4BxzzJOkor9U2jLKux{G!KW8o!GD&XTG1R6jzjDqd( z37myuck^8kh=RxA-)L(#4`GYg%-H5TY{~w1#3tHz#CJY_HNR%w%)z5U@WBvm$fnn#R0%@UfDvbS%W zhw|->D6e-)iD&gu_ei~tg~vc_bTe#$?U3KecpvVDd!Pn{Ltl6p2E$T#9k#)KH~4A!)8(IH8SBZ&+N^Er$rfkyd{59)wSCCwN*isP zV{y};ntQAxb-thL(YCZ0n+9*GanE^X$+Byy*|&|Xt<~IPv@Tiu_T*u0{WT4p7eHsC zYeT=U5f;6QE=7-`KSO7tm(HQA+dx+s0@3g-oP%HBS14GCIU(5mzAa*N&bEjxnJr>V zW{cR8`HuK@!M=~!@;RqiKDHyiBWEn9D95c4+c^iVM{Hr6haXxM7pwa9UyCttQs@0z zNWLBHIVug+(i7NZNP|#Hi!Ggp>rermH3UPqX9;`jbTax}0I$MgI1OjuB3y!>;41tM zB`V{?p&ZnM7SI-$t9^6r%e~t_Shw=km!5m+iP7s;rX~#Bo0_nJ!GFwQcGJGOV-Xb?ZG@#s>;j(E78))UaOVJyh`%0OUt^)I8S;X zQAbJjuPL7^lD*crEq`yHVV2V7NzA>XKvNdiD7VWkD+gven};J0YrMSJXA|fReL(ap zdffsiz+1nfThXgkr^AS+!YgnQEZ0Fb_zD0lNIo%ctlzUs!oa{3+Y3Q2usv(>8N<+=o1K3ijGF9o)gzVC# z=_w_}9u57?CoDRA6%0KFqsI`)2cpvfFc4xP4o1SGFdD|dICui4z%1Ae2cd0M#*Xj+ z^n#z@D*OpIA-Ed(Kuw5%M$j0NAQ{pi9cIETm=Bxa1K0(h!JlvwUI}9j8h*L-&9MVJ zwrqHN!`lZod~;0x5k0Wun;pxyEIPJj(d=)I?bu>)Z2A`dPv5afG-KM1RI_Pg=3Z|r z_Kw@^)$~55zp#1}>@oX1Y%)kCXPYH4Wy-mBuO#QGdpJj$jd_k)dh6bXR-KCew!tT` z0}OrjL_ealp^ykqz(QCD`{6Qp>(i=BLytB?x0#GBa^7kiwj()=*jzvk&#`6tS0cXi z$zgG9nfjJyOJ>%TX9N&py({4H5?SqE^$leW`?{5FiA`mKUl(%NDfP&7tN#n zKpFpew62CiZqXvjik}o6z6aajCIoruZ2<9sFae%~DXu0y_Xp8kPya2Hg7iVzCd zE`NLZ%gdi{|Ms%}{~93!2A%6Z_~FRf5Y?z5PCIJzzE zJ+Gr#_sChQ?=^j`>@hts9@#r?^YZ3dw!NHX>tD=Ks^R-h`DU+R_5tP|BdyF!C2X8i zU+mJ*t?2bt5Zzk!YSpP#pQ6iIVCYkHY1N|(J≫r>#NT%p-?K{%4Y1^yx4Dio>ya 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 literal 0 HcmV?d00001 diff --git a/share/doc/ISO 639-2 codes.txt b/share/doc/ISO 639-2 codes.txt new file mode 100644 index 0000000..7296581 --- /dev/null +++ b/share/doc/ISO 639-2 codes.txt @@ -0,0 +1,487 @@ +Codes used in GPAC are according to ISO/IEC 639-2/T + + "Abkhazian","abk","ab", + "Achinese","ace","", + "Acoli","ach","", + "Adangme","ada","", + "Adyghe; Adygei","ady","", + "Afar","aar","aa", + "Afrihili","afh","", + "Afrikaans","afr","af", + "Afro-Asiatic languages","afa","", + "Ainu","ain","", + "Akan","aka","ak", + "Akkadian","akk","", + "Albanian","sqi","sq", + "Aleut","ale","", + "Algonquian languages","alg","", + "Altaic languages","tut","", + "Amharic","amh","am", + "Angika","anp","", + "Apache languages","apa","", + "Arabic","ara","ar", + "Aragonese","arg","an", + "Arapaho","arp","", + "Arawak","arw","", + "Armenian","hye","hy", + "Aromanian; Arumanian; Macedo-Romanian","rup","", + "Artificial languages","art","", + "Assamese","asm","as", + "Asturian; Bable; Leonese; Asturleonese","ast","", + "Athapascan languages","ath","", + "Australian languages","aus","", + "Austronesian languages","map","", + "Avaric","ava","av", + "Avestan","ave","ae", + "Awadhi","awa","", + "Aymara","aym","ay", + "Azerbaijani","aze","az", + "Balinese","ban","", + "Baltic languages","bat","", + "Baluchi","bal","", + "Bambara","bam","bm", + "Bamileke languages","bai","", + "Banda languages","bad","", + "Bantu languages","bnt","", + "Basa","bas","", + "Bashkir","bak","ba", + "Basque","eus","eu", + "Batak languages","btk","", + "Beja; Bedawiyet","bej","", + "Belarusian","bel","be", + "Bemba","bem","", + "Bengali","ben","bn", + "Berber languages","ber","", + "Bhojpuri","bho","", + "Bihari languages","bih","bh", + "Bikol","bik","", + "Bini; Edo","bin","", + "Bislama","bis","bi", + "Blin; Bilin","byn","", + "Blissymbols; Blissymbolics; Bliss","zbl","", + "BokmÃ¥l, Norwegian; Norwegian BokmÃ¥l","nob","nb", + "Bosnian","bos","bs", + "Braj","bra","", + "Breton","bre","br", + "Buginese","bug","", + "Bulgarian","bul","bg", + "Buriat","bua","", + "Burmese","mya","my", + "Caddo","cad","", + "Catalan; Valencian","cat","ca", + "Caucasian languages","cau","", + "Cebuano","ceb","", + "Celtic languages","cel","", + "Central American Indian languages","cai","", + "Central Khmer","khm","km", + "Chagatai","chg","", + "Chamic languages","cmc","", + "Chamorro","cha","ch", + "Chechen","che","ce", + "Cherokee","chr","", + "Cheyenne","chy","", + "Chibcha","chb","", + "Chichewa; Chewa; Nyanja","nya","ny", + "Chinese","zho","zh", + "Chinook jargon","chn","", + "Chipewyan; Dene Suline","chp","", + "Choctaw","cho","", + "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic","chu","cu", + "Chuukese","chk","", + "Chuvash","chv","cv", + "Classical Newari; Old Newari; Classical Nepal Bhasa","nwc","", + "Classical Syriac","syc","", + "Coptic","cop","", + "Cornish","cor","kw", + "Corsican","cos","co", + "Cree","cre","cr", + "Creek","mus","", + "Creoles and pidgins","crp","", + "Creoles and pidgins, English based","cpe","", + "Creoles and pidgins, French-based","cpf","", + "Creoles and pidgins, Portuguese-based","cpp","", + "Crimean Tatar; Crimean Turkish","crh","", + "Croatian","hrv","hr", + "Cushitic languages","cus","", + "Czech","ces","cs", + "Dakota","dak","", + "Danish","dan","da", + "Dargwa","dar","", + "Delaware","del","", + "Dinka","din","", + "Divehi; Dhivehi; Maldivian","div","dv", + "Dogri","doi","", + "Dogrib","dgr","", + "Dravidian languages","dra","", + "Duala","dua","", + "Dutch, Middle (ca.1050-1350)","dum","", + "Dutch; Flemish","nld","nl", + "Dyula","dyu","", + "Dzongkha","dzo","dz", + "Eastern Frisian","frs","", + "Efik","efi","", + "Egyptian (Ancient)","egy","", + "Ekajuk","eka","", + "Elamite","elx","", + "English","eng","en", + "English, Middle (1100-1500)","enm","", + "English, Old (ca.450-1100)","ang","", + "Erzya","myv","", + "Esperanto","epo","eo", + "Estonian","est","et", + "Ewe","ewe","ee", + "Ewondo","ewo","", + "Fang","fan","", + "Fanti","fat","", + "Faroese","fao","fo", + "Fijian","fij","fj", + "Filipino; Pilipino","fil","", + "Finnish","fin","fi", + "Finno-Ugrian languages","fiu","", + "Fon","fon","", + "French","fra","fr", + "French, Middle (ca.1400-1600)","frm","", + "French, Old (842-ca.1400)","fro","", + "Friulian","fur","", + "Fulah","ful","ff", + "Ga","gaa","", + "Gaelic; Scottish Gaelic","gla","gd", + "Galibi Carib","car","", + "Galician","glg","gl", + "Ganda","lug","lg", + "Gayo","gay","", + "Gbaya","gba","", + "Geez","gez","", + "Georgian","kat","ka", + "German","deu","de", + "German, Middle High (ca.1050-1500)","gmh","", + "German, Old High (ca.750-1050)","goh","", + "Germanic languages","gem","", + "Gilbertese","gil","", + "Gondi","gon","", + "Gorontalo","gor","", + "Gothic","got","", + "Grebo","grb","", + "Greek, Ancient (to 1453)","grc","", + "Greek, Modern (1453-)","ell","el", + "Guarani","grn","gn", + "Gujarati","guj","gu", + "Gwich'in","gwi","", + "Haida","hai","", + "Haitian; Haitian Creole","hat","ht", + "Hausa","hau","ha", + "Hawaiian","haw","", + "Hebrew","heb","he", + "Herero","her","hz", + "Hiligaynon","hil","", + "Himachali languages; Western Pahari languages","him","", + "Hindi","hin","hi", + "Hiri Motu","hmo","ho", + "Hittite","hit","", + "Hmong; Mong","hmn","", + "Hungarian","hun","hu", + "Hupa","hup","", + "Iban","iba","", + "Icelandic","isl","is", + "Ido","ido","io", + "Igbo","ibo","ig", + "Ijo languages","ijo","", + "Iloko","ilo","", + "Inari Sami","smn","", + "Indic languages","inc","", + "Indo-European languages","ine","", + "Indonesian","ind","id", + "Ingush","inh","", + "Interlingua (International Auxiliary Language Association)","ina","ia", + "Interlingue; Occidental","ile","ie", + "Inuktitut","iku","iu", + "Inupiaq","ipk","ik", + "Iranian languages","ira","", + "Irish","gle","ga", + "Irish, Middle (900-1200)","mga","", + "Irish, Old (to 900)","sga","", + "Iroquoian languages","iro","", + "Italian","ita","it", + "Japanese","jpn","ja", + "Javanese","jav","jv", + "Judeo-Arabic","jrb","", + "Judeo-Persian","jpr","", + "Kabardian","kbd","", + "Kabyle","kab","", + "Kachin; Jingpho","kac","", + "Kalaallisut; Greenlandic","kal","kl", + "Kalmyk; Oirat","xal","", + "Kamba","kam","", + "Kannada","kan","kn", + "Kanuri","kau","kr", + "Kara-Kalpak","kaa","", + "Karachay-Balkar","krc","", + "Karelian","krl","", + "Karen languages","kar","", + "Kashmiri","kas","ks", + "Kashubian","csb","", + "Kawi","kaw","", + "Kazakh","kaz","kk", + "Khasi","kha","", + "Khoisan languages","khi","", + "Khotanese; Sakan","kho","", + "Kikuyu; Gikuyu","kik","ki", + "Kimbundu","kmb","", + "Kinyarwanda","kin","rw", + "Kirghiz; Kyrgyz","kir","ky", + "Klingon; tlhIngan-Hol","tlh","", + "Komi","kom","kv", + "Kongo","kon","kg", + "Konkani","kok","", + "Korean","kor","ko", + "Kosraean","kos","", + "Kpelle","kpe","", + "Kru languages","kro","", + "Kuanyama; Kwanyama","kua","kj", + "Kumyk","kum","", + "Kurdish","kur","ku", + "Kurukh","kru","", + "Kutenai","kut","", + "Ladino","lad","", + "Lahnda","lah","", + "Lamba","lam","", + "Land Dayak languages","day","", + "Lao","lao","lo", + "Latin","lat","la", + "Latvian","lav","lv", + "Lezghian","lez","", + "Limburgan; Limburger; Limburgish","lim","li", + "Lingala","lin","ln", + "Lithuanian","lit","lt", + "Lojban","jbo","", + "Low German; Low Saxon; German, Low; Saxon, Low","nds","", + "Lower Sorbian","dsb","", + "Lozi","loz","", + "Luba-Katanga","lub","lu", + "Luba-Lulua","lua","", + "Luiseno","lui","", + "Lule Sami","smj","", + "Lunda","lun","", + "Luo (Kenya and Tanzania)","luo","", + "Lushai","lus","", + "Luxembourgish; Letzeburgesch","ltz","lb", + "Macedonian","mkd","mk", + "Madurese","mad","", + "Magahi","mag","", + "Maithili","mai","", + "Makasar","mak","", + "Malagasy","mlg","mg", + "Malay","msa","ms", + "Malayalam","mal","ml", + "Maltese","mlt","mt", + "Manchu","mnc","", + "Mandar","mdr","", + "Mandingo","man","", + "Manipuri","mni","", + "Manobo languages","mno","", + "Manx","glv","gv", + "Maori","mri","mi", + "Mapudungun; Mapuche","arn","", + "Marathi","mar","mr", + "Mari","chm","", + "Marshallese","mah","mh", + "Marwari","mwr","", + "Masai","mas","", + "Mayan languages","myn","", + "Mende","men","", + "Mi'kmaq; Micmac","mic","", + "Minangkabau","min","", + "Mirandese","mwl","", + "Mohawk","moh","", + "Moksha","mdf","", + "Mon-Khmer languages","mkh","", + "Mongo","lol","", + "Mongolian","mon","mn", + "Mossi","mos","", + "Multiple languages","mul","", + "Munda languages","mun","", + "N'Ko","nqo","", + "Nahuatl languages","nah","", + "Nauru","nau","na", + "Navajo; Navaho","nav","nv", + "Ndebele, North; North Ndebele","nde","nd", + "Ndebele, South; South Ndebele","nbl","nr", + "Ndonga","ndo","ng", + "Neapolitan","nap","", + "Nepal Bhasa; Newari","new","", + "Nepali","nep","ne", + "Nias","nia","", + "Niger-Kordofanian languages","nic","", + "Nilo-Saharan languages","ssa","", + "Niuean","niu","", + "No linguistic content; Not applicable","zxx","", + "Nogai","nog","", + "Norse, Old","non","", + "North American Indian languages","nai","", + "Northern Frisian","frr","", + "Northern Sami","sme","se", + "Norwegian","nor","no", + "Norwegian Nynorsk; Nynorsk, Norwegian","nno","nn", + "Nubian languages","nub","", + "Nyamwezi","nym","", + "Nyankole","nyn","", + "Nyoro","nyo","", + "Nzima","nzi","", + "Occitan (post 1500)","oci","oc", + "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)","arc","", + "Ojibwa","oji","oj", + "Oriya","ori","or", + "Oromo","orm","om", + "Osage","osa","", + "Ossetian; Ossetic","oss","os", + "Otomian languages","oto","", + "Pahlavi","pal","", + "Palauan","pau","", + "Pali","pli","pi", + "Pampanga; Kapampangan","pam","", + "Pangasinan","pag","", + "Panjabi; Punjabi","pan","pa", + "Papiamento","pap","", + "Papuan languages","paa","", + "Pedi; Sepedi; Northern Sotho","nso","", + "Persian","fas","fa", + "Persian, Old (ca.600-400 B.C.)","peo","", + "Philippine languages","phi","", + "Phoenician","phn","", + "Pohnpeian","pon","", + "Polish","pol","pl", + "Portuguese","por","pt", + "Prakrit languages","pra","", + "Provençal, Old (to 1500);Occitan, Old (to 1500)","pro","", + "Pushto; Pashto","pus","ps", + "Quechua","que","qu", + "Rajasthani","raj","", + "Rapanui","rap","", + "Rarotongan; Cook Islands Maori","rar","", + "Reserved for local use","qaa-qtz","", + "Romance languages","roa","", + "Romanian; Moldavian; Moldovan","ron","ro", + "Romansh","roh","rm", + "Romany","rom","", + "Rundi","run","rn", + "Russian","rus","ru", + "Salishan languages","sal","", + "Samaritan Aramaic","sam","", + "Sami languages","smi","", + "Samoan","smo","sm", + "Sandawe","sad","", + "Sango","sag","sg", + "Sanskrit","san","sa", + "Santali","sat","", + "Sardinian","srd","sc", + "Sasak","sas","", + "Scots","sco","", + "Selkup","sel","", + "Semitic languages","sem","", + "Serbian","srp","sr", + "Serer","srr","", + "Shan","shn","", + "Shona","sna","sn", + "Sichuan Yi; Nuosu","iii","ii", + "Sicilian","scn","", + "Sidamo","sid","", + "Sign Languages","sgn","", + "Siksika","bla","", + "Sindhi","snd","sd", + "Sinhala; Sinhalese","sin","si", + "Sino-Tibetan languages","sit","", + "Siouan languages","sio","", + "Skolt Sami","sms","", + "Slave (Athapascan)","den","", + "Slavic languages","sla","", + "Slovak","slk","sk", + "Slovenian","slv","sl", + "Sogdian","sog","", + "Somali","som","so", + "Songhai languages","son","", + "Soninke","snk","", + "Sorbian languages","wen","", + "Sotho, Southern","sot","st", + "South American Indian languages","sai","", + "Southern Altai","alt","", + "Southern Sami","sma","", + "Spanish; Castilian","spa","es", + "Sranan Tongo","srn","", + "Sukuma","suk","", + "Sumerian","sux","", + "Sundanese","sun","su", + "Susu","sus","", + "Swahili","swa","sw", + "Swati","ssw","ss", + "Swedish","swe","sv", + "Swiss German; Alemannic; Alsatian","gsw","", + "Syriac","syr","", + "Tagalog","tgl","tl", + "Tahitian","tah","ty", + "Tai languages","tai","", + "Tajik","tgk","tg", + "Tamashek","tmh","", + "Tamil","tam","ta", + "Tatar","tat","tt", + "Telugu","tel","te", + "Tereno","ter","", + "Tetum","tet","", + "Thai","tha","th", + "Tibetan","bod","bo", + "Tigre","tig","", + "Tigrinya","tir","ti", + "Timne","tem","", + "Tiv","tiv","", + "Tlingit","tli","", + "Tok Pisin","tpi","", + "Tokelau","tkl","", + "Tonga (Nyasa)","tog","", + "Tonga (Tonga Islands)","ton","to", + "Tsimshian","tsi","", + "Tsonga","tso","ts", + "Tswana","tsn","tn", + "Tumbuka","tum","", + "Tupi languages","tup","", + "Turkish","tur","tr", + "Turkish, Ottoman (1500-1928)","ota","", + "Turkmen","tuk","tk", + "Tuvalu","tvl","", + "Tuvinian","tyv","", + "Twi","twi","tw", + "Udmurt","udm","", + "Ugaritic","uga","", + "Uighur; Uyghur","uig","ug", + "Ukrainian","ukr","uk", + "Umbundu","umb","", + "Uncoded languages","mis","", + "Undetermined","und","", + "Upper Sorbian","hsb","", + "Urdu","urd","ur", + "Uzbek","uzb","uz", + "Vai","vai","", + "Venda","ven","ve", + "Vietnamese","vie","vi", + "Volapük","vol","vo", + "Votic","vot","", + "Wakashan languages","wak","", + "Walloon","wln","wa", + "Waray","war","", + "Washo","was","", + "Welsh","cym","cy", + "Western Frisian","fry","fy", + "Wolaitta; Wolaytta","wal","", + "Wolof","wol","wo", + "Xhosa","xho","xh", + "Yakut","sah","", + "Yao","yao","", + "Yapese","yap","", + "Yiddish","yid","yi", + "Yoruba","yor","yo", + "Yupik languages","ypk","", + "Zande languages","znd","", + "Zapotec","zap","", + "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki","zza","", + "Zenaga","zen","", + "Zhuang; Chuang","zha","za", + "Zulu","zul","zu", + "Zuni","zun","", diff --git a/share/doc/SceneGenerators b/share/doc/SceneGenerators new file mode 100644 index 0000000..5229e70 --- /dev/null +++ b/share/doc/SceneGenerators @@ -0,0 +1,148 @@ + GPAC Scene Graph generator documentation - v0.4.0 + + Last Modified: July 2005 + + + +0 - Foreword + + + +The sets (MPEG4, X3D and SVG) of nodes handled by the GPAC are genrated through "Scene Generators" applications: + +applications/generators/MPEG4 for MPEG4 along with the official MPEG-4 template files needed to generate nodes and their encoding tables. + +applications/generators/X3D for X3D along with the X3D template file needed to generate nodes. + +applications/generators/SVG for SVG along with the SVG template. + + + +MPEG-4 Template files are numbered by versions of amendments to the MPEG-4 systems standard. + +X3D and SVG templates do not have versionning. + + + +1 - Regenerating the scene graph + + + +First recompile the appropriated scene generator application. You don't need to recompile GPAC to recompile it. + +The generators DIRECTLY overwrites source code files in the GPAC distribution, you MUST NOT try to run it from a different + +location than gpac/applications/generators/* + + + +For MPEG-4 you must provide MPEG4Gen with the set of template files. For example, if you're planning to use only nodes defined in the + +first version of the standard (1998) described in templates1.txt file, just type: + + + +MPEG4Gen templates1.txt + + + +Template files MUST be fed in order, and versions cannot be skipped: you SHALL NOT try to generate version1 and version3 without version2. + +To generate a scene graph handling v1 to v3 of the BIFS system, type: + +MPEG4Gen templates1.txt templates2.txt templates3.txt + + + +For X3D, simply run X3DGen, it will automatically load the "templates_X3D.txt" file + +For SVG, simply run SVGGen completesvgt12rng.xml + + + + + +2 - Customizing the scene graph + + + +As of 0.2.2, all nodes in current gpac version are supported by renderers and cannot be removed. You will therefore have to REMOVE some code in the renderers + +and the scene graph in order to recompile GPAC. You should therefore not try to customize the scene graph unless you know what you're doing. + + + + 2.1 - Customizing the MPEG-4 scene graph + + + +As said above, it is not possible to skip a BIFS version when regenerating the scene graph since this will break binary encoding of the nodes. + +However MPEG4Gen allows you to specify which nodes should be supported or not in the scene graph. This is currently specified + +with a simple text file where unwanted nodes are listed one by line ('#' acting as a line comment). The file is specified by + +the "-p " switch + + + +For example, generating a scene graph for BIFS V1 without support for the BIFS audio nodes will be: + +MPEG4Gen -p skip_audio.txt templates1.txt + + + +and the content of skip_audio.txt file will be + + + +AudioBuffer + +AudioClip + +AudioDelay + +AudioFX + +AudioMix + +AudioSwitch + +AudioSource + + + + 2.2 - Customizing the X3D scene graph + + + +X3DGen uses the same mechanism as MPEG4Gen for node skinpping, eg: X3DGen skipfile + + + + 2.3 - Customizing the SVG scene graph + + + +This is undocumented and probably not supported + + + +3 - Advanced Manupulations + + + + It is possible to develop custom templates. The resulting encoding/decoding will not be compliant with MPEG-4 BIFS but it can + +be interesting to see how a single-version mechanism with only the desired nodes reduces applications and bitstreams sizes + +To write your own templates, you must: + + follow the syntax of regular templates + + make sure the SFWorldNode type is defined and used by all nodes (needed for BIFS updates) + + make sure at least one node will be of type SFTopNode (needed for BIFS Scene Replace command) + + + diff --git a/share/doc/configuration.html b/share/doc/configuration.html new file mode 100644 index 0000000..8251200 --- /dev/null +++ b/share/doc/configuration.html @@ -0,0 +1,103 @@ + + + + + + GPAC Configuration documentation + + +

+
+GPAC Configuration file documentation +
GPAC Version 2.0
+

+ +

+ +
Overview +

Applications in the GPAC framework use a configuration file common to libgpac and modules (video and audio output, compositor modules). This configuration file is called GPAC.cfg. It is located: +

    +
  • on Windows platforms, in C:\\Users\\FOO\\AppData\\Roaming\\GPAC, or in C:\\Program Files\\GPAC.
  • +
  • on iOS platforms, in a .gpac folder in the app storage directory.
  • +
  • on Android platforms, in /sdcard/osmo/ or, if not found there, created in /data/data/com.gpac.Osmo4.
  • +
  • on other platforms, in a .gpac folder in the user home directory (for ex, /home/foo/.gpac/ or /Users/foo/.gpac/).
  • +
+GPAC can use different configuration profiles other than the default, check GPAC wiki. +
+The config file is structured in sections, each made of one or more keys. A section is declared as [SectionName], and key is declared as keyName=value. The key value is not interpreted and always handled as ASCII text. + +

This document describes player-specific options defined in section General and old DirectFB options described in section DirectFB. +
+Check GPAC wiki for the documentation of libgpac core options and filter options configuration. +

+
+ + +Note on module names +
Module names as given in the config file are names exported by each interface and not name of the physical library file (.dll, .so, ...). The physical file name can however be used to identify a module - it will then be replaced by the module name. + + +

+ + +Section "General" Back to top +

+The General section of the config file holds player-specific options. +

+StartupFile [value: filename] +

+Specifies file to load upon startup of most clients (Osmo4/MP4Client). If not specified, no file is loaded. +

+ +LogFile [value: filename, Android only] +

+Specifies where to output GPAC's log. By default, the logs are written to stdout. Note that GPAC may be compiled without log support. This is not used by MP4Client. +

+Logs [value: tool[:tool]@level:tool[:tool]@level, Android only] +

+Specifies log level for each tool. For more information on available tools and levels, check GPAC wiki. +

+ + +NoMIMETypeFetch [value: "yes" "no"] +

+Specifies whether mime type should be fetched when checking a link to a new file (currently deprecated). +

+LastWorkingDir [value: string] +

+Indicates the last directory from which a file was opened. +

+iOSDocumentsDir [value: string, iOS only] +

+Path to the iOS document directory for the client (automatically set upon first launch of the player). +

+ +

+ + +Section "DirectFB" Back to top +

+The "DirectFB" section of the config file holds the configuration options for the DirectFB output module. You may also want to check the official documentation. +

+DisableAcceleration [value: "yes" "no"] +

+Forces to disable hardware acceleration.

+DisableDisplay [value: "yes" "no"] +

+Specifies the DisableDisplay parameter value.

+FlipSyncMode [value: "waitsync" "wait" "sync" "swap"] +

+Specifies the flip sync mode.

+DisableBlit [value: "yes" "no"] +

+Forces to disable hardware blitting.

+WindowMode [value: "X11" "SDL"] +

+Specifies the underlying windowing library.

+ + +

+ + + + diff --git a/share/doc/doxyfile b/share/doc/doxyfile new file mode 100644 index 0000000..9b66b4e --- /dev/null +++ b/share/doc/doxyfile @@ -0,0 +1,2433 @@ +# Doxyfile 1.8.10 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = libgpac + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# 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 = "Documentation of the core library of GPAC" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = ../res/gpac.png + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +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 +# 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. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# 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 +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# 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. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# 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. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = NO + +# 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 +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = NO + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# 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. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# 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 +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# 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, +# C#, C, C++, D, PHP, Objective-C, Python, 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), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# 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 +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# 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. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# 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. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = NO + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# 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. +# +# 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 +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +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. +# The default value is: YES. + +WARN_IF_DOC_ERROR = 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. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# 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) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# 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). + +WARN_LOGFILE = doxygen_warnings.txt + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +#for some weird reason on OSX enumeration does not work properly, add IDL files by hand +INPUT=../../include/gpac/ ../../README.md ../../share/doc/idl/core.idl ../../share/doc/idl/jsf.idl ../../share/doc/idl/xhr.idl ../../share/doc/idl/evg.idl ../../share/doc/idl/scenejs.idl ../../share/doc/idl/storage.idl ../../share/doc/idl/webgl.idl ../../share/doc/idl/filtersession.idl ../../share/doc/idl/dash_algo.idl ../../share/doc/idl/nodejs.idl ../../share/python/libgpac.py + +# 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: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# 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. +# +# 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 +# read by doxygen. +# +# 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, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, +# *.vhdl, *.ucf, *.qsf, *.as and *.js. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# 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 +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = ../../include/gpac/nodes_mpeg4.h \ + ../../include/gpac/nodes_svg.h \ + ../../include/gpac/nodes_x3d.h \ + ../../include/gpac/nodes_xbl.h \ + ../../include/gpac/configuration.h \ + ../../include/gpac/revision.h \ + ../../include/gpac/ait.h \ + ../../include/gpac/dsmcc.h \ + ../../include/gpac/dvb_mpe.h + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */doc/* \ + */tests/* \ + */build/* \ + */bin/* \ + */applications/* \ + */modules/* + +# 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/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = ../../README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# 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. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs 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: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html-libgpac + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# 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 +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what 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. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# 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 +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# 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. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# 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 +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# 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 +# http://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. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +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 +# 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. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +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_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = 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 +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name 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. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# 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. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# 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: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# 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 +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# 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 +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# 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. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# 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. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT 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 + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# 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/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# 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 http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# 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 +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /