├── .gitignore ├── .npmrc ├── examples ├── stream-disk │ ├── .gitignore │ ├── README.md │ └── stream-disk.js ├── reference-implementation │ ├── stream-disk │ │ ├── .gitignore │ │ ├── README.md │ │ └── stream-disk.js │ └── README.md ├── display-details │ ├── README.md │ └── display-details.js └── README.md ├── .editorconfig ├── index.js ├── src ├── utilities │ ├── constants.js │ ├── assert-libuvc-result.js │ ├── get-pointer.js │ ├── libuvc-error.js │ └── get-pointers.js ├── index.js ├── frame-stream.js ├── device-handle.js ├── libuvc.js ├── device.js ├── device-descriptor.js ├── libuvc-standard-units.js ├── context.js ├── controls.js └── frame-streamer.js ├── package.json ├── README.md ├── LICENSE └── data └── standard-units.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /examples/stream-disk/.gitignore: -------------------------------------------------------------------------------- 1 | stream-disk.mjpeg 2 | stream-disk.mp4 3 | stream-disk.frame-*.jpg 4 | -------------------------------------------------------------------------------- /examples/reference-implementation/stream-disk/.gitignore: -------------------------------------------------------------------------------- 1 | stream-disk.mjpeg 2 | stream-disk.mp4 3 | stream-disk.frame-*.jpg 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | end_of_line = lf 8 | 9 | [*.{js,json,md}] 10 | indent_style = space 11 | 12 | [*.{js}] 13 | indent_size = 4 14 | 15 | [*.{json,md}] 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /examples/reference-implementation/README.md: -------------------------------------------------------------------------------- 1 | # [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Reference implementations 2 | 3 | Because the code spans multiple languages as well as hardware, some lower-level examples/tests are in place to reduce system-dependent uncertainty. 4 | 5 | --- 6 | 7 | [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Copyright © 2020, 2021 [Joel Purra](https://joelpurra.com/). Released under [GNU Lesser General Public License version 3.0 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl.html). [Your donations are appreciated!](https://joelpurra.com/donate/) 8 | -------------------------------------------------------------------------------- /examples/display-details/README.md: -------------------------------------------------------------------------------- 1 | # [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Display all camera details 2 | 3 | Detects cameras, iterates over all available camera details, and prints them to screen. 4 | 5 | # Requirements 6 | 7 | - Libraries: 8 | - [`@ffi-libraries/libuvc-v0.0.6`](https://github.com/node-ffi-libraries/node-ffi-library-libuvc-v0.0.6) for the Node.js wrapper. 9 | - [`libuvc`](https://ken.tossell.net/libuvc/) for capturing the stream of images. 10 | - Optional: 11 | - One or more supported, and connected, UVC cameras. 12 | 13 | # Usage 14 | 15 | ```shell 16 | node display-details.js 17 | ``` 18 | 19 | # Output 20 | 21 | An unstructured set of available data for all available UVC cameras. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | module.exports = require("./src/"); 20 | -------------------------------------------------------------------------------- /src/utilities/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | module.exports = { 20 | maxNumberOfUvcDevices: 100, 21 | }; 22 | -------------------------------------------------------------------------------- /src/utilities/assert-libuvc-result.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const LibuvcError = require("./libuvc-error"); 20 | 21 | module.exports = function assertLibuvcResult(libuvc, result, message) { 22 | if (result < 0) { 23 | throw new LibuvcError(libuvc, result, message); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/utilities/get-pointer.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const ref = require("ref-napi"); 20 | 21 | module.exports = function getPointer(firstPointerPointer) { 22 | const nonNullPointer = ref.reinterpretUntilZeros(firstPointerPointer, 1); 23 | 24 | if (nonNullPointer.length === 0) { 25 | return null; 26 | } 27 | 28 | const pointer = ref.reinterpret(nonNullPointer, ref.types.size_t.size, 0); 29 | 30 | return pointer; 31 | }; 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | module.exports = { 20 | Context: require("./context"), 21 | Controls: require("./controls"), 22 | Device: require("./device"), 23 | DeviceDescriptor: require("./device-descriptor"), 24 | DeviceHandle: require("./device-handle"), 25 | FrameStream: require("./frame-stream"), 26 | FrameStreamer: require("./frame-streamer"), 27 | LibUvc: require("./libuvc"), 28 | LibuvcStandardUnits: require("./libuvc-standard-units"), 29 | }; 30 | -------------------------------------------------------------------------------- /examples/reference-implementation/stream-disk/README.md: -------------------------------------------------------------------------------- 1 | # [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Stream webcam video to disk 2 | 3 | Checks that a compatible camera is connected, stream 10 seconds of video (image frames), and saves them to a file on disk. 4 | 5 | This is the [`libuvc` documentation example](https://ken.tossell.net/libuvc/doc/) by [Ken Tossell](https://ken.tossell.net/), but it saves the video to disk. 6 | 7 | # Requirements 8 | 9 | - Libraries: 10 | - [`@ffi-libraries/libuvc-v0.0.6`](https://github.com/node-ffi-libraries/node-ffi-library-libuvc-v0.0.6) for the Node.js wrapper. 11 | - [`libuvc`](https://ken.tossell.net/libuvc/) for capturing the stream of images. 12 | - Optional: 13 | - [`ffmpeg`](https://ffmpeg.org/) to convert from [Motion JPEG](https://en.wikipedia.org/wiki/Motion_JPEG) to a different format. 14 | - A supported, and connected, UVC camera. 15 | 16 | # Usage 17 | 18 | See the terminal for some debugging output, and the output file `stream-disk.mjpeg`. 19 | 20 | ```shell 21 | node stream-disk.js 22 | ``` 23 | 24 | # Output 25 | 26 | The output file is `stream-disk.mjpeg`. See [file formats](../README.md#file-formats) for convenient conversions. 27 | 28 | --- 29 | 30 | [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Copyright © 2020, 2021 [Joel Purra](https://joelpurra.com/). Released under [GNU Lesser General Public License version 3.0 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl.html). [Your donations are appreciated!](https://joelpurra.com/donate/) 31 | -------------------------------------------------------------------------------- /src/utilities/libuvc-error.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | module.exports = class LibuvcError extends Error { 22 | errno = null; 23 | 24 | constructor(libuvc, errno, message) { 25 | super(message); 26 | 27 | assert(typeof errno === "number"); 28 | assert(typeof message === "string"); 29 | assert(message.length > 0); 30 | 31 | this.errno = errno; 32 | 33 | Object.defineProperty(this, "code", { 34 | enumerable: true, 35 | value: 36 | String(libuvc.constants.uvc_error[this.errno]) || this.errno.toString(), 37 | }); 38 | } 39 | 40 | toString() { 41 | return `${super.toString()} (${JSON.stringify(this.code)})`; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /examples/stream-disk/README.md: -------------------------------------------------------------------------------- 1 | # [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Stream webcam video to disk 2 | 3 | Checks that a compatible camera is connected, stream 10 seconds of video image frames, and saves them to a file on disk. 4 | 5 | This is the [`libuvc` documentation example](https://ken.tossell.net/libuvc/doc/) by [Ken Tossell](https://ken.tossell.net/) using the [`node-uvc`](https://joelpurra.com/projects/node-uvc/) wrapper, but it saves the video to disk. 6 | 7 | # Requirements 8 | 9 | - Libraries: 10 | - [`@ffi-libraries/libuvc-v0.0.6`](https://github.com/node-ffi-libraries/node-ffi-library-libuvc-v0.0.6) for the Node.js wrapper. 11 | - [`libuvc`](https://ken.tossell.net/libuvc/) for capturing the stream of images. 12 | - Optional: 13 | - [`ffmpeg`](https://ffmpeg.org/) to convert from [Motion JPEG](https://en.wikipedia.org/wiki/Motion_JPEG) to a different format. 14 | - A supported, and connected, UVC camera. 15 | 16 | # Usage 17 | 18 | See the terminal for some debugging output, and the output file `stream-disk.mjpeg`. 19 | 20 | ```shell 21 | node stream-disk.js 22 | ``` 23 | 24 | # Output 25 | 26 | The output file is `stream-disk.mjpeg`. See [file formats](../README.md#file-formats) for convenient conversions. 27 | 28 | --- 29 | 30 | [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Copyright © 2020, 2021 [Joel Purra](https://joelpurra.com/). Released under [GNU Lesser General Public License version 3.0 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl.html). [Your donations are appreciated!](https://joelpurra.com/donate/) 31 | -------------------------------------------------------------------------------- /src/utilities/get-pointers.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const ref = require("ref-napi"); 20 | 21 | // TODO: use ref-array-di instead? 22 | module.exports = function getPointers( 23 | maxNumberOfPointers, 24 | firstPointerPointer 25 | ) { 26 | const nonNullPointers = ref.reinterpretUntilZeros(firstPointerPointer, 1); 27 | const pointers = []; 28 | 29 | for (let i = 0; i < maxNumberOfPointers; i++) { 30 | let offset = i * ref.types.size_t.size; 31 | 32 | if (offset >= nonNullPointers.length) { 33 | break; 34 | } 35 | 36 | const pointer = ref.reinterpret( 37 | nonNullPointers, 38 | ref.types.size_t.size, 39 | offset 40 | ); 41 | 42 | pointers.push(pointer); 43 | } 44 | 45 | return pointers; 46 | }; 47 | -------------------------------------------------------------------------------- /src/frame-stream.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const { Transform } = require("stream"); 20 | const ref = require("ref-napi"); 21 | 22 | module.exports = class FrameStream extends Transform { 23 | constructor() { 24 | super({ 25 | readableObjectMode: true, 26 | writableObjectMode: true, 27 | }); 28 | } 29 | 30 | _transform(framePointer, encoding, callback) { 31 | const frame = framePointer.deref(); 32 | const image = ref.reinterpret(frame.data, frame.data_bytes, 0); 33 | 34 | const frameObject = { 35 | image: image, 36 | width: frame.width, 37 | height: frame.height, 38 | format: frame.format, 39 | step: frame.step, 40 | sequence: frame.sequence, 41 | captureTime: { 42 | seconds: frame.capture_time.tv_sec, 43 | microseconds: frame.capture_time.tv_usec, 44 | }, 45 | }; 46 | 47 | return callback(null, frameObject); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/device-handle.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | const { intersection, without } = require("lodash"); 22 | 23 | const ref = require("ref-napi"); 24 | const libuvc = require("@ffi-libraries/libuvc-v0.0.6"); 25 | 26 | const assertLibuvcResult = require("./utilities/assert-libuvc-result"); 27 | 28 | module.exports = class DeviceHandle { 29 | libuvc = null; 30 | deviceHandle = null; 31 | 32 | constructor(libuvc, deviceHandle) { 33 | assert(libuvc); 34 | assert(deviceHandle); 35 | 36 | this.libuvc = libuvc; 37 | this.deviceHandle = deviceHandle; 38 | } 39 | 40 | async initialize() { 41 | assert.notStrictEqual(this.deviceHandle, null); 42 | } 43 | 44 | async uninitialize() { 45 | assert.notStrictEqual(this.libuvc, null); 46 | assert.notStrictEqual(this.deviceHandle, null); 47 | 48 | this.libuvc.functions.uvc_close(this.deviceHandle.deref()); 49 | this.deviceHandle = null; 50 | } 51 | 52 | async getControlLength(unit, control) { 53 | assert(unit >= 0); 54 | assert(control >= 0); 55 | 56 | return this.libuvc.functions.uvc_get_ctrl_len( 57 | this.deviceHandle.deref(), 58 | unit, 59 | control 60 | ); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # [`node-uvc`](https://joelpurra.com/projects/node-uvc/) examples 2 | 3 | Examples are mainly written in javascript. There are also reference implementations to ensure the underlying libraries, such as [`@ffi-libraries/libuvc-v0.0.6`](https://github.com/node-ffi-libraries/node-ffi-library-libuvc-v0.0.6), work as expected. 4 | 5 | ## File formats 6 | 7 | Some examples output files in various formats. 8 | 9 | ### Motion JPEG 10 | 11 | The output file `stream-disk.mjpeg` is in [Motion JPEG](https://en.wikipedia.org/wiki/Motion_JPEG) (MJPEG) format. It is just a series of concatenated [JPEG](https://en.wikipedia.org/wiki/JPEG) images, and does not contain the usual video metadata, such as framerate. Your regular video player should be able to open it, but because it lacks a defined framerate, the "video" renders at maximum speed -- and you might only see a single frame. 12 | 13 | It is often possible to losslessly extract each frame as a JPEG image. Some cameras might have a slightly different MJPEG format where some repeated JPEG data is optimized away, which might not work with the lossless "copy" -- in that case, try removing `-vcodec copy` to recompress each JPEG image. 14 | 15 | Note that the below command will produce hundreds of JPEG images, named `stream-disk.frame-0001.jpg` and so on. 16 | 17 | ```shell 18 | ffmpeg -f mjpeg -i stream-disk.mjpeg -vcodec copy stream-disk.frame-%4d.jpg 19 | ``` 20 | 21 | The stream is captured at 30 frames per second (but may vary dynamically, depending on your camera and scene), so it can be converted to another video format which has this metadata. This will also generally reduce the file size significantly, as most video formats do not store each full frame image but the only difference between subsequent frames. 22 | 23 | Use the below command to create `stream-disk.mp4`. 24 | 25 | ```shell 26 | ffmpeg -f mjpeg -framerate 30 -i stream-disk.mjpeg stream-disk.mp4 27 | ``` 28 | 29 | --- 30 | 31 | [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Copyright © 2020, 2021 [Joel Purra](https://joelpurra.com/). Released under [GNU Lesser General Public License version 3.0 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl.html). [Your donations are appreciated!](https://joelpurra.com/donate/) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uvc", 3 | "version": "1.0.0", 4 | "description": "Library for USB Video Class (UVC) devices. Used to write software for webcams, camcorders, etcetera.", 5 | "homepage": "https://joelpurra.com/projects/node-uvc/", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "npm run --silent lint:fast", 9 | "lint": "npm run --silent lint:full", 10 | "lint:fast": "npm run --silent lint:prettier && npm run --silent lint:copyright", 11 | "lint:full": "npm run --silent lint:fast", 12 | "lint:fix": "npm run --silent lint:prettier:fix", 13 | "lint:copyright": "find . -not \\( -path './.git/*' -or -path './node_modules/*' -or -path '*/reference-implementation/*' \\) -type f \\( -iname '*.js' -and -not \\( -path './.huskyrc.js' \\) \\) -print0 | xargs -0 grep -L 'This file is part of node-uvc' | sed 's/^/File is missing copyright notice: /'", 14 | "lint:prettier": "prettier --list-different './**/*.js' './**/*.json' './**/*.md'", 15 | "lint:prettier:fix": "prettier --write './**/*.js' './**/*.json' './**/*.md'" 16 | }, 17 | "dependencies": { 18 | "@ffi-libraries/libuvc-v0.0.6": "github:node-ffi-libraries/node-ffi-library-libuvc-v0.0.6#semver:^2.0.1", 19 | "bluebird": "^3.7.2", 20 | "lodash": "^4.17.21", 21 | "yaml": "^1.10.2" 22 | }, 23 | "devDependencies": { 24 | "ffi-napi": "^4.0.3", 25 | "husky": "^4.3.8", 26 | "prettier": "^2.3.1", 27 | "ref-array-di": "^1.2.2", 28 | "ref-napi": "^3.0.3", 29 | "ref-struct-di": "^1.1.1", 30 | "ref-union-di": "^1.0.1" 31 | }, 32 | "engines": { 33 | "node": "^12.0.0 || ^14.0.0 || ^16.0.0" 34 | }, 35 | "keywords": [ 36 | "usb", 37 | "usb video class", 38 | "uvc", 39 | "camera", 40 | "webcamera", 41 | "video", 42 | "photo", 43 | "capture", 44 | "controls", 45 | "configuration", 46 | "libusb", 47 | "libuvc" 48 | ], 49 | "bugs": { 50 | "url": "https://github.com/joelpurra/node-uvc/issues" 51 | }, 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/joelpurra/node-uvc.git" 55 | }, 56 | "author": { 57 | "name": "Joel Purra", 58 | "email": "mig@joelpurra.se", 59 | "url": "https://joelpurra.com/" 60 | }, 61 | "license": "LGPL-3.0", 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "npm run --silent test", 65 | "pre-push": "npm run --silent test" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/libuvc.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | const { fromPairs } = require("lodash"); 22 | 23 | const ref = require("ref-napi"); 24 | const { load } = require("@ffi-libraries/libuvc-v0.0.6"); 25 | 26 | const Device = require("./device"); 27 | 28 | const getPointer = require("./utilities/get-pointer"); 29 | const getPointers = require("./utilities/get-pointers"); 30 | const { maxNumberOfUvcDevices } = require("./utilities/constants"); 31 | 32 | module.exports = class LibUvc { 33 | library = null; 34 | 35 | // TODO: dynamically select the header file for the current operating system. 36 | headerFile = "./include/libuvc/libuvc.h"; 37 | 38 | header = null; 39 | 40 | async initialize() { 41 | assert.strictEqual(this.library, null); 42 | assert.strictEqual(this.header, null); 43 | 44 | this.library = await load(); 45 | const headerLoader = this.library.headers[this.headerFile]; 46 | this.header = await headerLoader(); 47 | 48 | Object.defineProperties(this, { 49 | constants: { 50 | configurable: true, 51 | get: () => this.header.constants, 52 | }, 53 | functions: { 54 | configurable: true, 55 | get: () => this.header.functions, 56 | }, 57 | types: { 58 | configurable: true, 59 | get: () => this.header.types, 60 | }, 61 | }); 62 | } 63 | 64 | async uninitialize() { 65 | assert.notStrictEqual(this.library, null); 66 | assert.notStrictEqual(this.header, null); 67 | 68 | delete this.constants; 69 | delete this.functions; 70 | delete this.types; 71 | 72 | this.header = null; 73 | this.library = null; 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /examples/display-details/display-details.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const Bluebird = require("bluebird"); 20 | 21 | const { 22 | Context, 23 | Device, 24 | DeviceDescriptor, 25 | DeviceHandle, 26 | FrameStreamer, 27 | LibUvc, 28 | } = require("../../"); 29 | 30 | const main = async () => { 31 | const libuvc = new LibUvc(); 32 | await libuvc.initialize(); 33 | 34 | const context = new Context(libuvc, null, null); 35 | await context.initialize(); 36 | 37 | const devices = context.getDeviceList(); 38 | await Bluebird.map(devices, (device) => device.initialize()); 39 | 40 | const deviceDescriptors = await Bluebird.map(devices, (device) => 41 | device.getDescriptor() 42 | ); 43 | 44 | await Bluebird.map(deviceDescriptors, (deviceDescriptor) => 45 | deviceDescriptor.initialize() 46 | ); 47 | 48 | console.log(deviceDescriptors); 49 | 50 | // Bluebird.each(deviceDeviceDescriptors, async deviceDeviceDescriptor => { 51 | // const device = new Device( 52 | // libuvc, 53 | // context, 54 | // device.vendor.id, 55 | // device.product.id, 56 | // device.serialNumber, 57 | // libuvc 58 | // ); 59 | // await device.initialize(); 60 | 61 | // const deviceHandle = new DeviceHandle(libuvc, device); 62 | // await deviceHandle.initialize(); 63 | 64 | // await deviceHandle.uninitialize(); 65 | // await device.uninitialize(); 66 | // }); 67 | 68 | await Bluebird.map(deviceDescriptors, (deviceDescriptor) => 69 | deviceDescriptor.uninitialize() 70 | ); 71 | await Bluebird.map(devices, (device) => device.uninitialize()); 72 | await context.uninitialize(); 73 | await libuvc.uninitialize(); 74 | }; 75 | 76 | const mainWrapper = async () => { 77 | try { 78 | await main(); 79 | } catch (error) { 80 | console.error("main", error); 81 | 82 | process.exitCode = 1; 83 | } 84 | }; 85 | 86 | try { 87 | mainWrapper(); 88 | } catch (error) { 89 | console.error("mainWrapper", error); 90 | 91 | process.exitCode = 1; 92 | } 93 | -------------------------------------------------------------------------------- /src/device.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | const ref = require("ref-napi"); 22 | const libuvc = require("@ffi-libraries/libuvc-v0.0.6"); 23 | 24 | const DeviceHandle = require("./device-handle"); 25 | const DeviceDescriptor = require("./device-descriptor"); 26 | 27 | module.exports = class Device { 28 | pointer = null; 29 | libuvc = null; 30 | 31 | constructor(libuvc, pointer) { 32 | assert(libuvc); 33 | assert(pointer); 34 | 35 | this.libuvc = libuvc; 36 | this.pointer = pointer; 37 | } 38 | 39 | async initialize() { 40 | assert.notStrictEqual(this.libuvc, null); 41 | assert.notStrictEqual(this.pointer, null); 42 | } 43 | 44 | async uninitialize() { 45 | assert.notStrictEqual(this.libuvc, null); 46 | assert.notStrictEqual(this.pointer, null); 47 | 48 | this.libuvc.functions.uvc_unref_device(this.pointer.deref()); 49 | this.pointer = null; 50 | } 51 | 52 | async open() { 53 | assert.notStrictEqual(this.libuvc, null); 54 | assert.notStrictEqual(this.pointer, null); 55 | 56 | const deviceHandlePointer = ref.alloc( 57 | this.libuvc.types.uvc_device_handle_tPointer 58 | ); 59 | 60 | const result = this.libuvc.functions.uvc_open( 61 | this.pointer.deref(), 62 | deviceHandlePointer 63 | ); 64 | 65 | if (result !== 0) { 66 | throw new Error(`this.libuvc.functions.uvc_open(...): ${result}`); 67 | } 68 | 69 | const deviceHandle = new DeviceHandle(this.libuvc, deviceHandlePointer); 70 | 71 | return deviceHandle; 72 | } 73 | 74 | async getDescriptor() { 75 | assert.notStrictEqual(this.libuvc, null); 76 | assert.notStrictEqual(this.pointer, null); 77 | 78 | const deviceDescriptorPointer = ref.alloc( 79 | this.libuvc.types.uvc_device_descriptor_tPointer 80 | ); 81 | 82 | const result = this.libuvc.functions.uvc_get_device_descriptor( 83 | this.pointer.deref(), 84 | deviceDescriptorPointer 85 | ); 86 | 87 | if (result !== 0) { 88 | throw new Error( 89 | `this.libuvc.functions.uvc_get_device_descriptor(...): ${result}` 90 | ); 91 | } 92 | 93 | const deviceDescriptor = new DeviceDescriptor( 94 | this.libuvc, 95 | deviceDescriptorPointer 96 | ); 97 | 98 | return deviceDescriptor; 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /src/device-descriptor.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | const ref = require("ref-napi"); 22 | 23 | const getPointer = require("./utilities/get-pointer"); 24 | const getPointers = require("./utilities/get-pointers"); 25 | 26 | module.exports = class DeviceDescriptor { 27 | libuvc = null; 28 | vendorId = null; 29 | vendorName = null; 30 | productId = null; 31 | productName = null; 32 | serialNumber = null; 33 | complianceLevel = null; 34 | pointer = null; 35 | 36 | constructor(libuvc, pointer) { 37 | assert(libuvc); 38 | assert(pointer); 39 | 40 | this.libuvc = libuvc; 41 | this.pointer = pointer; 42 | } 43 | 44 | async initialize() { 45 | assert.notStrictEqual(this.pointer, null); 46 | 47 | const uvcDeviceDescriptor = this.pointer.deref().deref(); 48 | 49 | this.vendorId = uvcDeviceDescriptor.idVendor; 50 | this.vendorName = uvcDeviceDescriptor.manufacturer; 51 | this.productId = uvcDeviceDescriptor.idProduct; 52 | this.productName = uvcDeviceDescriptor.product; 53 | this.serialNumber = uvcDeviceDescriptor.serialNumber; 54 | this.complianceLevel = uvcDeviceDescriptor.bcdUVC; 55 | } 56 | 57 | async uninitialize() { 58 | assert.notStrictEqual(this.libuvc, null); 59 | assert.notStrictEqual(this.pointer, null); 60 | 61 | this.vendorId = null; 62 | this.vendorName = null; 63 | this.productId = null; 64 | this.productName = null; 65 | this.serialNumber = null; 66 | this.complianceLevel = null; 67 | 68 | this.libuvc.functions.uvc_free_device_descriptor(this.pointer.deref()); 69 | 70 | this.pointer = null; 71 | } 72 | 73 | toString() { 74 | assert.notStrictEqual(this.deviceDescriptor, null); 75 | 76 | return `DeviceDescriptor (vendor 0x${this.vendorId.toString(16)}${ 77 | this.vendorName ? `, "${this.vendorName}"` : "" 78 | }, product 0x${this.productId.toString(16)}${ 79 | this.productName ? `, "${this.productName}"` : "" 80 | }${this.serialNumber ? `, serial number ${this.serialNumber}` : ""})`; 81 | } 82 | 83 | toJSON(key) { 84 | assert.notStrictEqual(this.deviceDescriptor, null); 85 | 86 | const object = { 87 | vendor: { 88 | id: this.vendorId, 89 | name: this.vendorName, 90 | }, 91 | product: { 92 | id: this.productId, 93 | name: this.productName, 94 | }, 95 | serialNumber: this.serialNumber, 96 | complianceLevel: this.complianceLevel, 97 | }; 98 | 99 | return object; 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /src/libuvc-standard-units.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | const fs = require("fs"); 21 | const { join } = require("path"); 22 | const { promisify } = require("util"); 23 | 24 | const { fromPairs, intersection, without, sortBy } = require("lodash"); 25 | const YAML = require("yaml"); 26 | 27 | const ref = require("ref-napi"); 28 | const libuvc = require("@ffi-libraries/libuvc-v0.0.6"); 29 | 30 | const assertLibuvcResult = require("./utilities/assert-libuvc-result"); 31 | const { DH_CHECK_P_NOT_SAFE_PRIME } = require("constants"); 32 | 33 | module.exports = class LibuvcStandardUnits { 34 | standardUnitsYamlPath = null; 35 | license = null; 36 | standardUnits = null; 37 | controls = null; 38 | 39 | constructor(standardUnitsYamlPath) { 40 | this.standardUnitsYamlPath = standardUnitsYamlPath 41 | ? standardUnitsYamlPath 42 | : join(__dirname, "..", "data", "standard-units.yaml"); 43 | 44 | this.readFile = promisify(fs.readFile); 45 | } 46 | 47 | async initialize() { 48 | assert.notStrictEqual(this.standardUnitsYamlPath, null); 49 | assert.strictEqual(this.license, null); 50 | assert.strictEqual(this.standardUnits, null); 51 | assert.strictEqual(this.controls, null); 52 | 53 | const standardUnitsYamlFile = await this.readFile( 54 | this.standardUnitsYamlPath 55 | ); 56 | const standardUnitsYaml = YAML.parseAllDocuments( 57 | standardUnitsYamlFile.toString() 58 | ); 59 | 60 | this.license = Object.freeze(standardUnitsYaml[0].contents.toJSON()); 61 | this.standardUnits = Object.freeze(standardUnitsYaml[1].contents.toJSON()); 62 | 63 | // TODO: use Object.fromEntries(). 64 | this.controls = fromPairs( 65 | sortBy( 66 | Object.entries(this.standardUnits.units) 67 | .map(([unitName, unit]) => 68 | Object.entries(unit.controls).map(([controlName, control]) => { 69 | const { controls, ...unitWithoutControls } = unit; 70 | 71 | return [ 72 | controlName, 73 | { 74 | ...control, 75 | unit: { 76 | ...unitWithoutControls, 77 | }, 78 | }, 79 | ]; 80 | }) 81 | ) 82 | .reduce( 83 | (controls, unitControls) => controls.concat(unitControls), 84 | [] 85 | ), 86 | ([controlName, control]) => controlName 87 | ) 88 | ); 89 | } 90 | 91 | async uninitialize() { 92 | assert.notStrictEqual(this.license, null); 93 | assert.notStrictEqual(this.standardUnits, null); 94 | assert.notStrictEqual(this.controls, null); 95 | 96 | this.license = null; 97 | this.standardUnits = null; 98 | this.controls = null; 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Node.js library for USB Video Class (UVC) devices](https://joelpurra.com/projects/node-uvc/) (`node-uvc`) 2 | 3 | Node.js library for [USB Video Class](https://en.wikipedia.org/wiki/USB_video_device_class) (UVC) devices. Used to write software for webcams, camcorders, etcetera. 4 | 5 | [UVC-compliant devices](https://en.wikipedia.org/wiki/List_of_USB_video_class_devices) include webcams, digital camcorders, transcoders, analog video converters and still-image cameras. 6 | 7 | ## Features 8 | 9 | Functional: 10 | 11 | - Locate UVC devices on the system and retrieve out device details. 12 | - Change camera controls, such as image contrast, zoom, and toggling automatic settings. 13 | - Stream video and individual image frames. 14 | - Transform image data using [standard Node.js streams](https://nodejs.org/api/stream.html). 15 | 16 | Technical: 17 | 18 | - Thin translation layer built for Javascript developer convenience. 19 | - Ships with pre-built binaries from [`@ffi-libraries/libuvc-v0.0.6`](https://github.com/node-ffi-libraries/node-ffi-library-libuvc-v0.0.6). 20 | - Based on the [`libuvc`](https://ken.tossell.net/libuvc/) cross-platform C library. 21 | - Javascript `class` implementation. 22 | - Asynchronous `async`/`await` class methods. 23 | - Resource management using `.initialize()`/`.uninitialize()` methods. 24 | 25 | ## Installation 26 | 27 | Requires [Node.js](https://nodejs.org/) (`node` and `npm` commands). Published on npm as [`uvc`](https://www.npmjs.com/package/uvc). 28 | 29 | ```shell 30 | npm install --save uvc 31 | ``` 32 | 33 | ## Usage 34 | 35 | See [`./examples/`](./examples/) for ready-to-run code. 36 | 37 | ```javascript 38 | const { Context, Device, DeviceHandle, LibUvc } = require("uvc"); 39 | 40 | const libuvc = new LibUvc(); 41 | await libuvc.initialize(); 42 | 43 | const context = new Context(libuvc); 44 | await context.initialize(); 45 | 46 | const device = await context.findDevice(); 47 | await device.initialize(); 48 | 49 | const deviceHandle = await device.open(); 50 | await deviceHandle.initialize(); 51 | 52 | // NOTE: use the UVC device here, for example using the Controls and FrameStreamer classes. 53 | 54 | await deviceHandle.uninitialize(); 55 | await device.uninitialize(); 56 | await context.uninitialize(); 57 | await libuvc.uninitialize(); 58 | ``` 59 | 60 | ## Development 61 | 62 | - Requires a UVC device, such as a compatible webcam. 63 | - Get the source code from the [`node-uvc` repository](https://github.com/joelpurra/node-uvc). 64 | - Follow [git-flow](https://danielkummer.github.io/git-flow-cheatsheet/) and use [git-flow-avh](https://github.com/petervanderdoes/gitflow-avh). 65 | - Make sure that all example code works by testing them manually. 66 | 67 | ```shell 68 | # Make sure git-flow is initialized. 69 | git flow init -d 70 | 71 | npm run --silent test 72 | ``` 73 | 74 | ## See also 75 | 76 | - [`uvcc`](https://joelpurra.com/projects/uvcc) for a command line interface (CLI). 77 | - [USB Video Class](https://en.wikipedia.org/wiki/USB_video_device_class) on Wikipedia. 78 | - [List of USB video class devices](https://en.wikipedia.org/wiki/List_of_USB_video_class_devices) on Wikipedia. 79 | - The [`@ffi-libraries/libuvc-v0.0.6`](https://github.com/node-ffi-libraries/node-ffi-library-libuvc-v0.0.6) Node.js wrapper for [`libuvc`](https://ken.tossell.net/libuvc/). 80 | - The [`v4l-utils`](https://linuxtv.org/wiki/index.php/V4l-utils) for [video4linux](https://www.linuxtv.org) ([Wikipedia](https://en.wikipedia.org/wiki/Video4Linux)), which includes [`v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl). 81 | 82 | --- 83 | 84 | [`node-uvc`](https://joelpurra.com/projects/node-uvc/) Copyright © 2020, 2021 [Joel Purra](https://joelpurra.com/). Released under [GNU Lesser General Public License version 3.0 (LGPL-3.0)](https://www.gnu.org/licenses/lgpl.html). [Your donations are appreciated!](https://joelpurra.com/donate/) 85 | -------------------------------------------------------------------------------- /examples/stream-disk/stream-disk.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const { once } = require("events"); 20 | const fs = require("fs"); 21 | const util = require("util"); 22 | const stream = require("stream"); 23 | 24 | const { 25 | Context, 26 | Controls, 27 | Device, 28 | DeviceHandle, 29 | FrameStreamer, 30 | LibUvc, 31 | } = require("../../"); 32 | 33 | const { Transform } = stream; 34 | const finished = util.promisify(stream.finished); 35 | 36 | const sleep = async (s) => 37 | new Promise((resolve) => { 38 | setTimeout(resolve, s * 1000); 39 | }); 40 | 41 | class FrameImageTransform extends Transform { 42 | constructor() { 43 | super({ 44 | writableObjectMode: true, 45 | }); 46 | } 47 | 48 | _transform(frame, encoding, callback) { 49 | if (frame.sequence % 30 == 0) { 50 | console.log("* got image", frame.sequence); 51 | } 52 | 53 | return callback(null, frame.image); 54 | } 55 | } 56 | 57 | const main = async () => { 58 | const filename = "stream-disk.mjpeg"; 59 | 60 | const libuvc = new LibUvc(); 61 | await libuvc.initialize(); 62 | 63 | const context = new Context(libuvc); 64 | await context.initialize(); 65 | 66 | const device = await context.findDevice(); 67 | await device.initialize(); 68 | 69 | const deviceHandle = await device.open(); 70 | await deviceHandle.initialize(); 71 | 72 | const controls = new Controls(libuvc, deviceHandle); 73 | await controls.initialize(); 74 | 75 | // https://ken.tossell.net/libuvc/doc/group__ctrl.html#gaa583133ed035c141c42061d5c13a36bf 76 | const UVC_AUTO_EXPOSURE_MODE_AUTO = 2; 77 | const UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY = 8; 78 | 79 | try { 80 | await controls.ae_mode.set(UVC_AUTO_EXPOSURE_MODE_AUTO); 81 | } catch (error) { 82 | if (error.code === "UVC_ERROR_PIPE") { 83 | await controls.ae_mode.set(UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY); 84 | } else { 85 | throw error; 86 | } 87 | } 88 | 89 | const frameStreamer = new FrameStreamer( 90 | libuvc, 91 | deviceHandle, 92 | libuvc.constants.uvc_frame_format.UVC_FRAME_FORMAT_MJPEG, 93 | 640, 94 | 480, 95 | 30 96 | ); 97 | const frameStream = await frameStreamer.initialize(); 98 | 99 | const frameImageTransform = new FrameImageTransform(); 100 | const fileWriteStream = fs.createWriteStream(filename); 101 | frameStream.pipe(frameImageTransform).pipe(fileWriteStream); 102 | 103 | console.log("Streaming..."); 104 | 105 | await sleep(10); 106 | 107 | fileWriteStream.end(); 108 | await finished(fileWriteStream); 109 | await frameStreamer.uninitialize(); 110 | await controls.uninitialize(); 111 | await deviceHandle.uninitialize(); 112 | await device.uninitialize(); 113 | await context.uninitialize(); 114 | await libuvc.uninitialize(); 115 | }; 116 | 117 | const mainWrapper = async () => { 118 | try { 119 | await main(); 120 | } catch (error) { 121 | console.error("main", error); 122 | 123 | process.exitCode = 1; 124 | } 125 | }; 126 | 127 | try { 128 | mainWrapper(); 129 | } catch (error) { 130 | console.error("mainWrapper", error); 131 | 132 | process.exitCode = 1; 133 | } 134 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | const ref = require("ref-napi"); 22 | 23 | const Device = require("./device"); 24 | 25 | const getPointer = require("./utilities/get-pointer"); 26 | const getPointers = require("./utilities/get-pointers"); 27 | const { maxNumberOfUvcDevices } = require("./utilities/constants"); 28 | 29 | module.exports = class Context { 30 | libuvc = null; 31 | usbContext = null; 32 | uvcContext = null; 33 | 34 | constructor(libuvc, uvcContext = null, usbContext = null) { 35 | assert(libuvc); 36 | 37 | this.libuvc = libuvc; 38 | this.uvcContext = uvcContext; 39 | this.usbContext = usbContext; 40 | } 41 | 42 | async initialize() { 43 | assert.notStrictEqual(this.libuvc, null); 44 | 45 | if (this.uvcContext === null) { 46 | this.uvcContext = ref.alloc(this.libuvc.types.uvc_context_tPointer); 47 | } 48 | 49 | { 50 | const result = this.libuvc.functions.uvc_init( 51 | this.uvcContext, 52 | this.usbContext 53 | ); 54 | 55 | if (result !== 0) { 56 | throw new Error(`this.libuvc.functions.uvc_init(...): ${result}`); 57 | } 58 | } 59 | } 60 | 61 | async uninitialize() { 62 | assert.notStrictEqual(this.libuvc, null); 63 | assert.notStrictEqual(this.uvcContext, null); 64 | 65 | this.libuvc.functions.uvc_exit(this.uvcContext.deref()); 66 | this.uvcContext = null; 67 | } 68 | 69 | async getDeviceList() { 70 | assert.notStrictEqual(this.context, null); 71 | 72 | // TODO: better ffi-generate support for lists. 73 | const deviceListPointer = Buffer.alloc( 74 | maxNumberOfUvcDevices * ref.types.size_t.size 75 | ); 76 | 77 | const result = this.libuvc.functions.uvc_get_device_list( 78 | this.uvcContext.deref(), 79 | deviceListPointer 80 | ); 81 | 82 | if (result !== 0) { 83 | throw new Error( 84 | `this.libuvc.functions.uvc_get_device_list(...): ${result}` 85 | ); 86 | } 87 | 88 | const pointerToDevicePointer = (pointer) => 89 | ref.get(pointer, 0, this.libuvc.types.uvc_device_tPointer); 90 | 91 | const devicePointers = getPointers(maxNumberOfUvcDevices, deviceListPointer) 92 | .map(pointerToDevicePointer) 93 | .filter((devicePointer) => !ref.isNull(devicePointer.deref())); 94 | 95 | const devices = devicePointers.map( 96 | (devicePointer) => new Device(this.libuvc, devicePointer) 97 | ); 98 | 99 | return devices; 100 | } 101 | 102 | async findDevice(vendorId = 0, productId = 0, serialNumber = null) { 103 | assert.notStrictEqual(this.libuvc, null); 104 | assert.notStrictEqual(this.uvcContext, null); 105 | 106 | const devicePointer = ref.alloc(this.libuvc.types.uvc_device_tPointer); 107 | 108 | const result = this.libuvc.functions.uvc_find_device( 109 | this.uvcContext.deref(), 110 | devicePointer, 111 | vendorId, 112 | productId, 113 | serialNumber 114 | ); 115 | 116 | if (result !== 0) { 117 | throw new Error(`this.libuvc.functions.uvc_find_device(...): ${result}`); 118 | } 119 | 120 | const device = new Device(this.libuvc, devicePointer); 121 | 122 | return device; 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /src/controls.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | 21 | const { intersection, without } = require("lodash"); 22 | 23 | const ref = require("ref-napi"); 24 | const libuvc = require("@ffi-libraries/libuvc-v0.0.6"); 25 | 26 | const assertLibuvcResult = require("./utilities/assert-libuvc-result"); 27 | const LibuvcStandardUnits = require("./libuvc-standard-units"); 28 | 29 | module.exports = class Controls { 30 | libuvc = null; 31 | deviceHandle = null; 32 | libuvcStandardUnits = null; 33 | 34 | constructor(libuvc, deviceHandle) { 35 | assert(libuvc); 36 | assert(deviceHandle); 37 | 38 | this.libuvc = libuvc; 39 | this.deviceHandle = deviceHandle; 40 | } 41 | 42 | static getTypeArray(control) { 43 | return Object.entries(control.fields) 44 | .map( 45 | ([name, field]) => `${field.signed ? "" : "u"}int${field.length * 8}` 46 | ) 47 | .map((type) => ref.alloc(type)); 48 | } 49 | 50 | valueGetterGenerator = (name, control) => () => { 51 | const valueGetter = this.libuvc.functions[`uvc_get_${name}`]; 52 | const valueGetterArguments = [ 53 | this.deviceHandle.deviceHandle.deref(), 54 | ...Controls.getTypeArray(control), 55 | this.libuvc.constants.uvc_req_code.UVC_GET_CUR, 56 | ]; 57 | const result = valueGetter(...valueGetterArguments); 58 | 59 | assertLibuvcResult( 60 | this.libuvc, 61 | result, 62 | `Error getting value for control ${JSON.stringify(name)}.` 63 | ); 64 | 65 | const values = valueGetterArguments 66 | .slice(1, -1) 67 | .map((value) => value.deref()); 68 | 69 | return values; 70 | }; 71 | 72 | valueSetterGenerator = (name) => (value) => { 73 | const valueSetter = this.libuvc.functions[`uvc_set_${name}`]; 74 | const setterArguments = [this.deviceHandle.deviceHandle.deref()].concat( 75 | value 76 | ); 77 | const result = valueSetter(...setterArguments); 78 | 79 | assertLibuvcResult( 80 | this.libuvc, 81 | result, 82 | `Error setting value ${JSON.stringify( 83 | value 84 | )} for control ${JSON.stringify(name)}.` 85 | ); 86 | }; 87 | 88 | async initialize() { 89 | assert.notStrictEqual(this.deviceHandle, null); 90 | assert.strictEqual(this.libuvcStandardUnits, null); 91 | 92 | this.libuvcStandardUnits = new LibuvcStandardUnits(); 93 | await this.libuvcStandardUnits.initialize(); 94 | 95 | for (const [name, control] of Object.entries( 96 | this.libuvcStandardUnits.controls 97 | )) { 98 | const longName = control.control.toLowerCase(); 99 | 100 | this[longName] = { 101 | // NOTE: asynchronous getter. 102 | get: async () => this.valueGetterGenerator(name, control)(), 103 | // NOTE: asynchronous setter. 104 | set: async (value) => this.valueSetterGenerator(name)(value), 105 | 106 | // TODO: fix ffi-generate to output the uvc_ct_ctrl_selector constants, then use them for uvc_get_ctrl(...). 107 | // TODO: move/copy uvc_ct_ctrl_selector to standard-units.yaml? 108 | // TODO: use uvc_ct_ctrl_selector for the "regular" control value getters/setters? 109 | // min: minGetterGenerator(name), 110 | // max: maxGetterGenerator(name), 111 | // def: defGetterGenerator(name), 112 | // info: infoGetterGenerator(name) 113 | }; 114 | } 115 | 116 | // TODO: seal self to avoid accidental controls.typo = 999; 117 | Object.seal(this); 118 | } 119 | 120 | async uninitialize() { 121 | assert.notStrictEqual(this.libuvc, null); 122 | assert.notStrictEqual(this.deviceHandle, null); 123 | assert.notStrictEqual(this.libuvcStandardUnits, null); 124 | 125 | await this.libuvcStandardUnits.uninitialize(); 126 | this.libuvcStandardUnits = null; 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /src/frame-streamer.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of node-uvc -- Library for USB Video Class (UVC) devices. 3 | Copyright (C) 2020 Joel Purra 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | const assert = require("assert"); 20 | const stream = require("stream"); 21 | const util = require("util"); 22 | 23 | const ffi = require("ffi-napi"); 24 | const ref = require("ref-napi"); 25 | const libuvc = require("@ffi-libraries/libuvc-v0.0.6"); 26 | 27 | const FrameStream = require("./frame-stream"); 28 | 29 | const finished = util.promisify(stream.finished); 30 | const js_void = ref.types.void; 31 | const js_voidPointer = ref.refType(js_void); 32 | 33 | module.exports = class FrameStreamer { 34 | libuvc = null; 35 | deviceHandle = null; 36 | format = null; 37 | width = 0; 38 | height = 0; 39 | fps = 0; 40 | deviceHandle = null; 41 | frameStream = null; 42 | onFrameCallback = null; 43 | 44 | constructor(libuvc, deviceHandle, format, width, height, fps) { 45 | assert(libuvc); 46 | assert(deviceHandle); 47 | assert(format > 0); 48 | assert(width > 0); 49 | assert(height > 0); 50 | assert(fps > 0); 51 | 52 | this.libuvc = libuvc; 53 | this.deviceHandle = deviceHandle; 54 | this.format = format; 55 | this.width = width; 56 | this.height = height; 57 | this.fps = fps; 58 | } 59 | 60 | async initialize() { 61 | assert.notStrictEqual(this.libuvc, null); 62 | assert.notStrictEqual(this.deviceHandle, null); 63 | assert.strictEqual(this.frameStream, null); 64 | assert.strictEqual(this.onFrameCallback, null); 65 | 66 | const controlBlockFormatSize = ref.alloc( 67 | this.libuvc.types.uvc_stream_ctrl_t 68 | ); 69 | 70 | { 71 | const result = this.libuvc.functions.uvc_get_stream_ctrl_format_size( 72 | this.deviceHandle.deviceHandle.deref(), 73 | controlBlockFormatSize, 74 | this.format, 75 | this.width, 76 | this.height, 77 | this.fps 78 | ); 79 | 80 | if (result !== 0) { 81 | throw new Error( 82 | `this.libuvc.functions.uvc_get_stream_ctrl_format_size(...): ${result}` 83 | ); 84 | } 85 | } 86 | 87 | { 88 | this.frameStream = new FrameStream(); 89 | this.onFrameCallback = ffi.Callback( 90 | "void", 91 | [this.libuvc.types.uvc_frame_tPointer, js_voidPointer], 92 | (frame, userPointer) => this.frameStream.write(frame) 93 | ); 94 | 95 | // NOTE: keeping state in the class rather than in the user pointer. 96 | // Creation: const userPointer = ref.alloc("Object", anything); 97 | // Consumption in callback: const anything = ref.readObject(userPointer, 0); 98 | const userPointer = ref.NULL_POINTER; 99 | 100 | // NOTE: Stream setup flags, currently undefined. Set this to zero. The lower bit is reserved for backward compatibility. 101 | // https://ken.tossell.net/libuvc/doc/group__streaming.html#gaa7edf40956feeca14794f6cd6462e316 102 | const streamSetupFlags = 0; 103 | 104 | const result = this.libuvc.functions.uvc_start_streaming( 105 | this.deviceHandle.deviceHandle.deref(), 106 | controlBlockFormatSize, 107 | this.onFrameCallback, 108 | userPointer, 109 | streamSetupFlags 110 | ); 111 | 112 | if (result !== 0) { 113 | throw new Error( 114 | `this.libuvc.functions.uvc_start_streaming(...): ${result}` 115 | ); 116 | } 117 | } 118 | 119 | return this.frameStream; 120 | } 121 | 122 | async uninitialize() { 123 | assert.notStrictEqual(this.libuvc, null); 124 | assert.notStrictEqual(this.deviceHandle, null); 125 | assert.notStrictEqual(this.onFrameCallback, null); 126 | assert.notStrictEqual(this.frameStream, null); 127 | 128 | this.libuvc.functions.uvc_stop_streaming( 129 | this.deviceHandle.deviceHandle.deref() 130 | ); 131 | 132 | this.onFrameCallback = null; 133 | 134 | this.frameStream.end(); 135 | 136 | // TODO: figure out why the application crashes silently when waiting to finish the writable part of the transform stream. 137 | await finished(this.frameStream, { 138 | writable: false, 139 | }); 140 | 141 | this.frameStream = null; 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /examples/reference-implementation/stream-disk/stream-disk.js: -------------------------------------------------------------------------------- 1 | /* Software License Agreement (BSD License) 2 | * 3 | * Copyright (C) 2010-2015 Ken Tossell 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials provided 15 | * with the distribution. 16 | * * Neither the name of the author nor other contributors may be 17 | * used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | * POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | // #include "libuvc/libuvc.h" 35 | // #include 36 | 37 | const { once } = require("events"); 38 | const fs = require("fs"); 39 | const util = require("util"); 40 | const stream = require("stream"); 41 | 42 | const ffi = require("ffi-napi"); 43 | const ref = require("ref-napi"); 44 | 45 | const { load } = require("@ffi-libraries/libuvc-v0.0.6"); 46 | 47 | const finished = util.promisify(stream.finished); 48 | const js_void = ref.types.void; 49 | const js_voidPointer = ref.refType(js_void); 50 | 51 | const sleep = async (s) => 52 | new Promise((resolve) => { 53 | setTimeout(resolve, s * 1000); 54 | }); 55 | 56 | /* This callback function runs once per frame. Use it to perform any 57 | * quick processing you need, or have it put the frame into your application's 58 | * input queue. If this function takes too long, you'll start losing frames. */ 59 | async function /* void */ cb(/* uvc_frame_t * */ frame, /* void * */ ptr) { 60 | const fileWriteStream = ref.readObject(ptr, 0); 61 | const frameDeref = frame.deref(); 62 | const mjpeg = ref.reinterpret(frameDeref.data, frameDeref.data_bytes, 0); 63 | 64 | if (!fileWriteStream.write(mjpeg)) { 65 | await once(fileWriteStream, "drain"); 66 | } 67 | 68 | if (frameDeref.sequence % 30 == 0) { 69 | console.log(" * got image", frameDeref.sequence); 70 | } 71 | } 72 | 73 | async function /* int */ main(/* int */ argc, /* char ** */ argv) { 74 | const library = await load(); 75 | const headerLoader = library.headers["./include/libuvc/libuvc.h"]; 76 | const libuvc = await headerLoader(); 77 | 78 | const filename = "stream-disk.mjpeg"; 79 | const /* uvc_context_t * */ ctx = ref.alloc( 80 | libuvc.types.uvc_context_tPointer 81 | ); 82 | const /* uvc_device_t * */ dev = ref.alloc(libuvc.types.uvc_device_tPointer); 83 | const /* uvc_device_handle_t * */ devh = ref.alloc( 84 | libuvc.types.uvc_device_handle_tPointer 85 | ); 86 | const /* uvc_stream_ctrl_t */ ctrl = ref.alloc( 87 | libuvc.types.uvc_stream_ctrl_t 88 | ); 89 | let /* uvc_error_t */ res; 90 | 91 | /* Initialize a UVC service context. Libuvc will set up its own libusb 92 | * context. Replace NULL with a libusb_context pointer to run libuvc 93 | * from an existing libusb context. */ 94 | res = libuvc.functions.uvc_init(/* & */ ctx, null); 95 | 96 | if (res < 0) { 97 | libuvc.functions.uvc_perror(res, "uvc_init"); 98 | return res; 99 | } 100 | 101 | console.log("UVC initialized"); 102 | 103 | /* Locates the first attached UVC device, stores in dev */ 104 | res = libuvc.functions.uvc_find_device( 105 | ctx.deref(), 106 | /* & */ dev, 107 | 0, 108 | 0, 109 | null 110 | ); /* filter devices: vendor_id, product_id, "serial_num" */ 111 | 112 | if (res < 0) { 113 | libuvc.functions.uvc_perror(res, "uvc_find_device"); /* no devices found */ 114 | } else { 115 | console.log("Device found"); 116 | 117 | /* Try to open the device: requires exclusive access */ 118 | res = libuvc.functions.uvc_open(dev.deref(), /* & */ devh); 119 | 120 | if (res < 0) { 121 | libuvc.functions.uvc_perror(res, "uvc_open"); /* unable to open device */ 122 | } else { 123 | console.log("Device opened"); 124 | 125 | /* Print out a message containing all the information that libuvc 126 | * knows about the device */ 127 | libuvc.functions.uvc_print_diag(devh.deref(), null); 128 | 129 | /* Try to negotiate a 640x480 30 fps YUYV stream profile */ 130 | res = libuvc.functions.uvc_get_stream_ctrl_format_size( 131 | devh.deref(), 132 | /* & */ ctrl /* result stored in ctrl */, 133 | libuvc.constants.uvc_frame_format 134 | .UVC_FRAME_FORMAT_MJPEG /* MJPEG for writing to disk */, 135 | 640, 136 | 480, 137 | 30 /* width, height, fps */ 138 | ); 139 | 140 | /* Print out the result */ 141 | libuvc.functions.uvc_print_stream_ctrl(/* & */ ctrl, null); 142 | 143 | if (res < 0) { 144 | libuvc.functions.uvc_perror( 145 | res, 146 | "get_mode" 147 | ); /* device doesn't provide a matching stream */ 148 | } else { 149 | /* Start the video stream. The library will call user function cb: 150 | * cb(frame, (void*) user_ptr) 151 | */ 152 | const callback = ffi.Callback( 153 | "void", 154 | [libuvc.types.uvc_frame_tPointer, js_voidPointer], 155 | cb 156 | ); 157 | const fileWriteStream = fs.createWriteStream(filename); 158 | const user_ptr = ref.alloc("Object", fileWriteStream); 159 | res = libuvc.functions.uvc_start_streaming( 160 | devh.deref(), 161 | /* & */ ctrl, 162 | callback, 163 | user_ptr, 164 | 0 165 | ); 166 | 167 | if (res < 0) { 168 | fileWriteStream.end(); 169 | await finished(fileWriteStream); 170 | libuvc.functions.uvc_perror( 171 | res, 172 | "start_streaming" 173 | ); /* unable to start stream */ 174 | } else { 175 | console.log("Streaming..."); 176 | 177 | /* enable auto exposure - see uvc_set_ae_mode documentation */ 178 | console.log("Enabling auto exposure ..."); 179 | 180 | const /* uint8_t */ UVC_AUTO_EXPOSURE_MODE_AUTO = 2; 181 | res = libuvc.functions.uvc_set_ae_mode( 182 | devh.deref(), 183 | UVC_AUTO_EXPOSURE_MODE_AUTO 184 | ); 185 | 186 | if (res == libuvc.constants.uvc_error.UVC_SUCCESS) { 187 | console.log(" ... enabled auto exposure"); 188 | } else if (res == libuvc.constants.uvc_error.UVC_ERROR_PIPE) { 189 | /* this error indicates that the camera does not support the full AE mode; 190 | * try again, using aperture priority mode (fixed aperture, variable exposure time) */ 191 | console.log( 192 | " ... full AE not supported, trying aperture priority mode" 193 | ); 194 | const /* uint8_t */ UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY = 8; 195 | res = libuvc.functions.uvc_set_ae_mode( 196 | devh.deref(), 197 | UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY 198 | ); 199 | 200 | if (res < 0) { 201 | libuvc.functions.uvc_perror( 202 | res, 203 | " ... uvc_set_ae_mode failed to enable aperture priority mode" 204 | ); 205 | } else { 206 | console.log(" ... enabled aperture priority auto exposure mode"); 207 | } 208 | } else { 209 | libuvc.functions.uvc_perror( 210 | res, 211 | " ... uvc_set_ae_mode failed to enable auto exposure mode" 212 | ); 213 | } 214 | 215 | await sleep(10); /* stream for 10 seconds */ 216 | 217 | fileWriteStream.end(); 218 | await finished(fileWriteStream); 219 | 220 | /* End the stream. Blocks until last callback is serviced */ 221 | libuvc.functions.uvc_stop_streaming(devh.deref()); 222 | console.log("Done streaming."); 223 | } 224 | } 225 | 226 | /* Release our handle on the device */ 227 | libuvc.functions.uvc_close(devh.deref()); 228 | console.log("Device closed"); 229 | } 230 | 231 | /* Release the device descriptor */ 232 | libuvc.functions.uvc_unref_device(dev.deref()); 233 | } 234 | 235 | /* Close the UVC context. This closes and cleans up any existing device handles, 236 | * and it closes the libusb context if one was not provided. */ 237 | libuvc.functions.uvc_exit(ctx.deref()); 238 | 239 | await library.unload(); 240 | 241 | console.log("UVC exited"); 242 | 243 | return 0; 244 | } 245 | 246 | async function mainWrapper() { 247 | try { 248 | await main(); 249 | } catch (error) { 250 | console.log("main", error); 251 | 252 | process.exitCode = 1; 253 | } 254 | } 255 | 256 | try { 257 | mainWrapper(); 258 | } catch (error) { 259 | console.error("mainWrapper", error); 260 | 261 | process.exitCode = 1; 262 | } 263 | -------------------------------------------------------------------------------- /data/standard-units.yaml: -------------------------------------------------------------------------------- 1 | # LICENSE.txt 2 | --- | 3 | Software License Agreement (BSD License) 4 | 5 | Copyright (C) 2010-2015 Ken Tossell 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions 10 | are met: 11 | 12 | * Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above 15 | copyright notice, this list of conditions and the following 16 | disclaimer in the documentation and/or other materials provided 17 | with the distribution. 18 | * Neither the name of the author nor other contributors may be 19 | used to endorse or promote products derived from this software 20 | without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # standard-units.yaml 36 | --- 37 | units: 38 | camera_terminal: 39 | type: standard 40 | description: Standard camera input terminal (captures images from sensor) 41 | control_prefix: CT 42 | controls: 43 | scanning_mode: 44 | control: SCANNING_MODE 45 | length: 1 46 | fields: 47 | mode: 48 | type: int 49 | position: 0 50 | length: 1 51 | doc: "0: interlaced, 1: progressive" 52 | ae_mode: 53 | control: AE_MODE 54 | length: 1 55 | fields: 56 | mode: 57 | type: int 58 | position: 0 59 | length: 1 60 | doc: 61 | "1: manual mode; 2: auto mode; 4: shutter priority mode; 8: aperture 62 | priority mode" 63 | doc: 64 | get: |- 65 | @brief Reads camera's auto-exposure mode. 66 | 67 | See uvc_set_ae_mode() for a description of the available modes. 68 | set: |- 69 | @brief Sets camera's auto-exposure mode. 70 | 71 | Cameras may support any of the following AE modes: 72 | * UVC_AUTO_EXPOSURE_MODE_MANUAL (1) - manual exposure time, manual iris 73 | * UVC_AUTO_EXPOSURE_MODE_AUTO (2) - auto exposure time, auto iris 74 | * UVC_AUTO_EXPOSURE_MODE_SHUTTER_PRIORITY (4) - manual exposure time, auto iris 75 | * UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY (8) - auto exposure time, manual iris 76 | 77 | Most cameras provide manual mode and aperture priority mode. 78 | ae_priority: 79 | control: AE_PRIORITY 80 | length: 1 81 | fields: 82 | priority: 83 | type: int 84 | position: 0 85 | length: 1 86 | doc: 87 | "0: frame rate must remain constant; 1: frame rate may be varied 88 | for AE purposes" 89 | doc: 90 | get: |- 91 | @brief Checks whether the camera may vary the frame rate for exposure control reasons. 92 | See uvc_set_ae_priority() for a description of the `priority` field. 93 | set: |- 94 | @brief Chooses whether the camera may vary the frame rate for exposure control reasons. 95 | A `priority` value of zero means the camera may not vary its frame rate. A value of 1 96 | means the frame rate is variable. This setting has no effect outside of the `auto` and 97 | `shutter_priority` auto-exposure modes. 98 | exposure_abs: 99 | control: EXPOSURE_TIME_ABSOLUTE 100 | length: 4 101 | fields: 102 | time: 103 | type: int 104 | position: 0 105 | length: 4 106 | doc: "" 107 | doc: 108 | get: |- 109 | @brief Gets the absolute exposure time. 110 | 111 | See uvc_set_exposure_abs() for a description of the `time` field. 112 | set: |- 113 | @brief Sets the absolute exposure time. 114 | 115 | The `time` parameter should be provided in units of 0.0001 seconds (e.g., use the value 100 116 | for a 10ms exposure period). Auto exposure should be set to `manual` or `shutter_priority` 117 | before attempting to change this setting. 118 | exposure_rel: 119 | control: EXPOSURE_TIME_RELATIVE 120 | length: 1 121 | fields: 122 | step: 123 | type: int 124 | position: 0 125 | length: 1 126 | signed: true 127 | doc: 128 | number of steps by which to change the exposure time, or zero to 129 | set the default exposure time 130 | doc: "@brief {gets_sets} the exposure time relative to the current setting." 131 | focus_abs: 132 | control: FOCUS_ABSOLUTE 133 | length: 2 134 | fields: 135 | focus: 136 | type: int 137 | position: 0 138 | length: 2 139 | doc: focal target distance in millimeters 140 | doc: "@brief {gets_sets} the distance at which an object is optimally focused." 141 | focus_rel: 142 | control: FOCUS_RELATIVE 143 | length: 2 144 | fields: 145 | focus_rel: 146 | type: int 147 | position: 0 148 | length: 1 149 | signed: true 150 | doc: TODO 151 | speed: 152 | type: int 153 | position: 1 154 | length: 1 155 | doc: TODO 156 | focus_simple_range: 157 | control: FOCUS_SIMPLE 158 | length: 1 159 | fields: 160 | focus: 161 | type: int 162 | position: 0 163 | length: 1 164 | doc: TODO 165 | focus_auto: 166 | control: FOCUS_AUTO 167 | length: 1 168 | fields: 169 | state: 170 | type: int 171 | position: 0 172 | length: 1 173 | doc: TODO 174 | iris_abs: 175 | control: IRIS_ABSOLUTE 176 | length: 2 177 | fields: 178 | iris: 179 | type: int 180 | position: 0 181 | length: 2 182 | doc: TODO 183 | iris_rel: 184 | control: IRIS_RELATIVE 185 | length: 1 186 | fields: 187 | iris_rel: 188 | type: int 189 | position: 0 190 | length: 1 191 | doc: TODO 192 | zoom_abs: 193 | control: ZOOM_ABSOLUTE 194 | length: 2 195 | fields: 196 | focal_length: 197 | type: int 198 | position: 0 199 | length: 2 200 | doc: TODO 201 | zoom_rel: 202 | control: ZOOM_RELATIVE 203 | length: 3 204 | fields: 205 | zoom_rel: 206 | type: int 207 | position: 0 208 | length: 1 209 | signed: true 210 | doc: TODO 211 | digital_zoom: 212 | type: int 213 | position: 1 214 | length: 1 215 | doc: TODO 216 | speed: 217 | type: int 218 | position: 2 219 | length: 1 220 | doc: TODO 221 | pantilt_abs: 222 | control: PANTILT_ABSOLUTE 223 | length: 8 224 | fields: 225 | pan: 226 | type: int 227 | position: 0 228 | length: 4 229 | signed: true 230 | doc: TODO 231 | tilt: 232 | type: int 233 | position: 4 234 | length: 4 235 | signed: true 236 | doc: TODO 237 | pantilt_rel: 238 | control: PANTILT_RELATIVE 239 | length: 4 240 | fields: 241 | pan_rel: 242 | type: int 243 | position: 0 244 | length: 1 245 | signed: true 246 | doc: TODO 247 | pan_speed: 248 | type: int 249 | position: 1 250 | length: 1 251 | doc: TODO 252 | tilt_rel: 253 | type: int 254 | position: 2 255 | length: 1 256 | signed: true 257 | doc: TODO 258 | tilt_speed: 259 | type: int 260 | position: 3 261 | length: 1 262 | doc: TODO 263 | roll_abs: 264 | control: ROLL_ABSOLUTE 265 | length: 2 266 | fields: 267 | roll: 268 | type: int 269 | position: 0 270 | length: 2 271 | signed: true 272 | doc: TODO 273 | roll_rel: 274 | control: ROLL_RELATIVE 275 | length: 2 276 | fields: 277 | roll_rel: 278 | type: int 279 | position: 0 280 | length: 1 281 | signed: true 282 | doc: TODO 283 | speed: 284 | type: int 285 | position: 1 286 | length: 1 287 | doc: TODO 288 | privacy: 289 | control: PRIVACY 290 | length: 1 291 | fields: 292 | privacy: 293 | type: int 294 | position: 0 295 | length: 1 296 | doc: TODO 297 | digital_window: 298 | control: DIGITAL_WINDOW 299 | length: 12 300 | fields: 301 | window_top: 302 | type: int 303 | position: 0 304 | length: 2 305 | doc: TODO 306 | window_left: 307 | type: int 308 | position: 2 309 | length: 2 310 | doc: TODO 311 | window_bottom: 312 | type: int 313 | position: 4 314 | length: 2 315 | doc: TODO 316 | window_right: 317 | type: int 318 | position: 6 319 | length: 2 320 | doc: TODO 321 | num_steps: 322 | type: int 323 | position: 8 324 | length: 2 325 | doc: TODO 326 | num_steps_units: 327 | type: int 328 | position: 10 329 | length: 2 330 | doc: TODO 331 | digital_roi: 332 | control: REGION_OF_INTEREST 333 | length: 10 334 | fields: 335 | roi_top: 336 | type: int 337 | position: 0 338 | length: 2 339 | doc: TODO 340 | roi_left: 341 | type: int 342 | position: 2 343 | length: 2 344 | doc: TODO 345 | roi_bottom: 346 | type: int 347 | position: 4 348 | length: 2 349 | doc: TODO 350 | roi_right: 351 | type: int 352 | position: 6 353 | length: 2 354 | doc: TODO 355 | auto_controls: 356 | type: int 357 | position: 8 358 | length: 2 359 | doc: TODO 360 | processing_unit: 361 | type: standard 362 | description: Standard processing unit (processes images between other units) 363 | control_prefix: PU 364 | controls: 365 | backlight_compensation: 366 | control: BACKLIGHT_COMPENSATION 367 | length: 2 368 | fields: 369 | backlight_compensation: 370 | type: int 371 | position: 0 372 | length: 2 373 | doc: 374 | device-dependent backlight compensation mode; zero means backlight 375 | compensation is disabled 376 | brightness: 377 | control: BRIGHTNESS 378 | length: 2 379 | fields: 380 | brightness: 381 | type: int 382 | position: 0 383 | length: 2 384 | signed: true 385 | doc: TODO 386 | contrast: 387 | control: CONTRAST 388 | length: 2 389 | fields: 390 | contrast: 391 | type: int 392 | position: 0 393 | length: 2 394 | doc: TODO 395 | contrast_auto: 396 | control: CONTRAST_AUTO 397 | length: 1 398 | fields: 399 | contrast_auto: 400 | type: int 401 | position: 0 402 | length: 1 403 | doc: TODO 404 | gain: 405 | control: GAIN 406 | length: 2 407 | fields: 408 | gain: 409 | type: int 410 | position: 0 411 | length: 2 412 | doc: TODO 413 | power_line_frequency: 414 | control: POWER_LINE_FREQUENCY 415 | length: 1 416 | fields: 417 | power_line_frequency: 418 | type: int 419 | position: 0 420 | length: 1 421 | doc: TODO 422 | hue: 423 | control: HUE 424 | length: 2 425 | fields: 426 | hue: 427 | type: int 428 | position: 0 429 | length: 2 430 | signed: true 431 | doc: TODO 432 | hue_auto: 433 | control: HUE_AUTO 434 | length: 1 435 | fields: 436 | hue_auto: 437 | type: int 438 | position: 0 439 | length: 1 440 | doc: TODO 441 | saturation: 442 | control: SATURATION 443 | length: 2 444 | fields: 445 | saturation: 446 | type: int 447 | position: 0 448 | length: 2 449 | doc: TODO 450 | sharpness: 451 | control: SHARPNESS 452 | length: 2 453 | fields: 454 | sharpness: 455 | type: int 456 | position: 0 457 | length: 2 458 | doc: TODO 459 | gamma: 460 | control: GAMMA 461 | length: 2 462 | fields: 463 | gamma: 464 | type: int 465 | position: 0 466 | length: 2 467 | doc: TODO 468 | white_balance_temperature: 469 | control: WHITE_BALANCE_TEMPERATURE 470 | length: 2 471 | fields: 472 | temperature: 473 | type: int 474 | position: 0 475 | length: 2 476 | doc: TODO 477 | white_balance_temperature_auto: 478 | control: WHITE_BALANCE_TEMPERATURE_AUTO 479 | length: 1 480 | fields: 481 | temperature_auto: 482 | type: int 483 | position: 0 484 | length: 1 485 | doc: TODO 486 | white_balance_component: 487 | control: WHITE_BALANCE_COMPONENT 488 | length: 4 489 | fields: 490 | blue: 491 | type: int 492 | position: 0 493 | length: 2 494 | doc: TODO 495 | red: 496 | type: int 497 | position: 2 498 | length: 2 499 | doc: TODO 500 | white_balance_component_auto: 501 | control: WHITE_BALANCE_COMPONENT_AUTO 502 | length: 1 503 | fields: 504 | white_balance_component_auto: 505 | type: int 506 | position: 0 507 | length: 1 508 | doc: TODO 509 | digital_multiplier: 510 | control: DIGITAL_MULTIPLIER 511 | length: 2 512 | fields: 513 | multiplier_step: 514 | type: int 515 | position: 0 516 | length: 2 517 | doc: TODO 518 | digital_multiplier_limit: 519 | control: DIGITAL_MULTIPLIER_LIMIT 520 | length: 2 521 | fields: 522 | multiplier_step: 523 | type: int 524 | position: 0 525 | length: 2 526 | doc: TODO 527 | analog_video_standard: 528 | control: ANALOG_VIDEO_STANDARD 529 | length: 1 530 | fields: 531 | video_standard: 532 | type: int 533 | position: 0 534 | length: 1 535 | doc: TODO 536 | analog_video_lock_status: 537 | control: ANALOG_LOCK_STATUS 538 | length: 1 539 | fields: 540 | status: 541 | type: int 542 | position: 0 543 | length: 1 544 | doc: TODO 545 | selector_unit: 546 | type: standard 547 | description: Standard selector unit (controls connectivity between other units) 548 | control_prefix: SU 549 | controls: 550 | input_select: 551 | control: INPUT_SELECT 552 | length: 1 553 | fields: 554 | selector: 555 | type: int 556 | position: 0 557 | length: 1 558 | doc: TODO 559 | 560 | --------------------------------------------------------------------------------