├── .gitignore ├── LICENSE ├── README.md ├── binding.gyp ├── examples ├── still-frame-benchmark.js ├── timelapse.js └── webserver.js ├── index.js ├── lib └── raspberry-pi-camera.js ├── package.json └── src ├── RaspberryPiCamera.cpp └── RaspberryPiCamera.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | build/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sandeep Mistry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-raspberry-pi-camera-native 2 | 3 | Use your [Raspberry Pi Camera Module](https://www.raspberrypi.org/documentation/hardware/camera/README.md) with [Node.js](https://nodejs.org) 4 | 5 | **NOTE:** Currently only supports still image capture. 6 | 7 | ## Prerequisites 8 | 9 | * Hardware 10 | * Raspberry Pi 11 | * [Raspberry Pi Camera module](https://www.raspberrypi.org/documentation/hardware/camera/README.md) 12 | 13 | * Software 14 | * [Raspberry Pi camera enabled](https://www.raspberrypi.org/documentation/configuration/camera.md) 15 | * Node.js 8 or later installed 16 | 17 | ## Install 18 | 19 | ``` 20 | npm install raspberry-pi-camera-native 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```javascript 26 | // require module 27 | const raspberryPiCamera = require('raspberry-pi-camera-native'); 28 | 29 | // add frame data event listener 30 | raspberryPiCamera.on('frame', (frameData) => { 31 | // frameData is a Node.js Buffer 32 | // ... 33 | }); 34 | 35 | // start capture 36 | raspberryPiCamera.start(); 37 | ``` 38 | 39 | ### Events 40 | 41 | #### Data 42 | 43 | Listen for raw data events (partial frame data), `data` is a [Node.js Buffer](https://nodejs.org/dist/latest/docs/api/buffer.html) 44 | 45 | ```javascript 46 | raspberryPiCamera.on('data', callback(data)); 47 | ``` 48 | 49 | #### Frame 50 | 51 | Listen for frame events (full frame data), `frameData` is a [Node.js Buffer](https://nodejs.org/dist/latest/docs/api/buffer.html) 52 | 53 | ```javascript 54 | raspberryPiCamera.on('frame', callback(frameData)); 55 | ``` 56 | 57 | ### Actions 58 | 59 | #### Start Capture 60 | 61 | ```javascript 62 | raspberryPiCamera.start(options, callback); 63 | ``` 64 | 65 | Options is a object, with the following defaults: 66 | ```javascript 67 | { 68 | width: 1280, 69 | height: 720, 70 | fps: 30, 71 | encoding: 'JPEG', 72 | quality: 75 73 | } 74 | ``` 75 | 76 | Supported values: 77 | * `width`: `32` to `2592` (v1 camera) or `3280` (v2 camera) 78 | * `height`: `16` to `1944` (v1 camera) or `2464` (v2 camera) 79 | * `fps`: `1` to `90` 80 | * `encoding`: `'JPEG'` (hardware accelerated), `'GIF'`, `'PNG'`, `'PPM'`, `'TGA'`, `'BMP'` (see [mmal_encodings.h](https://github.com/raspberrypi/userland/blob/master/interface/mmal/mmal_encodings.h) for others) 81 | * `quality`: 1 - 100 (JPEG encoding quality) 82 | 83 | #### Pause Capture 84 | 85 | ```javascript 86 | raspberryPiCamera.pause(callback); 87 | ``` 88 | 89 | #### Resume Capture 90 | 91 | ```javascript 92 | raspberryPiCamera.resume(callback); 93 | ``` 94 | 95 | #### Stop Capture 96 | 97 | ```javascript 98 | raspberryPiCamera.stop(callback); 99 | ``` 100 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "raspberry-pi-camera", 5 | "sources": [ "src/RaspberryPiCamera.cpp" ], 6 | "include_dirs": [ 7 | " { 13 | const loadAvg = (os.loadavg()[0] * 100); 14 | 15 | console.log('fps %d, load average = %f%%', fps, loadAvg.toFixed(1)); 16 | fps = 0; 17 | }, 1000); 18 | 19 | raspberryPiCamera.on('frame', (data) => { 20 | fps++; 21 | }); 22 | 23 | raspberryPiCamera.start({ 24 | width: 1920, 25 | height: 1080, 26 | fps: 30, 27 | quality: 80, 28 | encoding: 'JPEG' 29 | }); 30 | -------------------------------------------------------------------------------- /examples/timelapse.js: -------------------------------------------------------------------------------- 1 | // 2 | // This example starts camera capture saves each frame to a file on disk. 3 | // 4 | 5 | const fs = require('fs'); 6 | 7 | const raspberryPiCamera = require('../index'); // or require('raspberry-pi-camera-native'); 8 | 9 | let count = 0; 10 | 11 | raspberryPiCamera.on('frame', (frameData) => { 12 | const filename = 'img' + (count + '').padStart(3, '0') + '.jpg'; 13 | 14 | console.log('writing file: ', filename); 15 | 16 | fs.writeFile(filename, frameData, (err) => { 17 | if (err) { 18 | throw err; 19 | } 20 | 21 | count++; 22 | }); 23 | }); 24 | 25 | raspberryPiCamera.start({ 26 | width: 1920, 27 | height: 1080, 28 | fps: 1, 29 | quality: 80, 30 | encoding: 'JPEG' 31 | }); 32 | -------------------------------------------------------------------------------- /examples/webserver.js: -------------------------------------------------------------------------------- 1 | // 2 | // This example starts camera capture and a web server on port 8000 to serve live images. 3 | // 4 | 5 | const http = require('http'); 6 | 7 | const raspberryPiCamera = require('../index'); // or require('raspberry-pi-camera-native'); 8 | 9 | raspberryPiCamera.start({ 10 | width: 1280, 11 | height: 720, 12 | fps: 30, 13 | quality: 80, 14 | encoding: 'JPEG' 15 | }); 16 | 17 | const server = http.createServer((req, res) => { 18 | raspberryPiCamera.once('frame', (data) => { 19 | res.end(data); 20 | }); 21 | }); 22 | 23 | server.listen(8000); 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const RaspberryPiCamera = require('./lib/raspberry-pi-camera'); 2 | 3 | module.exports = new RaspberryPiCamera(); 4 | -------------------------------------------------------------------------------- /lib/raspberry-pi-camera.js: -------------------------------------------------------------------------------- 1 | const events = require('events'); 2 | 3 | const RaspberryPiCameraNative = require('bindings')('raspberry-pi-camera').Native; 4 | 5 | class RaspberryPiCamera extends events.EventEmitter { 6 | constructor() { 7 | super(); 8 | 9 | this._native = new RaspberryPiCameraNative((data) => this._onData(data)); 10 | this._frameData = []; 11 | } 12 | 13 | start(options, callback) { 14 | let err = undefined; 15 | 16 | try { 17 | this._native.start(options); 18 | } catch (e) { 19 | e = err; 20 | } 21 | 22 | if (callback) { 23 | process.nextTick(() => { 24 | callback(err); 25 | }); 26 | } else if (err) { 27 | throw err; 28 | } 29 | } 30 | 31 | pause(callback) { 32 | let err = undefined; 33 | 34 | try { 35 | this._native.pause(); 36 | } catch (e) { 37 | e = err; 38 | } 39 | 40 | if (callback) { 41 | process.nextTick(() => { 42 | callback(err); 43 | }); 44 | } else if (err) { 45 | throw err; 46 | } 47 | } 48 | 49 | resume(callback) { 50 | let err = undefined; 51 | 52 | try { 53 | this._native.resume(); 54 | } catch (e) { 55 | e = err; 56 | } 57 | 58 | if (callback) { 59 | process.nextTick(() => { 60 | callback(err); 61 | }); 62 | } else if (err) { 63 | throw err; 64 | } 65 | } 66 | 67 | stop(callback) { 68 | let err = undefined; 69 | 70 | try { 71 | this._native.stop(); 72 | } catch (e) { 73 | e = err; 74 | } 75 | 76 | if (callback) { 77 | process.nextTick(() => { 78 | callback(err); 79 | }); 80 | } else if (err) { 81 | throw err; 82 | } 83 | } 84 | 85 | _onData(data) { 86 | this.emit('data', data); 87 | 88 | this._frameData.push(data); 89 | 90 | if (data.length < 81920) { 91 | let frame = Buffer.concat(this._frameData); 92 | 93 | this.emit('frame', frame); 94 | 95 | this._frameData = []; 96 | } 97 | } 98 | } 99 | 100 | module.exports = RaspberryPiCamera; 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raspberry-pi-camera-native", 3 | "version": "0.0.0", 4 | "description": "Use your Raspberry Pi Camera Module with Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sandeepmistry/node-raspberry-pi-camera.git" 12 | }, 13 | "keywords": [ 14 | "Raspberry Pi", 15 | "Raspberry", 16 | "Pi", 17 | "Camera", 18 | "native" 19 | ], 20 | "author": "Sandeep Mistry", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/sandeepmistry/node-raspberry-pi-camera-native/issues" 24 | }, 25 | "homepage": "https://github.com/sandeepmistry/node-raspberry-pi-camera-native#readme", 26 | "dependencies": { 27 | "bindings": "^1.3.0", 28 | "node-addon-api": "^1.2.0" 29 | }, 30 | "devDependencies": { 31 | "node-gyp": "^3.6.2" 32 | }, 33 | "engines": { 34 | "node": ">=8.0" 35 | }, 36 | "os": [ 37 | "linux" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/RaspberryPiCamera.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "RaspberryPiCamera.h" 9 | 10 | Napi::FunctionReference RaspberryPiCamera::constructor; 11 | 12 | Napi::Object RaspberryPiCamera::Init(Napi::Env env, Napi::Object exports) { 13 | Napi::HandleScope scope(env); 14 | 15 | Napi::Function func = DefineClass(env, "RaspberryPiCamera", { 16 | InstanceMethod("start", &RaspberryPiCamera::Start), 17 | InstanceMethod("pause", &RaspberryPiCamera::Pause), 18 | InstanceMethod("resume", &RaspberryPiCamera::Resume), 19 | InstanceMethod("stop", &RaspberryPiCamera::Stop) 20 | }); 21 | 22 | constructor = Napi::Persistent(func); 23 | constructor.SuppressDestruct(); 24 | 25 | exports.Set("Native", func); 26 | return exports; 27 | } 28 | 29 | RaspberryPiCamera::RaspberryPiCamera(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { 30 | Napi::Env env = info.Env(); 31 | Napi::HandleScope scope(env); 32 | 33 | int length = info.Length(); 34 | 35 | if (length <= 0 || !info[0].IsFunction()) { 36 | Napi::TypeError::New(env, "Function expected").ThrowAsJavaScriptException(); 37 | } 38 | 39 | this->_dataCallback = Napi::Persistent(info[0].As()); 40 | } 41 | 42 | Napi::Value RaspberryPiCamera::Start(const Napi::CallbackInfo& info) { 43 | Napi::Env env = info.Env(); 44 | Napi::HandleScope scope(env); 45 | 46 | int length = info.Length(); 47 | 48 | _width = 1280; 49 | _height = 720; 50 | _fps = 30; 51 | _encoding = MMAL_ENCODING_JPEG; 52 | _quality = 75; 53 | 54 | if (length > 0 && info[0].IsObject()) { 55 | Napi::Object options = info[0].ToObject(); 56 | 57 | if (options.Has("width") && options.Get("width").IsNumber()) { 58 | _width = options.Get("width").ToNumber().Int32Value(); 59 | } 60 | 61 | if (options.Has("height") && options.Get("height").IsNumber()) { 62 | _height = options.Get("height").ToNumber().Int32Value(); 63 | } 64 | 65 | if (options.Has("fps") && options.Get("fps").IsNumber()) { 66 | _fps = options.Get("fps").ToNumber().Int32Value(); 67 | } 68 | 69 | if (options.Has("encoding") && options.Get("encoding").IsString()) { 70 | std::string encoding = options.Get("encoding").ToString(); 71 | 72 | while (encoding.length() < 4) { 73 | encoding += ' '; 74 | } 75 | 76 | _encoding = MMAL_FOURCC(encoding[0], encoding[1], encoding[2], encoding[3]); 77 | } 78 | 79 | if (options.Has("quality") && options.Get("quality").IsNumber()) { 80 | _quality = options.Get("quality").ToNumber().Int32Value(); 81 | } 82 | } 83 | 84 | this->_asyncHandle = new uv_async_t; 85 | 86 | uv_async_init(uv_default_loop(), this->_asyncHandle, (uv_async_cb)RaspberryPiCamera::AsyncCallback); 87 | uv_mutex_init(&this->_bufferQueueMutex); 88 | 89 | this->_asyncHandle->data = this; 90 | 91 | // create camera 92 | if (mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &_camera)!= MMAL_SUCCESS) { 93 | // Failed to create camera component 94 | Napi::TypeError::New(env, "Failed to create camera!").ThrowAsJavaScriptException(); 95 | return env.Undefined(); 96 | } 97 | 98 | if (!_camera->output_num) { 99 | // Camera doesn't have output ports 100 | mmal_component_destroy(_camera); 101 | Napi::TypeError::New(env, "Camera has no output ports!").ThrowAsJavaScriptException(); 102 | return env.Undefined(); 103 | } 104 | 105 | // Enable the camera control 106 | if (mmal_port_enable(_camera->control, RaspberryPiCamera::CameraControlCallback)!= MMAL_SUCCESS) { 107 | // Unable to enable control port 108 | mmal_component_destroy(_camera); 109 | Napi::TypeError::New(env, "Failed to enable camera control!").ThrowAsJavaScriptException(); 110 | return env.Undefined(); 111 | } 112 | 113 | // set camera config 114 | MMAL_PARAMETER_CAMERA_CONFIG_T cameraConfig; 115 | 116 | cameraConfig.hdr.id = MMAL_PARAMETER_CAMERA_CONFIG; 117 | cameraConfig.hdr.size = sizeof(cameraConfig); 118 | cameraConfig.max_stills_w = _width; 119 | cameraConfig.max_stills_h = _height; 120 | cameraConfig.stills_yuv422 = 0; 121 | cameraConfig.one_shot_stills = 0; 122 | cameraConfig.max_preview_video_w = _width; 123 | cameraConfig.max_preview_video_h = _height; 124 | cameraConfig.num_preview_video_frames = 1; 125 | cameraConfig.stills_capture_circular_buffer_height = 0; 126 | cameraConfig.fast_preview_resume = 0; 127 | cameraConfig.use_stc_timestamp = MMAL_PARAM_TIMESTAMP_MODE_RESET_STC; 128 | 129 | if (mmal_port_parameter_set(_camera->control, &cameraConfig.hdr) != MMAL_SUCCESS) { 130 | mmal_component_destroy(_camera); 131 | Napi::TypeError::New(env, "Failed to set camera config!").ThrowAsJavaScriptException(); 132 | return env.Undefined(); 133 | } 134 | 135 | // setup port format 136 | MMAL_ES_FORMAT_T *format = _camera->output[0]->format; 137 | 138 | format->es->video.width = VCOS_ALIGN_UP(_width, 32); 139 | format->es->video.height = VCOS_ALIGN_UP(_height, 16); 140 | format->es->video.crop.x = 0; 141 | format->es->video.crop.y = 0; 142 | format->es->video.crop.width = _width; 143 | format->es->video.crop.height = _height; 144 | format->es->video.frame_rate.num = _fps; 145 | format->es->video.frame_rate.den = 1; 146 | 147 | if (mmal_port_format_commit(_camera->output[0]) != MMAL_SUCCESS) { 148 | // Couldn't set video port format 149 | mmal_component_destroy(_camera); 150 | Napi::TypeError::New(env, "Failed to set camera format!").ThrowAsJavaScriptException(); 151 | return env.Undefined(); 152 | } 153 | 154 | //enable the camera 155 | if (mmal_component_enable(_camera) != MMAL_SUCCESS) { 156 | // Couldn't enable camera 157 | mmal_component_destroy(_camera); 158 | Napi::TypeError::New(env, "Failed to enable camera capture!").ThrowAsJavaScriptException(); 159 | return env.Undefined(); 160 | } 161 | 162 | // create image encoder 163 | if (mmal_component_create(MMAL_COMPONENT_DEFAULT_IMAGE_ENCODER, &_encoder) != MMAL_SUCCESS) { 164 | mmal_component_disable(_camera); 165 | mmal_component_destroy(_camera); 166 | Napi::TypeError::New(env, "Failed to create image encoder!").ThrowAsJavaScriptException(); 167 | return env.Undefined(); 168 | } 169 | 170 | // setup encoder format 171 | mmal_format_copy(_encoder->output[0]->format, _encoder->input[0]->format); 172 | _encoder->output[0]->format->encoding = _encoding; 173 | 174 | _encoder->output[0]->buffer_size = _encoder->output[0]->buffer_size_recommended; 175 | _encoder->output[0]->buffer_num = _encoder->output[0]->buffer_num_recommended; 176 | 177 | if (mmal_port_format_commit(_encoder->output[0]) != MMAL_SUCCESS) { 178 | mmal_component_disable(_camera); 179 | mmal_component_destroy(_encoder); 180 | mmal_component_destroy(_camera); 181 | Napi::TypeError::New(env, "Failed to set encoder format!").ThrowAsJavaScriptException(); 182 | return env.Undefined(); 183 | } 184 | 185 | if (mmal_port_parameter_set_uint32(_encoder->output[0], MMAL_PARAMETER_JPEG_Q_FACTOR, _quality) != MMAL_SUCCESS) { 186 | mmal_component_disable(_camera); 187 | mmal_component_destroy(_encoder); 188 | mmal_component_destroy(_camera); 189 | Napi::TypeError::New(env, "Failed to set encoder JPEG quality factor!").ThrowAsJavaScriptException(); 190 | return env.Undefined(); 191 | } 192 | 193 | if (mmal_port_parameter_set_uint32(_encoder->output[0], MMAL_PARAMETER_JPEG_RESTART_INTERVAL, 0) != MMAL_SUCCESS) { 194 | mmal_component_disable(_camera); 195 | mmal_component_destroy(_encoder); 196 | mmal_component_destroy(_camera); 197 | Napi::TypeError::New(env, "Failed to set encoder JPEG restart interval!").ThrowAsJavaScriptException(); 198 | return env.Undefined(); 199 | } 200 | 201 | // enable encoder 202 | if (mmal_component_enable(_encoder) != MMAL_SUCCESS) { 203 | mmal_component_disable(_camera); 204 | mmal_component_destroy(_encoder); 205 | mmal_component_destroy(_camera); 206 | Napi::TypeError::New(env, "Failed to enable encoder!").ThrowAsJavaScriptException(); 207 | return env.Undefined(); 208 | } 209 | 210 | // create buffer pool 211 | _bufferPool = mmal_port_pool_create(_encoder->output[0], _encoder->output[0]->buffer_num, _encoder->output[0]->buffer_size); 212 | if (!_bufferPool) { 213 | mmal_component_disable(_encoder); 214 | mmal_component_disable(_camera); 215 | mmal_component_destroy(_encoder); 216 | mmal_component_destroy(_camera); 217 | Napi::TypeError::New(env, "Failed to create buffer pool!").ThrowAsJavaScriptException(); 218 | return env.Undefined(); 219 | } 220 | 221 | if (mmal_connection_create(&_encoderConnection, _camera->output[0], _encoder->input[0], MMAL_CONNECTION_FLAG_TUNNELLING | MMAL_CONNECTION_FLAG_ALLOCATION_ON_INPUT) != MMAL_SUCCESS) { 222 | mmal_port_pool_destroy(_encoder->output[0], _bufferPool); 223 | mmal_component_disable(_encoder); 224 | mmal_component_disable(_camera); 225 | mmal_component_destroy(_encoder); 226 | mmal_component_destroy(_camera); 227 | Napi::TypeError::New(env, "Failed to create camera encoder connection!").ThrowAsJavaScriptException(); 228 | return env.Undefined(); 229 | } 230 | 231 | if (mmal_connection_enable(_encoderConnection) != MMAL_SUCCESS) { 232 | mmal_connection_destroy(_encoderConnection); 233 | mmal_port_pool_destroy(_encoder->output[0], _bufferPool); 234 | mmal_component_disable(_encoder); 235 | mmal_component_disable(_camera); 236 | mmal_component_destroy(_encoder); 237 | mmal_component_destroy(_camera); 238 | Napi::TypeError::New(env, "Failed to enable camera encoder connection!").ThrowAsJavaScriptException(); 239 | return env.Undefined(); 240 | } 241 | 242 | // enable the port and hand it the callback 243 | _encoder->output[0]->userdata = (struct MMAL_PORT_USERDATA_T *)this; 244 | 245 | if (mmal_port_enable(_encoder->output[0], RaspberryPiCamera::BufferCallback) != MMAL_SUCCESS) { 246 | // Failed to set video buffer callback 247 | mmal_port_pool_destroy(_encoder->output[0], _bufferPool); 248 | mmal_component_disable(_encoder); 249 | mmal_component_disable(_camera); 250 | mmal_component_destroy(_encoder); 251 | mmal_component_destroy(_camera); 252 | Napi::TypeError::New(env, "Failed to enable encoder output port!").ThrowAsJavaScriptException(); 253 | return env.Undefined(); 254 | } 255 | 256 | //send all the buffers in our pool to the video port ready for use 257 | int queueLength = mmal_queue_length(_bufferPool->queue); 258 | 259 | for (int i = 0; i < queueLength; i++) { 260 | MMAL_BUFFER_HEADER_T* buffer = mmal_queue_get(_bufferPool->queue); 261 | 262 | if (!buffer) { 263 | // Unable to get a required buffer %d from pool queue 264 | mmal_port_pool_destroy(_encoder->output[0], _bufferPool); 265 | mmal_component_disable(_encoder); 266 | mmal_component_disable(_camera); 267 | mmal_component_destroy(_encoder); 268 | mmal_component_destroy(_camera); 269 | Napi::TypeError::New(env, "Failed to get buffer from pool!").ThrowAsJavaScriptException(); 270 | return env.Undefined(); 271 | } 272 | 273 | if (mmal_port_send_buffer(_encoder->output[0], buffer) != MMAL_SUCCESS) { 274 | mmal_port_pool_destroy(_encoder->output[0], _bufferPool); 275 | mmal_component_disable(_encoder); 276 | mmal_component_disable(_camera); 277 | mmal_component_destroy(_encoder); 278 | mmal_component_destroy(_camera); 279 | Napi::TypeError::New(env, "Failed to send buffer to encoder output!").ThrowAsJavaScriptException(); 280 | return env.Undefined(); 281 | } 282 | } 283 | 284 | return env.Undefined(); 285 | } 286 | 287 | Napi::Value RaspberryPiCamera::Pause(const Napi::CallbackInfo& info) { 288 | Napi::Env env = info.Env(); 289 | 290 | if (mmal_port_parameter_set_boolean(_camera->output[0], MMAL_PARAMETER_CAPTURE, 0) != MMAL_SUCCESS) { 291 | Napi::TypeError::New(env, "Failed to disable camera capture!").ThrowAsJavaScriptException(); 292 | return env.Undefined(); 293 | } 294 | 295 | return env.Undefined(); 296 | } 297 | 298 | Napi::Value RaspberryPiCamera::Resume(const Napi::CallbackInfo& info) { 299 | Napi::Env env = info.Env(); 300 | 301 | if (mmal_port_parameter_set_boolean(_camera->output[0], MMAL_PARAMETER_CAPTURE, 1) != MMAL_SUCCESS) { 302 | Napi::TypeError::New(env, "Failed to enable camera capture!").ThrowAsJavaScriptException(); 303 | return env.Undefined(); 304 | } 305 | 306 | return env.Undefined(); 307 | } 308 | 309 | Napi::Value RaspberryPiCamera::Stop(const Napi::CallbackInfo& info) { 310 | Napi::Env env = info.Env(); 311 | 312 | mmal_port_disable(_encoder->output[0]); 313 | mmal_port_pool_destroy(_encoder->output[0], _bufferPool); 314 | mmal_connection_destroy(_encoderConnection); 315 | mmal_component_disable(_encoder); 316 | mmal_component_disable(_camera); 317 | mmal_component_destroy(_encoder); 318 | mmal_component_destroy(_camera); 319 | 320 | uv_close((uv_handle_t*)this->_asyncHandle, (uv_close_cb)RaspberryPiCamera::AsyncCloseCallback); 321 | uv_mutex_destroy(&this->_bufferQueueMutex); 322 | 323 | return env.Undefined(); 324 | } 325 | 326 | void RaspberryPiCamera::_processBufferQueue() { 327 | uv_mutex_lock(&this->_bufferQueueMutex); 328 | 329 | Napi::Env env = this->Env(); 330 | Napi::HandleScope scope(env); 331 | 332 | while (!this->_bufferQueue.empty()) { 333 | MMAL_BUFFER_HEADER_T* buffer = this->_bufferQueue.front(); 334 | this->_bufferQueue.pop(); 335 | 336 | Napi::Buffer data = Napi::Buffer::Copy(env, (uint8_t*)(buffer->data + buffer->offset), buffer->length); 337 | 338 | this->_dataCallback.MakeCallback(env.Global(), { data }); 339 | 340 | mmal_port_send_buffer(_encoder->output[0], buffer); 341 | } 342 | 343 | uv_mutex_unlock(&this->_bufferQueueMutex); 344 | } 345 | 346 | void RaspberryPiCamera::AsyncCallback(uv_async_t* handle) 347 | { 348 | RaspberryPiCamera* raspberryPiCamera = (RaspberryPiCamera*)handle->data; 349 | 350 | raspberryPiCamera->_processBufferQueue(); 351 | } 352 | 353 | void RaspberryPiCamera::AsyncCloseCallback(uv_async_t* handle) 354 | { 355 | delete handle; 356 | } 357 | 358 | void RaspberryPiCamera::CameraControlCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer) { 359 | mmal_buffer_header_release(buffer); 360 | } 361 | 362 | void RaspberryPiCamera::BufferCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer) { 363 | RaspberryPiCamera* raspberryPiCamera = (RaspberryPiCamera*)port->userdata; 364 | 365 | uv_mutex_lock(&raspberryPiCamera->_bufferQueueMutex); 366 | raspberryPiCamera->_bufferQueue.push(buffer); 367 | uv_mutex_unlock(&raspberryPiCamera->_bufferQueueMutex); 368 | 369 | uv_async_send(raspberryPiCamera->_asyncHandle); 370 | } 371 | 372 | Napi::Object InitAll(Napi::Env env, Napi::Object exports) { 373 | bcm_host_init(); 374 | 375 | return RaspberryPiCamera::Init(env, exports); 376 | } 377 | 378 | NODE_API_MODULE(addon, InitAll); 379 | -------------------------------------------------------------------------------- /src/RaspberryPiCamera.h: -------------------------------------------------------------------------------- 1 | #ifndef RASPBERRY_PI_CAMERA_H 2 | #define RASPBERRY_PI_CAMERA_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | class RaspberryPiCamera : public Napi::ObjectWrap { 14 | 15 | public: 16 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 17 | RaspberryPiCamera(const Napi::CallbackInfo& info); 18 | 19 | Napi::Value Start(const Napi::CallbackInfo& info); 20 | Napi::Value Pause(const Napi::CallbackInfo& info); 21 | Napi::Value Resume(const Napi::CallbackInfo& info); 22 | Napi::Value Stop(const Napi::CallbackInfo& info); 23 | 24 | private: 25 | static Napi::FunctionReference constructor; 26 | 27 | void _processBufferQueue(); 28 | 29 | static void AsyncCallback(uv_async_t* handle); 30 | static void AsyncCloseCallback(uv_async_t* handle); 31 | 32 | static void CameraControlCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer); 33 | static void BufferCallback(MMAL_PORT_T* port, MMAL_BUFFER_HEADER_T* buffer); 34 | 35 | private: 36 | MMAL_COMPONENT_T* _camera; 37 | MMAL_COMPONENT_T* _encoder; 38 | MMAL_POOL_T* _bufferPool; 39 | MMAL_CONNECTION_T *_encoderConnection; 40 | int _width; 41 | int _height; 42 | int _fps; 43 | MMAL_FOURCC_T _encoding; 44 | int _quality; 45 | 46 | uv_async_t* _asyncHandle; 47 | uv_mutex_t _bufferQueueMutex; 48 | std::queue _bufferQueue; 49 | 50 | Napi::FunctionReference _dataCallback; 51 | }; 52 | 53 | 54 | #endif 55 | --------------------------------------------------------------------------------