Last active
January 13, 2018 21:01
-
-
Save Fusl/acd469ee5d498eb57884101a5371d665 to your computer and use it in GitHub Desktop.
quickndirty: serving a /dev/video0 webcam via network
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
#!/usr/bin/env ndoe | |
var devicepath = '/dev/v4l/by-id/usb-Hewlett_Packard_HP_Webcam_HD_4310-video-index0'; | |
var devicefps = 15; | |
var width = 1280; | |
var height = 720; | |
var bufsize = 512 * 1024 * 1024; // 512 MiB (perfect for a RPi) | |
var net = require('net'); | |
var http = require('http'); | |
var EventEmitter = require('events').EventEmitter; | |
var v4l2camera = require("v4l2camera"); | |
var exec = require('child_process').exec; | |
var CronJob = require('cron').CronJob; | |
var stream = new EventEmitter(); | |
var preframetime = Date.now(); | |
var framerate = 0; | |
var framebuffer = new Buffer(0); | |
var jpegheader = new Buffer([0xFF, 0xD8, 0xFF, 0xDB]); | |
var framecounter = 0; | |
var log = function () { | |
console.log.bind(this).apply(this, ['\r\33[2K' + new Date()].concat(Array.prototype.slice.call(arguments))); | |
}; | |
var autoexposure = function (cb1, cb2) { | |
log('Setting exposure to automatic'); | |
exec('v4l2-ctl -c exposure_auto=3 -d ' + devicepath, function (err, stdout, stderr) { | |
if (typeof cb1 === 'function') { | |
cb1(); | |
} | |
setTimeout(function () { | |
log('Setting exposure to fixed'); | |
exec('v4l2-ctl -c exposure_auto=1 -d ' + devicepath, function (err, stdout, stderr) { | |
if (typeof cb2 === 'functioon') { | |
cb2(); | |
} | |
}); | |
}, 5000); | |
}); | |
}; | |
/*new CronJob('0 0 * * * *', function () { | |
autoexposure(); | |
}, null, true, 'Etc/UTC');*/ | |
exec('v4l2-ctl -v width=' + width + ',height=' + height + ',pixelformat=MJPG -d ' + devicepath, function (err, stdout, stderr) { | |
exec('v4l2-ctl -p ' + devicefps + ' -d ' + devicepath, function (err, stdout, stderr) { | |
exec('v4l2-ctl -c focus_auto=0 -d ' + devicepath, function (err, stdout, stderr) { | |
exec('v4l2-ctl -c focus_absolute=0 -d ' + devicepath, function (err, stdout, stderr) { | |
var firstframe = true; | |
var cam = new v4l2camera.Camera(devicepath); | |
if (cam.configGet().formatName !== "MJPG") { | |
log('NOTICE: MJPG camera required'); | |
process.exit(1); | |
} | |
cam.start(); | |
autoexposure(); | |
var getimage = function () { | |
cam.capture(function (success) { | |
if (stream.listeners('frame').length) { | |
process.nextTick(getimage); | |
} else { | |
setTimeout(getimage, 1000); | |
} | |
if (!success) { | |
return; | |
} | |
var frame = Buffer(cam.frameRaw()); | |
if (!frame.slice(0, 4).equals(jpegheader)) { | |
return; | |
} | |
if (firstframe) { | |
//exec('v4l2-ctl -c exposure_auto=1 -d ' + devicepath); | |
firstframe = false; | |
} | |
var curframetime = Date.now(); | |
stream.emit('frame', curframetime, frame); | |
framecounter++; | |
framerate = (framerate * 99 + 1000 / (curframetime - preframetime)) / 100; | |
preframetime = curframetime; | |
framebuffer = frame; | |
}); | |
}; | |
getimage(); | |
}); | |
}); | |
}); | |
}); | |
setInterval(function () { | |
process.stdout.write([ | |
'\r\33[2K' + (new Date()), | |
'framebuffer.length=' + framebuffer.length, | |
'frame.rate=' + Math.round(framerate * 100) / 100, | |
'listeners.length=' + stream.listeners('frame').length, | |
'framecounter=' + framecounter | |
].join(' ')); | |
}, 500); | |
var tcpserver = net.createServer(function (socket) { | |
var addr = socket.remoteAddress; | |
log(addr, 'connect', 'tcp'); | |
var stats = {total:0,sent:0,dropped:0}; | |
var forward = function (frametime, frame) { | |
stats.total++; | |
if (socket._writableState.length < bufsize / stream.listeners('frame').length) { | |
stats.sent++; | |
socket.write(frame); | |
} else { | |
stats.dropped++; | |
} | |
}; | |
stream.on('frame', forward); | |
socket.on('error', new Function()); | |
socket.on('close', function () { | |
log(addr, 'disconnect', 'tcp', 'total=' + stats.total, 'sent=' + stats.sent, 'dropped=' + stats.dropped, '(' + Math.round(stats.sent / stats.total * 10000) / 100 + '%/' + Math.round(stats.dropped / stats.total * 10000) / 100 + '%)'); | |
stream.removeListener('frame', forward); | |
}); | |
}); | |
var httpserver = http.createServer(function (req, res) { | |
var addr = req.socket.remoteAddress; | |
log(addr, 'connect', 'http'); | |
var stats = {total:0,sent:0,dropped:0}; | |
var forward = function (frametime, frame) { | |
stats.total++; | |
if (res._writableState.length < bufsize / stream.listeners('frame').length) { | |
stats.sent++; | |
socket.write(frame); | |
} else { | |
stats.dropped++; | |
} | |
}; | |
stream.on('frame', forward); | |
req.on('error', new Function()); | |
res.on('error', new Function()); | |
req.on('close', function () { | |
log(addr, 'disconnect', 'http', 'total=' + stats.total, 'sent=' + stats.sent, 'dropped=' + stats.dropped, '(' + Math.round(stats.sent / stats.total * 10000) / 100 + '%/' + Math.round(stats.dropped / stats.total * 10000) / 100 + '%)'); | |
}); | |
}); | |
var picserver = net.createServer(function (socket) { | |
socket.on('error', new Function()); | |
socket.end(framebuffer); | |
}); | |
tcpserver.listen(9000); | |
httpserver.listen(8000); | |
picserver.listen(7000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment