├── .gitignore
├── LICENSE
├── README.md
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Tim Perry
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 | # raspivid-stream
2 | Raspberry pi cam video, as a stream you can send straight to web clients.
3 |
4 | The first request for a stream starts the camera recording and streams from there.
5 |
6 | All subsequent calls will be given a stream starting with the initial parameter frames (so
7 | it's renderable, but starting the actual video frames from the current time.
8 |
9 | You should be able to pass the output client-side into a renderer like [Broadway.js](https://github.com/mbebenita/Broadway),
10 | or [h264-live-player](https://www.npmjs.com/package/h264-live-player) (broadway + logic + canvas renderer)
11 | and immediately get live streaming video. See [pi-cam](https://github.com/pimterry/pi-cam) for a simple working demo.
12 |
13 | ## Installation
14 |
15 | ```
16 | npm install raspivid-stream
17 | ```
18 |
19 | ## Usage
20 |
21 | Server-side:
22 |
23 | ```js
24 | var raspividStream = require('raspivid-stream');
25 |
26 | var videoStream = raspividStream();
27 |
28 | // To stream over websockets:
29 | videoStream.on('data', (data) => {
30 | ws.send(data, { binary: true }, (error) => { if (error) console.error(error); });
31 | });
32 | ```
33 |
34 | Client-side:
35 |
36 | ```html
37 |
38 |
46 | ```
47 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const Splitter = require('stream-split');
2 | const stream = require('stream');
3 | const StreamConcat = require('stream-concat');
4 | const raspivid = require('raspivid');
5 |
6 | const NALseparator = new Buffer([0,0,0,1]);
7 |
8 | const headerData = {
9 | _waitingStream: new stream.PassThrough(),
10 | _firstFrames: [],
11 | _lastIdrFrame: null,
12 |
13 | set idrFrame(frame) {
14 | this._lastIdrFrame = frame;
15 |
16 | if (this._waitingStream) {
17 | const waitingStream = this._waitingStream;
18 | this._waitingStream = null;
19 | this.getStream().pipe(waitingStream);
20 | }
21 | },
22 |
23 | addParameterFrame: function (frame) {
24 | this._firstFrames.push(frame)
25 | },
26 |
27 | getStream: function () {
28 | if (this._waitingStream) {
29 | return this._waitingStream;
30 | } else {
31 | const headersStream = new stream.PassThrough();
32 | this._firstFrames.forEach((frame) => headersStream.push(frame));
33 | headersStream.push(this._lastIdrFrame);
34 | headersStream.end();
35 | return headersStream;
36 | }
37 | }
38 | };
39 |
40 | // This returns the live stream only, without the parameter chunks
41 | function getLiveStream(options) {
42 | return raspivid(Object.assign({
43 | width: 960,
44 | height: 540,
45 | framerate: 20,
46 | profile: 'baseline',
47 | timeout: 0
48 | }, options))
49 | .pipe(new Splitter(NALseparator))
50 | .pipe(new stream.Transform({ transform: function (chunk, encoding, callback) {
51 | const chunkWithSeparator = Buffer.concat([NALseparator, chunk]);
52 |
53 | const chunkType = chunk[0] & 0b11111;
54 |
55 | // Capture the first SPS & PPS frames, so we can send stream parameters on connect.
56 | if (chunkType === 7 || chunkType === 8) {
57 | headerData.addParameterFrame(chunkWithSeparator);
58 | } else {
59 | // The live stream only includes the non-parameter chunks
60 | this.push(chunkWithSeparator);
61 |
62 | // Keep track of the latest IDR chunk, so we can start clients off with a near-current image
63 | if (chunkType === 5) {
64 | headerData.idrFrame = chunkWithSeparator;
65 | }
66 | }
67 |
68 | callback();
69 | }}));
70 | }
71 |
72 | var liveStream = null;
73 |
74 | module.exports = function (options) {
75 | if (!liveStream) {
76 | liveStream = getLiveStream(options);
77 | }
78 |
79 | return new StreamConcat([headerData.getStream(), liveStream]);
80 | }
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "raspivid-stream",
3 | "version": "0.2.1",
4 | "description": "Raspberry pi cam video, as a stream you can send straight to web clients",
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/pimterry/raspivid-stream.git"
12 | },
13 | "keywords": [
14 | "raspberrypi",
15 | "video",
16 | "raspivid",
17 | "camera",
18 | "stream",
19 | "websockets"
20 | ],
21 | "author": "Tim Perry ",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/pimterry/raspivid-stream/issues"
25 | },
26 | "homepage": "https://github.com/pimterry/raspivid-stream#readme",
27 | "dependencies": {
28 | "raspivid": "^1.0.0",
29 | "stream-concat": "^0.1.0",
30 | "stream-split": "^1.1.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------