├── .github └── workflows │ └── node.yaml ├── .gitignore ├── LICENSE ├── README.md ├── binding-options.js ├── binding.gyp ├── include ├── LICENSE ├── Processing.NDI.DynamicLoad.h ├── Processing.NDI.Find.h ├── Processing.NDI.FrameSync.h ├── Processing.NDI.Lib.cplusplus.h ├── Processing.NDI.Lib.h ├── Processing.NDI.Recv.ex.h ├── Processing.NDI.Recv.h ├── Processing.NDI.Routing.h ├── Processing.NDI.Send.h ├── Processing.NDI.compat.h ├── Processing.NDI.deprecated.h ├── Processing.NDI.structs.h └── Processing.NDI.utilities.h ├── index.d.ts ├── index.js ├── lib ├── NDI SDK License Agreement.txt ├── libndi_licenses.txt ├── linux_arm64 │ └── libndi.so.5 ├── linux_x64 │ └── libndi.so.5 ├── mac_universal │ └── libndi.dylib ├── win_x64 │ ├── Processing.NDI.Lib.x64.dll │ └── Processing.NDI.Lib.x64.lib └── win_x86 │ ├── Processing.NDI.Lib.x86.dll │ └── Processing.NDI.Lib.x86.lib ├── package.json ├── scratch ├── api_sketch.txt ├── find-watcher.js ├── scratchReceiveAudio.js ├── scratchReceiveData.js ├── scratchReceiveMetadata.js └── scratchReceiveVideo.js ├── src ├── grandiose.cc ├── grandiose_find.cc ├── grandiose_find.h ├── grandiose_receive.cc ├── grandiose_receive.h ├── grandiose_send.cc ├── grandiose_send.h ├── grandiose_util.cc ├── grandiose_util.h └── util.h └── yarn.lock /.github/workflows/node.yaml: -------------------------------------------------------------------------------- 1 | name: Prebuild 2 | 3 | on: 4 | push: 5 | # tags: 6 | # - v* 7 | 8 | env: 9 | NAPI_VERSION: 7 10 | BINDING_NAME: grandiose 11 | 12 | jobs: 13 | build-and-test: 14 | name: Build ${{ matrix.arch }} on ${{ matrix.os }} ${{ matrix.libc }} 15 | runs-on: ${{ matrix.os }} 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | include: 21 | # windows 22 | - os: windows-2019 23 | arch: x64 24 | is-native: true 25 | extra-files: "Processing.NDI.Lib.x64.dll" 26 | # - os: windows-2019 27 | # arch: ia32 28 | # is-native: false 29 | # macos - look into making a universal build 30 | - os: macos-11 31 | arch: arm64 32 | is-native: false 33 | - os: macos-11 34 | arch: x64 35 | is-native: true 36 | # linux 37 | - os: ubuntu-20.04 38 | arch: x64 39 | is-native: true 40 | # linux-libc 41 | # - os: ubuntu-latest 42 | # arch: arm64 43 | # is-native: false 44 | # docker-arch: linux/arm64 45 | # docker-image: node:16-buster 46 | # - os: ubuntu-latest 47 | # arch: arm 48 | # is-native: false 49 | # docker-arch: linux/arm/v7 50 | # docker-image: node:16-buster 51 | # linux-musl - no musl version of libndi 52 | # - os: ubuntu-latest 53 | # arch: x64 54 | # is-native: false 55 | # docker-arch: linux/amd64 56 | # docker-image: node:16-alpine 57 | # libc: musl 58 | 59 | steps: 60 | - uses: actions/checkout@v3 61 | 62 | - name: Use Node.js 16.x 63 | uses: actions/setup-node@v3 64 | with: 65 | node-version: 16.x 66 | 67 | - name: rebuild 68 | if: ${{ !matrix.docker-arch }} 69 | shell: bash 70 | run: | 71 | yarn 72 | 73 | if [ -n "${{ matrix.is-native }}" ]; then 74 | yarn test 75 | fi 76 | 77 | yarn rebuild --arch=${{ matrix.arch }} 78 | yarn pkg-prebuilds-copy --baseDir=build/Release \ 79 | --source=$BINDING_NAME.node \ 80 | --name=$BINDING_NAME \ 81 | --strip \ 82 | --napi_version=$NAPI_VERSION \ 83 | --arch=${{ matrix.arch }} \ 84 | --extra-files=${{ matrix.extra-files }} 85 | env: 86 | CI: true 87 | npm_config_build_from_source: true 88 | 89 | - name: Set up QEMU 90 | uses: docker/setup-qemu-action@v2 91 | if: matrix.docker-arch 92 | - name: rebuild (in docker) 93 | uses: addnab/docker-run-action@v3 94 | if: matrix.docker-arch 95 | with: 96 | image: ${{ matrix.docker-image }} 97 | # shell: bash 98 | options: --platform=${{ matrix.docker-arch }} -v ${{ github.workspace }}:/work -e CI=1 -e npm_config_build_from_source=1 -e NAPI_VERSION -e BINDING_NAME 99 | run: | 100 | if command -v apt-get &> /dev/null 101 | then 102 | echo "deb http://deb.debian.org/debian buster-backports main contrib non-free" | tee -a /etc/apt/sources.list.d/backports.list 103 | apt update 104 | apt install -y cmake/buster-backports yasm 105 | elif command -v apk &> /dev/null 106 | then 107 | apk update 108 | apk add cmake make g++ gcc yasm 109 | fi 110 | 111 | cd /work 112 | 113 | yarn 114 | yarn test 115 | 116 | yarn pkg-prebuilds-copy --baseDir=build/Release \ 117 | --source=$BINDING_NAME.node \ 118 | --name=$BINDING_NAME \ 119 | --strip \ 120 | --napi_version=$NAPI_VERSION \ 121 | --arch=${{ matrix.arch }} \ 122 | --libc=${{ matrix.libc }} 123 | 124 | - name: Upload artifacts 125 | uses: actions/upload-artifact@v3 126 | with: 127 | name: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.libc }}-prebuilds 128 | path: prebuilds 129 | retention-days: 1 130 | 131 | bundle: 132 | name: Bundle prebuilds 133 | needs: build-and-test 134 | runs-on: ubuntu-latest 135 | steps: 136 | - uses: actions/download-artifact@v3 137 | with: 138 | path: tmp 139 | 140 | - name: Display structure of downloaded files 141 | run: | 142 | mkdir prebuilds 143 | mv tmp/*/* prebuilds/ 144 | 145 | - name: Upload artifacts 146 | uses: actions/upload-artifact@v3 147 | with: 148 | name: all-prebuilds 149 | path: prebuilds 150 | retention-days: 7 151 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | prebuilds 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # node-gyp built artifacts 65 | build 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grandiose 2 | [Node.js](http://nodejs.org/) native bindings to NewTek NDI(tm). For more information on NDI(tm), see: 3 | 4 | http://NDI.NewTek.com/ 5 | 6 | This module will allow a Node.JS program to find, receive and send NDI(tm) video, audio and metadata streams over IP networks. All calls a asynchronous and use Javascript promises with all of the underlying work of NDI running on separate threads from the event loop. 7 | 8 | NDI(tm) is a realisation of a grand vision for what IP media streams should and can be, hence a steampunk themed name of _gra-NDI-ose_. 9 | 10 | ## Installation 11 | 12 | Grandiose supports Windows (x64), MacOS (x64 & arm64) as well as Linux (x64 only). Additional platforms may be added in the future. 13 | 14 | Install [Node.js](http://nodejs.org/) for your platform. This software has been developed against the long term stable (LTS) release. 15 | 16 | On Windows, the NDI(tm) DLL requires that the Visual Studio 2013 C run-times are installed, available from: 17 | 18 | https://www.microsoft.com/en-us/download/details.aspx?id=40784 19 | 20 | Grandiose is designed to be `require`d to use from your own application to provide async processing. For example: 21 | 22 | npm install --save grandiose 23 | 24 | ## Using grandiose 25 | 26 | ### Finding streams 27 | 28 | A list of all currently available NDI(tm) sources available on the current local area network (or VLAN) can be retrieved. For example, to print a list of sources to the console, try: 29 | 30 | ```javascript 31 | const { GrandioseFinder } = require('grandiose'); 32 | 33 | const finder = new GrandioseFinder() 34 | setTimeout(() => { 35 | // Log the discovered sources after 1000ms wait 36 | console.log(finder.getCurrentSources()) 37 | }, 1000) 38 | ``` 39 | 40 | The result is an array, for example here are some local sources to machine : 41 | 42 | ```javascript 43 | [ { name: 'GINGER (Intel(R) HD Graphics 520 1)', 44 | urlAddress: '169.254.82.1:5962' }, 45 | { name: 'GINGER (Test Pattern)', 46 | urlAddress: '169.254.82.1:5961' }, 47 | { name: 'GINGER (TOSHIBA Web Camera - HD)', 48 | urlAddress: '169.254.82.1:5963' } ] 49 | ``` 50 | 51 | The finder can be configured with an options object and a wait time in measured in milliseconds: 52 | 53 | new GrandioseFinder(); 54 | 55 | The options are as follows: 56 | 57 | ```javascript 58 | grandiose.find({ 59 | // Should sources on the same system be found? 60 | showLocalSources: true, 61 | // Show only sources in a named group. May be an array. 62 | groups: "studio3", 63 | // Specific IP addresses or machine names to check 64 | // These are possibly on a different VLAN and not visible over MDNS 65 | extraIPs: [ "192.168.1.122", "mixer.studio7.zbc.com" ] 66 | }) // ... 67 | ``` 68 | 69 | ### Receiving streams 70 | 71 | First of all, find a stream using the method above or create an object representing a source: 72 | 73 | ```javascript 74 | const grandiose = require('grandiose'); 75 | let source = { name: "", urlAddress: ":" }; 76 | ``` 77 | 78 | In an `async` function, create a receiver as follows: 79 | 80 | ```javascript 81 | let receiver = await grandiose.receive({ source: source }); 82 | ``` 83 | 84 | An example of the receiver object resolved by this promise is shown below: 85 | 86 | ```javascript 87 | { embedded: [External], 88 | video: [Function: video], 89 | audio: [Function: audio], 90 | metadata: [Function: metadata], 91 | data: [Function: data], 92 | source: 93 | { name: 'LEMARR (Test Pattern)', 94 | urlAddress: '169.254.82.1:5961' }, 95 | colorFormat: 100, // grandiose.COLOR_FORMAT_FASTEST 96 | bandwidth: 100, // grandiose.BANDWIDTH_HIGHEST 97 | allowVideoFields: true } 98 | ``` 99 | 100 | The `embedded` value is the native receiver returned by the NDI(tm) SDK. The `video`, `audio`, `metadata` and `data` functions return promises to retrieve data from the source. These promises are backed by calls that are thread safe. 101 | 102 | The `colorFormat`, `bandwidth` and `allowVideoFields` parameters are those used to set up the receiver. These can be configured as options when creating the receiver as follows: 103 | 104 | ```javascript 105 | let receiver = await grandiose.receive({ 106 | source: source, // required source parameter 107 | // Preferred colour space - without and with alpha channel 108 | // One of COLOR_FORMAT_RGBX_RGBA, COLOR_FORMAT_BGRX_BGRA, 109 | // COLOR_FORMAT_UYVY_RGBA, COLOR_FORMAT_UYVY_BGRA or 110 | // the default of COLOR_FORMAT_FASTEST 111 | colorFormat: grandiose.COLOR_FORMAT_UYVY_RGBA, 112 | // Select bandwidth level. One of grandiose.BANDWIDTH_METADATA_ONLY, 113 | // BANDWIDTH_AUDIO_ONLY, BANDWIDTH_LOWEST and the default value 114 | // of BANDWIDTH_HIGHEST 115 | bandwidth: grandiose.BANDWIDTH_AUDIO_ONLY, 116 | // Set to false to receive only progressive video frames 117 | allowVideoFields: true, // default is true 118 | // An optional name for the receiver, otherwise one will be generated 119 | name: "rooftop" 120 | }, ); 121 | ``` 122 | 123 | #### Video 124 | 125 | Request video frames from the source as follows: 126 | 127 | ```javascript 128 | let timeout = 5000; // Optional timeout, default is 10000ms 129 | try { 130 | for ( let x = 0 ; x < 10 ; x++) { 131 | let videoFrame = await receiver.video(timeout); 132 | console.log(videoFrame); 133 | } 134 | } catch (e) { console.error(e); } 135 | ``` 136 | 137 | Here is the output associated with a video frame created by an NDI(tm) test pattern: 138 | 139 | ```javascript 140 | { type: 'video', 141 | xres: 1920, 142 | yres: 1080, 143 | frameRateN: 30000, 144 | frameRateD: 1001, 145 | pictureAspectRatio: 1.7777777910232544, // 16:9 146 | timestamp: [ 1538569443, 717845600 ], // PTP timestamp 147 | frameFormatType: 1, // grandiose.FORMAT_TYPE_INTERLACED 148 | timecode: [ 0, 0 ], // Measured in nanoseconds 149 | lineStrideBytes: 3840, 150 | data: } 151 | ``` 152 | 153 | NDI presents 8-bit integer data for video. 154 | 155 | Note that the returned promise may be rejected if the request times out or another error occurs. 156 | 157 | The `receiver` instance will disconnect on the next garbage collection, so make sure that you don't hold onto a reference. 158 | 159 | #### Audio 160 | 161 | Audio follows a similar pattern to video, except that a couple of options are available to control for format of audio returned into Javasript. 162 | 163 | ```javascript 164 | let timeout = 8000; // Optional timeout value in ms 165 | let audioFrame = await receiver.audio({ 166 | // One of three audio formats that NDI(tm) utilities can provide: 167 | // grandiose.AUDIO_FORMAT_INT_16_INTERLEAVED, 168 | // AUDIO_FORMAT_FLOAT_32_INTERLEAVED and the default value of 169 | // AUDIO_FORMAT_FLOAT_32_SEPARATE 170 | audioFormat: grandiose.AUDIO_FORMAT_INT_16_INTERLEAVED, 171 | // The audio reference level in dB. This specifies how many dB above 172 | // the reference level (+4dBU) is the full range of integer audio. 173 | referenceLevel: 0 // default is 0dB 174 | }, timeout); 175 | ``` 176 | 177 | An example of an audio frame resolved from this promise is: 178 | 179 | ```javascript 180 | { type: 'audio', 181 | audioFormat: 2, // grandiose.AUDIO_FORMAT_INT_16_INTERLEAVED 182 | referenceLevel: 0, // 0dB above reference level 183 | sampleRate: 48000, // Hz 184 | channels: 4, 185 | samples: 4800, // Number of samples in this frame 186 | channelStrideInBytes: 9600, // number of bytes per channel in buffer 187 | timestamp: [ 1538578787, 132614500 ], // PTP timestamp 188 | timecode: [ 0, 800000000 ], // timecode as PTP value 189 | data: } 190 | ``` 191 | 192 | #### Metadata 193 | 194 | Follows a similar pattern to video and audio, waiting for any metadata messages in the stream. 195 | 196 | ```javascript 197 | let metadataFrame = await receiver.metadata(); 198 | ``` 199 | 200 | Result is an object with a data property that is string containing the metadata, expected to be a short XML document. 201 | 202 | #### Next available data 203 | 204 | A means to receive the next available data payload in the stream, whether that is video, audio or metadata, allowing the application to filter the streams as required based on the `type` parameter. The optional arguments used for audio can also be used here. 205 | 206 | ```javascript 207 | let dataFrame = await receiver.data(); 208 | if (dataFrame.type == 'video') { /* Process just the video */ } 209 | else if (dataFrame.type == 'metadata') { console.log(dataFrame.data); } 210 | ``` 211 | 212 | ### Sending streams 213 | 214 | To follow. 215 | 216 | ### Other 217 | 218 | To find out the version of NDI(tm), use: 219 | 220 | grandiose.version(); // e.g. 'NDI SDK WIN64 00:29:47 Jun 26 2018 3.5.9.0' 221 | 222 | To check if the installed CPU is supported for NDI(tm), use: 223 | 224 | grandiose.isSupportedCPU(); // e.g. true 225 | 226 | ## Status, support and further development 227 | 228 | Support for sending streams is in progress. Support for x86, Mac and Linux platforms is being considered. 229 | 230 | Although the architecture of grandiose is such that it could be used at scale in production environments, development is not yet complete. In its current state, it is recommended that this software is used in development environments and for building prototypes. Future development will make this more appropriate for production use. 231 | 232 | Contributions can be made via pull requests and will be considered by the author on their merits. Enhancement requests and bug reports should be raised as github issues. For support, please contact [Streampunk Media](http://www.streampunk.media/). 233 | 234 | ## License 235 | 236 | Apart from the exceptions in the following section, this software is released under the Apache 2.0 license. Copyright 2018 Streampunk Media Ltd. 237 | 238 | ### License exceptions 239 | 240 | The software uses libraries provided under a royalty-free license from NewTek, Inc.. 241 | 242 | * The `include` files are licensed separately by a NewTek under the MIT license. 243 | * The NDI SDK library files are provided for convenience of installation and are covered by the NewTek license contained in the `lib` folder. 244 | 245 | ## Trademarks 246 | 247 | NDI(tm) is a trademark of NewTek, Inc.. 248 | -------------------------------------------------------------------------------- /binding-options.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'grandiose', 3 | napi_versions: [8], 4 | } -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "grandiose", 5 | "sources": [ 6 | "src/grandiose_util.cc", 7 | "src/grandiose_find.cc", 8 | "src/grandiose_send.cc", 9 | "src/grandiose_receive.cc", 10 | "src/grandiose.cc" 11 | ], 12 | "include_dirs": [ "include", " 32 | #endif 33 | 34 | #include 35 | 36 | #ifndef INFINITE 37 | //#define INFINITE INFINITE 38 | static const uint32_t INFINITE = 0xFFFFFFFF; 39 | #endif 40 | -------------------------------------------------------------------------------- /include/Processing.NDI.deprecated.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review 4 | // the SDK documentation for the description of the full license terms, which are also provided in the file 5 | // "NDI License Agreement.pdf" within the SDK or online at http://new.tk/ndisdk_license/. Your use of any 6 | // part of this SDK is acknowledgment that you agree to the SDK license terms. The full NDI SDK may be 7 | // downloaded at http://ndi.tv/ 8 | // 9 | //*********************************************************************************************************** 10 | // 11 | // Copyright (C)2014-2022, NewTek, inc. 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 14 | // associated documentation files(the "Software"), to deal in the Software without restriction, including 15 | // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 16 | // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 17 | // following conditions : 18 | // 19 | // The above copyright notice and this permission notice shall be included in all copies or substantial 20 | // portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 24 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 26 | // THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | //*********************************************************************************************************** 29 | 30 | // This describes a video frame 31 | PROCESSINGNDILIB_DEPRECATED 32 | typedef struct NDIlib_video_frame_t { 33 | // The resolution of this frame. 34 | int xres, yres; 35 | 36 | // What FourCC this is with. This can be two values. 37 | NDIlib_FourCC_video_type_e FourCC; 38 | 39 | // What is the frame rate of this frame. 40 | // For instance NTSC is 30000,1001 = 30000/1001 = 29.97 fps 41 | int frame_rate_N, frame_rate_D; 42 | 43 | // What is the picture aspect ratio of this frame. 44 | // For instance 16.0/9.0 = 1.778 is 16:9 video. If this is zero, then square pixels are assumed (xres/yres). 45 | float picture_aspect_ratio; 46 | 47 | // Is this a fielded frame, or is it progressive. 48 | NDIlib_frame_format_type_e frame_format_type; 49 | 50 | // The timecode of this frame in 100-nanosecond intervals. 51 | int64_t timecode; 52 | 53 | // The video data itself. 54 | uint8_t* p_data; 55 | 56 | // The inter-line stride of the video data, in bytes. 57 | int line_stride_in_bytes; 58 | 59 | #if NDILIB_CPP_DEFAULT_CONSTRUCTORS 60 | NDIlib_video_frame_t( 61 | int xres_ = 0, int yres_ = 0, 62 | NDIlib_FourCC_video_type_e FourCC_ = NDIlib_FourCC_type_UYVY, 63 | int frame_rate_N_ = 30000, int frame_rate_D_ = 1001, 64 | float picture_aspect_ratio_ = 0.0f, 65 | NDIlib_frame_format_type_e frame_format_type_ = NDIlib_frame_format_type_progressive, 66 | int64_t timecode_ = NDIlib_send_timecode_synthesize, 67 | uint8_t* p_data_ = NULL, int line_stride_in_bytes_ = 0 68 | ); 69 | #endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS 70 | } NDIlib_video_frame_t; 71 | 72 | // This describes an audio frame 73 | PROCESSINGNDILIB_DEPRECATED 74 | typedef struct NDIlib_audio_frame_t { 75 | // The sample-rate of this buffer. 76 | int sample_rate; 77 | 78 | // The number of audio channels. 79 | int no_channels; 80 | 81 | // The number of audio samples per channel. 82 | int no_samples; 83 | 84 | // The timecode of this frame in 100-nanosecond intervals. 85 | int64_t timecode; 86 | 87 | // The audio data. 88 | float* p_data; 89 | 90 | // The inter channel stride of the audio channels, in bytes. 91 | int channel_stride_in_bytes; 92 | 93 | #if NDILIB_CPP_DEFAULT_CONSTRUCTORS 94 | NDIlib_audio_frame_t( 95 | int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, 96 | int64_t timecode_ = NDIlib_send_timecode_synthesize, 97 | float* p_data_ = NULL, int channel_stride_in_bytes_ = 0 98 | ); 99 | #endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS 100 | } NDIlib_audio_frame_t; 101 | 102 | // For legacy reasons I called this the wrong thing. For backwards compatibility. 103 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 104 | NDIlib_find_instance_t NDIlib_find_create2(const NDIlib_find_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(NULL)); 105 | 106 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 107 | NDIlib_find_instance_t NDIlib_find_create(const NDIlib_find_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(NULL)); 108 | 109 | // DEPRECATED. This function is basically exactly the following and was confusing to use. 110 | // if ((!timeout_in_ms) || (NDIlib_find_wait_for_sources(timeout_in_ms))) 111 | // return NDIlib_find_get_current_sources(p_instance, p_no_sources); 112 | // return NULL; 113 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 114 | const NDIlib_source_t* NDIlib_find_get_sources(NDIlib_find_instance_t p_instance, uint32_t* p_no_sources, uint32_t timeout_in_ms); 115 | 116 | // The creation structure that is used when you are creating a receiver. 117 | PROCESSINGNDILIB_DEPRECATED 118 | typedef struct NDIlib_recv_create_t { 119 | // The source that you wish to connect to. 120 | NDIlib_source_t source_to_connect_to; 121 | 122 | // Your preference of color space. See above. 123 | NDIlib_recv_color_format_e color_format; 124 | 125 | // The bandwidth setting that you wish to use for this video source. Bandwidth 126 | // controlled by changing both the compression level and the resolution of the source. 127 | // A good use for low bandwidth is working on WIFI connections. 128 | NDIlib_recv_bandwidth_e bandwidth; 129 | 130 | // When this flag is FALSE, all video that you receive will be progressive. For sources that provide 131 | // fields, this is de-interlaced on the receiving side (because we cannot change what the up-stream 132 | // source was actually rendering. This is provided as a convenience to down-stream sources that do not 133 | // wish to understand fielded video. There is almost no performance impact of using this function. 134 | bool allow_video_fields; 135 | 136 | #if NDILIB_CPP_DEFAULT_CONSTRUCTORS 137 | NDIlib_recv_create_t( 138 | const NDIlib_source_t source_to_connect_to_ = NDIlib_source_t(), 139 | NDIlib_recv_color_format_e color_format_ = NDIlib_recv_color_format_UYVY_BGRA, 140 | NDIlib_recv_bandwidth_e bandwidth_ = NDIlib_recv_bandwidth_highest, 141 | bool allow_video_fields_ = true 142 | ); 143 | #endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS 144 | } NDIlib_recv_create_t; 145 | 146 | // This function is deprecated, please use NDIlib_recv_create_v3 if you can. Using this function will 147 | // continue to work, and be supported for backwards compatibility. If the input parameter is NULL it will be 148 | // created with default settings and an automatically determined receiver name. 149 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 150 | NDIlib_recv_instance_t NDIlib_recv_create_v2(const NDIlib_recv_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(NULL)); 151 | 152 | // For legacy reasons I called this the wrong thing. For backwards compatibility. If the input parameter is 153 | // NULL it will be created with default settings and an automatically determined receiver name. 154 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 155 | NDIlib_recv_instance_t NDIlib_recv_create2(const NDIlib_recv_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(NULL)); 156 | 157 | // This function is deprecated, please use NDIlib_recv_create_v3 if you can. Using this function will 158 | // continue to work, and be supported for backwards compatibility. This version sets bandwidth to highest and 159 | // allow fields to true. If the input parameter is NULL it will be created with default settings and an 160 | // automatically determined receiver name. 161 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 162 | NDIlib_recv_instance_t NDIlib_recv_create(const NDIlib_recv_create_t* p_create_settings); 163 | 164 | // This will allow you to receive video, audio and metadata frames. Any of the buffers can be NULL, in which 165 | // case data of that type will not be captured in this call. This call can be called simultaneously on 166 | // separate threads, so it is entirely possible to receive audio, video, metadata all on separate threads. 167 | // This function will return NDIlib_frame_type_none if no data is received within the specified timeout and 168 | // NDIlib_frame_type_error if the connection is lost. Buffers captured with this must be freed with the 169 | // appropriate free function below. 170 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 171 | NDIlib_frame_type_e NDIlib_recv_capture( 172 | NDIlib_recv_instance_t p_instance, // The library instance. 173 | NDIlib_video_frame_t* p_video_data, // The video data received (can be NULL). 174 | NDIlib_audio_frame_t* p_audio_data, // The audio data received (can be NULL). 175 | NDIlib_metadata_frame_t* p_metadata, // The metadata received (can be NULL). 176 | uint32_t timeout_in_ms // The amount of time in milliseconds to wait for data. 177 | ); 178 | 179 | // Free the buffers returned by capture for video. 180 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 181 | void NDIlib_recv_free_video(NDIlib_recv_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); 182 | 183 | // Free the buffers returned by capture for audio. 184 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 185 | void NDIlib_recv_free_audio(NDIlib_recv_instance_t p_instance, const NDIlib_audio_frame_t* p_audio_data); 186 | 187 | // This will add a video frame. 188 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 189 | void NDIlib_send_send_video(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); 190 | 191 | // This will add a video frame and will return immediately, having scheduled the frame to be displayed. All 192 | // processing and sending of the video will occur asynchronously. The memory accessed by NDIlib_video_frame_t 193 | // cannot be freed or re-used by the caller until a synchronizing event has occurred. In general the API is 194 | // better able to take advantage of asynchronous processing than you might be able to by simple having a 195 | // separate thread to submit frames. 196 | // 197 | // This call is particularly beneficial when processing BGRA video since it allows any color conversion, 198 | // compression and network sending to all be done on separate threads from your main rendering thread. 199 | // 200 | // Synchronizing events are : 201 | // - a call to NDIlib_send_send_video 202 | // - a call to NDIlib_send_send_video_async with another frame to be sent 203 | // - a call to NDIlib_send_send_video with p_video_data=NULL 204 | // - a call to NDIlib_send_destroy 205 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 206 | void NDIlib_send_send_video_async(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); 207 | 208 | // This will add an audio frame 209 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 210 | void NDIlib_send_send_audio(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_t* p_audio_data); 211 | 212 | // Convert an planar floating point audio buffer into a interleaved short audio buffer. 213 | // IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. 214 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 215 | void NDIlib_util_audio_to_interleaved_16s(const NDIlib_audio_frame_t* p_src, NDIlib_audio_frame_interleaved_16s_t* p_dst); 216 | 217 | // Convert an interleaved short audio buffer audio buffer into a planar floating point one. 218 | // IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. 219 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 220 | void NDIlib_util_audio_from_interleaved_16s(const NDIlib_audio_frame_interleaved_16s_t* p_src, NDIlib_audio_frame_t* p_dst); 221 | 222 | // Convert an planar floating point audio buffer into a interleaved floating point audio buffer. 223 | // IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. 224 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 225 | void NDIlib_util_audio_to_interleaved_32f(const NDIlib_audio_frame_t* p_src, NDIlib_audio_frame_interleaved_32f_t* p_dst); 226 | 227 | // Convert an interleaved floating point audio buffer into a planar floating point one. 228 | // IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. 229 | PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED 230 | void NDIlib_util_audio_from_interleaved_32f(const NDIlib_audio_frame_interleaved_32f_t* p_src, NDIlib_audio_frame_t* p_dst); 231 | -------------------------------------------------------------------------------- /include/Processing.NDI.utilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review 4 | // the SDK documentation for the description of the full license terms, which are also provided in the file 5 | // "NDI License Agreement.pdf" within the SDK or online at http://new.tk/ndisdk_license/. Your use of any 6 | // part of this SDK is acknowledgment that you agree to the SDK license terms. The full NDI SDK may be 7 | // downloaded at http://ndi.tv/ 8 | // 9 | //*********************************************************************************************************** 10 | // 11 | // Copyright (C)2014-2022, NewTek, inc. 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 14 | // associated documentation files(the "Software"), to deal in the Software without restriction, including 15 | // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 16 | // copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 17 | // following conditions : 18 | // 19 | // The above copyright notice and this permission notice shall be included in all copies or substantial 20 | // portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 23 | // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 24 | // EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 26 | // THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | //*********************************************************************************************************** 29 | 30 | // Because many applications like submitting 16-bit interleaved audio, these functions will convert in and 31 | // out of that format. It is important to note that the NDI SDK does define fully audio levels, something 32 | // that most applications that you use do not. Specifically, the floating-point range, -1.0 to +1.0, is 33 | // defined as a professional audio reference level of +4 dBU. If we take 16-bit audio and scale it into this 34 | // range it is almost always correct for sending and will cause no problems. For receiving however it is not 35 | // at all uncommon that the user has audio that exceeds reference level and in this case it is likely that 36 | // audio exceeds the reference level and so if you are not careful you will end up having audio clipping when 37 | // you use the 16-bit range. 38 | 39 | // This describes an audio frame. 40 | typedef struct NDIlib_audio_frame_interleaved_16s_t { 41 | // The sample-rate of this buffer. 42 | int sample_rate; 43 | 44 | // The number of audio channels. 45 | int no_channels; 46 | 47 | // The number of audio samples per channel. 48 | int no_samples; 49 | 50 | // The timecode of this frame in 100-nanosecond intervals. 51 | int64_t timecode; 52 | 53 | // The audio reference level in dB. This specifies how many dB above the reference level (+4 dBU) is the 54 | // full range of 16-bit audio. If you do not understand this and want to just use numbers: 55 | // - If you are sending audio, specify +0 dB. Most common applications produce audio at reference level. 56 | // - If receiving audio, specify +20 dB. This means that the full 16-bit range corresponds to 57 | // professional level audio with 20 dB of headroom. Note that if you are writing it into a file it 58 | // might sound soft because you have 20 dB of headroom before clipping. 59 | int reference_level; 60 | 61 | // The audio data, interleaved 16-bit samples. 62 | int16_t* p_data; 63 | 64 | #if NDILIB_CPP_DEFAULT_CONSTRUCTORS 65 | NDIlib_audio_frame_interleaved_16s_t( 66 | int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, 67 | int64_t timecode_ = NDIlib_send_timecode_synthesize, 68 | int reference_level_ = 0, 69 | int16_t* p_data_ = NULL 70 | ); 71 | #endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS 72 | } NDIlib_audio_frame_interleaved_16s_t; 73 | 74 | // This describes an audio frame. 75 | typedef struct NDIlib_audio_frame_interleaved_32s_t { 76 | // The sample-rate of this buffer. 77 | int sample_rate; 78 | 79 | // The number of audio channels. 80 | int no_channels; 81 | 82 | // The number of audio samples per channel. 83 | int no_samples; 84 | 85 | // The timecode of this frame in 100-nanosecond intervals. 86 | int64_t timecode; 87 | 88 | // The audio reference level in dB. This specifies how many dB above the reference level (+4 dBU) is the 89 | // full range of 32-bit audio. If you do not understand this and want to just use numbers: 90 | // - If you are sending audio, specify +0 dB. Most common applications produce audio at reference level. 91 | // - If receiving audio, specify +20 dB. This means that the full 32-bit range corresponds to 92 | // professional level audio with 20 dB of headroom. Note that if you are writing it into a file it 93 | // might sound soft because you have 20 dB of headroom before clipping. 94 | int reference_level; 95 | 96 | // The audio data, interleaved 32-bit samples. 97 | int32_t* p_data; 98 | 99 | #if NDILIB_CPP_DEFAULT_CONSTRUCTORS 100 | NDIlib_audio_frame_interleaved_32s_t( 101 | int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, 102 | int64_t timecode_ = NDIlib_send_timecode_synthesize, 103 | int reference_level_ = 0, 104 | int32_t* p_data_ = NULL 105 | ); 106 | #endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS 107 | } NDIlib_audio_frame_interleaved_32s_t; 108 | 109 | // This describes an audio frame. 110 | typedef struct NDIlib_audio_frame_interleaved_32f_t { 111 | // The sample-rate of this buffer. 112 | int sample_rate; 113 | 114 | // The number of audio channels. 115 | int no_channels; 116 | 117 | // The number of audio samples per channel. 118 | int no_samples; 119 | 120 | // The timecode of this frame in 100-nanosecond intervals. 121 | int64_t timecode; 122 | 123 | // The audio data, interleaved 32-bit floating-point samples. 124 | float* p_data; 125 | 126 | #if NDILIB_CPP_DEFAULT_CONSTRUCTORS 127 | NDIlib_audio_frame_interleaved_32f_t( 128 | int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, 129 | int64_t timecode_ = NDIlib_send_timecode_synthesize, 130 | float* p_data_ = NULL 131 | ); 132 | #endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS 133 | } NDIlib_audio_frame_interleaved_32f_t; 134 | 135 | // This will add an audio frame in interleaved 16-bit. 136 | PROCESSINGNDILIB_API 137 | void NDIlib_util_send_send_audio_interleaved_16s(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_16s_t* p_audio_data); 138 | 139 | // This will add an audio frame in interleaved 32-bit. 140 | PROCESSINGNDILIB_API 141 | void NDIlib_util_send_send_audio_interleaved_32s(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_32s_t* p_audio_data); 142 | 143 | // This will add an audio frame in interleaved floating point. 144 | PROCESSINGNDILIB_API 145 | void NDIlib_util_send_send_audio_interleaved_32f(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_32f_t* p_audio_data); 146 | 147 | // Convert to interleaved 16-bit. 148 | PROCESSINGNDILIB_API 149 | void NDIlib_util_audio_to_interleaved_16s_v2(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_16s_t* p_dst); 150 | 151 | // Convert from interleaved 16-bit. 152 | PROCESSINGNDILIB_API 153 | void NDIlib_util_audio_from_interleaved_16s_v2(const NDIlib_audio_frame_interleaved_16s_t* p_src, NDIlib_audio_frame_v2_t* p_dst); 154 | 155 | // Convert to interleaved 32-bit. 156 | PROCESSINGNDILIB_API 157 | void NDIlib_util_audio_to_interleaved_32s_v2(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_32s_t* p_dst); 158 | 159 | // Convert from interleaved 32-bit. 160 | PROCESSINGNDILIB_API 161 | void NDIlib_util_audio_from_interleaved_32s_v2(const NDIlib_audio_frame_interleaved_32s_t* p_src, NDIlib_audio_frame_v2_t* p_dst); 162 | 163 | // Convert to interleaved floating point. 164 | PROCESSINGNDILIB_API 165 | void NDIlib_util_audio_to_interleaved_32f_v2(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_32f_t* p_dst); 166 | 167 | // Convert from interleaved floating point. 168 | PROCESSINGNDILIB_API 169 | void NDIlib_util_audio_from_interleaved_32f_v2(const NDIlib_audio_frame_interleaved_32f_t* p_src, NDIlib_audio_frame_v2_t* p_dst); 170 | 171 | // This is a helper function that you may use to convert from 10-bit packed UYVY into 16-bit semi-planar. The 172 | // FourCC on the source is ignored in this function since we do not define a V210 format in NDI. You must 173 | // make sure that there is memory and a stride allocated in p_dst. 174 | PROCESSINGNDILIB_API 175 | void NDIlib_util_V210_to_P216(const NDIlib_video_frame_v2_t* p_src_v210, NDIlib_video_frame_v2_t* p_dst_p216); 176 | 177 | // This converts from 16-bit semi-planar to 10-bit. You must make sure that there is memory and a stride 178 | // allocated in p_dst. 179 | PROCESSINGNDILIB_API 180 | void NDIlib_util_P216_to_V210(const NDIlib_video_frame_v2_t* p_src_p216, NDIlib_video_frame_v2_t* p_dst_v210); 181 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export interface AudioFrame { 2 | type: 'audio' 3 | audioFormat: AudioFormat 4 | referenceLevel: number 5 | sampleRate: number // Hz 6 | channels: number 7 | samples: number 8 | channelStrideInBytes: number 9 | timestamp: [number, number] // PTP timestamp 10 | timecode: [number, number] // timecode as PTP value 11 | data: Buffer 12 | } 13 | 14 | export interface VideoFrame { 15 | type: 'video' 16 | xres: number 17 | yres: number 18 | frameRateN: number 19 | frameRateD: number 20 | fourCC: FourCC 21 | pictureAspectRatio: number 22 | timestamp: [ number, number ] // PTP timestamp 23 | frameFormatType: FrameType 24 | timecode: [ number, number ] // Measured in nanoseconds 25 | lineStrideBytes: number 26 | data: Buffer 27 | } 28 | 29 | export interface Receiver { 30 | embedded: unknown 31 | video: (timeout?: number) => Promise 32 | audio: (params: { 33 | audioFormat: AudioFormat 34 | referenceLevel: number 35 | }, timeout?: number) => Promise 36 | metadata: any 37 | data: any 38 | source: Source 39 | colorFormat: ColorFormat 40 | bandwidth: Bandwidth 41 | allowVideoFields: boolean 42 | } 43 | 44 | export interface Sender { 45 | embedded: unknown 46 | video: (frame: VideoFrame) => Promise 47 | audio: (frame: AudioFrame) => Promise 48 | name: string 49 | groups?: string | string[] 50 | clockVideo: boolean 51 | clockAudio: boolean 52 | } 53 | 54 | export interface Source { 55 | name: string 56 | urlAddress?: string 57 | ipAddress?: string 58 | } 59 | 60 | export const enum FrameType { 61 | Progressive = 1, 62 | Interlaced = 0, 63 | Field0 = 2, 64 | Field1 = 3, 65 | } 66 | export const FORMAT_TYPE_PROGRESSIVE: FrameType 67 | export const FORMAT_TYPE_INTERLACED: FrameType 68 | export const FORMAT_TYPE_FIELD_0: FrameType 69 | export const FORMAT_TYPE_FIELD_1: FrameType 70 | 71 | export const enum ColorFormat { 72 | BGRX_BGRA = 0, 73 | UYVY_BGRA = 1, 74 | RGBX_RGBA = 2, 75 | UYVY_RGBA = 3, 76 | Fastest = 100, 77 | Best = 101 78 | } 79 | 80 | export const COLOR_FORMAT_BGRX_BGRA: ColorFormat 81 | export const COLOR_FORMAT_UYVY_BGRA: ColorFormat 82 | export const COLOR_FORMAT_RGBX_RGBA: ColorFormat 83 | export const COLOR_FORMAT_UYVY_RGBA: ColorFormat 84 | export const COLOR_FORMAT_BGRX_BGRA_FLIPPED: ColorFormat 85 | export const COLOR_FORMAT_FASTEST: ColorFormat 86 | 87 | export const enum FourCC { 88 | UYVY = 1498831189, 89 | UYVA = 1096178005, 90 | P216 = 909193808, 91 | PA16 = 909197648, 92 | YV12 = 842094169, 93 | I420 = 808596553, 94 | NV12 = 842094158, 95 | BGRA = 1095911234, 96 | BGRX = 1481787202, 97 | RGBA = 1094862674, 98 | RGBX = 1480738642 99 | } 100 | 101 | export const enum AudioFormat { 102 | Float32Separate = 0, 103 | Float32Interleaved = 1, 104 | Int16Interleaved = 2 105 | } 106 | 107 | export const AUDIO_FORMAT_FLOAT_32_SEPARATE: AudioFormat 108 | export const AUDIO_FORMAT_FLOAT_32_INTERLEAVED: AudioFormat 109 | export const AUDIO_FORMAT_INT_16_INTERLEAVED: AudioFormat 110 | 111 | export const enum Bandwidth { 112 | MetadataOnly = -10, 113 | AudioOnly = 10, 114 | Lowest = 0, 115 | Highest = 100 116 | } 117 | 118 | export const BANDWIDTH_METADATA_ONLY: Bandwidth 119 | export const BANDWIDTH_AUDIO_ONLY: Bandwidth 120 | export const BANDWIDTH_LOWEST: Bandwidth 121 | export const BANDWIDTH_HIGHEST: Bandwidth 122 | 123 | export function receive(params: { 124 | source: Source 125 | colorFormat?: ColorFormat 126 | bandwidth?: Bandwidth 127 | allowVideoFields?: boolean 128 | name?: string 129 | }): Promise 130 | 131 | export function send(params: { 132 | name: string 133 | groups?: string | string[] 134 | clockVideo?: boolean 135 | clockAudio?: boolean 136 | }): Sender 137 | 138 | /** @deprecated use GrandioseFinder instead */ 139 | export function find(params: GrandioseFinderOptions, waitMs?: number): Promise> 140 | 141 | export interface GrandioseFinderOptions { 142 | // Should sources on the same system be found? 143 | showLocalSources?: boolean, 144 | // Show only sources in the named groups 145 | groups?: string[] | string, 146 | // Specific IP addresses or machine names to check 147 | // These are possibly on a different VLAN and not visible over MDNS 148 | extraIPs?: string[] 149 | } 150 | 151 | /** 152 | * An instance of the NDI source finder. 153 | * This will monitor for sources in the background, and you can poll it for the current list at useful times. 154 | */ 155 | export class GrandioseFinder{ 156 | constructor(options?: GrandioseFinderOptions) 157 | 158 | /** 159 | * Dispose of the finder once you are finished with it 160 | * Failing to do so will block the application from terminating 161 | */ 162 | dispose(): void 163 | 164 | /** 165 | * Get the list of currently known Sources 166 | */ 167 | getCurrentSources(): Array 168 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | const addon = require("pkg-prebuilds")( 17 | __dirname, 18 | require("./binding-options") 19 | ); 20 | const { promisify } = require('util') 21 | const setTimeoutPromise = promisify(setTimeout) 22 | 23 | // TODO: reenable segfault-handler when the NDI lib is fixed 24 | // const SegfaultHandler = require('segfault-handler'); 25 | // SegfaultHandler.registerHandler("crash.log"); // With no argument, SegfaultHandler will generate a generic log file name 26 | 27 | const COLOR_FORMAT_BGRX_BGRA = 0; // No alpha channel: BGRX, Alpha channel: BGRA 28 | const COLOR_FORMAT_UYVY_BGRA = 1; // No alpha channel: UYVY, Alpha channel: BGRA 29 | const COLOR_FORMAT_RGBX_RGBA = 2; // No alpha channel: RGBX, Alpha channel: RGBA 30 | const COLOR_FORMAT_UYVY_RGBA = 3; // No alpha channel: UYVY, Alpha channel: RGBA 31 | 32 | // On Windows there are some APIs that require bottom to top images in RGBA format. Specifying 33 | // this format will return images in this format. The image data pointer will still point to the 34 | // "top" of the image, althought he stride will be negative. You can get the "bottom" line of the image 35 | // using : video_data.p_data + (video_data.yres - 1)*video_data.line_stride_in_bytes 36 | const COLOR_FORMAT_BGRX_BGRA_FLIPPED = 200; 37 | 38 | const COLOR_FORMAT_FASTEST = 100; 39 | 40 | const BANDWIDTH_METADATA_ONLY = -10; // Receive metadata. 41 | const BANDWIDTH_AUDIO_ONLY = 10; // Receive metadata, audio. 42 | const BANDWIDTH_LOWEST = 0; // Receive metadata, audio, video at a lower bandwidth and resolution. 43 | const BANDWIDTH_HIGHEST = 100; // Receive metadata, audio, video at full resolution. 44 | 45 | const FORMAT_TYPE_PROGRESSIVE = 1; 46 | const FORMAT_TYPE_INTERLACED = 0; 47 | const FORMAT_TYPE_FIELD_0 = 2; 48 | const FORMAT_TYPE_FIELD_1 = 3; 49 | 50 | // Default NDI audio format 51 | // Channels stored one after the other in each block - 32-bit floating point values 52 | const AUDIO_FORMAT_FLOAT_32_SEPARATE = 0; 53 | // Alternative NDI audio foramt 54 | // Channels stored as channel-interleaved 32-bit floating point values 55 | const AUDIO_FORMAT_FLOAT_32_INTERLEAVED = 1; 56 | // Alternative NDI audio format 57 | // Channels stored as channel-interleaved 16-bit integer values 58 | const AUDIO_FORMAT_INT_16_INTERLEAVED = 2; 59 | 60 | class GrandioseFinder{ 61 | #addon 62 | 63 | constructor(options) { 64 | const newOptions = options ? { 65 | showLocalSources: options.showLocalSources, 66 | groups: Array.isArray(options.groups) ? options.groups.join(','):options.groups, 67 | extraIPs: Array.isArray(options.extraIPs) ? options.extraIPs.join(','):options.extraIPs, 68 | } : undefined 69 | this.#addon = new addon.GrandioseFinder(newOptions) 70 | } 71 | 72 | dispose(...args) { 73 | return this.#addon.dispose(...args) 74 | } 75 | 76 | getCurrentSources(...args) { 77 | return this.#addon.getCurrentSources(...args) 78 | } 79 | } 80 | 81 | // API compataibility in a find implemenation 82 | async function findCompat(options = {}, waitMs = 0) { 83 | if (options.showLocalSources === undefined) options.showLocalSources = true 84 | const finder = new GrandioseFinder(options) 85 | 86 | if (!waitMs || typeof waitMs !== 'number') waitMs = 10000 87 | const maxTime = Date.now() + waitMs 88 | 89 | try { 90 | // Until the timeout has been reached 91 | while(Date.now() < maxTime) { 92 | // Sleep for a short period 93 | await setTimeoutPromise(50) 94 | 95 | // Check if any have been found 96 | const sources = finder.getCurrentSources() 97 | if (sources.length > 0) { 98 | return sources 99 | } 100 | } 101 | 102 | throw new Error('No sources were found') 103 | 104 | } finally { 105 | finder.dispose() 106 | } 107 | } 108 | 109 | module.exports = { 110 | version: addon.version, 111 | find: findCompat, 112 | GrandioseFinder: GrandioseFinder, 113 | isSupportedCPU: addon.isSupportedCPU, 114 | receive: addon.receive, 115 | send: addon.send, 116 | COLOR_FORMAT_BGRX_BGRA, COLOR_FORMAT_UYVY_BGRA, 117 | COLOR_FORMAT_RGBX_RGBA, COLOR_FORMAT_UYVY_RGBA, 118 | COLOR_FORMAT_BGRX_BGRA_FLIPPED, COLOR_FORMAT_FASTEST, 119 | BANDWIDTH_METADATA_ONLY, BANDWIDTH_AUDIO_ONLY, 120 | BANDWIDTH_LOWEST, BANDWIDTH_HIGHEST, 121 | FORMAT_TYPE_PROGRESSIVE, FORMAT_TYPE_INTERLACED, 122 | FORMAT_TYPE_FIELD_0, FORMAT_TYPE_FIELD_1, 123 | AUDIO_FORMAT_FLOAT_32_SEPARATE, AUDIO_FORMAT_FLOAT_32_INTERLEAVED, 124 | AUDIO_FORMAT_INT_16_INTERLEAVED 125 | }; 126 | -------------------------------------------------------------------------------- /lib/libndi_licenses.txt: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | NewTek NDI 3 | Copyright (C) 2014-2022, NewTek, inc. 4 | ******************************************************************************** 5 | 6 | The NDI SDK license available at: 7 | http://new.tk/ndisdk_license/ 8 | 9 | The NDI Embedded SDK license is available at 10 | http://new.tk/ndisdk_embedded_license/ 11 | 12 | For more information about NDI please visit: 13 | http://ndi.tv/ 14 | 15 | This file should be included with all distribution of the binary files included with the NDI SDK. 16 | 17 | We are truly thankful for the NDI community and the amazing support that it has shown us over the years. 18 | 19 | ******************************************************************************** 20 | NDI gratefully uses the following third party libraries. 21 | 22 | ******************************************************************************** 23 | RapidJSON (https://rapidjson.org) 24 | 25 | Tencent is pleased to support the open source community by making RapidJSON available. 26 | 27 | Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. 28 | 29 | Licensed under the MIT License (the "License"); you may not use this file except 30 | in compliance with the License. You may obtain a copy of the License at 31 | 32 | http://opensource.org/licenses/MIT 33 | 34 | Unless required by applicable law or agreed to in writing, software distributed 35 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 36 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 37 | specific language governing permissions and limitations under the License. 38 | 39 | **************************************************** 40 | SpeexDSP (https://www.speex.org), resampling code only. 41 | 42 | © 2002-2003, Jean-Marc Valin/Xiph.Org Foundation 43 | 44 | Redistribution and use in source and binary forms, with or without modification, are permitted provided 45 | that the following conditions are met: 46 | 47 | Redistributions of source code must retain the above copyright notice, this list of conditions and the 48 | following disclaimer. 49 | 50 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 51 | following disclaimer in the documentation and/or other materials provided with the distribution. 52 | Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or 53 | promote products derived from this software without specific prior written permission. 54 | 55 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, 56 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 57 | ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 58 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 59 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 60 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 61 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 62 | 63 | ******************************************************************************** 64 | RapidXML (http://rapidxml.sourceforge.net) 65 | 66 | Copyright (c) 2006, 2007 Marcin Kalicinski 67 | 68 | Permission is hereby granted, free of charge, to any person or organization 69 | obtaining a copy of the software and accompanying documentation covered by 70 | this license (the "Software") to use, reproduce, display, distribute, 71 | execute, and transmit the Software, and to prepare derivative works of the 72 | Software, and to permit third-parties to whom the Software is furnished to 73 | do so, all subject to the following: 74 | 75 | The copyright notices in the Software and this entire statement, including 76 | the above license grant, this restriction and the following disclaimer, 77 | must be included in all copies of the Software, in whole or in part, and 78 | all derivative works of the Software, unless such copies or derivative 79 | works are solely in the form of machine-executable object code generated by 80 | a source language processor. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 84 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 85 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 86 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 87 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 88 | DEALINGS IN THE SOFTWARE. 89 | 90 | ******************************************************************************** 91 | CxxUrl (https://github.com/chmike/CxxUrl) 92 | 93 | The MIT License (MIT) 94 | 95 | Copyright (c) 2015 Christophe Meessen 96 | 97 | Permission is hereby granted, free of charge, to any person obtaining a copy 98 | of this software and associated documentation files (the "Software"), to deal 99 | in the Software without restriction, including without limitation the rights 100 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 101 | copies of the Software, and to permit persons to whom the Software is 102 | furnished to do so, subject to the following conditions: 103 | 104 | The above copyright notice and this permission notice shall be included in all 105 | copies or substantial portions of the Software. 106 | 107 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 108 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 109 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 110 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 111 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 112 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 113 | SOFTWARE. 114 | 115 | ******************************************************************************** 116 | ASIO (https://think-async.com) 117 | 118 | Boost Software License - Version 1.0 - August 17th, 2003 119 | 120 | Permission is hereby granted, free of charge, to any person or organization 121 | obtaining a copy of the software and accompanying documentation covered by 122 | this license (the "Software") to use, reproduce, display, distribute, 123 | execute, and transmit the Software, and to prepare derivative works of the 124 | Software, and to permit third-parties to whom the Software is furnished to 125 | do so, all subject to the following: 126 | 127 | The copyright notices in the Software and this entire statement, including 128 | the above license grant, this restriction and the following disclaimer, 129 | must be included in all copies of the Software, in whole or in part, and 130 | all derivative works of the Software, unless such copies or derivative 131 | works are solely in the form of machine-executable object code generated by 132 | a source language processor. 133 | 134 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 135 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 136 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 137 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 138 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 139 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 140 | DEALINGS IN THE SOFTWARE. 141 | 142 | Copyright (c) Microsoft Corporation. 143 | 144 | ******************************************************************************** 145 | MsQuic (https://github.com/microsoft/msquic) 146 | 147 | MIT License 148 | 149 | Permission is hereby granted, free of charge, to any person obtaining a copy 150 | of this software and associated documentation files (the "Software"), to deal 151 | in the Software without restriction, including without limitation the rights 152 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 153 | copies of the Software, and to permit persons to whom the Software is 154 | furnished to do so, subject to the following conditions: 155 | 156 | The above copyright notice and this permission notice shall be included in all 157 | copies or substantial portions of the Software. 158 | 159 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 160 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 161 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 162 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 163 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 164 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 165 | SOFTWARE. 166 | 167 | ******************************************************************************** 168 | Opus Interactive Audio Codec (https://opus-codec.org) 169 | 170 | Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, 171 | Jean-Marc Valin, Timothy B. Terriberry, 172 | CSIRO, Gregory Maxwell, Mark Borgerding, 173 | Erik de Castro Lopo 174 | 175 | Redistribution and use in source and binary forms, with or without 176 | modification, are permitted provided that the following conditions 177 | are met: 178 | 179 | - Redistributions of source code must retain the above copyright 180 | notice, this list of conditions and the following disclaimer. 181 | 182 | - Redistributions in binary form must reproduce the above copyright 183 | notice, this list of conditions and the following disclaimer in the 184 | documentation and/or other materials provided with the distribution. 185 | 186 | - Neither the name of Internet Society, IETF or IETF Trust, nor the 187 | names of specific contributors, may be used to endorse or promote 188 | products derived from this software without specific prior written 189 | permission. 190 | 191 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 192 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 193 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 194 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 195 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 196 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 197 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 198 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 199 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 200 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 201 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | 203 | Opus is subject to the royalty-free patent licenses which are 204 | specified at: 205 | 206 | Xiph.Org Foundation: 207 | https://datatracker.ietf.org/ipr/1524/ 208 | 209 | Microsoft Corporation: 210 | https://datatracker.ietf.org/ipr/1914/ 211 | 212 | Broadcom Corporation: 213 | https://datatracker.ietf.org/ipr/1526/ 214 | 215 | ******************************************************************************** 216 | Updated to C++, zedwood.com 2012 217 | Based on Olivier Gay's version 218 | See Modified BSD License below: 219 | 220 | FIPS 180-2 SHA-224/256/384/512 implementation 221 | Issue date: 04/30/2005 222 | http://www.ouah.org/ogay/sha2/ 223 | 224 | Copyright (C) 2005, 2007 Olivier Gay 225 | All rights reserved. 226 | 227 | Redistribution and use in source and binary forms, with or without 228 | modification, are permitted provided that the following conditions 229 | are met: 230 | 1. Redistributions of source code must retain the above copyright 231 | notice, this list of conditions and the following disclaimer. 232 | 2. Redistributions in binary form must reproduce the above copyright 233 | notice, this list of conditions and the following disclaimer in the 234 | documentation and/or other materials provided with the distribution. 235 | 3. Neither the name of the project nor the names of its contributors 236 | may be used to endorse or promote products derived from this software 237 | without specific prior written permission. 238 | 239 | THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 240 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 241 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 242 | ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 243 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 244 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 245 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 246 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 247 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 248 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 249 | SUCH DAMAGE. 250 | 251 | -------------------------------------------------------------------------------- /lib/linux_arm64/libndi.so.5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/linux_arm64/libndi.so.5 -------------------------------------------------------------------------------- /lib/linux_x64/libndi.so.5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/linux_x64/libndi.so.5 -------------------------------------------------------------------------------- /lib/mac_universal/libndi.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/mac_universal/libndi.dylib -------------------------------------------------------------------------------- /lib/win_x64/Processing.NDI.Lib.x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/win_x64/Processing.NDI.Lib.x64.dll -------------------------------------------------------------------------------- /lib/win_x64/Processing.NDI.Lib.x64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/win_x64/Processing.NDI.Lib.x64.lib -------------------------------------------------------------------------------- /lib/win_x86/Processing.NDI.Lib.x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/win_x86/Processing.NDI.Lib.x86.dll -------------------------------------------------------------------------------- /lib/win_x86/Processing.NDI.Lib.x86.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streampunk/grandiose/c350e0fb6e74bbf2e4b10144fee456aa1af93f47/lib/win_x86/Processing.NDI.Lib.x86.lib -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grandiose", 3 | "version": "0.1.0", 4 | "description": "Node.JS native bindings to the Newtek NDI SDK.", 5 | "main": "index.js", 6 | "scripts": { 7 | "install": "pkg-prebuilds-verify ./binding-options.js || node-gyp rebuild", 8 | "build": "node-gyp build", 9 | "rebuild": "node-gyp clean configure build", 10 | "test": "echo \"Error: no test specified\" && exit 0" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Streampunk/grandiose.git" 15 | }, 16 | "keywords": [ 17 | "Newtek", 18 | "NDI", 19 | "network", 20 | "device", 21 | "interface" 22 | ], 23 | "author": "Streampunk Media (http://www.streampunk.media)", 24 | "contributors": [ 25 | { 26 | "name": "Julian Waller", 27 | "email": "git@julusian.co.uk" 28 | } 29 | ], 30 | "license": "Apache-2.0", 31 | "bugs": { 32 | "url": "https://github.com/Streampunk/grandiose/issues" 33 | }, 34 | "homepage": "https://github.com/Streampunk/grandiose#readme", 35 | "dependencies": { 36 | "node-addon-api": "^7.0.0", 37 | "pkg-prebuilds": "^0.2.1" 38 | }, 39 | "devDependencies": { 40 | "segfault-handler": "^1.3.0" 41 | }, 42 | "binary": { 43 | "napi_versions": [ 44 | 8 45 | ] 46 | }, 47 | "engines": { 48 | "node": ">=16.0" 49 | }, 50 | "files": [ 51 | "src", 52 | "lib", 53 | "include", 54 | "index.d.ts", 55 | "index.js", 56 | "binding-options.js", 57 | "binding.gyp", 58 | "prebuilds" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /scratch/api_sketch.txt: -------------------------------------------------------------------------------- 1 | const grandiose = require('grandiose'); 2 | 3 | # Find 4 | 5 | let sources = await grandiose.find(, ); 6 | 7 | is { 8 | showLocalSources: , 9 | groups: or >, 10 | extraIPs: or > 11 | } 12 | is number of milliseconds to wait before failing 13 | 14 | sources is an > where source is { 15 | name: , 16 | urlAddress: , 17 | embedded: > 18 | } 19 | 20 | # Receive 21 | 22 | let receiver = grandiose.receive(); 23 | 24 | is { 25 | source: , 26 | colorFormat: > = COLOR_FORMAT_FASTEST, 27 | bandwidth > = BANDWIDTH_HIGHEST, 28 | allowVideoFields: = true, 29 | name: = nullptr 30 | }; 31 | 32 | receiver is { 33 | settings: , 34 | embedded: >, 35 | video: >>, 36 | audio: >>, 37 | metadata: >>, 38 | data: >> 39 | } 40 | 41 | let video = await receiver.video( = 10000); 42 | 43 | video is { 44 | type: 'video', 45 | xres: , 46 | yres: , 47 | fourcc: , // define constants FOURCC_TYPE_* 48 | frameRate: >, 49 | aspectRatio: , 50 | formatType: >, 51 | lineStrideBytes: , 52 | timecode: [, ], 53 | data: , 54 | metadata: , 55 | timestamp: or 56 | } 57 | 58 | let audio = await receiver.audio( = { 59 | audioFormat = AUDIO_FORMAT_FLOAT_32_SEPARATE, 60 | referenceLevel = 20 // Only relevant for 16-bit ints 61 | }, = 10000); 62 | 63 | audio is { 64 | type: 'audio', 65 | audioFormat: > 66 | sampleRateN: , 67 | sampleRateD: , 68 | channels: , 69 | samples: , 70 | timecode: [, ], 71 | data: , 72 | channelStrideBytes: , 73 | metadata: , 74 | timestamp: or 75 | } 76 | 77 | let metadata = await receiver.metadata(); 78 | 79 | metadata is { 80 | type: 'metadata', 81 | timecode: , 82 | data: 83 | } 84 | 85 | let data = await receiver.data(); // Whatever comes next 86 | 87 | data is one of type video, audio or data, whatever comes next. Also, tally and 88 | change. 89 | 90 | tally is { 91 | type: 'tally', 92 | onProgram: = false, 93 | onPreview: = false 94 | } 95 | 96 | change is { 97 | type: 'change' 98 | } 99 | 100 | # send 101 | 102 | let sender = grandiose.send(); 103 | 104 | is { 105 | name: , 106 | groups: or > = nullptr, 107 | clockVideo: = true, 108 | clockAudio: = false 109 | }; 110 | 111 | is { 112 | settings: , 113 | embedded: > 114 | } 115 | 116 | await sender.send({ 117 | type: 'audio', 118 | ... 119 | }); 120 | 121 | await sender.send({ 122 | type: 'video', 123 | ... 124 | }); 125 | 126 | await sender.send({ 127 | type: 'metadata', 128 | ... 129 | }); 130 | -------------------------------------------------------------------------------- /scratch/find-watcher.js: -------------------------------------------------------------------------------- 1 | const grandiose = require('../index.js'); 2 | 3 | // const finder = grandiose.createFinderHandle() 4 | 5 | console.log(grandiose.version(), grandiose.isSupportedCPU()) 6 | 7 | const finder = new grandiose.GrandioseFinder({ 8 | showLocalSources: true 9 | }) 10 | console.log(finder) 11 | 12 | // finder.dispose() 13 | 14 | setInterval(() => { 15 | console.log(finder.getCurrentSources()) 16 | }, 500) 17 | 18 | setTimeout(() => { 19 | // Dipose eventually 20 | finder.dispose() 21 | }, 10000) 22 | 23 | // grandiose.find().then(ls => { 24 | // console.log(ls) 25 | // }) -------------------------------------------------------------------------------- /scratch/scratchReceiveAudio.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | const g = require('../index.js'); 17 | 18 | async function run() { 19 | let l = await g.find(); 20 | console.log('>>> FOUND >>>', l); 21 | let r = await g.receive({ source: l[0] }); 22 | console.log('>>> RECEIVER >>>', r); 23 | for ( let x = 0 ; x < 100 ; x++ ) { 24 | let a = await r.audio({ audioFormat : g.AUDIO_FORMAT_INT_16_INTERLEAVED, referenceLevel: 0 }); 25 | console.log('>>> AUDIO >>>', a); 26 | console.log(a.data.length); 27 | v = null; 28 | } 29 | l = null; 30 | r = null; 31 | v = null; 32 | console.log(process.memoryUsage()); 33 | setTimeout(() => { global.gc(); 34 | console.log("that's almost all folks", process.memoryUsage()); }, 1000); 35 | setTimeout(() => { global.gc(); console.log("that's it", process.memoryUsage()); }, 2000); 36 | setTimeout(() => { global.gc(); console.log("that's really it", process.memoryUsage()); }, 3000); 37 | } 38 | 39 | run(); 40 | -------------------------------------------------------------------------------- /scratch/scratchReceiveData.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | const g = require('../index.js'); 17 | 18 | async function run() { 19 | let l = await g.find(); 20 | console.log('>>> FOUND >>>', l); 21 | let r = await g.receive({ source: l[0] }); 22 | console.log('>>> RECEIVER >>>', r); 23 | for ( let x = 0 ; x < 1000 ; x++ ) { 24 | let d = await r.data({ audioFormat : g.AUDIO_FORMAT_INT_16_INTERLEAVED, referenceLevel: 0 }); 25 | console.log('>>> DATA >>>', d.type); 26 | // console.log(a.data.length); 27 | v = null; 28 | } 29 | l = null; 30 | r = null; 31 | d = null; 32 | console.log(process.memoryUsage()); 33 | setTimeout(() => { global.gc(); 34 | console.log("that's almost all folks", process.memoryUsage()); }, 1000); 35 | setTimeout(() => { global.gc(); console.log("that's it", process.memoryUsage()); }, 2000); 36 | setTimeout(() => { global.gc(); console.log("that's really it", process.memoryUsage()); }, 3000); 37 | } 38 | 39 | run(); 40 | -------------------------------------------------------------------------------- /scratch/scratchReceiveMetadata.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | const g = require('../index.js'); 17 | 18 | async function run() { 19 | let l = await g.find(); 20 | console.log('>>> FOUND >>>', l); 21 | let r = await g.receive({ source: l[0] }); 22 | console.log('>>> RECEIVER >>>', r); 23 | for ( let x = 0 ; x < 10 ; x++ ) { 24 | let m = await r.metadata(); 25 | console.log('>>> METADATA >>>', m); 26 | console.log(process.memoryUsage()); 27 | m = null; 28 | } 29 | l = null; 30 | r = null; 31 | m = null; 32 | console.log(process.memoryUsage()); 33 | setTimeout(() => { global.gc(); 34 | console.log("that's almost all folks", process.memoryUsage()); }, 1000); 35 | setTimeout(() => { global.gc(); console.log("that's it", process.memoryUsage()); }, 2000); 36 | setTimeout(() => { global.gc(); console.log("that's really it", process.memoryUsage()); }, 3000); 37 | } 38 | 39 | run(); 40 | -------------------------------------------------------------------------------- /scratch/scratchReceiveVideo.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | const g = require('../index.js'); 17 | 18 | async function run() { 19 | let l = await g.find(); 20 | console.log('>>> FOUND >>>', l); 21 | let r = await g.receive({ source: l[0] }); 22 | console.log('>>> RECEIVER >>>', r); 23 | for ( let x = 0 ; x < 10 ; x++ ) { 24 | let v = await r.video(); 25 | console.log('>>> VIDEO >>>', v); 26 | console.log(process.memoryUsage()); 27 | v = null; 28 | } 29 | l = null; 30 | r = null; 31 | v = null; 32 | console.log(process.memoryUsage()); 33 | setTimeout(() => { global.gc(); 34 | console.log("that's almost all folks", process.memoryUsage()); }, 1000); 35 | setTimeout(() => { global.gc(); console.log("that's it", process.memoryUsage()); }, 2000); 36 | setTimeout(() => { global.gc(); console.log("that's really it", process.memoryUsage()); }, 3000); 37 | } 38 | 39 | run(); 40 | -------------------------------------------------------------------------------- /src/grandiose.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifdef _WIN32 22 | #ifdef _WIN64 23 | #pragma comment(lib, "Processing.NDI.Lib.x64.lib") 24 | #else // _WIN64 25 | #pragma comment(lib, "Processing.NDI.Lib.x86.lib") 26 | #endif // _WIN64 27 | #endif // _WIN32 28 | 29 | #include "grandiose_util.h" 30 | #include "grandiose_find.h" 31 | #include "grandiose_send.h" 32 | #include "grandiose_receive.h" 33 | #include "napi.h" 34 | 35 | Napi::Value version(const Napi::CallbackInfo &info) 36 | { 37 | const char *ndiVersion = NDIlib_version(); 38 | return Napi::String::New(info.Env(), ndiVersion); 39 | } 40 | 41 | Napi::Value isSupportedCPU(const Napi::CallbackInfo &info) 42 | { 43 | return Napi::Boolean::New(info.Env(), NDIlib_is_supported_CPU()); 44 | } 45 | 46 | struct GrandioseInstanceData 47 | { 48 | std::unique_ptr finder; 49 | }; 50 | 51 | Napi::Object Init(Napi::Env env, Napi::Object exports) 52 | { 53 | 54 | // Not required, but "correct" (see the SDK documentation). 55 | if (!NDIlib_initialize()) // TODO - throw in a way that users can catch 56 | return exports; 57 | 58 | napi_status status; 59 | napi_property_descriptor desc[] = { 60 | DECLARE_NAPI_METHOD("send", send), 61 | DECLARE_NAPI_METHOD("receive", receive)}; 62 | status = napi_define_properties(env, exports, 2, desc); 63 | 64 | exports.Set("version", Napi::Function::New(env, version)); 65 | exports.Set("isSupportedCPU", Napi::Function::New(env, isSupportedCPU)); 66 | 67 | auto finderRef = GrandioseFinder::Initialize(env, exports); 68 | 69 | // Store the constructor as the add-on instance data. This will allow this 70 | // add-on to support multiple instances of itself running on multiple worker 71 | // threads, as well as multiple instances of itself running in different 72 | // contexts on the same thread. 73 | env.SetInstanceData(new GrandioseInstanceData{ 74 | std::move(finderRef), 75 | }); 76 | 77 | return exports; 78 | } 79 | 80 | NODE_API_MODULE(grandiose, Init) 81 | -------------------------------------------------------------------------------- /src/grandiose_find.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #include 17 | #include 18 | 19 | #ifdef _WIN32 20 | #ifdef _WIN64 21 | #pragma comment(lib, "Processing.NDI.Lib.x64.lib") 22 | #else // _WIN64 23 | #pragma comment(lib, "Processing.NDI.Lib.x86.lib") 24 | #endif // _WIN64 25 | #endif // _WIN32 26 | 27 | #include "grandiose_util.h" 28 | #include "grandiose_find.h" 29 | #include "util.h" 30 | 31 | std::unique_ptr GrandioseFinder::Initialize(const Napi::Env &env, Napi::Object exports) 32 | { 33 | Napi::HandleScope scope(env); 34 | 35 | Napi::Function func = DefineClass(env, "GrandioseFinder", { 36 | InstanceMethod<&GrandioseFinder::Dispose>("dispose", static_cast(napi_writable | napi_configurable)), 37 | 38 | InstanceMethod<&GrandioseFinder::GetSources>("getCurrentSources", static_cast(napi_writable | napi_configurable)), 39 | 40 | }); 41 | 42 | // Create a persistent reference to the class constructor 43 | std::unique_ptr constructor = std::make_unique(); 44 | *constructor = Napi::Persistent(func); 45 | exports.Set("GrandioseFinder", func); 46 | 47 | return constructor; 48 | } 49 | 50 | GrandioseFinder::GrandioseFinder(const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) 51 | { 52 | if (info.Length() > 0 && !info[0].IsUndefined() && !info[0].IsNull()) 53 | { 54 | if (!info[0].IsObject()) 55 | { 56 | Napi::Error::New(info.Env(), "Expected an options object").ThrowAsJavaScriptException(); 57 | return; 58 | } 59 | Napi::Object rawOptions = info[0].As(); 60 | 61 | bool rawShowLocalSources = false; 62 | if (parseBoolean(info.Env(), rawOptions.Get("showLocalSources"), rawShowLocalSources)) 63 | { 64 | options.showLocalSources = rawShowLocalSources; 65 | } 66 | else 67 | { 68 | Napi::Error::New(info.Env(), "options.showLocalSources must be a boolean").ThrowAsJavaScriptException(); 69 | return; 70 | } 71 | 72 | std::string rawGroups = ""; 73 | if (parseString(info.Env(), rawOptions.Get("groups"), rawGroups)) 74 | { 75 | options.groups = rawGroups; 76 | } 77 | else 78 | { 79 | Napi::Error::New(info.Env(), "options.groups must be an array of strings").ThrowAsJavaScriptException(); 80 | return; 81 | } 82 | 83 | std::string rawExtraIps = ""; 84 | if (parseString(info.Env(), rawOptions.Get("extraIPs"), rawExtraIps)) 85 | { 86 | options.extraIPs = rawExtraIps; 87 | } 88 | else 89 | { 90 | Napi::Error::New(info.Env(), "options.extraIPs must be an array of strings").ThrowAsJavaScriptException(); 91 | return; 92 | } 93 | } 94 | 95 | NDIlib_find_create_t find_create; 96 | find_create.show_local_sources = options.showLocalSources; 97 | find_create.p_groups = options.groups.length() > 0 ? options.groups.c_str() : nullptr; 98 | find_create.p_extra_ips = options.extraIPs.length() > 0 ? options.extraIPs.c_str() : nullptr; 99 | 100 | handle = NDIlib_find_create2(&find_create); 101 | if (!handle) 102 | { 103 | Napi::Error::New(info.Env(), "Failed to initialize NDI finder").ThrowAsJavaScriptException(); 104 | return; 105 | } 106 | } 107 | 108 | GrandioseFinder::~GrandioseFinder() 109 | { 110 | cleanup(); 111 | } 112 | 113 | void GrandioseFinder::cleanup() 114 | { 115 | if (handle != nullptr) 116 | { 117 | NDIlib_find_destroy(handle); 118 | handle = nullptr; 119 | } 120 | } 121 | 122 | Napi::Value GrandioseFinder::Dispose(const Napi::CallbackInfo &info) 123 | { 124 | cleanup(); 125 | 126 | return info.Env().Null(); 127 | } 128 | 129 | Napi::Value GrandioseFinder::GetSources(const Napi::CallbackInfo &info) 130 | { 131 | Napi::Env env = info.Env(); 132 | 133 | if (!handle) 134 | { 135 | Napi::Error::New(info.Env(), "GrandioseFinder has been disposed").ThrowAsJavaScriptException(); 136 | return env.Null(); 137 | } 138 | 139 | uint32_t count = 0; 140 | const NDIlib_source_t *sources = NDIlib_find_get_current_sources(handle, &count); 141 | 142 | if (!sources || count == 0) 143 | return Napi::Array::New(env, 0); 144 | 145 | Napi::Array result = Napi::Array::New(env, count); 146 | for (size_t i = 0; i < count; i++) 147 | { 148 | const NDIlib_source_t &source = sources[i]; 149 | result[i] = convertSourceToNapi(env, source); 150 | } 151 | 152 | return result; 153 | } 154 | -------------------------------------------------------------------------------- /src/grandiose_find.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "napi.h" 19 | #include "grandiose_util.h" 20 | #include 21 | #include 22 | 23 | struct GrandioseFinderOptions 24 | { 25 | bool showLocalSources; 26 | std::string groups; 27 | std::string extraIPs; 28 | }; 29 | 30 | class GrandioseFinder : public Napi::ObjectWrap 31 | { 32 | public: 33 | GrandioseFinder(const Napi::CallbackInfo &info); 34 | static std::unique_ptr Initialize(const Napi::Env &env, Napi::Object exports); 35 | 36 | ~GrandioseFinder(); 37 | 38 | private: 39 | // Napi::Value GetProperties(const Napi::CallbackInfo &info); 40 | Napi::Value Dispose(const Napi::CallbackInfo &info); 41 | 42 | Napi::Value GetSources(const Napi::CallbackInfo &info); 43 | 44 | void cleanup(); 45 | 46 | NDIlib_find_instance_t handle = nullptr; 47 | GrandioseFinderOptions options; 48 | }; 49 | -------------------------------------------------------------------------------- /src/grandiose_receive.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #ifndef GRANDIOSE_RECEIVE_H 17 | #define GRANDIOSE_RECEIVE_H 18 | 19 | #include "node_api.h" 20 | #include "grandiose_util.h" 21 | 22 | napi_value receive(napi_env env, napi_callback_info info); 23 | napi_value videoReceive(napi_env env, napi_callback_info info); 24 | napi_value audioReceive(napi_env env, napi_callback_info info); 25 | napi_value metadataReceive(napi_env env, napi_callback_info info); 26 | napi_value dataReceive(napi_env env, napi_callback_info info); 27 | 28 | struct receiveCarrier : carrier { 29 | NDIlib_source_t* source = nullptr; 30 | NDIlib_recv_color_format_e colorFormat = NDIlib_recv_color_format_fastest; 31 | NDIlib_recv_bandwidth_e bandwidth = NDIlib_recv_bandwidth_highest; 32 | bool allowVideoFields = true; 33 | char* name = nullptr; 34 | NDIlib_recv_instance_t recv; 35 | ~receiveCarrier() { 36 | free(name); 37 | if (source != nullptr) { 38 | delete source; 39 | } 40 | } 41 | }; 42 | 43 | struct dataCarrier : carrier { 44 | uint32_t wait = 10000; 45 | NDIlib_recv_instance_t recv; 46 | NDIlib_frame_type_e frameType; 47 | NDIlib_video_frame_v2_t videoFrame; 48 | NDIlib_audio_frame_v2_t audioFrame; 49 | NDIlib_audio_frame_interleaved_16s_t audioFrame16s; 50 | NDIlib_audio_frame_interleaved_32f_t audioFrame32fIlvd; 51 | int32_t referenceLevel = 20; 52 | Grandiose_audio_format_e audioFormat = Grandiose_audio_format_float_32_separate; 53 | NDIlib_metadata_frame_t metadataFrame; 54 | ~dataCarrier() { 55 | delete[] audioFrame16s.p_data; 56 | delete[] audioFrame32fIlvd.p_data; 57 | } 58 | }; 59 | 60 | #endif /* GRANDIOSE_RECEIVE_H */ 61 | -------------------------------------------------------------------------------- /src/grandiose_send.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #include 17 | #include 18 | 19 | #ifdef _WIN32 20 | #ifdef _WIN64 21 | #pragma comment(lib, "Processing.NDI.Lib.x64.lib") 22 | #else // _WIN64 23 | #pragma comment(lib, "Processing.NDI.Lib.x86.lib") 24 | #endif // _WIN64 25 | #endif // _WIN32 26 | 27 | #include "grandiose_send.h" 28 | #include "grandiose_util.h" 29 | 30 | napi_value videoSend(napi_env env, napi_callback_info info); 31 | 32 | void sendExecute(napi_env env, void* data) { 33 | sendCarrier* c = (sendCarrier *) data; 34 | 35 | NDIlib_send_create_t NDI_send_create_desc; 36 | 37 | NDI_send_create_desc.p_ndi_name = c->name; 38 | NDI_send_create_desc.p_groups = c->groups; 39 | NDI_send_create_desc.clock_video = c->clockVideo; 40 | NDI_send_create_desc.clock_audio = c->clockAudio; 41 | c->send = NDIlib_send_create(&NDI_send_create_desc); 42 | if (!c->send) { 43 | c->status = GRANDIOSE_SEND_CREATE_FAIL; 44 | c->errorMsg = "Failed to create NDI sender."; 45 | return; 46 | } 47 | } 48 | 49 | void finalizeSend(napi_env env, void* data, void* hint) { 50 | printf("Releasing sender.\n"); 51 | NDIlib_send_destroy((NDIlib_send_instance_t) data); 52 | } 53 | 54 | void sendComplete(napi_env env, napi_status asyncStatus, void* data) { 55 | sendCarrier* c = (sendCarrier*) data; 56 | 57 | printf("Completing some send creation work.\n"); 58 | 59 | if (asyncStatus != napi_ok) { 60 | c->status = asyncStatus; 61 | c->errorMsg = "Async sender creation failed to complete."; 62 | } 63 | REJECT_STATUS; 64 | 65 | napi_value result; 66 | c->status = napi_create_object(env, &result); 67 | REJECT_STATUS; 68 | 69 | napi_value embedded; 70 | c->status = napi_create_external(env, c->send, finalizeSend, nullptr, &embedded); 71 | REJECT_STATUS; 72 | c->status = napi_set_named_property(env, result, "embedded", embedded); 73 | REJECT_STATUS; 74 | 75 | napi_value videoFn; 76 | c->status = napi_create_function(env, "video", NAPI_AUTO_LENGTH, videoSend, 77 | nullptr, &videoFn); 78 | REJECT_STATUS; 79 | c->status = napi_set_named_property(env, result, "video", videoFn); 80 | REJECT_STATUS; 81 | 82 | // napi_value audioFn; 83 | // c->status = napi_create_function(env, "audio", NAPI_AUTO_LENGTH, audioSend, 84 | // nullptr, &audioFn); 85 | // REJECT_STATUS; 86 | // c->status = napi_set_named_property(env, result, "audio", audioFn); 87 | // REJECT_STATUS; 88 | 89 | // napi_value metadataFn; 90 | // c->status = napi_create_function(env, "metadata", NAPI_AUTO_LENGTH, metadataReceive, 91 | // nullptr, &metadataFn); 92 | // REJECT_STATUS; 93 | // c->status = napi_set_named_property(env, result, "metadata", metadataFn); 94 | // REJECT_STATUS; 95 | 96 | // napi_value dataFn; 97 | // c->status = napi_create_function(env, "data", NAPI_AUTO_LENGTH, dataReceive, 98 | // nullptr, &dataFn); 99 | // REJECT_STATUS; 100 | // c->status = napi_set_named_property(env, result, "data", dataFn); 101 | // REJECT_STATUS; 102 | 103 | napi_value name, groups, clockVideo, clockAudio; 104 | c->status = napi_create_string_utf8(env, c->name, NAPI_AUTO_LENGTH, &name); 105 | REJECT_STATUS; 106 | c->status = napi_set_named_property(env, result, "name", name); 107 | REJECT_STATUS; 108 | 109 | c->status = napi_get_boolean(env, c->clockVideo, &clockVideo); 110 | REJECT_STATUS; 111 | c->status = napi_set_named_property(env, result, "clockVideo", clockVideo); 112 | REJECT_STATUS; 113 | 114 | c->status = napi_get_boolean(env, c->clockAudio, &clockAudio); 115 | REJECT_STATUS; 116 | c->status = napi_set_named_property(env, result, "clockAudio", clockAudio); 117 | REJECT_STATUS; 118 | 119 | napi_status status; 120 | status = napi_resolve_deferred(env, c->_deferred, result); 121 | FLOATING_STATUS; 122 | 123 | tidyCarrier(env, c); 124 | } 125 | 126 | napi_value send(napi_env env, napi_callback_info info) { 127 | napi_valuetype type; 128 | sendCarrier* c = new sendCarrier; 129 | 130 | napi_value promise; 131 | c->status = napi_create_promise(env, &c->_deferred, &promise); 132 | REJECT_RETURN; 133 | 134 | size_t argc = 1; 135 | napi_value args[1]; 136 | c->status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 137 | REJECT_RETURN; 138 | 139 | if (argc != (size_t) 1) REJECT_ERROR_RETURN( 140 | "Sender must be created with an object containing at least a 'name' property.", 141 | GRANDIOSE_INVALID_ARGS); 142 | 143 | c->status = napi_typeof(env, args[0], &type); 144 | REJECT_RETURN; 145 | bool isArray; 146 | c->status = napi_is_array(env, args[0], &isArray); 147 | REJECT_RETURN; 148 | if ((type != napi_object) || isArray) REJECT_ERROR_RETURN( 149 | "Single argument must be an object, not an array, containing at least a 'name' property.", 150 | GRANDIOSE_INVALID_ARGS); 151 | 152 | napi_value config = args[0]; 153 | napi_value name, groups, clockVideo, clockAudio; 154 | 155 | c->status = napi_get_named_property(env, config, "name", &name); 156 | REJECT_RETURN; 157 | c->status = napi_typeof(env, name, &type); 158 | REJECT_RETURN; 159 | if (type != napi_string) REJECT_ERROR_RETURN( 160 | "Name property must be of type string.", 161 | GRANDIOSE_INVALID_ARGS); 162 | size_t namel; 163 | c->status = napi_get_value_string_utf8(env, name, nullptr, 0, &namel); 164 | REJECT_RETURN; 165 | c->name = (char *) malloc(namel + 1); 166 | c->status = napi_get_value_string_utf8(env, name, c->name, namel + 1, &namel); 167 | REJECT_RETURN; 168 | 169 | // c->status = napi_get_named_property(env, config, "groups", &groups); 170 | // REJECT_RETURN; 171 | // c->status = napi_typeof(env, groups, &type); 172 | // REJECT_RETURN; 173 | 174 | // if (type != napi_undefined && type != napi_string) REJECT_ERROR_RETURN( 175 | // "Groups must be of type string or ....", 176 | // GRANDIOSE_INVALID_ARGS); 177 | 178 | c->status = napi_get_named_property(env, config, "clockVideo", &clockVideo); 179 | REJECT_RETURN; 180 | c->status = napi_typeof(env, clockVideo, &type); 181 | REJECT_RETURN; 182 | if (type != napi_undefined) { 183 | if (type != napi_boolean) REJECT_ERROR_RETURN( 184 | "ClockVideo property must be of type boolean.", 185 | GRANDIOSE_INVALID_ARGS); 186 | c->status = napi_get_value_bool(env, clockVideo, &c->clockVideo); 187 | REJECT_RETURN; 188 | } 189 | 190 | c->status = napi_get_named_property(env, config, "clockAudio", &clockAudio); 191 | REJECT_RETURN; 192 | c->status = napi_typeof(env, clockAudio, &type); 193 | REJECT_RETURN; 194 | if (type != napi_undefined) { 195 | if (type != napi_boolean) REJECT_ERROR_RETURN( 196 | "ClockAudio property must be of type boolean.", 197 | GRANDIOSE_INVALID_ARGS); 198 | c->status = napi_get_value_bool(env, clockAudio, &c->clockAudio); 199 | REJECT_RETURN; 200 | } 201 | 202 | napi_value resource_name; 203 | c->status = napi_create_string_utf8(env, "Send", NAPI_AUTO_LENGTH, &resource_name); 204 | REJECT_RETURN; 205 | c->status = napi_create_async_work(env, NULL, resource_name, sendExecute, 206 | sendComplete, c, &c->_request); 207 | REJECT_RETURN; 208 | c->status = napi_queue_async_work(env, c->_request); 209 | REJECT_RETURN; 210 | 211 | return promise; 212 | } 213 | 214 | 215 | void videoSendExecute(napi_env env, void* data) { 216 | sendDataCarrier* c = (sendDataCarrier*) data; 217 | 218 | NDIlib_send_send_video_v2(c->send, &c->videoFrame); 219 | } 220 | 221 | void videoSendComplete(napi_env env, napi_status asyncStatus, void* data) { 222 | sendDataCarrier* c = (sendDataCarrier*) data; 223 | napi_value result; 224 | napi_status status; 225 | 226 | c->status = napi_delete_reference(env, c->sourceBufferRef); 227 | REJECT_STATUS; 228 | 229 | if (asyncStatus != napi_ok) { 230 | c->status = asyncStatus; 231 | c->errorMsg = "Async video frame receive failed to complete."; 232 | } 233 | REJECT_STATUS; 234 | 235 | c->status = napi_create_object(env, &result); 236 | REJECT_STATUS; 237 | status = napi_resolve_deferred(env, c->_deferred, result); 238 | FLOATING_STATUS; 239 | 240 | tidyCarrier(env, c); 241 | } 242 | 243 | napi_value videoSend(napi_env env, napi_callback_info info) { 244 | napi_valuetype type; 245 | sendDataCarrier* c = new sendDataCarrier; 246 | 247 | napi_value promise; 248 | c->status = napi_create_promise(env, &c->_deferred, &promise); 249 | REJECT_RETURN; 250 | 251 | size_t argc = 1; 252 | napi_value args[1]; 253 | napi_value thisValue; 254 | c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr); 255 | REJECT_RETURN; 256 | 257 | napi_value sendValue; 258 | c->status = napi_get_named_property(env, thisValue, "embedded", &sendValue); 259 | REJECT_RETURN; 260 | void* sendData; 261 | c->status = napi_get_value_external(env, sendValue, &sendData); 262 | c->send = (NDIlib_send_instance_t) sendData; 263 | REJECT_RETURN; 264 | 265 | if (argc >= 1) { 266 | napi_value config; 267 | config = args[0]; 268 | c->status = napi_typeof(env, config, &type); 269 | REJECT_RETURN; 270 | if (type != napi_object) REJECT_ERROR_RETURN( 271 | "frame must be an object", 272 | GRANDIOSE_INVALID_ARGS); 273 | 274 | bool isArray, isBuffer; 275 | c->status = napi_is_array(env, config, &isArray); 276 | REJECT_RETURN; 277 | if (isArray) REJECT_ERROR_RETURN( 278 | "Argument to video send cannot be an array.", 279 | GRANDIOSE_INVALID_ARGS); 280 | 281 | napi_value param; 282 | c->status = napi_get_named_property(env, config, "xres", ¶m); 283 | REJECT_RETURN; 284 | c->status = napi_typeof(env, param, &type); 285 | REJECT_RETURN; 286 | if (type != napi_number) REJECT_ERROR_RETURN( 287 | "yres value must be a number", 288 | GRANDIOSE_INVALID_ARGS); 289 | c->status = napi_get_value_int32(env, param, &c->videoFrame.xres); 290 | REJECT_RETURN; 291 | 292 | c->status = napi_get_named_property(env, config, "yres", ¶m); 293 | REJECT_RETURN; 294 | c->status = napi_typeof(env, param, &type); 295 | REJECT_RETURN; 296 | if (type != napi_number) REJECT_ERROR_RETURN( 297 | "yres value must be a number", 298 | GRANDIOSE_INVALID_ARGS); 299 | c->status = napi_get_value_int32(env, param, &c->videoFrame.yres); 300 | REJECT_RETURN; 301 | 302 | c->status = napi_get_named_property(env, config, "frameRateN", ¶m); 303 | REJECT_RETURN; 304 | c->status = napi_typeof(env, param, &type); 305 | REJECT_RETURN; 306 | if (type != napi_number) REJECT_ERROR_RETURN( 307 | "frameRateN value must be a number", 308 | GRANDIOSE_INVALID_ARGS); 309 | c->status = napi_get_value_int32(env, param, &c->videoFrame.frame_rate_N); 310 | REJECT_RETURN; 311 | 312 | c->status = napi_get_named_property(env, config, "frameRateD", ¶m); 313 | REJECT_RETURN; 314 | c->status = napi_typeof(env, param, &type); 315 | REJECT_RETURN; 316 | if (type != napi_number) REJECT_ERROR_RETURN( 317 | "frameRateD value must be a number", 318 | GRANDIOSE_INVALID_ARGS); 319 | c->status = napi_get_value_int32(env, param, &c->videoFrame.frame_rate_D); 320 | REJECT_RETURN; 321 | 322 | c->status = napi_get_named_property(env, config, "pictureAspectRatio", ¶m); 323 | REJECT_RETURN; 324 | c->status = napi_typeof(env, param, &type); 325 | REJECT_RETURN; 326 | if (type != napi_number) REJECT_ERROR_RETURN( 327 | "pictureAspectRatio value must be a number", 328 | GRANDIOSE_INVALID_ARGS); 329 | double pictureAspectRatio; 330 | c->status = napi_get_value_double(env, param, &pictureAspectRatio); 331 | REJECT_RETURN; 332 | c->videoFrame.picture_aspect_ratio = (float) pictureAspectRatio; 333 | 334 | // TODO: timestamps 335 | // TODO: timecode 336 | 337 | c->status = napi_get_named_property(env, config, "frameFormatType", ¶m); 338 | REJECT_RETURN; 339 | c->status = napi_typeof(env, param, &type); 340 | REJECT_RETURN; 341 | if (type != napi_number) REJECT_ERROR_RETURN( 342 | "frameFormatType value must be a number", 343 | GRANDIOSE_INVALID_ARGS); 344 | int32_t formatType; 345 | c->status = napi_get_value_int32(env, param, &formatType); 346 | REJECT_RETURN; 347 | // TODO: checks 348 | c->videoFrame.frame_format_type = (NDIlib_frame_format_type_e) formatType; 349 | 350 | c->status = napi_get_named_property(env, config, "lineStrideBytes", ¶m); 351 | REJECT_RETURN; 352 | c->status = napi_typeof(env, param, &type); 353 | REJECT_RETURN; 354 | if (type != napi_number) REJECT_ERROR_RETURN( 355 | "lineStrideBytes value must be a number", 356 | GRANDIOSE_INVALID_ARGS); 357 | c->status = napi_get_value_int32(env, param, &c->videoFrame.line_stride_in_bytes); 358 | REJECT_RETURN; 359 | 360 | napi_value videoBuffer; 361 | c->status = napi_get_named_property(env, config, "data", &videoBuffer); 362 | REJECT_RETURN; 363 | c->status = napi_is_buffer(env, videoBuffer, &isBuffer); 364 | REJECT_RETURN; 365 | if (!isBuffer) REJECT_ERROR_RETURN( 366 | "data must be provided as a Node Buffer", 367 | GRANDIOSE_INVALID_ARGS); 368 | void * data; 369 | size_t length; 370 | c->status = napi_get_buffer_info(env, videoBuffer, &data, &length); 371 | REJECT_RETURN; 372 | c->videoFrame.p_data = (uint8_t*) data; 373 | c->status = napi_create_reference(env, videoBuffer, 1, &c->sourceBufferRef); 374 | REJECT_RETURN; 375 | // TODO: check length 376 | 377 | 378 | c->status = napi_get_named_property(env, config, "fourCC", ¶m); 379 | REJECT_RETURN; 380 | c->status = napi_typeof(env, param, &type); 381 | REJECT_RETURN; 382 | if (type != napi_number) REJECT_ERROR_RETURN( 383 | "fourCC value must be a number", 384 | GRANDIOSE_INVALID_ARGS); 385 | int32_t fourCC; 386 | c->status = napi_get_value_int32(env, param, &fourCC); 387 | REJECT_RETURN; 388 | // TODO: checks 389 | c->videoFrame.FourCC = (NDIlib_FourCC_video_type_e) fourCC; // TODO 390 | 391 | } else REJECT_ERROR_RETURN( 392 | "frame not provided", 393 | GRANDIOSE_INVALID_ARGS); 394 | 395 | napi_value resource_name; 396 | c->status = napi_create_string_utf8(env, "VideoSend", NAPI_AUTO_LENGTH, &resource_name); 397 | REJECT_RETURN; 398 | c->status = napi_create_async_work(env, NULL, resource_name, videoSendExecute, 399 | videoSendComplete, c, &c->_request); 400 | REJECT_RETURN; 401 | c->status = napi_queue_async_work(env, c->_request); 402 | REJECT_RETURN; 403 | 404 | return promise; 405 | } 406 | -------------------------------------------------------------------------------- /src/grandiose_send.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #ifndef GRANDIOSE_SEND_H 17 | #define GRANDIOSE_SEND_H 18 | 19 | #include "node_api.h" 20 | #include "grandiose_util.h" 21 | 22 | napi_value send(napi_env env, napi_callback_info info); 23 | 24 | struct sendCarrier : carrier { 25 | char* name = nullptr; 26 | char* groups = nullptr; 27 | bool clockVideo = false; 28 | bool clockAudio = false; 29 | NDIlib_send_instance_t send; 30 | ~sendCarrier() { 31 | free(name); 32 | } 33 | }; 34 | 35 | struct sendDataCarrier : carrier { 36 | NDIlib_send_instance_t send; 37 | NDIlib_video_frame_v2_t videoFrame; 38 | NDIlib_audio_frame_v2_t audioFrame; 39 | NDIlib_metadata_frame_t metadataFrame; 40 | napi_ref sourceBufferRef = nullptr; 41 | ~sendDataCarrier() { 42 | // TODO: free sourceBufferRef 43 | } 44 | }; 45 | 46 | 47 | #endif /* GRANDIOSE_SEND_H */ 48 | -------------------------------------------------------------------------------- /src/grandiose_util.cc: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "grandiose_util.h" 24 | #include "node_api.h" 25 | 26 | napi_status checkStatus(napi_env env, napi_status status, 27 | const char* file, uint32_t line) { 28 | 29 | napi_status infoStatus, throwStatus; 30 | const napi_extended_error_info *errorInfo; 31 | 32 | if (status == napi_ok) { 33 | // printf("Received status OK.\n"); 34 | return status; 35 | } 36 | 37 | infoStatus = napi_get_last_error_info(env, &errorInfo); 38 | assert(infoStatus == napi_ok); 39 | printf("NAPI error in file %s on line %i. Error %i: %s\n", file, line, 40 | errorInfo->error_code, errorInfo->error_message); 41 | 42 | if (status == napi_pending_exception) { 43 | printf("NAPI pending exception. Engine error code: %i\n", errorInfo->engine_error_code); 44 | return status; 45 | } 46 | 47 | char errorCode[20]; 48 | sprintf(errorCode, "%d", errorInfo->error_code); 49 | throwStatus = napi_throw_error(env, errorCode, errorInfo->error_message); 50 | assert(throwStatus == napi_ok); 51 | 52 | return napi_pending_exception; // Expect to be cast to void 53 | } 54 | 55 | long long microTime(std::chrono::high_resolution_clock::time_point start) { 56 | auto elapsed = std::chrono::high_resolution_clock::now() - start; 57 | return std::chrono::duration_cast(elapsed).count(); 58 | } 59 | 60 | const char* getNapiTypeName(napi_valuetype t) { 61 | switch (t) { 62 | case napi_undefined: return "undefined"; 63 | case napi_null: return "null"; 64 | case napi_boolean: return "boolean"; 65 | case napi_number: return "number"; 66 | case napi_string: return "string"; 67 | case napi_symbol: return "symbol"; 68 | case napi_object: return "object"; 69 | case napi_function: return "function"; 70 | case napi_external: return "external"; 71 | default: return "unknown"; 72 | } 73 | } 74 | 75 | napi_status checkArgs(napi_env env, napi_callback_info info, char* methodName, 76 | napi_value* args, size_t argc, napi_valuetype* types) { 77 | 78 | napi_status status; 79 | 80 | size_t realArgc = argc; 81 | status = napi_get_cb_info(env, info, &realArgc, args, nullptr, nullptr); 82 | if (status != napi_ok) return status; 83 | 84 | if (realArgc != argc) { 85 | char errorMsg[100]; 86 | sprintf(errorMsg, "For method %s, expected %zi arguments and got %zi.", 87 | methodName, argc, realArgc); 88 | napi_throw_error(env, nullptr, errorMsg); 89 | return napi_pending_exception; 90 | } 91 | 92 | napi_valuetype t; 93 | for ( int x = 0 ; x < argc ; x++ ) { 94 | status = napi_typeof(env, args[x], &t); 95 | if (status != napi_ok) return status; 96 | if (t != types[x]) { 97 | char errorMsg[100]; 98 | sprintf(errorMsg, "For method %s argument %i, expected type %s and got %s.", 99 | methodName, x + 1, getNapiTypeName(types[x]), getNapiTypeName(t)); 100 | napi_throw_error(env, nullptr, errorMsg); 101 | return napi_pending_exception; 102 | } 103 | } 104 | 105 | return napi_ok; 106 | }; 107 | 108 | 109 | void tidyCarrier(napi_env env, carrier* c) { 110 | napi_status status; 111 | if (c->passthru != nullptr) { 112 | status = napi_delete_reference(env, c->passthru); 113 | FLOATING_STATUS; 114 | } 115 | if (c->_request != nullptr) { 116 | status = napi_delete_async_work(env, c->_request); 117 | FLOATING_STATUS; 118 | } 119 | delete c; 120 | } 121 | 122 | int32_t rejectStatus(napi_env env, carrier* c, char* file, int32_t line) { 123 | if (c->status != GRANDIOSE_SUCCESS) { 124 | napi_value errorValue, errorCode, errorMsg; 125 | napi_status status; 126 | char errorChars[20]; 127 | if (c->status < GRANDIOSE_ERROR_START) { 128 | const napi_extended_error_info *errorInfo; 129 | status = napi_get_last_error_info(env, &errorInfo); 130 | FLOATING_STATUS; 131 | c->errorMsg = std::string(errorInfo->error_message); 132 | } 133 | char* extMsg = (char *) malloc(sizeof(char) * c->errorMsg.length() + 200); 134 | sprintf(extMsg, "In file %s on line %i, found error: %s", file, line, c->errorMsg.c_str()); 135 | sprintf(errorChars, "%d", c->status); 136 | status = napi_create_string_utf8(env, errorChars, NAPI_AUTO_LENGTH, &errorCode); 137 | FLOATING_STATUS; 138 | status = napi_create_string_utf8(env, extMsg, NAPI_AUTO_LENGTH, &errorMsg); 139 | FLOATING_STATUS; 140 | status = napi_create_error(env, errorCode, errorMsg, &errorValue); 141 | FLOATING_STATUS; 142 | status = napi_reject_deferred(env, c->_deferred, errorValue); 143 | FLOATING_STATUS; 144 | 145 | //free(extMsg); 146 | tidyCarrier(env, c); 147 | } 148 | return c->status; 149 | } 150 | 151 | bool validColorFormat(NDIlib_recv_color_format_e format) { 152 | switch (format) { 153 | case NDIlib_recv_color_format_BGRX_BGRA: 154 | case NDIlib_recv_color_format_UYVY_BGRA: 155 | case NDIlib_recv_color_format_RGBX_RGBA: 156 | case NDIlib_recv_color_format_UYVY_RGBA: 157 | case NDIlib_recv_color_format_fastest: 158 | #ifdef _WIN32 159 | case NDIlib_recv_color_format_BGRX_BGRA_flipped: 160 | #endif 161 | return true; 162 | default: 163 | return false; 164 | } 165 | } 166 | 167 | bool validBandwidth(NDIlib_recv_bandwidth_e bandwidth) { 168 | switch (bandwidth) { 169 | case NDIlib_recv_bandwidth_metadata_only: 170 | case NDIlib_recv_bandwidth_audio_only: 171 | case NDIlib_recv_bandwidth_lowest: 172 | case NDIlib_recv_bandwidth_highest: 173 | return true; 174 | default: 175 | return false; 176 | } 177 | } 178 | 179 | bool validFrameFormat(NDIlib_frame_format_type_e format) { 180 | switch (format) { 181 | case NDIlib_frame_format_type_progressive: 182 | case NDIlib_frame_format_type_interleaved: 183 | case NDIlib_frame_format_type_field_0: 184 | case NDIlib_frame_format_type_field_1: 185 | return true; 186 | default: 187 | return false; 188 | } 189 | } 190 | 191 | bool validAudioFormat(Grandiose_audio_format_e format) { 192 | switch (format) { 193 | case Grandiose_audio_format_float_32_separate: 194 | case Grandiose_audio_format_int_16_interleaved: 195 | case Grandiose_audio_format_float_32_interleaved: 196 | return true; 197 | default: 198 | return false; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/grandiose_util.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2018 Streampunk Media Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #ifndef GRANDIOSE_UTIL_H 17 | #define GRANDIOSE_UTIL_H 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "node_api.h" 25 | 26 | #include "napi.h" 27 | 28 | 29 | // The three different formats of raw audio data supported by NDI utility functions 30 | typedef enum Grandiose_audio_format_e { 31 | // Default NDI audio format 32 | // Channels stored one after the other in each block - 32-bit floating point values 33 | Grandiose_audio_format_float_32_separate = 0, 34 | // Alternative NDI audio foramt 35 | // Channels stored as channel-interleaved 32-bit floating point values 36 | Grandiose_audio_format_float_32_interleaved = 1, 37 | // Alternative NDI audio format 38 | // Channels stored as channel-interleaved 16-bit integer values 39 | Grandiose_audio_format_int_16_interleaved = 2 40 | } Grandiose_audio_format_e; 41 | 42 | #define DECLARE_NAPI_METHOD(name, func) { name, 0, func, 0, 0, 0, napi_default, 0 } 43 | 44 | // Handling NAPI errors - use "napi_status status;" where used 45 | #define CHECK_STATUS if (checkStatus(env, status, __FILE__, __LINE__ - 1) != napi_ok) return nullptr 46 | #define PASS_STATUS if (status != napi_ok) return status 47 | 48 | napi_status checkStatus(napi_env env, napi_status status, 49 | const char * file, uint32_t line); 50 | 51 | // High resolution timing 52 | #define HR_TIME_POINT std::chrono::high_resolution_clock::time_point 53 | #define NOW std::chrono::high_resolution_clock::now() 54 | long long microTime(std::chrono::high_resolution_clock::time_point start); 55 | 56 | // Argument processing 57 | napi_status checkArgs(napi_env env, napi_callback_info info, char* methodName, 58 | napi_value* args, size_t argc, napi_valuetype* types); 59 | 60 | // Async error handling 61 | #define GRANDIOSE_ERROR_START 4000 62 | #define GRANDIOSE_INVALID_ARGS 4001 63 | #define GRANDIOSE_OUT_OF_RANGE 4097 64 | #define GRANDIOSE_ASYNC_FAILURE 4098 65 | #define GRANDIOSE_BUILD_ERROR 4099 66 | #define GRANDIOSE_ALLOCATION_FAILURE 4100 67 | #define GRANDIOSE_RECEIVE_CREATE_FAIL 4101 68 | #define GRANDIOSE_SEND_CREATE_FAIL 4102 69 | #define GRANDIOSE_NOT_FOUND 4040 70 | #define GRANDIOSE_NOT_VIDEO 4140 71 | #define GRANDIOSE_NOT_AUDIO 4141 72 | #define GRANDIOSE_NOT_METADATA 4142 73 | #define GRANDIOSE_CONNECTION_LOST 4143 74 | #define GRANDIOSE_SUCCESS 0 75 | 76 | struct carrier { 77 | virtual ~carrier() {} 78 | napi_ref passthru = nullptr; 79 | int32_t status = GRANDIOSE_SUCCESS; 80 | std::string errorMsg; 81 | long long totalTime; 82 | napi_deferred _deferred; 83 | napi_async_work _request = nullptr; 84 | }; 85 | 86 | void tidyCarrier(napi_env env, carrier* c); 87 | int32_t rejectStatus(napi_env env, carrier* c, char* file, int32_t line); 88 | 89 | #define REJECT_STATUS if (rejectStatus(env, c, __FILE__, __LINE__) != GRANDIOSE_SUCCESS) return; 90 | #define REJECT_RETURN if (rejectStatus(env, c, __FILE__, __LINE__) != GRANDIOSE_SUCCESS) return promise; 91 | #define FLOATING_STATUS if (status != napi_ok) { \ 92 | printf("Unexpected N-API status not OK in file %s at line %d value %i.\n", \ 93 | __FILE__, __LINE__ - 1, status); \ 94 | } 95 | 96 | #define NAPI_THROW_ERROR(msg) { \ 97 | char errorMsg[100]; \ 98 | sprintf(errorMsg, msg); \ 99 | napi_throw_error(env, nullptr, errorMsg); \ 100 | return nullptr; \ 101 | } 102 | 103 | #define REJECT_ERROR(msg, status) { \ 104 | c->errorMsg = msg; \ 105 | c->status = status; \ 106 | REJECT_STATUS; \ 107 | } 108 | 109 | #define REJECT_ERROR_RETURN(msg, stat) { \ 110 | c->errorMsg = msg; \ 111 | c->status = stat; \ 112 | REJECT_RETURN; \ 113 | } 114 | 115 | bool validColorFormat(NDIlib_recv_color_format_e format); 116 | bool validBandwidth(NDIlib_recv_bandwidth_e bandwidth); 117 | bool validFrameFormat(NDIlib_frame_format_type_e format); 118 | bool validAudioFormat(Grandiose_audio_format_e format); 119 | 120 | #endif // GRANDIOSE_UTIL_H 121 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "napi.h" 4 | 5 | // TODO: It would be really nice to use std::optional here, but it is not widely enough supported yet to make that viable 6 | 7 | bool parseBoolean(const Napi::Env &env, const Napi::Maybe &value, bool &output) 8 | { 9 | Napi::Value rawValue = value.UnwrapOr(env.Null()); 10 | if (rawValue.IsUndefined() || rawValue.IsNull()) 11 | { 12 | output = false; 13 | return true; 14 | } 15 | 16 | if (!rawValue.IsBoolean()) 17 | return false; 18 | 19 | output = rawValue.As().Value(); 20 | return true; 21 | } 22 | 23 | bool parseString(const Napi::Env &env, const Napi::Maybe &value, std::string &output) 24 | { 25 | Napi::Value rawValue = value.UnwrapOr(env.Null()); 26 | if (rawValue.IsUndefined() || rawValue.IsNull()) 27 | { 28 | output = ""; 29 | return true; 30 | } 31 | 32 | if (!rawValue.IsString()) 33 | return false; 34 | 35 | output = rawValue.As().Utf8Value(); 36 | return true; 37 | } 38 | 39 | // std::optional> parseStringArray(const Napi::Env &env, const Napi::Maybe &value) 40 | // { 41 | // Napi::Value rawValue = value.UnwrapOr(env.Null()); 42 | // if (rawValue.IsUndefined() || rawValue.IsNull()) 43 | // return std::vector{}; 44 | 45 | // if (!rawValue.IsArray()) 46 | // return {}; 47 | 48 | // std::vector result{}; 49 | 50 | // Napi::Array rawArray = rawValue.As(); 51 | // size_t arrayLength = rawArray.Length(); 52 | // result.reserve(arrayLength); 53 | 54 | // for (size_t i = 0; i < arrayLength; i++) 55 | // { 56 | // Napi::Maybe entry = rawArray.Get(i); 57 | // Napi::Value rawEntry = entry.UnwrapOr(env.Null()); 58 | 59 | // // Invalid 60 | // if (!rawEntry.IsString()) 61 | // return {}; 62 | 63 | // result.push_back(rawArray.As().Utf8Value()); 64 | // } 65 | 66 | // return result; 67 | // } 68 | 69 | Napi::Object convertSourceToNapi(const Napi::Env &env, const NDIlib_source_t &source) 70 | { 71 | Napi::Object object = Napi::Object::New(env); 72 | 73 | object.Set("name", source.p_ndi_name); 74 | object.Set("urlAddress", source.p_url_address); 75 | object.Set("ipAddress", source.p_ip_address); 76 | 77 | return object; 78 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-regex@^5.0.1: 6 | version "5.0.1" 7 | resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" 8 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 9 | 10 | ansi-styles@^4.0.0: 11 | version "4.3.0" 12 | resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" 13 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 14 | dependencies: 15 | color-convert "^2.0.1" 16 | 17 | bindings@^1.2.1: 18 | version "1.3.0" 19 | resolved "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz" 20 | integrity sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw== 21 | 22 | cliui@^8.0.1: 23 | version "8.0.1" 24 | resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" 25 | integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== 26 | dependencies: 27 | string-width "^4.2.0" 28 | strip-ansi "^6.0.1" 29 | wrap-ansi "^7.0.0" 30 | 31 | color-convert@^2.0.1: 32 | version "2.0.1" 33 | resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" 34 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 35 | dependencies: 36 | color-name "~1.1.4" 37 | 38 | color-name@~1.1.4: 39 | version "1.1.4" 40 | resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" 41 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 42 | 43 | emoji-regex@^8.0.0: 44 | version "8.0.0" 45 | resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" 46 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 47 | 48 | escalade@^3.1.1: 49 | version "3.1.1" 50 | resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" 51 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 52 | 53 | get-caller-file@^2.0.5: 54 | version "2.0.5" 55 | resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" 56 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 57 | 58 | is-fullwidth-code-point@^3.0.0: 59 | version "3.0.0" 60 | resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" 61 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 62 | 63 | nan@^2.14.0: 64 | version "2.14.2" 65 | resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz" 66 | integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== 67 | 68 | node-addon-api@^7.0.0: 69 | version "7.0.0" 70 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.0.0.tgz#8136add2f510997b3b94814f4af1cce0b0e3962e" 71 | integrity sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA== 72 | 73 | pkg-prebuilds@^0.2.1: 74 | version "0.2.1" 75 | resolved "https://registry.yarnpkg.com/pkg-prebuilds/-/pkg-prebuilds-0.2.1.tgz#4b91f410ab600df4eb657634d623549cc188c5ed" 76 | integrity sha512-FdOlDiRqRL7i9aYzQflhGWCoiJf/8u6Qgzq48gKsRDYejtfjvGb1U5QGSzllcqpNg2a8Swx/9fMgtuVefwU+zw== 77 | dependencies: 78 | yargs "^17.5.1" 79 | 80 | require-directory@^2.1.1: 81 | version "2.1.1" 82 | resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" 83 | integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== 84 | 85 | segfault-handler@^1.3.0: 86 | version "1.3.0" 87 | resolved "https://registry.npmjs.org/segfault-handler/-/segfault-handler-1.3.0.tgz" 88 | integrity sha512-p7kVHo+4uoYkr0jmIiTBthwV5L2qmWtben/KDunDZ834mbos+tY+iO0//HpAJpOFSQZZ+wxKWuRo4DxV02B7Lg== 89 | dependencies: 90 | bindings "^1.2.1" 91 | nan "^2.14.0" 92 | 93 | string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 94 | version "4.2.3" 95 | resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" 96 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 97 | dependencies: 98 | emoji-regex "^8.0.0" 99 | is-fullwidth-code-point "^3.0.0" 100 | strip-ansi "^6.0.1" 101 | 102 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 103 | version "6.0.1" 104 | resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" 105 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 106 | dependencies: 107 | ansi-regex "^5.0.1" 108 | 109 | wrap-ansi@^7.0.0: 110 | version "7.0.0" 111 | resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" 112 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 113 | dependencies: 114 | ansi-styles "^4.0.0" 115 | string-width "^4.1.0" 116 | strip-ansi "^6.0.0" 117 | 118 | y18n@^5.0.5: 119 | version "5.0.8" 120 | resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" 121 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 122 | 123 | yargs-parser@^21.1.1: 124 | version "21.1.1" 125 | resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" 126 | integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== 127 | 128 | yargs@^17.5.1: 129 | version "17.6.2" 130 | resolved "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz" 131 | integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== 132 | dependencies: 133 | cliui "^8.0.1" 134 | escalade "^3.1.1" 135 | get-caller-file "^2.0.5" 136 | require-directory "^2.1.1" 137 | string-width "^4.2.3" 138 | y18n "^5.0.5" 139 | yargs-parser "^21.1.1" 140 | --------------------------------------------------------------------------------