Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ccb09e7
Showing
22 changed files
with
1,606 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
Copyright (c) 2012 Bernhard K. Weisshuhn (bkw@codingforce.com) and contributors | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
|
||
This code includes files from the Boradway.js project: | ||
https://github.com/mbebenita/Broadway . | ||
|
||
License and author information in included in the directory | ||
public/js/vendor/broadway. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# node-dronestream | ||
|
||
Get a realtime live video stream from your | ||
[Parrot AR Drone 2.0](http://ardrone2.parrot.com/) straight to your browser. | ||
|
||
## Requirements | ||
|
||
You'll needs a decent a decent and current browser and some cpu horsepower. | ||
This code uses web-sockets and the incredibly awesome | ||
[Broadway.js](https://github.com/mbebenita/Broadway) to render the video frames | ||
in your browser using a WebGL canvas. | ||
|
||
|
||
## How it works | ||
|
||
The drone sends a proprietary video feed on 192.168.1.1 port 5555. This is | ||
mostly a h264 baseline video, but adds custom framing. These frames are parsed | ||
and mostly disposed of. The remaining h264 payload is split into NAL units and | ||
sent to the browser via web sockets. | ||
|
||
In the browser broadway takes care of the rendering of the WebGL canvas. | ||
|
||
## Status | ||
|
||
For this release I was exclusively interested in the lowest possible latency. | ||
There is no error handling for the websockets, the connection to the drone or | ||
the video player what-so-ever. This may come eventually, or may not. I think it | ||
is enough to be used as a starting point for your own integration. | ||
|
||
## Thanks | ||
|
||
- Triple high fives to Felix 'felixge' Geisendörfer for getting the whole | ||
NodeCopter movement started and being extremely helpful in the process of | ||
getting this together. | ||
|
||
- André 'zoddy' Kussmann for supplying the drone and allowing me to keep | ||
hacking on it, even when he had to cancel the NodeCopter event for himself. | ||
|
||
- Michael Bebenita, Alon Zakai, Andreas Gal and Mathieu 'p01' Henri for the | ||
magic of Broadway.js | ||
|
||
- Johann Phillip Strathausen for being a great team mate at NodeCopter 2012 | ||
Berlin. | ||
|
||
- Brian Leroux for being not content with the original solution and for | ||
cleaning up the predecessor, nodecopter-stream. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
'use strict'; | ||
/** | ||
* Module dependencies. | ||
*/ | ||
|
||
var express = require('express') | ||
, routes = require('./routes') | ||
, http = require('http') | ||
, path = require('path') | ||
, app = express() | ||
, server = http.createServer(app) | ||
, WebSocketServer = require('ws').Server | ||
, wss = new WebSocketServer({server: server}) | ||
; | ||
|
||
wss.on('connection', function (socket) { | ||
var arDrone = require('ar-drone'), | ||
tcpVideoStream = new arDrone.Client.PngStream.TcpVideoStream({timeout: 4000}), | ||
Parser = require('./lib/PaVEParser'), | ||
p = new Parser(); | ||
|
||
// TODO: handle client disconnect | ||
p.on('data', function (data) { | ||
socket.send(data, {binary: true}); | ||
}); | ||
|
||
tcpVideoStream.connect(function () { | ||
tcpVideoStream.pipe(p); | ||
}); | ||
}); | ||
|
||
|
||
app.configure(function () { | ||
app.set('port', process.env.PORT || 3000); | ||
app.set('views', __dirname + '/views'); | ||
app.set('view engine', 'jade', { pretty: true }); | ||
app.use(express.favicon()); | ||
app.use(express.logger('dev')); | ||
app.use(express.bodyParser()); | ||
app.use(express.methodOverride()); | ||
app.use(app.router); | ||
app.use(express.static(path.join(__dirname, 'public'))); | ||
}); | ||
|
||
app.configure('development', function () { | ||
app.use(express.errorHandler()); | ||
app.locals.pretty = true; | ||
}); | ||
|
||
|
||
app.get('/', routes.index); | ||
|
||
if (module.parent) { | ||
module.exports = server; | ||
} else { | ||
server.listen(app.get('port'), function () { | ||
console.log("Express server listening on port " + app.get('port')); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Based on PaVEParser by Felix Geisendörfer | ||
// https://github.com/felixge/node-ar-drone/blob/master/lib/video/PaVEParser.js | ||
|
||
// The AR Drone 2.0 allows a tcp client to receive H264 (MPEG4.10 AVC) video | ||
// from the drone. However, the frames are wrapped by Parrot Video | ||
// Encapsulation (PaVE), which this class parses. | ||
'use strict'; | ||
|
||
var util = require('util') | ||
, Stream = require('stream').Stream | ||
, buffy = require('buffy') | ||
; | ||
|
||
module.exports = PaVEParser; | ||
util.inherits(PaVEParser, Stream); | ||
function PaVEParser() { | ||
Stream.call(this); | ||
|
||
this.writable = true; | ||
this.readable = true; | ||
|
||
this._parser = buffy.createReader(); | ||
this._state = 'header'; | ||
this._toRead = undefined; | ||
// TODO: search forward in buffer to last I-Frame | ||
this._frame_type = undefined; | ||
} | ||
|
||
PaVEParser.HEADER_SIZE = 68; // 64 in older firmwares, but doesn't matter. | ||
|
||
PaVEParser.prototype.write = function (buffer) { | ||
var parser = this._parser | ||
, signature | ||
, header_size | ||
, readable | ||
; | ||
|
||
parser.write(buffer); | ||
|
||
while (true) { | ||
switch (this._state) { | ||
case 'header': | ||
if (parser.bytesAhead() < PaVEParser.HEADER_SIZE) { | ||
return true; | ||
} | ||
signature = parser.ascii(4); | ||
|
||
if (signature !== 'PaVE') { | ||
// TODO: wait/look for next PaVE frame | ||
this.emit('error', new Error( | ||
'Invalid signature: ' + JSON.stringify(signature) | ||
)); | ||
return; | ||
} | ||
|
||
parser.skip(2); | ||
header_size = parser.uint16LE(); | ||
// payload_size | ||
this._toRead = parser.uint32LE(); | ||
// skip 18 bytes:: | ||
// encoded_stream_width 2 | ||
// encoded_stream_height 2 | ||
// display_width 2 | ||
// display_height 2 | ||
// frame_number 4 | ||
// timestamp 4 | ||
// total_chunks 1 | ||
// chunk_index 1 | ||
parser.skip(18); | ||
this._frame_type = parser.uint8(); | ||
|
||
// bytes consumed so far: 4 + 2 + 2 + 4 + 18 + 1 = 31. Skip ahead. | ||
parser.skip(header_size - 31); | ||
|
||
this._state = 'payload'; | ||
break; | ||
|
||
case 'payload': | ||
readable = parser.bytesAhead(); | ||
if (readable < this._toRead) { | ||
return true; | ||
} | ||
|
||
// also skip first NAL-Unit boundary: (4) | ||
parser.skip(4); | ||
this._toRead -= 4; | ||
this.sendData(parser.buffer(this._toRead), this._frame_type); | ||
this._toRead = undefined; | ||
this._state = 'header'; | ||
break; | ||
} | ||
} | ||
}; | ||
|
||
|
||
PaVEParser.prototype.sendData = function (data, frametype) { | ||
var lastBegin = 0, i, l; | ||
if (frametype === 1) { | ||
// I-Frame, split more | ||
// Todo: optimize. | ||
for (i = 0, l = data.length - 4; i < l; i++) { | ||
if ( | ||
data[i] === 0 && | ||
data[i + 1] === 0 && | ||
data[i + 2] === 0 && | ||
data[i + 3] === 1 | ||
) { | ||
if (lastBegin < i) { | ||
this.emit('data', data.slice(lastBegin, i)); | ||
lastBegin = i + 4; | ||
i += 4; | ||
} | ||
} | ||
} | ||
this.emit('data', data.slice(lastBegin)); | ||
} else { | ||
this.emit('data', data); | ||
} | ||
}; | ||
|
||
PaVEParser.prototype.end = function () { | ||
// nothing to do, just here so pipe() does not complain | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "node-dronestream", | ||
"description": "video live stream from your parrot ar.drone 2.0 to your browser in pure javascript", | ||
"version": "0.1.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:bkw/node-dronestream.git" | ||
}, | ||
"keywords": [ | ||
"drone", | ||
"nodecopter", | ||
"parrot", | ||
"video", | ||
"stream", | ||
"browser", | ||
"x264" | ||
], | ||
"scripts": { | ||
"start": "node app" | ||
}, | ||
"dependencies": { | ||
"express": "3.0.0rc5", | ||
"jade": "*", | ||
"ws": "~0.4.22", | ||
"ar-drone": "0.0.3", | ||
"buffy": "0.0.4" | ||
}, | ||
"author": "Bernhard K. Weisshuhn <bkw@codingforce.com>", | ||
"license": "BSD" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/*! normalize.css v1.0.1 | MIT License | git.io/normalize */ | ||
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block} | ||
audio,canvas,video{display:inline-block;*display:inline;*zoom:1} | ||
audio:not([controls]){display:none;height:0} | ||
[hidden]{display:none} | ||
html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%} | ||
html,button,input,select,textarea{font-family:sans-serif} | ||
body{margin:0} | ||
a:focus{outline:thin dotted} | ||
a:active,a:hover{outline:0} | ||
h1{font-size:2em;margin:.67em 0} | ||
h2{font-size:1.5em;margin:.83em 0} | ||
h3{font-size:1.17em;margin:1em 0} | ||
h4{font-size:1em;margin:1.33em 0} | ||
h5{font-size:.83em;margin:1.67em 0} | ||
h6{font-size:.75em;margin:2.33em 0} | ||
abbr[title]{border-bottom:1px dotted} | ||
b,strong{font-weight:bold} | ||
blockquote{margin:1em 40px} | ||
dfn{font-style:italic} | ||
mark{background:#ff0;color:#000} | ||
p,pre{margin:1em 0} | ||
code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em} | ||
pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word} | ||
q{quotes:none} | ||
q:before,q:after{content:'';content:none} | ||
small{font-size:80%} | ||
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} | ||
sup{top:-0.5em} | ||
sub{bottom:-0.25em} | ||
dl,menu,ol,ul{margin:1em 0} | ||
dd{margin:0 0 0 40px} | ||
menu,ol,ul{padding:0 0 0 40px} | ||
nav ul,nav ol{list-style:none;list-style-image:none} | ||
img{border:0;-ms-interpolation-mode:bicubic} | ||
svg:not(:root){overflow:hidden} | ||
figure{margin:0} | ||
form{margin:0} | ||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} | ||
legend{border:0;padding:0;white-space:normal;*margin-left:-7px} | ||
button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle} | ||
button,input{line-height:normal} | ||
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible} | ||
button[disabled],input[disabled]{cursor:default} | ||
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px} | ||
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box} | ||
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none} | ||
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} | ||
textarea{overflow:auto;vertical-align:top} | ||
table{border-collapse:collapse;border-spacing:0} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
body { | ||
padding: 50px; | ||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; | ||
} | ||
|
||
a { | ||
color: #00B7FF; | ||
} |
Oops, something went wrong.