-
Notifications
You must be signed in to change notification settings - Fork 4
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
1 parent
7bd8a0a
commit 939496f
Showing
40 changed files
with
7,766 additions
and
3 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,3 @@ | ||
node_modules | ||
public/_main.js | ||
|
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 @@ | ||
# Motivation | ||
|
||
This is a very simple h264 video player (that can run on live stream) for your browser. | ||
You might use this with raspicam raw h264 stream. | ||
This is a player around [Broadway](https://github.com/mbebenita/Broadway) Decoder, with very simple API. | ||
NAL unit (h264 frames) are split on the server side, transported using websocket, and sent to the decoded (with frame dropping, if necessary) | ||
|
||
[![Version](https://img.shields.io/npm/v/h264-live-player.svg)](https://www.npmjs.com/package/h264-live-player) | ||
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) | ||
|
||
|
||
# History | ||
* I was targetting a real-time camera video feedback (no audio/surveillance cam) in the browser | ||
* There is no solution for "real time" mp4 video creation / playback (ffmpeg, mp4box.js, mp4parser - _boxing_ _takes_ _time_) | ||
* Media Source Extension is a dead end (mp4 boxing is far too hard to re-create on the client side) | ||
* [Broadway](https://github.com/mbebenita/Broadway) provide the crazy emscripten/asm build of a h264 encoder accelerated by webGL canvas | ||
* Here is all the glue we need, enjoy ;-) | ||
|
||
|
||
# Installation/demo | ||
``` | ||
git clone [email protected]:131/h264-live-player.git player | ||
cd player | ||
npm install | ||
node server-rpi.js # run on a rpi for a webcam demo | ||
node server-static.js # for sample video (static) file delivery | ||
node server-tcp.js # for a remote tcp (rpi video feed) sample | ||
node server-ffmpeg # usefull on win32 to debug the live feed (use ffmpeg & your directshow device / webcam) | ||
# browse to http://127.0.0.1:8080/ for a demo player | ||
``` | ||
|
||
# Recommendations | ||
* Broadway h264 Decoder can only work with **h264 baseline profile** | ||
* [**Use a SANE birate**](https://www.dr-lex.be/info-stuff/videocalc.html) | ||
* Browserify FTW | ||
* Once you understand how to integrate the server-side, feel free to use [h264-live-player](https://www.npmjs.com/package/h264-live-player) npm package in your client side app (see vendor/) | ||
* Use [uws](https://github.com/uWebSockets/uWebSockets) (instead of ws) as websocket server | ||
|
||
|
||
# Credits | ||
* [131](mailto:[email protected]) | ||
* [Broadway](https://github.com/mbebenita/Broadway) | ||
* [urbenlegend/WebStreamer](https://github.com/urbenlegend/WebStreamer) | ||
|
||
|
||
# Keywords / shout box | ||
raspberry, mp4box, h264, nal, raspivid, mse, media source extension, iso, raspicam, bitrate, realtime, video, mp4, ffmpeg, websocket, ws, socket.io "Let's have a beer and talk in Paris" |
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,88 @@ | ||
"use strict"; | ||
|
||
|
||
const WebSocketServer = require('ws').Server; | ||
const Splitter = require('stream-split'); | ||
const merge = require('mout/object/merge'); | ||
|
||
const NALseparator = new Buffer([0,0,0,1]);//NAL break | ||
|
||
|
||
class _Server { | ||
|
||
constructor(server, options) { | ||
|
||
this.options = merge({ | ||
width : 960, | ||
height: 540, | ||
}, options); | ||
|
||
this.wss = new WebSocketServer({ server }); | ||
|
||
this.new_client = this.new_client.bind(this); | ||
this.start_feed = this.start_feed.bind(this); | ||
this.broadcast = this.broadcast.bind(this); | ||
|
||
this.wss.on('connection', this.new_client); | ||
} | ||
|
||
|
||
start_feed() { | ||
var readStream = this.get_feed(); | ||
this.readStream = readStream; | ||
|
||
readStream = readStream.pipe(new Splitter(NALseparator)); | ||
readStream.on("data", this.broadcast); | ||
} | ||
|
||
get_feed() { | ||
throw new Error("to be implemented"); | ||
} | ||
|
||
broadcast(data) { | ||
this.wss.clients.forEach(function(socket) { | ||
|
||
if(socket.buzy) | ||
return; | ||
|
||
socket.buzy = true; | ||
socket.buzy = false; | ||
|
||
socket.send(Buffer.concat([NALseparator, data]), { binary: true}, function ack(error) { | ||
socket.buzy = false; | ||
}); | ||
}); | ||
} | ||
|
||
new_client(socket) { | ||
|
||
var self = this; | ||
console.log('New guy'); | ||
|
||
socket.send(JSON.stringify({ | ||
action : "init", | ||
width : this.options.width, | ||
height : this.options.height, | ||
})); | ||
|
||
socket.on("message", function(data){ | ||
var cmd = "" + data, action = data.split(' ')[0]; | ||
console.log("Incomming action '%s'", action); | ||
|
||
if(action == "REQUESTSTREAM") | ||
self.start_feed(); | ||
if(action == "STOPSTREAM") | ||
self.readStream.pause(); | ||
}); | ||
|
||
socket.on('close', function() { | ||
self.readStream.end(); | ||
console.log('stopping client interval'); | ||
}); | ||
} | ||
|
||
|
||
}; | ||
|
||
|
||
module.exports = _Server; |
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,68 @@ | ||
"use strict"; | ||
|
||
|
||
const spawn = require('child_process').spawn; | ||
const merge = require('mout/object/merge'); | ||
|
||
const Server = require('./_server'); | ||
|
||
|
||
class FFMpegServer extends Server { | ||
|
||
constructor(server, opts) { | ||
super(server, merge({ | ||
fps : 15, | ||
}, opts)); | ||
} | ||
|
||
get_feed() { | ||
|
||
var args = [ | ||
"-f", "gdigrab", | ||
"-framerate", this.options.fps, | ||
"-offset_x", 10, | ||
"-offset_y", 20, | ||
"-video_size", this.options.width + 'x' + this.options.height, | ||
'-i', 'desktop', | ||
'-pix_fmt', 'yuv420p', | ||
'-c:v', 'libx264', | ||
'-vprofile', 'baseline', | ||
'-tune', 'zerolatency', | ||
'-f' ,'rawvideo', | ||
'-' | ||
]; | ||
|
||
//https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate | ||
var args = [ | ||
"-f", "dshow", | ||
"-i", "video=Integrated Webcam" , | ||
"-framerate", this.options.fps, | ||
"-video_size", this.options.width + 'x' + this.options.height, | ||
'-pix_fmt', 'yuv420p', | ||
'-c:v', 'libx264', | ||
'-b:v', '600k', | ||
'-bufsize', '600k', | ||
'-vprofile', 'baseline', | ||
'-tune', 'zerolatency', | ||
'-f' ,'rawvideo', | ||
'-' | ||
]; | ||
|
||
|
||
console.log("ffmpeg " + args.join(' ')); | ||
var streamer = spawn('ffmpeg', args); | ||
//streamer.stderr.pipe(process.stderr); | ||
|
||
streamer.on("exit", function(code){ | ||
console.log("Failure", code); | ||
}); | ||
|
||
return streamer.stdout; | ||
} | ||
|
||
}; | ||
|
||
|
||
module.exports = FFMpegServer; | ||
|
||
|
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,34 @@ | ||
"use strict"; | ||
|
||
const util = require('util'); | ||
const spawn = require('child_process').spawn; | ||
const merge = require('mout/object/merge'); | ||
|
||
const Server = require('./_server'); | ||
|
||
|
||
class RpiServer extends Server { | ||
|
||
constructor(server, opts) { | ||
super(server, merge({ | ||
fps : 12, | ||
}, opts)); | ||
} | ||
|
||
get_feed() { | ||
var msk = "raspivid -t 0 -o - -w %d -h %d -fps %d"; | ||
var cmd = util.format(msk, this.options.width, this.options.height, this.options.fps); | ||
console.log(cmd); | ||
var streamer = spawn('raspivid', ['-t', '0', '-o', '-', '-w', this.options.width, '-h', this.options.height, '-fps', this.options.fps, '-pf', 'baseline']); | ||
streamer.on("exit", function(code){ | ||
console.log("Failure", code); | ||
}); | ||
|
||
return streamer.stdout; | ||
} | ||
|
||
}; | ||
|
||
|
||
|
||
module.exports = RpiServer; |
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 @@ | ||
"use strict"; | ||
|
||
const net = require('net'); | ||
const merge = require('mout/object/merge'); | ||
|
||
const Server = require('./_server'); | ||
|
||
class TCPFeed extends Server { | ||
|
||
constructor(server, opts) { | ||
super(server, merge({ | ||
feed_ip : '127.0.0.1', | ||
feed_port : 5001, | ||
}, opts)); | ||
} | ||
|
||
get_feed() { | ||
|
||
var readStream = net.connect(this.options.feed_port, this.options.feed_ip, function(){ | ||
console.log("remote stream ready"); | ||
}); | ||
|
||
return readStream; | ||
} | ||
|
||
} | ||
|
||
|
||
|
||
module.exports = TCPFeed; |
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,37 @@ | ||
"use strict"; | ||
|
||
const fs = require('fs'); | ||
const Throttle = require('stream-throttle').Throttle; | ||
const merge = require('mout/object/merge'); | ||
|
||
const Server = require('./_server'); | ||
|
||
class StaticFeed extends Server { | ||
|
||
constructor(server, opts) { | ||
super(server, merge({ | ||
video_path : null, | ||
video_duration : 0, | ||
}, opts)); | ||
} | ||
|
||
get_feed() { | ||
var source = this.options.video_path; | ||
|
||
//throttle for "real time simulation" | ||
var sourceThrottleRate = Math.floor(fs.statSync(source)['size'] / this.options.video_duration); | ||
console.log("Generate a throttle rate of %s kBps", Math.floor(sourceThrottleRate/1024)); | ||
|
||
var readStream = fs.createReadStream(source); | ||
readStream = readStream.pipe(new Throttle({rate: sourceThrottleRate})); | ||
|
||
console.log("Generate a static feed from ", source); | ||
return readStream; | ||
} | ||
|
||
} | ||
|
||
|
||
|
||
|
||
module.exports = StaticFeed; |
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,22 @@ | ||
{ | ||
"name": "h264-live-stream-demo-server", | ||
"private": true, | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "server.js", | ||
"scripts": { | ||
"install": "cd vendor && npm install", | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"start": "node server.js" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"express": "^3.21.2", | ||
"mout": "^1.0.0", | ||
"stream-split": "^1.1.0", | ||
"stream-throttle": "^0.1.3", | ||
"ws": "^0.8.0" | ||
}, | ||
"devDependencies": {} | ||
} |
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,32 @@ | ||
<html> | ||
<head> | ||
<title>h264-live-player web client demo</title> | ||
</head> | ||
<body> | ||
|
||
<button type="button" onclick="wsavc.playStream()">Start Video</button> | ||
<button type="button" onclick="wsavc.stopStream()">Stop Video</button> | ||
<button type="button" onclick="wsavc.disconnect()">Disconnect</button> | ||
<br/> | ||
<br/> | ||
<br/> | ||
|
||
<!-- provide WSAvcPlayer --> | ||
<script type="text/javascript" src="http-live-player.js">;</script> | ||
<script type="text/javascript"> | ||
|
||
var canvas = document.createElement("canvas"); | ||
document.body.appendChild(canvas); | ||
|
||
// Create h264 player | ||
var uri = "ws://" + document.location.host; | ||
var wsavc = new WSAvcPlayer(canvas, "webgl", 1, 35); | ||
wsavc.connect(uri); | ||
|
||
|
||
//expose instance for button callbacks | ||
window.wsavc = wsavc; | ||
|
||
</script> | ||
</body> | ||
</html> |
Binary file not shown.
Binary file not shown.
Oops, something went wrong.