├── public_html
├── avc.wasm
├── stream.js
├── index.html
├── Player.js
├── YUVCanvas.js
├── mp4.js
└── Decoder.js
├── package.json
├── app.js
├── LICENSE
├── .gitignore
├── README.md
└── lib
├── nodetello_webclient.js
└── nodetello.js
/public_html/avc.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SovGVD/nodetello/HEAD/public_html/avc.wasm
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodetello",
3 | "version": "0.2.2",
4 | "description": "Ryze Tello Node.JS lib based on PyTello",
5 | "main": "app.js",
6 | "directories": {
7 | "lib": "lib"
8 | },
9 | "scripts": {
10 | "start": "node app.js",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "keywords": [
14 | "drone",
15 | "ryze",
16 | "tello",
17 | "node.js",
18 | "video stream",
19 | "h264"
20 | ],
21 | "author": "Gleb Devyatkin",
22 | "license": "MIT",
23 | "dependencies": {
24 | "ws": "^5.2.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //const cluster = require('cluster');
4 | //const doThreads = require('os').cpus().length;
5 |
6 |
7 | const NodeTello = require('./lib/nodetello.js');
8 | const NodeTello_webclient = require('./lib/nodetello_webclient.js');
9 |
10 | //var webclient = new NodeTello_webclient('ffmpeg'); // in-browser video
11 | var webclient = new NodeTello_webclient('mplayer'); // open mplayer window with low latency video
12 | webclient.init();
13 |
14 |
15 | var drone = new NodeTello();
16 | drone.save_video_path = "./video/";
17 | drone.save_photo_path = "./photo/";
18 | drone.tello_video_output = function (h264) { webclient.h264encoder_in(h264) };
19 | drone.tello_telemetry_config = {
20 | px: "MVO.PX",
21 | py: "MVO.PY",
22 | pz: "MVO.PZ"
23 | };
24 | drone.tello_telemetry_output = function (data) { webclient.telemetry_in(data); };
25 | drone.init();
26 |
27 | webclient.tello_cmd = function (data) { drone.tello_cmd(data); }
28 |
--------------------------------------------------------------------------------
/public_html/stream.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Stream = (function stream() {
4 | function constructor(url) {
5 | this.url = url;
6 | }
7 |
8 | constructor.prototype = {
9 | readAll: function(progress, complete) {
10 | var xhr = new XMLHttpRequest();
11 | var async = true;
12 | xhr.open("GET", this.url, async);
13 | xhr.responseType = "arraybuffer";
14 | if (progress) {
15 | xhr.onprogress = function (event) {
16 | progress(xhr.response, event.loaded, event.total);
17 | };
18 | }
19 | xhr.onreadystatechange = function (event) {
20 | if (xhr.readyState === 4) {
21 | complete(xhr.response);
22 | // var byteArray = new Uint8Array(xhr.response);
23 | // var array = Array.prototype.slice.apply(byteArray);
24 | // complete(array);
25 | }
26 | }
27 | xhr.send(null);
28 | }
29 | };
30 | return constructor;
31 | })();
32 |
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Gleb Devyatkin
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 |
--------------------------------------------------------------------------------
/.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 (https://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 | # next.js build output
61 | .next
62 |
63 | log.log
64 | video.h264
65 | video.mp4
66 | video.base64.raw
67 | tests/
68 | video/*
69 | test/
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ryze Tello drone Node.JS library
2 | Based on [PyTello](https://bitbucket.org/PingguSoft/pytello), [TelloLib](https://github.com/Kragrathea/TelloLib) and [the tello Go](https://github.com/SMerrony/tello)
3 |
4 | ## WARNING
5 |
6 | Software in development and provided "as is". Use at your own risk. Safety first! Remove props before test!
7 |
8 | ## How to
9 | ```
10 | const NodeTello = require('./lib/nodetello.js');
11 | const NodeTello_webclient = require('./lib/nodetello_webclient.js');
12 |
13 | // init web client
14 | //var webclient = new NodeTello_webclient('ffmpeg'); // in-browser video
15 | var webclient = new NodeTello_webclient('mplayer'); // open mplayer window with low latency video
16 |
17 | webclient.init();
18 |
19 | // init drone
20 | var drone = new NodeTello();
21 |
22 | // set video path
23 | drone.save_video_path = "./video/";
24 | // set callback for video feed
25 | drone.tello_video_output = function (h264) { webclient.h264encoder_in(h264); };
26 | // config drone telemetry (drone -> node.js -> browser)
27 | drone.tello_telemetry_config = {
28 | px: "MVO.PX",
29 | py: "MVO.PY",
30 | pz: "MVO.PZ"
31 | };
32 | // set callback for telemetry feed
33 | drone.tello_telemetry_output = function (data) { webclient.telemetry(data); };
34 |
35 | // lets go!
36 | drone.init();
37 |
38 | // browser -> webclient -> tello commands
39 | webclient.tello_cmd = function (data) { drone.tello_cmd(data); }
40 | ```
41 |
42 | ### Init
43 | Init process is basic at that moment:
44 | - connect to drone
45 | - get some settings/values (version and altitude limit)
46 | - set settings (stick position)
47 | - init video feed
48 | - init log and status receive and parse
49 | - start video transcode (required ffmpeg!)
50 | - start http server and websocket
51 |
52 | ## Web client
53 | Web client must be available on http://127.0.0.1:8080 after `npm start` and show almost fine video from drone with 0.5 seconds lag.
54 |
55 | ## Convert video
56 | Video feeds stored to `./video/TIMESTAMP.h264` and must be redecode, e.g. `ffmpeg -i TIMESTAMP.h264 -crf 20 video.mp4`
57 |
58 | ## Flight
59 |
60 | ### Keyboard
61 | - `Shift` + `Space` - Takeoff
62 | - `Space` - Land
63 | - `ArrowUp` and `ArrowDown` - Pitch (forward/backward)
64 | - `ArrowLeft` and `ArrowRight` - Roll (left/right)
65 | - `W` and `S` - Throttle (Up/Down)
66 | - `A` and `D` - Yaw (Rotate)
67 |
68 | Speed is limited = 0.5
69 |
70 | ### Gamepad/Joystick
71 | WARNING, used SHANWAN PS3/PC Gamepad (Vendor: 2563 Product: 0575) in Linux
72 |
73 | - `Left Stick` - trottle and yaw (MODE2)
74 | - `Right Stick` - pitch and roll (MODE2)
75 | - `X`, `Y`, `A`, `B` - land
76 | - `start` - takeoff
77 |
78 | Speed is limited = 0.5
79 |
80 | ## Flight data and log
81 |
82 | ### Flight data
83 | - altitude
84 | - version
85 | - speed
86 | - fly_time
87 | - wifi
88 | - other...
89 |
90 | ### Log
91 | - 29 - MVO (position)
92 | - 2048 - IMU + ANGLES (pitch, roll, yaw)
93 | - 16 - ULTRASONIC (basicaly duplicate IMU data)
94 | - 112
95 | - 1000
96 | - 1001
97 | - 1002
98 | - 1710
99 | - 1712
100 | - 2064
101 | - 2208
102 | - 10086
103 | - 10085
104 | - 32768
105 |
106 |
--------------------------------------------------------------------------------
/lib/nodetello_webclient.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const http = require('http');
4 | const fs = require('fs');
5 | const { spawn } = require('child_process');
6 | const WebSocket = require('ws');
7 |
8 |
9 | var nodetello_webclient = function (videobackend) {
10 | this.videobackend = videobackend;
11 | this.path = './public_html/';
12 | this.http_port = 8080;
13 | this.ws_video_port = 8081;
14 | this.ws_telemetry_port = 8082;
15 | this.file2mime = {
16 | ".html" : { t:'text/html', e:'utf8' },
17 | ".js" : { t:'application/javascript', e:'utf8' },
18 | ".wasm" : { t:'application/wasm', e:'binary' },
19 | ".ico" : { t:'image/x-icon', e:'binary' },
20 | ".css" : { t:'text/css', e:'utf8' },
21 | ".map" : { t:'application/json', e:'utf8' },
22 | };
23 | this.headers = {
24 | "png" : Buffer([137, 80, 78, 71, 13, 10, 26, 10]), // png header
25 | "jpg" : Buffer([255, 216, 255]), // jpeg header
26 | "h264_baseline" : Buffer([0,0,0,1]), // h264 NAL unit
27 | };
28 | this.wss_video = false;
29 | this.wss_telemetry = false;
30 | this._ws_video_client = false;
31 | this._ws_telemetry_client = false;
32 | if (this.videobackend == 'ffmpeg') {
33 | this.h264encoder_spawn = {
34 | "command" : 'ffmpeg', // for mac just change to ./ffmpeg and put ffmpeg binary into the same folder
35 | //"args" : [ '-fflags', 'nobuffer', '-f', 'h264', '-i', "-", '-c:v', 'libx264', '-b:v', '3M', '-profile', 'baseline', '-preset', 'ultrafast', '-pass', '1', '-coder', '0', '-bf', '0', '-flags', '-loop', '-wpredp', '0', '-an', '-f', 'h264', '-']
36 | //"args" : [ '-f', 'h264', '-i', "-", '-r', '35', '-c:v', 'libx264', '-b:v', '3M', '-profile', 'baseline', '-preset', 'ultrafast', '-tune', 'zerolatency', '-vsync', '0', '-async', '1', '-bsf:v', 'h264_mp4toannexb', '-x264-params','keyint=15:scenecut=0', '-an', '-f', 'h264', '-']
37 | //"args" : [ '-f', 'h264', '-i', "-", '-r', '30', '-c:v', 'libx264', '-b:v', '3M', '-profile', 'baseline', '-preset', 'ultrafast', '-tune', 'zerolatency', '-vsync', '0', '-async', '1', '-bsf:v', 'h264_mp4toannexb', '-an', '-f', 'h264', '-']
38 | "args" : [ '-fflags', 'nobuffer', '-f', 'h264', '-i', "-", '-r', '30', '-c:v', 'libx264', '-b:v', '2M', '-profile', 'baseline', '-preset', 'ultrafast', '-tune', 'zerolatency', '-vsync', '0', '-async', '0', '-bsf:v', 'h264_mp4toannexb', '-x264-params','keyint=5:scenecut=0', '-an', '-f', 'h264', '-']
39 | //"args" : [ '-f', 'h264', '-i', "-", '-r', '30', '-c:v', 'libx264', '-b:v', '3M', '-profile', 'baseline', '-preset', 'ultrafast', '-tune', 'zerolatency', '-async', '1', '-an', '-f', 'h264', '-']
40 | };
41 | } else if (this.videobackend == 'mplayer') {
42 | this.h264encoder_spawn = {
43 | "command" : 'mplayer',
44 | "args" : [ '-gui', '-nolirc' ,'-fps', '35', '-really-quiet', '-' ]
45 | };
46 | }
47 | this.h264encoder = false;
48 | this.h264chunks = [];
49 | this.h264unit = false;
50 |
51 | this.tello_cmd = false;
52 |
53 | this.init = function () {
54 | this.wss_init();
55 | this.h264encoder_init();
56 | http.createServer(function (req, res) {this._http_res(req, res)}.bind(this)).listen(this.http_port);
57 | }
58 |
59 | this.h264encoder_init = function () {
60 | this.h264encoder = spawn( this.h264encoder_spawn.command, this.h264encoder_spawn.args);
61 |
62 | this.h264encoder.stderr.on('data', function (data) {
63 | //console.log("ffmpeg error", data.toString());
64 | }.bind(this));
65 |
66 | this.h264encoder.stdout.on('data', function (data) {
67 | var idx = data.indexOf(this.headers['h264_baseline']);
68 | if (idx>-1 && this.h264chunks.length>0) {
69 | this.h264chunks.push(data.slice(0,idx));
70 | if (this._ws_video_client!==false) {
71 | try {
72 | this._ws_video_client.send(Buffer.concat(this.h264chunks).toString('binary'));
73 | } catch (e) { this._ws_video_client = false }
74 | }
75 | this.h264chunks=[];
76 | this.h264chunks.push(data.slice(idx));
77 | } else {
78 | this.h264chunks.push(data);
79 | }
80 | }.bind(this));
81 | }
82 | this.h264encoder_in = function (h264chunk) {
83 | this.h264encoder.stdin.write(h264chunk);
84 | }
85 |
86 | this.telemetry_in = function (data) {
87 | if (this._ws_telemetry_client !== false) {
88 | try {
89 | this._ws_telemetry_client.send(data);
90 | } catch (e) { this._ws_telemetry_client = false; }
91 | }
92 | }
93 |
94 |
95 | this.wss_init = function () {
96 | // Video Feed
97 | this.wss_video = new WebSocket.Server({ port: this.ws_video_port });
98 | this.wss_video.on('connection', function connection(ws) {
99 | ws.on('message', function (message) {
100 | this._ws_video_client = ws;
101 | try {
102 | // nothing to do
103 | } catch (e) {
104 | ws.send("false");
105 | console.log("WSVIDEOERROR:",e);
106 | }
107 | }.bind(this));
108 | }.bind(this));
109 |
110 | // Telemetry Feed
111 | this.wss_telemetry = new WebSocket.Server({ port: this.ws_telemetry_port });
112 | this.wss_telemetry.on('connection', function connection(ws) {
113 | ws.on('message', function (message) {
114 | this._ws_telemetry_client = ws;
115 | try {
116 | if (message[0] == '{') {
117 | this.tello_cmd(JSON.parse(message));
118 | } else {
119 | console.log("WebClient",message);
120 | }
121 | } catch (e) {
122 | ws.send("false");
123 | console.log("WSTELEMETRYERROR:",e,"Message",message);
124 | }
125 | }.bind(this));
126 | }.bind(this));
127 | }
128 |
129 | this._http_res = function (req, res) {
130 | var url = req.url.split("/"); url.shift(); url = (url.join("/")).replace(new RegExp("\\.\\.",'g'),"");
131 | if (url=='') url="index.html";
132 | url = this.path+url;
133 | var type = url.split(".");
134 | type="."+type.pop();
135 | console.log("HTTPREQ:", req.url, "as", url, "type", type);
136 | type = (typeof this.file2mime[type] !== undefined)?this.file2mime[type]:'text/html';
137 | fs.readFile(url, { encoding: type.e }, function (type, res, err, data) {
138 | if (err) {
139 | //console.log("HTTP404:", err);
140 | res.writeHead(404, {'Content-Type': 'text/html'});
141 | res.end("404 Not Found");
142 | } else {
143 | console.log("HTTP200:", type);
144 | res.writeHead(200, {'Content-Type': type.t});
145 | res.end(data, type.e);
146 | }
147 | }.bind(this, type, res));
148 | }
149 | }
150 |
151 | module.exports = nodetello_webclient;
152 |
--------------------------------------------------------------------------------
/public_html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tello client. NodeTello.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
315 |
316 |
317 |
--------------------------------------------------------------------------------
/public_html/Player.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 |
4 | usage:
5 |
6 | p = new Player({
7 | useWorker: ,
8 | workerFile: // give path to Decoder.js
9 | webgl: true | false | "auto" // defaults to "auto"
10 | });
11 |
12 | // canvas property represents the canvas node
13 | // put it somewhere in the dom
14 | p.canvas;
15 |
16 | p.webgl; // contains the used rendering mode. if you pass auto to webgl you can see what auto detection resulted in
17 |
18 | p.decode();
19 |
20 |
21 | */
22 |
23 |
24 |
25 | // universal module definition
26 | (function (root, factory) {
27 | if (typeof define === 'function' && define.amd) {
28 | // AMD. Register as an anonymous module.
29 | define(["./Decoder", "./YUVCanvas"], factory);
30 | } else if (typeof exports === 'object') {
31 | // Node. Does not work with strict CommonJS, but
32 | // only CommonJS-like environments that support module.exports,
33 | // like Node.
34 | module.exports = factory(require("./Decoder"), require("./YUVCanvas"));
35 | } else {
36 | // Browser globals (root is window)
37 | root.Player = factory(root.Decoder, root.YUVCanvas);
38 | }
39 | }(this, function (Decoder, WebGLCanvas) {
40 | "use strict";
41 |
42 |
43 | var nowValue = Decoder.nowValue;
44 |
45 |
46 | var Player = function(parOptions){
47 | var self = this;
48 | this._config = parOptions || {};
49 |
50 | this.render = true;
51 | if (this._config.render === false){
52 | this.render = false;
53 | };
54 |
55 | this.nowValue = nowValue;
56 |
57 | this._config.workerFile = this._config.workerFile || "Decoder.js";
58 | if (this._config.preserveDrawingBuffer){
59 | this._config.contextOptions = this._config.contextOptions || {};
60 | this._config.contextOptions.preserveDrawingBuffer = true;
61 | };
62 |
63 | var webgl = "auto";
64 | if (this._config.webgl === true){
65 | webgl = true;
66 | }else if (this._config.webgl === false){
67 | webgl = false;
68 | };
69 |
70 | if (webgl == "auto"){
71 | webgl = true;
72 | try{
73 | if (!window.WebGLRenderingContext) {
74 | // the browser doesn't even know what WebGL is
75 | webgl = false;
76 | } else {
77 | var canvas = document.createElement('canvas');
78 | var ctx = canvas.getContext("webgl");
79 | if (!ctx) {
80 | // browser supports WebGL but initialization failed.
81 | webgl = false;
82 | };
83 | };
84 | }catch(e){
85 | webgl = false;
86 | };
87 | };
88 |
89 | this.webgl = webgl;
90 |
91 | // choose functions
92 | if (this.webgl){
93 | this.createCanvasObj = this.createCanvasWebGL;
94 | this.renderFrame = this.renderFrameWebGL;
95 | }else{
96 | this.createCanvasObj = this.createCanvasRGB;
97 | this.renderFrame = this.renderFrameRGB;
98 | };
99 |
100 |
101 | var lastWidth;
102 | var lastHeight;
103 | var onPictureDecoded = function(buffer, width, height, infos) {
104 | self.onPictureDecoded(buffer, width, height, infos);
105 |
106 | var startTime = nowValue();
107 |
108 | if (!buffer || !self.render) {
109 | return;
110 | };
111 |
112 | self.renderFrame({
113 | canvasObj: self.canvasObj,
114 | data: buffer,
115 | width: width,
116 | height: height
117 | });
118 |
119 | if (self.onRenderFrameComplete){
120 | self.onRenderFrameComplete({
121 | data: buffer,
122 | width: width,
123 | height: height,
124 | infos: infos,
125 | canvasObj: self.canvasObj
126 | });
127 | };
128 |
129 | };
130 |
131 | // provide size
132 |
133 | if (!this._config.size){
134 | this._config.size = {};
135 | };
136 | this._config.size.width = this._config.size.width || 200;
137 | this._config.size.height = this._config.size.height || 200;
138 |
139 | if (this._config.useWorker){
140 | var worker = new Worker(this._config.workerFile);
141 | this.worker = worker;
142 | worker.addEventListener('message', function(e) {
143 | var data = e.data;
144 | if (data.consoleLog){
145 | console.log(data.consoleLog);
146 | return;
147 | };
148 |
149 | onPictureDecoded.call(self, new Uint8Array(data.buf, 0, data.length), data.width, data.height, data.infos);
150 |
151 | }, false);
152 |
153 | worker.postMessage({type: "Broadway.js - Worker init", options: {
154 | rgb: !webgl,
155 | memsize: this.memsize,
156 | reuseMemory: this._config.reuseMemory ? true : false
157 | }});
158 |
159 | if (this._config.transferMemory){
160 | this.decode = function(parData, parInfo){
161 | // no copy
162 | // instead we are transfering the ownership of the buffer
163 | // dangerous!!!
164 |
165 | worker.postMessage({buf: parData.buffer, offset: parData.byteOffset, length: parData.length, info: parInfo}, [parData.buffer]); // Send data to our worker.
166 | };
167 |
168 | }else{
169 | this.decode = function(parData, parInfo){
170 | // Copy the sample so that we only do a structured clone of the
171 | // region of interest
172 | var copyU8 = new Uint8Array(parData.length);
173 | copyU8.set( parData, 0, parData.length );
174 | worker.postMessage({buf: copyU8.buffer, offset: 0, length: parData.length, info: parInfo}, [copyU8.buffer]); // Send data to our worker.
175 | };
176 |
177 | };
178 |
179 | if (this._config.reuseMemory){
180 | this.recycleMemory = function(parArray){
181 | //this.beforeRecycle();
182 | worker.postMessage({reuse: parArray.buffer}, [parArray.buffer]); // Send data to our worker.
183 | //this.afterRecycle();
184 | };
185 | }
186 |
187 | }else{
188 |
189 | this.decoder = new Decoder({
190 | rgb: !webgl
191 | });
192 | this.decoder.onPictureDecoded = onPictureDecoded;
193 |
194 | this.decode = function(parData, parInfo){
195 | self.decoder.decode(parData, parInfo);
196 | };
197 |
198 | };
199 |
200 |
201 |
202 | if (this.render){
203 | this.canvasObj = this.createCanvasObj({
204 | contextOptions: this._config.contextOptions
205 | });
206 | this.canvas = this.canvasObj.canvas;
207 | };
208 |
209 | this.domNode = this.canvas;
210 |
211 | lastWidth = this._config.size.width;
212 | lastHeight = this._config.size.height;
213 |
214 | };
215 |
216 | Player.prototype = {
217 |
218 | onPictureDecoded: function(buffer, width, height, infos){},
219 |
220 | // call when memory of decoded frames is not used anymore
221 | recycleMemory: function(buf){
222 | },
223 | /*beforeRecycle: function(){},
224 | afterRecycle: function(){},*/
225 |
226 | // for both functions options is:
227 | //
228 | // width
229 | // height
230 | // enableScreenshot
231 | //
232 | // returns a object that has a property canvas which is a html5 canvas
233 | createCanvasWebGL: function(options){
234 | var canvasObj = this._createBasicCanvasObj(options);
235 | canvasObj.contextOptions = options.contextOptions;
236 | return canvasObj;
237 | },
238 |
239 | createCanvasRGB: function(options){
240 | var canvasObj = this._createBasicCanvasObj(options);
241 | return canvasObj;
242 | },
243 |
244 | // part that is the same for webGL and RGB
245 | _createBasicCanvasObj: function(options){
246 | options = options || {};
247 |
248 | var obj = {};
249 | var width = options.width;
250 | if (!width){
251 | width = this._config.size.width;
252 | };
253 | var height = options.height;
254 | if (!height){
255 | height = this._config.size.height;
256 | };
257 | obj.canvas = document.createElement('canvas');
258 | obj.canvas.width = width;
259 | obj.canvas.height = height;
260 | obj.canvas.style.backgroundColor = "#0D0E1B";
261 |
262 |
263 | return obj;
264 | },
265 |
266 | // options:
267 | //
268 | // canvas
269 | // data
270 | renderFrameWebGL: function(options){
271 |
272 | var canvasObj = options.canvasObj;
273 |
274 | var width = options.width || canvasObj.canvas.width;
275 | var height = options.height || canvasObj.canvas.height;
276 |
277 | if (canvasObj.canvas.width !== width || canvasObj.canvas.height !== height || !canvasObj.webGLCanvas){
278 | canvasObj.canvas.width = width;
279 | canvasObj.canvas.height = height;
280 | canvasObj.webGLCanvas = new WebGLCanvas({
281 | canvas: canvasObj.canvas,
282 | contextOptions: canvasObj.contextOptions,
283 | width: width,
284 | height: height
285 | });
286 | };
287 |
288 | var ylen = width * height;
289 | var uvlen = (width / 2) * (height / 2);
290 |
291 | canvasObj.webGLCanvas.drawNextOutputPicture({
292 | yData: options.data.subarray(0, ylen),
293 | uData: options.data.subarray(ylen, ylen + uvlen),
294 | vData: options.data.subarray(ylen + uvlen, ylen + uvlen + uvlen)
295 | });
296 |
297 | var self = this;
298 | self.recycleMemory(options.data);
299 |
300 | },
301 | renderFrameRGB: function(options){
302 | var canvasObj = options.canvasObj;
303 |
304 | var width = options.width || canvasObj.canvas.width;
305 | var height = options.height || canvasObj.canvas.height;
306 |
307 | if (canvasObj.canvas.width !== width || canvasObj.canvas.height !== height){
308 | canvasObj.canvas.width = width;
309 | canvasObj.canvas.height = height;
310 | };
311 |
312 | var ctx = canvasObj.ctx;
313 | var imgData = canvasObj.imgData;
314 |
315 | if (!ctx){
316 | canvasObj.ctx = canvasObj.canvas.getContext('2d');
317 | ctx = canvasObj.ctx;
318 |
319 | canvasObj.imgData = ctx.createImageData(width, height);
320 | imgData = canvasObj.imgData;
321 | };
322 |
323 | imgData.data.set(options.data);
324 | ctx.putImageData(imgData, 0, 0);
325 | var self = this;
326 | self.recycleMemory(options.data);
327 |
328 | }
329 |
330 | };
331 |
332 | return Player;
333 |
334 | }));
335 |
336 |
--------------------------------------------------------------------------------
/public_html/YUVCanvas.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2015 Paperspace Co. All rights reserved.
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to
6 | // deal in the Software without restriction, including without limitation the
7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 | // sell copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 | // IN THE SOFTWARE.
21 | //
22 |
23 |
24 | // universal module definition
25 | (function (root, factory) {
26 | if (typeof define === 'function' && define.amd) {
27 | // AMD. Register as an anonymous module.
28 | define([], factory);
29 | } else if (typeof exports === 'object') {
30 | // Node. Does not work with strict CommonJS, but
31 | // only CommonJS-like environments that support module.exports,
32 | // like Node.
33 | module.exports = factory();
34 | } else {
35 | // Browser globals (root is window)
36 | root.YUVCanvas = factory();
37 | }
38 | }(this, function () {
39 |
40 |
41 | /**
42 | * This class can be used to render output pictures from an H264bsdDecoder to a canvas element.
43 | * If available the content is rendered using WebGL.
44 | */
45 | function YUVCanvas(parOptions) {
46 |
47 | parOptions = parOptions || {};
48 |
49 | this.canvasElement = parOptions.canvas || document.createElement("canvas");
50 | this.contextOptions = parOptions.contextOptions;
51 |
52 | this.type = parOptions.type || "yuv420";
53 |
54 | this.customYUV444 = parOptions.customYUV444;
55 |
56 | this.conversionType = parOptions.conversionType || "rec601";
57 |
58 | this.width = parOptions.width || 640;
59 | this.height = parOptions.height || 320;
60 |
61 | this.animationTime = parOptions.animationTime || 0;
62 |
63 | this.canvasElement.width = this.width;
64 | this.canvasElement.height = this.height;
65 |
66 | this.initContextGL();
67 |
68 | if(this.contextGL) {
69 | this.initProgram();
70 | this.initBuffers();
71 | this.initTextures();
72 | };
73 |
74 |
75 | /**
76 | * Draw the next output picture using WebGL
77 | */
78 | if (this.type === "yuv420"){
79 | this.drawNextOuptutPictureGL = function(par) {
80 | var gl = this.contextGL;
81 | var texturePosBuffer = this.texturePosBuffer;
82 | var uTexturePosBuffer = this.uTexturePosBuffer;
83 | var vTexturePosBuffer = this.vTexturePosBuffer;
84 |
85 | var yTextureRef = this.yTextureRef;
86 | var uTextureRef = this.uTextureRef;
87 | var vTextureRef = this.vTextureRef;
88 |
89 | var yData = par.yData;
90 | var uData = par.uData;
91 | var vData = par.vData;
92 |
93 | var width = this.width;
94 | var height = this.height;
95 |
96 | var yDataPerRow = par.yDataPerRow || width;
97 | var yRowCnt = par.yRowCnt || height;
98 |
99 | var uDataPerRow = par.uDataPerRow || (width / 2);
100 | var uRowCnt = par.uRowCnt || (height / 2);
101 |
102 | var vDataPerRow = par.vDataPerRow || uDataPerRow;
103 | var vRowCnt = par.vRowCnt || uRowCnt;
104 |
105 | gl.viewport(0, 0, width, height);
106 |
107 | var tTop = 0;
108 | var tLeft = 0;
109 | var tBottom = height / yRowCnt;
110 | var tRight = width / yDataPerRow;
111 | var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
112 |
113 | gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
114 | gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
115 |
116 | if (this.customYUV444){
117 | tBottom = height / uRowCnt;
118 | tRight = width / uDataPerRow;
119 | }else{
120 | tBottom = (height / 2) / uRowCnt;
121 | tRight = (width / 2) / uDataPerRow;
122 | };
123 | var uTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
124 |
125 | gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
126 | gl.bufferData(gl.ARRAY_BUFFER, uTexturePosValues, gl.DYNAMIC_DRAW);
127 |
128 |
129 | if (this.customYUV444){
130 | tBottom = height / vRowCnt;
131 | tRight = width / vDataPerRow;
132 | }else{
133 | tBottom = (height / 2) / vRowCnt;
134 | tRight = (width / 2) / vDataPerRow;
135 | };
136 | var vTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
137 |
138 | gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
139 | gl.bufferData(gl.ARRAY_BUFFER, vTexturePosValues, gl.DYNAMIC_DRAW);
140 |
141 |
142 | gl.activeTexture(gl.TEXTURE0);
143 | gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
144 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, yDataPerRow, yRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
145 |
146 | gl.activeTexture(gl.TEXTURE1);
147 | gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
148 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, uDataPerRow, uRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uData);
149 |
150 | gl.activeTexture(gl.TEXTURE2);
151 | gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
152 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, vDataPerRow, vRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, vData);
153 |
154 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
155 | };
156 |
157 | }else if (this.type === "yuv422"){
158 | this.drawNextOuptutPictureGL = function(par) {
159 | var gl = this.contextGL;
160 | var texturePosBuffer = this.texturePosBuffer;
161 |
162 | var textureRef = this.textureRef;
163 |
164 | var data = par.data;
165 |
166 | var width = this.width;
167 | var height = this.height;
168 |
169 | var dataPerRow = par.dataPerRow || (width * 2);
170 | var rowCnt = par.rowCnt || height;
171 |
172 | gl.viewport(0, 0, width, height);
173 |
174 | var tTop = 0;
175 | var tLeft = 0;
176 | var tBottom = height / rowCnt;
177 | var tRight = width / (dataPerRow / 2);
178 | var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
179 |
180 | gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
181 | gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
182 |
183 | gl.uniform2f(gl.getUniformLocation(this.shaderProgram, 'resolution'), dataPerRow, height);
184 |
185 | gl.activeTexture(gl.TEXTURE0);
186 | gl.bindTexture(gl.TEXTURE_2D, textureRef);
187 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dataPerRow, rowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
188 |
189 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
190 | };
191 | };
192 |
193 | };
194 |
195 | /**
196 | * Returns true if the canvas supports WebGL
197 | */
198 | YUVCanvas.prototype.isWebGL = function() {
199 | return this.contextGL;
200 | };
201 |
202 | /**
203 | * Create the GL context from the canvas element
204 | */
205 | YUVCanvas.prototype.initContextGL = function() {
206 | var canvas = this.canvasElement;
207 | var gl = null;
208 |
209 | var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
210 | var nameIndex = 0;
211 |
212 | while(!gl && nameIndex < validContextNames.length) {
213 | var contextName = validContextNames[nameIndex];
214 |
215 | try {
216 | if (this.contextOptions){
217 | gl = canvas.getContext(contextName, this.contextOptions);
218 | }else{
219 | gl = canvas.getContext(contextName);
220 | };
221 | } catch (e) {
222 | gl = null;
223 | }
224 |
225 | if(!gl || typeof gl.getParameter !== "function") {
226 | gl = null;
227 | }
228 |
229 | ++nameIndex;
230 | };
231 |
232 | this.contextGL = gl;
233 | };
234 |
235 | /**
236 | * Initialize GL shader program
237 | */
238 | YUVCanvas.prototype.initProgram = function() {
239 | var gl = this.contextGL;
240 |
241 | // vertex shader is the same for all types
242 | var vertexShaderScript;
243 | var fragmentShaderScript;
244 |
245 | if (this.type === "yuv420"){
246 |
247 | vertexShaderScript = [
248 | 'attribute vec4 vertexPos;',
249 | 'attribute vec4 texturePos;',
250 | 'attribute vec4 uTexturePos;',
251 | 'attribute vec4 vTexturePos;',
252 | 'varying vec2 textureCoord;',
253 | 'varying vec2 uTextureCoord;',
254 | 'varying vec2 vTextureCoord;',
255 |
256 | 'void main()',
257 | '{',
258 | ' gl_Position = vertexPos;',
259 | ' textureCoord = texturePos.xy;',
260 | ' uTextureCoord = uTexturePos.xy;',
261 | ' vTextureCoord = vTexturePos.xy;',
262 | '}'
263 | ].join('\n');
264 |
265 | fragmentShaderScript = [
266 | 'precision highp float;',
267 | 'varying highp vec2 textureCoord;',
268 | 'varying highp vec2 uTextureCoord;',
269 | 'varying highp vec2 vTextureCoord;',
270 | 'uniform sampler2D ySampler;',
271 | 'uniform sampler2D uSampler;',
272 | 'uniform sampler2D vSampler;',
273 | 'uniform mat4 YUV2RGB;',
274 |
275 | 'void main(void) {',
276 | ' highp float y = texture2D(ySampler, textureCoord).r;',
277 | ' highp float u = texture2D(uSampler, uTextureCoord).r;',
278 | ' highp float v = texture2D(vSampler, vTextureCoord).r;',
279 | ' gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
280 | '}'
281 | ].join('\n');
282 |
283 | }else if (this.type === "yuv422"){
284 | vertexShaderScript = [
285 | 'attribute vec4 vertexPos;',
286 | 'attribute vec4 texturePos;',
287 | 'varying vec2 textureCoord;',
288 |
289 | 'void main()',
290 | '{',
291 | ' gl_Position = vertexPos;',
292 | ' textureCoord = texturePos.xy;',
293 | '}'
294 | ].join('\n');
295 |
296 | fragmentShaderScript = [
297 | 'precision highp float;',
298 | 'varying highp vec2 textureCoord;',
299 | 'uniform sampler2D sampler;',
300 | 'uniform highp vec2 resolution;',
301 | 'uniform mat4 YUV2RGB;',
302 |
303 | 'void main(void) {',
304 |
305 | ' highp float texPixX = 1.0 / resolution.x;',
306 | ' highp float logPixX = 2.0 / resolution.x;', // half the resolution of the texture
307 | ' highp float logHalfPixX = 4.0 / resolution.x;', // half of the logical resolution so every 4th pixel
308 | ' highp float steps = floor(textureCoord.x / logPixX);',
309 | ' highp float uvSteps = floor(textureCoord.x / logHalfPixX);',
310 | ' highp float y = texture2D(sampler, vec2((logPixX * steps) + texPixX, textureCoord.y)).r;',
311 | ' highp float u = texture2D(sampler, vec2((logHalfPixX * uvSteps), textureCoord.y)).r;',
312 | ' highp float v = texture2D(sampler, vec2((logHalfPixX * uvSteps) + texPixX + texPixX, textureCoord.y)).r;',
313 |
314 | //' highp float y = texture2D(sampler, textureCoord).r;',
315 | //' gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
316 | ' gl_FragColor = vec4(y, u, v, 1.0) * YUV2RGB;',
317 | '}'
318 | ].join('\n');
319 | };
320 |
321 | var YUV2RGB = [];
322 |
323 | if (this.conversionType == "rec709") {
324 | // ITU-T Rec. 709
325 | YUV2RGB = [
326 | 1.16438, 0.00000, 1.79274, -0.97295,
327 | 1.16438, -0.21325, -0.53291, 0.30148,
328 | 1.16438, 2.11240, 0.00000, -1.13340,
329 | 0, 0, 0, 1,
330 | ];
331 | } else {
332 | // assume ITU-T Rec. 601
333 | YUV2RGB = [
334 | 1.16438, 0.00000, 1.59603, -0.87079,
335 | 1.16438, -0.39176, -0.81297, 0.52959,
336 | 1.16438, 2.01723, 0.00000, -1.08139,
337 | 0, 0, 0, 1
338 | ];
339 | };
340 |
341 | var vertexShader = gl.createShader(gl.VERTEX_SHADER);
342 | gl.shaderSource(vertexShader, vertexShaderScript);
343 | gl.compileShader(vertexShader);
344 | if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
345 | console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
346 | }
347 |
348 | var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
349 | gl.shaderSource(fragmentShader, fragmentShaderScript);
350 | gl.compileShader(fragmentShader);
351 | if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
352 | console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
353 | }
354 |
355 | var program = gl.createProgram();
356 | gl.attachShader(program, vertexShader);
357 | gl.attachShader(program, fragmentShader);
358 | gl.linkProgram(program);
359 | if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
360 | console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
361 | }
362 |
363 | gl.useProgram(program);
364 |
365 | var YUV2RGBRef = gl.getUniformLocation(program, 'YUV2RGB');
366 | gl.uniformMatrix4fv(YUV2RGBRef, false, YUV2RGB);
367 |
368 | this.shaderProgram = program;
369 | };
370 |
371 | /**
372 | * Initialize vertex buffers and attach to shader program
373 | */
374 | YUVCanvas.prototype.initBuffers = function() {
375 | var gl = this.contextGL;
376 | var program = this.shaderProgram;
377 |
378 | var vertexPosBuffer = gl.createBuffer();
379 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
380 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
381 |
382 | var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
383 | gl.enableVertexAttribArray(vertexPosRef);
384 | gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
385 |
386 | if (this.animationTime){
387 |
388 | var animationTime = this.animationTime;
389 | var timePassed = 0;
390 | var stepTime = 15;
391 |
392 | var aniFun = function(){
393 |
394 | timePassed += stepTime;
395 | var mul = ( 1 * timePassed ) / animationTime;
396 |
397 | if (timePassed >= animationTime){
398 | mul = 1;
399 | }else{
400 | setTimeout(aniFun, stepTime);
401 | };
402 |
403 | var neg = -1 * mul;
404 | var pos = 1 * mul;
405 |
406 | var vertexPosBuffer = gl.createBuffer();
407 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
408 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([pos, pos, neg, pos, pos, neg, neg, neg]), gl.STATIC_DRAW);
409 |
410 | var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
411 | gl.enableVertexAttribArray(vertexPosRef);
412 | gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
413 |
414 | try{
415 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
416 | }catch(e){};
417 |
418 | };
419 | aniFun();
420 |
421 | };
422 |
423 |
424 |
425 | var texturePosBuffer = gl.createBuffer();
426 | gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
427 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
428 |
429 | var texturePosRef = gl.getAttribLocation(program, 'texturePos');
430 | gl.enableVertexAttribArray(texturePosRef);
431 | gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
432 |
433 | this.texturePosBuffer = texturePosBuffer;
434 |
435 | if (this.type === "yuv420"){
436 | var uTexturePosBuffer = gl.createBuffer();
437 | gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
438 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
439 |
440 | var uTexturePosRef = gl.getAttribLocation(program, 'uTexturePos');
441 | gl.enableVertexAttribArray(uTexturePosRef);
442 | gl.vertexAttribPointer(uTexturePosRef, 2, gl.FLOAT, false, 0, 0);
443 |
444 | this.uTexturePosBuffer = uTexturePosBuffer;
445 |
446 |
447 | var vTexturePosBuffer = gl.createBuffer();
448 | gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
449 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
450 |
451 | var vTexturePosRef = gl.getAttribLocation(program, 'vTexturePos');
452 | gl.enableVertexAttribArray(vTexturePosRef);
453 | gl.vertexAttribPointer(vTexturePosRef, 2, gl.FLOAT, false, 0, 0);
454 |
455 | this.vTexturePosBuffer = vTexturePosBuffer;
456 | };
457 |
458 | };
459 |
460 | /**
461 | * Initialize GL textures and attach to shader program
462 | */
463 | YUVCanvas.prototype.initTextures = function() {
464 | var gl = this.contextGL;
465 | var program = this.shaderProgram;
466 |
467 | if (this.type === "yuv420"){
468 |
469 | var yTextureRef = this.initTexture();
470 | var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
471 | gl.uniform1i(ySamplerRef, 0);
472 | this.yTextureRef = yTextureRef;
473 |
474 | var uTextureRef = this.initTexture();
475 | var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
476 | gl.uniform1i(uSamplerRef, 1);
477 | this.uTextureRef = uTextureRef;
478 |
479 | var vTextureRef = this.initTexture();
480 | var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
481 | gl.uniform1i(vSamplerRef, 2);
482 | this.vTextureRef = vTextureRef;
483 |
484 | }else if (this.type === "yuv422"){
485 | // only one texture for 422
486 | var textureRef = this.initTexture();
487 | var samplerRef = gl.getUniformLocation(program, 'sampler');
488 | gl.uniform1i(samplerRef, 0);
489 | this.textureRef = textureRef;
490 |
491 | };
492 | };
493 |
494 | /**
495 | * Create and configure a single texture
496 | */
497 | YUVCanvas.prototype.initTexture = function() {
498 | var gl = this.contextGL;
499 |
500 | var textureRef = gl.createTexture();
501 | gl.bindTexture(gl.TEXTURE_2D, textureRef);
502 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
503 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
504 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
505 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
506 | gl.bindTexture(gl.TEXTURE_2D, null);
507 |
508 | return textureRef;
509 | };
510 |
511 | /**
512 | * Draw picture data to the canvas.
513 | * If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
514 | * Otherwise, data must be an RGBA formatted ArrayBuffer.
515 | */
516 | YUVCanvas.prototype.drawNextOutputPicture = function(width, height, croppingParams, data) {
517 | var gl = this.contextGL;
518 |
519 | if(gl) {
520 | this.drawNextOuptutPictureGL(width, height, croppingParams, data);
521 | } else {
522 | this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
523 | }
524 | };
525 |
526 |
527 |
528 | /**
529 | * Draw next output picture using ARGB data on a 2d canvas.
530 | */
531 | YUVCanvas.prototype.drawNextOuptutPictureRGBA = function(width, height, croppingParams, data) {
532 | var canvas = this.canvasElement;
533 |
534 | var croppingParams = null;
535 |
536 | var argbData = data;
537 |
538 | var ctx = canvas.getContext('2d');
539 | var imageData = ctx.getImageData(0, 0, width, height);
540 | imageData.data.set(argbData);
541 |
542 | if(croppingParams === null) {
543 | ctx.putImageData(imageData, 0, 0);
544 | } else {
545 | ctx.putImageData(imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
546 | }
547 | };
548 |
549 | return YUVCanvas;
550 |
551 | }));
552 |
--------------------------------------------------------------------------------
/public_html/mp4.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | function assert(condition, message) {
5 | if (!condition) {
6 | error(message);
7 | }
8 | };
9 |
10 |
11 | /**
12 | * Represents a 2-dimensional size value.
13 | */
14 | var Size = (function size() {
15 | function constructor(w, h) {
16 | this.w = w;
17 | this.h = h;
18 | }
19 | constructor.prototype = {
20 | toString: function () {
21 | return "(" + this.w + ", " + this.h + ")";
22 | },
23 | getHalfSize: function() {
24 | return new Size(this.w >>> 1, this.h >>> 1);
25 | },
26 | length: function() {
27 | return this.w * this.h;
28 | }
29 | };
30 | return constructor;
31 | })();
32 |
33 |
34 |
35 |
36 |
37 | var Bytestream = (function BytestreamClosure() {
38 | function constructor(arrayBuffer, start, length) {
39 | this.bytes = new Uint8Array(arrayBuffer);
40 | this.start = start || 0;
41 | this.pos = this.start;
42 | this.end = (start + length) || this.bytes.length;
43 | }
44 | constructor.prototype = {
45 | get length() {
46 | return this.end - this.start;
47 | },
48 | get position() {
49 | return this.pos;
50 | },
51 | get remaining() {
52 | return this.end - this.pos;
53 | },
54 | readU8Array: function (length) {
55 | if (this.pos > this.end - length)
56 | return null;
57 | var res = this.bytes.subarray(this.pos, this.pos + length);
58 | this.pos += length;
59 | return res;
60 | },
61 | readU32Array: function (rows, cols, names) {
62 | cols = cols || 1;
63 | if (this.pos > this.end - (rows * cols) * 4)
64 | return null;
65 | if (cols == 1) {
66 | var array = new Uint32Array(rows);
67 | for (var i = 0; i < rows; i++) {
68 | array[i] = this.readU32();
69 | }
70 | return array;
71 | } else {
72 | var array = new Array(rows);
73 | for (var i = 0; i < rows; i++) {
74 | var row = null;
75 | if (names) {
76 | row = {};
77 | for (var j = 0; j < cols; j++) {
78 | row[names[j]] = this.readU32();
79 | }
80 | } else {
81 | row = new Uint32Array(cols);
82 | for (var j = 0; j < cols; j++) {
83 | row[j] = this.readU32();
84 | }
85 | }
86 | array[i] = row;
87 | }
88 | return array;
89 | }
90 | },
91 | read8: function () {
92 | return this.readU8() << 24 >> 24;
93 | },
94 | readU8: function () {
95 | if (this.pos >= this.end)
96 | return null;
97 | return this.bytes[this.pos++];
98 | },
99 | read16: function () {
100 | return this.readU16() << 16 >> 16;
101 | },
102 | readU16: function () {
103 | if (this.pos >= this.end - 1)
104 | return null;
105 | var res = this.bytes[this.pos + 0] << 8 | this.bytes[this.pos + 1];
106 | this.pos += 2;
107 | return res;
108 | },
109 | read24: function () {
110 | return this.readU24() << 8 >> 8;
111 | },
112 | readU24: function () {
113 | var pos = this.pos;
114 | var bytes = this.bytes;
115 | if (pos > this.end - 3)
116 | return null;
117 | var res = bytes[pos + 0] << 16 | bytes[pos + 1] << 8 | bytes[pos + 2];
118 | this.pos += 3;
119 | return res;
120 | },
121 | peek32: function (advance) {
122 | var pos = this.pos;
123 | var bytes = this.bytes;
124 | if (pos > this.end - 4)
125 | return null;
126 | var res = bytes[pos + 0] << 24 | bytes[pos + 1] << 16 | bytes[pos + 2] << 8 | bytes[pos + 3];
127 | if (advance) {
128 | this.pos += 4;
129 | }
130 | return res;
131 | },
132 | read32: function () {
133 | return this.peek32(true);
134 | },
135 | readU32: function () {
136 | return this.peek32(true) >>> 0;
137 | },
138 | read4CC: function () {
139 | var pos = this.pos;
140 | if (pos > this.end - 4)
141 | return null;
142 | var res = "";
143 | for (var i = 0; i < 4; i++) {
144 | res += String.fromCharCode(this.bytes[pos + i]);
145 | }
146 | this.pos += 4;
147 | return res;
148 | },
149 | readFP16: function () {
150 | return this.read32() / 65536;
151 | },
152 | readFP8: function () {
153 | return this.read16() / 256;
154 | },
155 | readISO639: function () {
156 | var bits = this.readU16();
157 | var res = "";
158 | for (var i = 0; i < 3; i++) {
159 | var c = (bits >>> (2 - i) * 5) & 0x1f;
160 | res += String.fromCharCode(c + 0x60);
161 | }
162 | return res;
163 | },
164 | readUTF8: function (length) {
165 | var res = "";
166 | for (var i = 0; i < length; i++) {
167 | res += String.fromCharCode(this.readU8());
168 | }
169 | return res;
170 | },
171 | readPString: function (max) {
172 | var len = this.readU8();
173 | assert (len <= max);
174 | var res = this.readUTF8(len);
175 | this.reserved(max - len - 1, 0);
176 | return res;
177 | },
178 | skip: function (length) {
179 | this.seek(this.pos + length);
180 | },
181 | reserved: function (length, value) {
182 | for (var i = 0; i < length; i++) {
183 | assert (this.readU8() == value);
184 | }
185 | },
186 | seek: function (index) {
187 | if (index < 0 || index > this.end) {
188 | error("Index out of bounds (bounds: [0, " + this.end + "], index: " + index + ").");
189 | }
190 | this.pos = index;
191 | },
192 | subStream: function (start, length) {
193 | return new Bytestream(this.bytes.buffer, start, length);
194 | }
195 | };
196 | return constructor;
197 | })();
198 |
199 |
200 | var PARANOID = true; // Heavy-weight assertions.
201 |
202 | /**
203 | * Reads an mp4 file and constructs a object graph that corresponds to the box/atom
204 | * structure of the file. Mp4 files are based on the ISO Base Media format, which in
205 | * turn is based on the Apple Quicktime format. The Quicktime spec is available at:
206 | * http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF. An mp4 spec
207 | * also exists, but I cannot find it freely available.
208 | *
209 | * Mp4 files contain a tree of boxes (or atoms in Quicktime). The general structure
210 | * is as follows (in a pseudo regex syntax):
211 | *
212 | * Box / Atom Structure:
213 | *
214 | * [size type [version flags] field* box*]
215 | * <32> <4C> <--8--> <24-> <-?-> >
216 | * <------------- box size ------------>
217 | *
218 | * The box size indicates the entire size of the box and its children, we can use it
219 | * to skip over boxes that are of no interest. Each box has a type indicated by a
220 | * four character code (4C), this describes how the box should be parsed and is also
221 | * used as an object key name in the resulting box tree. For example, the expression:
222 | * "moov.trak[0].mdia.minf" can be used to access individual boxes in the tree based
223 | * on their 4C name. If two or more boxes with the same 4C name exist in a box, then
224 | * an array is built with that name.
225 | *
226 | */
227 | var MP4Reader = (function reader() {
228 | var BOX_HEADER_SIZE = 8;
229 | var FULL_BOX_HEADER_SIZE = BOX_HEADER_SIZE + 4;
230 |
231 | function constructor(stream) {
232 | this.stream = stream;
233 | this.tracks = {};
234 | }
235 |
236 | constructor.prototype = {
237 | readBoxes: function (stream, parent) {
238 | while (stream.peek32()) {
239 | var child = this.readBox(stream);
240 | if (child.type in parent) {
241 | var old = parent[child.type];
242 | if (!(old instanceof Array)) {
243 | parent[child.type] = [old];
244 | }
245 | parent[child.type].push(child);
246 | } else {
247 | parent[child.type] = child;
248 | }
249 | }
250 | },
251 | readBox: function readBox(stream) {
252 | var box = { offset: stream.position };
253 |
254 | function readHeader() {
255 | box.size = stream.readU32();
256 | box.type = stream.read4CC();
257 | }
258 |
259 | function readFullHeader() {
260 | box.version = stream.readU8();
261 | box.flags = stream.readU24();
262 | }
263 |
264 | function remainingBytes() {
265 | return box.size - (stream.position - box.offset);
266 | }
267 |
268 | function skipRemainingBytes () {
269 | stream.skip(remainingBytes());
270 | }
271 |
272 | var readRemainingBoxes = function () {
273 | var subStream = stream.subStream(stream.position, remainingBytes());
274 | this.readBoxes(subStream, box);
275 | stream.skip(subStream.length);
276 | }.bind(this);
277 |
278 | readHeader();
279 |
280 | switch (box.type) {
281 | case 'ftyp':
282 | box.name = "File Type Box";
283 | box.majorBrand = stream.read4CC();
284 | box.minorVersion = stream.readU32();
285 | box.compatibleBrands = new Array((box.size - 16) / 4);
286 | for (var i = 0; i < box.compatibleBrands.length; i++) {
287 | box.compatibleBrands[i] = stream.read4CC();
288 | }
289 | break;
290 | case 'moov':
291 | box.name = "Movie Box";
292 | readRemainingBoxes();
293 | break;
294 | case 'mvhd':
295 | box.name = "Movie Header Box";
296 | readFullHeader();
297 | assert (box.version == 0);
298 | box.creationTime = stream.readU32();
299 | box.modificationTime = stream.readU32();
300 | box.timeScale = stream.readU32();
301 | box.duration = stream.readU32();
302 | box.rate = stream.readFP16();
303 | box.volume = stream.readFP8();
304 | stream.skip(10);
305 | box.matrix = stream.readU32Array(9);
306 | stream.skip(6 * 4);
307 | box.nextTrackId = stream.readU32();
308 | break;
309 | case 'trak':
310 | box.name = "Track Box";
311 | readRemainingBoxes();
312 | this.tracks[box.tkhd.trackId] = new Track(this, box);
313 | break;
314 | case 'tkhd':
315 | box.name = "Track Header Box";
316 | readFullHeader();
317 | assert (box.version == 0);
318 | box.creationTime = stream.readU32();
319 | box.modificationTime = stream.readU32();
320 | box.trackId = stream.readU32();
321 | stream.skip(4);
322 | box.duration = stream.readU32();
323 | stream.skip(8);
324 | box.layer = stream.readU16();
325 | box.alternateGroup = stream.readU16();
326 | box.volume = stream.readFP8();
327 | stream.skip(2);
328 | box.matrix = stream.readU32Array(9);
329 | box.width = stream.readFP16();
330 | box.height = stream.readFP16();
331 | break;
332 | case 'mdia':
333 | box.name = "Media Box";
334 | readRemainingBoxes();
335 | break;
336 | case 'mdhd':
337 | box.name = "Media Header Box";
338 | readFullHeader();
339 | assert (box.version == 0);
340 | box.creationTime = stream.readU32();
341 | box.modificationTime = stream.readU32();
342 | box.timeScale = stream.readU32();
343 | box.duration = stream.readU32();
344 | box.language = stream.readISO639();
345 | stream.skip(2);
346 | break;
347 | case 'hdlr':
348 | box.name = "Handler Reference Box";
349 | readFullHeader();
350 | stream.skip(4);
351 | box.handlerType = stream.read4CC();
352 | stream.skip(4 * 3);
353 | var bytesLeft = box.size - 32;
354 | if (bytesLeft > 0) {
355 | box.name = stream.readUTF8(bytesLeft);
356 | }
357 | break;
358 | case 'minf':
359 | box.name = "Media Information Box";
360 | readRemainingBoxes();
361 | break;
362 | case 'stbl':
363 | box.name = "Sample Table Box";
364 | readRemainingBoxes();
365 | break;
366 | case 'stsd':
367 | box.name = "Sample Description Box";
368 | readFullHeader();
369 | box.sd = [];
370 | var entries = stream.readU32();
371 | readRemainingBoxes();
372 | break;
373 | case 'avc1':
374 | stream.reserved(6, 0);
375 | box.dataReferenceIndex = stream.readU16();
376 | assert (stream.readU16() == 0); // Version
377 | assert (stream.readU16() == 0); // Revision Level
378 | stream.readU32(); // Vendor
379 | stream.readU32(); // Temporal Quality
380 | stream.readU32(); // Spatial Quality
381 | box.width = stream.readU16();
382 | box.height = stream.readU16();
383 | box.horizontalResolution = stream.readFP16();
384 | box.verticalResolution = stream.readFP16();
385 | assert (stream.readU32() == 0); // Reserved
386 | box.frameCount = stream.readU16();
387 | box.compressorName = stream.readPString(32);
388 | box.depth = stream.readU16();
389 | assert (stream.readU16() == 0xFFFF); // Color Table Id
390 | readRemainingBoxes();
391 | break;
392 | case 'mp4a':
393 | stream.reserved(6, 0);
394 | box.dataReferenceIndex = stream.readU16();
395 | box.version = stream.readU16();
396 | stream.skip(2);
397 | stream.skip(4);
398 | box.channelCount = stream.readU16();
399 | box.sampleSize = stream.readU16();
400 | box.compressionId = stream.readU16();
401 | box.packetSize = stream.readU16();
402 | box.sampleRate = stream.readU32() >>> 16;
403 |
404 | // TODO: Parse other version levels.
405 | assert (box.version == 0);
406 | readRemainingBoxes();
407 | break;
408 | case 'esds':
409 | box.name = "Elementary Stream Descriptor";
410 | readFullHeader();
411 | // TODO: Do we really need to parse this?
412 | skipRemainingBytes();
413 | break;
414 | case 'avcC':
415 | box.name = "AVC Configuration Box";
416 | box.configurationVersion = stream.readU8();
417 | box.avcProfileIndication = stream.readU8();
418 | box.profileCompatibility = stream.readU8();
419 | box.avcLevelIndication = stream.readU8();
420 | box.lengthSizeMinusOne = stream.readU8() & 3;
421 | assert (box.lengthSizeMinusOne == 3, "TODO");
422 | var count = stream.readU8() & 31;
423 | box.sps = [];
424 | for (var i = 0; i < count; i++) {
425 | box.sps.push(stream.readU8Array(stream.readU16()));
426 | }
427 | var count = stream.readU8() & 31;
428 | box.pps = [];
429 | for (var i = 0; i < count; i++) {
430 | box.pps.push(stream.readU8Array(stream.readU16()));
431 | }
432 | skipRemainingBytes();
433 | break;
434 | case 'btrt':
435 | box.name = "Bit Rate Box";
436 | box.bufferSizeDb = stream.readU32();
437 | box.maxBitrate = stream.readU32();
438 | box.avgBitrate = stream.readU32();
439 | break;
440 | case 'stts':
441 | box.name = "Decoding Time to Sample Box";
442 | readFullHeader();
443 | box.table = stream.readU32Array(stream.readU32(), 2, ["count", "delta"]);
444 | break;
445 | case 'stss':
446 | box.name = "Sync Sample Box";
447 | readFullHeader();
448 | box.samples = stream.readU32Array(stream.readU32());
449 | break;
450 | case 'stsc':
451 | box.name = "Sample to Chunk Box";
452 | readFullHeader();
453 | box.table = stream.readU32Array(stream.readU32(), 3,
454 | ["firstChunk", "samplesPerChunk", "sampleDescriptionId"]);
455 | break;
456 | case 'stsz':
457 | box.name = "Sample Size Box";
458 | readFullHeader();
459 | box.sampleSize = stream.readU32();
460 | var count = stream.readU32();
461 | if (box.sampleSize == 0) {
462 | box.table = stream.readU32Array(count);
463 | }
464 | break;
465 | case 'stco':
466 | box.name = "Chunk Offset Box";
467 | readFullHeader();
468 | box.table = stream.readU32Array(stream.readU32());
469 | break;
470 | case 'smhd':
471 | box.name = "Sound Media Header Box";
472 | readFullHeader();
473 | box.balance = stream.readFP8();
474 | stream.reserved(2, 0);
475 | break;
476 | case 'mdat':
477 | box.name = "Media Data Box";
478 | assert (box.size >= 8, "Cannot parse large media data yet.");
479 | box.data = stream.readU8Array(remainingBytes());
480 | break;
481 | default:
482 | skipRemainingBytes();
483 | break;
484 | };
485 | return box;
486 | },
487 | read: function () {
488 | var start = (new Date).getTime();
489 | this.file = {};
490 | this.readBoxes(this.stream, this.file);
491 | console.info("Parsed stream in " + ((new Date).getTime() - start) + " ms");
492 | },
493 | traceSamples: function () {
494 | var video = this.tracks[1];
495 | var audio = this.tracks[2];
496 |
497 | console.info("Video Samples: " + video.getSampleCount());
498 | console.info("Audio Samples: " + audio.getSampleCount());
499 |
500 | var vi = 0;
501 | var ai = 0;
502 |
503 | for (var i = 0; i < 100; i++) {
504 | var vo = video.sampleToOffset(vi);
505 | var ao = audio.sampleToOffset(ai);
506 |
507 | var vs = video.sampleToSize(vi, 1);
508 | var as = audio.sampleToSize(ai, 1);
509 |
510 | if (vo < ao) {
511 | console.info("V Sample " + vi + " Offset : " + vo + ", Size : " + vs);
512 | vi ++;
513 | } else {
514 | console.info("A Sample " + ai + " Offset : " + ao + ", Size : " + as);
515 | ai ++;
516 | }
517 | }
518 | }
519 | };
520 | return constructor;
521 | })();
522 |
523 | var Track = (function track () {
524 | function constructor(file, trak) {
525 | this.file = file;
526 | this.trak = trak;
527 | }
528 |
529 | constructor.prototype = {
530 | getSampleSizeTable: function () {
531 | return this.trak.mdia.minf.stbl.stsz.table;
532 | },
533 | getSampleCount: function () {
534 | return this.getSampleSizeTable().length;
535 | },
536 | /**
537 | * Computes the size of a range of samples, returns zero if length is zero.
538 | */
539 | sampleToSize: function (start, length) {
540 | var table = this.getSampleSizeTable();
541 | var size = 0;
542 | for (var i = start; i < start + length; i++) {
543 | size += table[i];
544 | }
545 | return size;
546 | },
547 | /**
548 | * Computes the chunk that contains the specified sample, as well as the offset of
549 | * the sample in the computed chunk.
550 | */
551 | sampleToChunk: function (sample) {
552 |
553 | /* Samples are grouped in chunks which may contain a variable number of samples.
554 | * The sample-to-chunk table in the stsc box describes how samples are arranged
555 | * in chunks. Each table row corresponds to a set of consecutive chunks with the
556 | * same number of samples and description ids. For example, the following table:
557 | *
558 | * +-------------+-------------------+----------------------+
559 | * | firstChunk | samplesPerChunk | sampleDescriptionId |
560 | * +-------------+-------------------+----------------------+
561 | * | 1 | 3 | 23 |
562 | * | 3 | 1 | 23 |
563 | * | 5 | 1 | 24 |
564 | * +-------------+-------------------+----------------------+
565 | *
566 | * describes 5 chunks with a total of (2 * 3) + (2 * 1) + (1 * 1) = 9 samples,
567 | * each chunk containing samples 3, 3, 1, 1, 1 in chunk order, or
568 | * chunks 1, 1, 1, 2, 2, 2, 3, 4, 5 in sample order.
569 | *
570 | * This function determines the chunk that contains a specified sample by iterating
571 | * over every entry in the table. It also returns the position of the sample in the
572 | * chunk which can be used to compute the sample's exact position in the file.
573 | *
574 | * TODO: Determine if we should memoize this function.
575 | */
576 |
577 | var table = this.trak.mdia.minf.stbl.stsc.table;
578 |
579 | if (table.length === 1) {
580 | var row = table[0];
581 | assert (row.firstChunk === 1);
582 | return {
583 | index: Math.floor(sample / row.samplesPerChunk),
584 | offset: sample % row.samplesPerChunk
585 | };
586 | }
587 |
588 | var totalChunkCount = 0;
589 | for (var i = 0; i < table.length; i++) {
590 | var row = table[i];
591 | if (i > 0) {
592 | var previousRow = table[i - 1];
593 | var previousChunkCount = row.firstChunk - previousRow.firstChunk;
594 | var previousSampleCount = previousRow.samplesPerChunk * previousChunkCount;
595 | if (sample >= previousSampleCount) {
596 | sample -= previousSampleCount;
597 | if (i == table.length - 1) {
598 | return {
599 | index: totalChunkCount + previousChunkCount + Math.floor(sample / row.samplesPerChunk),
600 | offset: sample % row.samplesPerChunk
601 | };
602 | }
603 | } else {
604 | return {
605 | index: totalChunkCount + Math.floor(sample / previousRow.samplesPerChunk),
606 | offset: sample % previousRow.samplesPerChunk
607 | };
608 | }
609 | totalChunkCount += previousChunkCount;
610 | }
611 | }
612 | assert(false);
613 | },
614 | chunkToOffset: function (chunk) {
615 | var table = this.trak.mdia.minf.stbl.stco.table;
616 | return table[chunk];
617 | },
618 | sampleToOffset: function (sample) {
619 | var res = this.sampleToChunk(sample);
620 | var offset = this.chunkToOffset(res.index);
621 | return offset + this.sampleToSize(sample - res.offset, res.offset);
622 | },
623 | /**
624 | * Computes the sample at the specified time.
625 | */
626 | timeToSample: function (time) {
627 | /* In the time-to-sample table samples are grouped by their duration. The count field
628 | * indicates the number of consecutive samples that have the same duration. For example,
629 | * the following table:
630 | *
631 | * +-------+-------+
632 | * | count | delta |
633 | * +-------+-------+
634 | * | 4 | 3 |
635 | * | 2 | 1 |
636 | * | 3 | 2 |
637 | * +-------+-------+
638 | *
639 | * describes 9 samples with a total time of (4 * 3) + (2 * 1) + (3 * 2) = 20.
640 | *
641 | * This function determines the sample at the specified time by iterating over every
642 | * entry in the table.
643 | *
644 | * TODO: Determine if we should memoize this function.
645 | */
646 | var table = this.trak.mdia.minf.stbl.stts.table;
647 | var sample = 0;
648 | for (var i = 0; i < table.length; i++) {
649 | var delta = table[i].count * table[i].delta;
650 | if (time >= delta) {
651 | time -= delta;
652 | sample += table[i].count;
653 | } else {
654 | return sample + Math.floor(time / table[i].delta);
655 | }
656 | }
657 | },
658 | /**
659 | * Gets the total time of the track.
660 | */
661 | getTotalTime: function () {
662 | if (PARANOID) {
663 | var table = this.trak.mdia.minf.stbl.stts.table;
664 | var duration = 0;
665 | for (var i = 0; i < table.length; i++) {
666 | duration += table[i].count * table[i].delta;
667 | }
668 | assert (this.trak.mdia.mdhd.duration == duration);
669 | }
670 | return this.trak.mdia.mdhd.duration;
671 | },
672 | getTotalTimeInSeconds: function () {
673 | return this.timeToSeconds(this.getTotalTime());
674 | },
675 | getTimeScale: function () {
676 | return this.trak.mdia.mdhd.timeScale;
677 | },
678 | /**
679 | * Converts time units to real time (seconds).
680 | */
681 | timeToSeconds: function (time) {
682 | return time / this.getTimeScale();
683 | },
684 | /**
685 | * Converts real time (seconds) to time units.
686 | */
687 | secondsToTime: function (seconds) {
688 | return seconds * this.getTimeScale();
689 | },
690 | foo: function () {
691 | /*
692 | for (var i = 0; i < this.getSampleCount(); i++) {
693 | var res = this.sampleToChunk(i);
694 | console.info("Sample " + i + " -> " + res.index + " % " + res.offset +
695 | " @ " + this.chunkToOffset(res.index) +
696 | " @@ " + this.sampleToOffset(i));
697 | }
698 | console.info("Total Time: " + this.timeToSeconds(this.getTotalTime()));
699 | var total = this.getTotalTimeInSeconds();
700 | for (var i = 50; i < total; i += 0.1) {
701 | // console.info("Time: " + i.toFixed(2) + " " + this.secondsToTime(i));
702 |
703 | console.info("Time: " + i.toFixed(2) + " " + this.timeToSample(this.secondsToTime(i)));
704 | }
705 | */
706 | },
707 | /**
708 | * AVC samples contain one or more NAL units each of which have a length prefix.
709 | * This function returns an array of NAL units without their length prefixes.
710 | */
711 | getSampleNALUnits: function (sample) {
712 | var bytes = this.file.stream.bytes;
713 | var offset = this.sampleToOffset(sample);
714 | var end = offset + this.sampleToSize(sample, 1);
715 | var nalUnits = [];
716 | while(end - offset > 0) {
717 | var length = (new Bytestream(bytes.buffer, offset)).readU32();
718 | nalUnits.push(bytes.subarray(offset + 4, offset + length + 4));
719 | offset = offset + length + 4;
720 | }
721 | return nalUnits;
722 | }
723 | };
724 | return constructor;
725 | })();
726 |
727 |
728 | // Only add setZeroTimeout to the window object, and hide everything
729 | // else in a closure. (http://dbaron.org/log/20100309-faster-timeouts)
730 | (function() {
731 | var timeouts = [];
732 | var messageName = "zero-timeout-message";
733 |
734 | // Like setTimeout, but only takes a function argument. There's
735 | // no time argument (always zero) and no arguments (you have to
736 | // use a closure).
737 | function setZeroTimeout(fn) {
738 | timeouts.push(fn);
739 | window.postMessage(messageName, "*");
740 | }
741 |
742 | function handleMessage(event) {
743 | if (event.source == window && event.data == messageName) {
744 | event.stopPropagation();
745 | if (timeouts.length > 0) {
746 | var fn = timeouts.shift();
747 | fn();
748 | }
749 | }
750 | }
751 |
752 | window.addEventListener("message", handleMessage, true);
753 |
754 | // Add the one thing we want added to the window object.
755 | window.setZeroTimeout = setZeroTimeout;
756 | })();
757 |
758 | var MP4Player = (function reader() {
759 | var defaultConfig = {
760 | filter: "original",
761 | filterHorLuma: "optimized",
762 | filterVerLumaEdge: "optimized",
763 | getBoundaryStrengthsA: "optimized"
764 | };
765 |
766 | function constructor(stream, useWorkers, webgl, render) {
767 | this.stream = stream;
768 | this.useWorkers = useWorkers;
769 | this.webgl = webgl;
770 | this.render = render;
771 |
772 | this.statistics = {
773 | videoStartTime: 0,
774 | videoPictureCounter: 0,
775 | windowStartTime: 0,
776 | windowPictureCounter: 0,
777 | fps: 0,
778 | fpsMin: 1000,
779 | fpsMax: -1000,
780 | webGLTextureUploadTime: 0
781 | };
782 |
783 | this.onStatisticsUpdated = function () {};
784 |
785 | this.avc = new Player({
786 | useWorker: useWorkers,
787 | reuseMemory: true,
788 | webgl: webgl,
789 | size: {
790 | width: 640,
791 | height: 368
792 | }
793 | });
794 |
795 | this.webgl = this.avc.webgl;
796 |
797 | var self = this;
798 | this.avc.onPictureDecoded = function(){
799 | updateStatistics.call(self);
800 | };
801 |
802 | this.canvas = this.avc.canvas;
803 | }
804 |
805 | function updateStatistics() {
806 | var s = this.statistics;
807 | s.videoPictureCounter += 1;
808 | s.windowPictureCounter += 1;
809 | var now = Date.now();
810 | if (!s.videoStartTime) {
811 | s.videoStartTime = now;
812 | }
813 | var videoElapsedTime = now - s.videoStartTime;
814 | s.elapsed = videoElapsedTime / 1000;
815 | if (videoElapsedTime < 1000) {
816 | return;
817 | }
818 |
819 | if (!s.windowStartTime) {
820 | s.windowStartTime = now;
821 | return;
822 | } else if ((now - s.windowStartTime) > 1000) {
823 | var windowElapsedTime = now - s.windowStartTime;
824 | var fps = (s.windowPictureCounter / windowElapsedTime) * 1000;
825 | s.windowStartTime = now;
826 | s.windowPictureCounter = 0;
827 |
828 | if (fps < s.fpsMin) s.fpsMin = fps;
829 | if (fps > s.fpsMax) s.fpsMax = fps;
830 | s.fps = fps;
831 | }
832 |
833 | var fps = (s.videoPictureCounter / videoElapsedTime) * 1000;
834 | s.fpsSinceStart = fps;
835 | this.onStatisticsUpdated(this.statistics);
836 | return;
837 | }
838 |
839 | constructor.prototype = {
840 | readAll: function(callback) {
841 | console.info("MP4Player::readAll()");
842 | this.stream.readAll(null, function (buffer) {
843 | this.reader = new MP4Reader(new Bytestream(buffer));
844 | this.reader.read();
845 | var video = this.reader.tracks[1];
846 | this.size = new Size(video.trak.tkhd.width, video.trak.tkhd.height);
847 | console.info("MP4Player::readAll(), length: " + this.reader.stream.length);
848 | if (callback) callback();
849 | }.bind(this));
850 | },
851 | play: function() {
852 | var reader = this.reader;
853 |
854 | if (!reader) {
855 | this.readAll(this.play.bind(this));
856 | return;
857 | };
858 |
859 | var video = reader.tracks[1];
860 | var audio = reader.tracks[2];
861 |
862 | var avc = reader.tracks[1].trak.mdia.minf.stbl.stsd.avc1.avcC;
863 | var sps = avc.sps[0];
864 | var pps = avc.pps[0];
865 |
866 | /* Decode Sequence & Picture Parameter Sets */
867 | this.avc.decode(sps);
868 | this.avc.decode(pps);
869 |
870 | /* Decode Pictures */
871 | var pic = 0;
872 | setTimeout(function foo() {
873 | var avc = this.avc;
874 | video.getSampleNALUnits(pic).forEach(function (nal) {
875 | avc.decode(nal);
876 | });
877 | pic ++;
878 | if (pic < 3000) {
879 | setTimeout(foo.bind(this), 1);
880 | };
881 | }.bind(this), 1);
882 | }
883 | };
884 |
885 | return constructor;
886 | })();
887 |
888 | var Broadway = (function broadway() {
889 | function constructor(div) {
890 | var src = div.attributes.src ? div.attributes.src.value : undefined;
891 | var width = div.attributes.width ? div.attributes.width.value : 640;
892 | var height = div.attributes.height ? div.attributes.height.value : 480;
893 |
894 | var controls = document.createElement('div');
895 | controls.setAttribute('style', "z-index: 100; position: absolute; bottom: 0px; background-color: rgba(0,0,0,0.8); height: 30px; width: 100%; text-align: left;");
896 | this.info = document.createElement('div');
897 | this.info.setAttribute('style', "font-size: 14px; font-weight: bold; padding: 6px; color: lime;");
898 | controls.appendChild(this.info);
899 | div.appendChild(controls);
900 |
901 | var useWorkers = div.attributes.workers ? div.attributes.workers.value == "true" : false;
902 | var render = div.attributes.render ? div.attributes.render.value == "true" : false;
903 |
904 | var webgl = "auto";
905 | if (div.attributes.webgl){
906 | if (div.attributes.webgl.value == "true"){
907 | webgl = true;
908 | };
909 | if (div.attributes.webgl.value == "false"){
910 | webgl = false;
911 | };
912 | };
913 |
914 | var infoStrPre = "Click canvas to load and play - ";
915 | var infoStr = "";
916 | if (useWorkers){
917 | infoStr += "worker thread ";
918 | }else{
919 | infoStr += "main thread ";
920 | };
921 |
922 | this.player = new MP4Player(new Stream(src), useWorkers, webgl, render);
923 | this.canvas = this.player.canvas;
924 | this.canvas.onclick = function () {
925 | this.play();
926 | }.bind(this);
927 | div.appendChild(this.canvas);
928 |
929 |
930 | infoStr += " - webgl: " + this.player.webgl;
931 | this.info.innerHTML = infoStrPre + infoStr;
932 |
933 |
934 | this.score = null;
935 | this.player.onStatisticsUpdated = function (statistics) {
936 | if (statistics.videoPictureCounter % 10 != 0) {
937 | return;
938 | }
939 | var info = "";
940 | if (statistics.fps) {
941 | info += " fps: " + statistics.fps.toFixed(2);
942 | }
943 | if (statistics.fpsSinceStart) {
944 | info += " avg: " + statistics.fpsSinceStart.toFixed(2);
945 | }
946 | var scoreCutoff = 1200;
947 | if (statistics.videoPictureCounter < scoreCutoff) {
948 | this.score = scoreCutoff - statistics.videoPictureCounter;
949 | } else if (statistics.videoPictureCounter == scoreCutoff) {
950 | this.score = statistics.fpsSinceStart.toFixed(2);
951 | }
952 | // info += " score: " + this.score;
953 |
954 | this.info.innerHTML = infoStr + info;
955 | }.bind(this);
956 | }
957 | constructor.prototype = {
958 | play: function () {
959 | this.player.play();
960 | }
961 | };
962 | return constructor;
963 | })();
964 |
--------------------------------------------------------------------------------
/lib/nodetello.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* Ryze Tello drone Node.JS
4 | * based on PyTello https://bitbucket.org/PingguSoft/pytello
5 | * sovgvd@gmail.com
6 | */
7 |
8 | // TODO, make it better... sometime...
9 |
10 | const dgram = require('dgram');
11 | const fs = require('fs');
12 |
13 |
14 | var nodetello = function () {
15 |
16 | this.tello_control = dgram.createSocket('udp4');
17 | this.tello_video = dgram.createSocket('udp4');
18 | this.tello_video_output = false; // callback function
19 | this.tello_telemetry_output = false; // callback function
20 | this.tello_telemetry_config = false; // fields to request
21 | this.save_video_path = false;
22 | this.save_photo_path = false;
23 | this.DEG = Math.PI / 180;
24 |
25 | this.TYPE = {
26 | BYTE : 1,
27 | SHORT: 2,
28 | FLOAT: 3,
29 | };
30 |
31 | // exposure values
32 | this.EV = {
33 | '-3.0' : -9,
34 | '-2.7' : -8,
35 | '-2.3' : -7,
36 | '-2.0' : -6,
37 | '-1.7' : -5,
38 | '-1.3' : -4,
39 | '-1.0' : -3,
40 | '-0.7' : -2,
41 | '-0.3' : -1,
42 | '0.0' : 0,
43 | '+0.3' : 1,
44 | '+0.7' : 2,
45 | '+1.0' : 3,
46 | '+1.3' : 4,
47 | '+1.7' : 5,
48 | '+2.0' : 6,
49 | '+2.3' : 7,
50 | '+2.7' : 8,
51 | '+3.0' : 9
52 | };
53 |
54 | this.status = {
55 | altitude: {
56 | max: null,
57 | },
58 | version: null,
59 | speed: {
60 | north: null,
61 | east: null,
62 | ground: null,
63 | vertical: null,
64 | },
65 | fly_time: {
66 | time: null,
67 | },
68 | wifi: {
69 | signal: null,
70 | interference: null,
71 | },
72 | LOG: {
73 | },
74 | state: {
75 | imu: {
76 | state: null,
77 | calibration: null,
78 | },
79 | pressure: null,
80 | down_visual: null,
81 | power: null,
82 | battery: {
83 | state: null,
84 | percentage: null,
85 | left: null,
86 | low: null,
87 | lower: null,
88 | voltage: null,
89 | },
90 | gravity: null,
91 | wind: null,
92 | em: {
93 | state: null,
94 | sky: null,
95 | ground: null,
96 | open: null,
97 | },
98 | temperature_height: null,
99 | factory_mode: null,
100 | fly_mode: null,
101 | drone_hover: null,
102 | outage_recording: null,
103 | throw_fly_timer: null,
104 | camera: null,
105 | front: {
106 | in: null,
107 | out: null,
108 | lsc: null,
109 | }
110 | }
111 | }
112 |
113 | this.tello = {
114 | control: {
115 | host: '192.168.10.1',
116 | port: 8889
117 | },
118 | video: {
119 | port: 6037
120 | },
121 | commands: {
122 | TELLO_CMD_CONN: 1,
123 | TELLO_CMD_CONN_ACK: 2,
124 | TELLO_CMD_SSID: 17,
125 | TELLO_CMD_SET_SSID: 18,
126 | TELLO_CMD_SSID_PASS: 19,
127 | TELLO_CMD_SET_SSID_PASS: 20,
128 | TELLO_CMD_REGION: 21,
129 | TELLO_CMD_SET_REGION: 22,
130 | TELLO_CMD_REQ_VIDEO_SPS_PPS: 37,
131 | TELLO_CMD_TAKE_PICTURE: 48,
132 | TELLO_CMD_SWITCH_PICTURE_VIDEO: 49,
133 | TELLO_CMD_START_RECORDING: 50,
134 | TELLO_CMD_SET_EV: 52,
135 | TELLO_CMD_DATE_TIME: 70,
136 | TELLO_CMD_STICK: 80,
137 | TELLO_CMD_LOG_HEADER_WRITE: 4176,
138 | TELLO_CMD_LOG_DATA_WRITE: 4177,
139 | TELLO_CMD_LOG_CONFIGURATION: 4178,
140 | TELLO_CMD_WIFI_SIGNAL: 26,
141 | TELLO_CMD_VIDEO_BIT_RATE: 40,
142 | TELLO_CMD_LIGHT_STRENGTH: 53,
143 | TELLO_CMD_VERSION_STRING: 69,
144 | TELLO_CMD_ACTIVATION_TIME: 71,
145 | TELLO_CMD_LOADER_VERSION: 73,
146 | TELLO_CMD_STATUS: 86,
147 | TELLO_CMD_ALT_LIMIT: 4182,
148 | TELLO_CMD_LOW_BATT_THRESHOLD: 4183,
149 | TELLO_CMD_ATT_ANGLE: 4185,
150 | TELLO_CMD_SET_JPEG_QUALITY: 55,
151 | TELLO_CMD_TAKEOFF: 84,
152 | TELLO_CMD_LANDING: 85,
153 | TELLO_CMD_SET_ALT_LIMIT: 88,
154 | TELLO_CMD_FLIP: 92,
155 | TELLO_CMD_THROW_FLY: 93,
156 | TELLO_CMD_PALM_LANDING: 94,
157 | TELLO_CMD_PLANE_CALIBRATION: 4180,
158 | TELLO_CMD_SET_LOW_BATTERY_THRESHOLD: 4181,
159 | TELLO_CMD_SET_ATTITUDE_ANGLE: 4184,
160 | TELLO_CMD_ERROR1: 67,
161 | TELLO_CMD_ERROR2: 68,
162 | TELLO_CMD_FILE_SIZE: 98,
163 | TELLO_CMD_FILE_DATA: 99,
164 | TELLO_CMD_FILE_COMPLETE: 100,
165 | TELLO_CMD_HANDLE_IMU_ANGLE: 90,
166 | TELLO_CMD_SET_VIDEO_BIT_RATE: 32,
167 | TELLO_CMD_SET_DYN_ADJ_RATE: 33,
168 | TELLO_CMD_SET_EIS: 36,
169 | TELLO_CMD_SMART_VIDEO_START: 128,
170 | TELLO_CMD_SMART_VIDEO_STATUS: 129,
171 | TELLO_CMD_BOUNCE: 4179
172 | },
173 | log: {
174 | SEPARATOR: 85, // U symbol
175 | // Full log spec https://github.com/Kragrathea/TelloLib/blob/master/TelloLib/parsedRecSpecs.json
176 | LOGREC: {
177 | 16: {
178 | NAME: "USONIC",
179 | length: 4,
180 | fields: {
181 | H: {
182 | offset: 0,
183 | type: this.TYPE.SHORT
184 | },
185 | FLAG: {
186 | offset: 2,
187 | type: this.TYPE.BYTE
188 | },
189 | CNT: {
190 | offset: 3,
191 | type: this.TYPE.BYTE
192 | }
193 | }
194 | },
195 | 29: {
196 | NAME: "MVO",
197 | length: 80,
198 | fields: {
199 | VX: {
200 | offset: 2,
201 | type: this.TYPE.SHORT
202 | },
203 | VY: {
204 | offset: 4,
205 | type: this.TYPE.SHORT
206 | },
207 | VZ: {
208 | offset: 6,
209 | type: this.TYPE.SHORT
210 | },
211 | PX: {
212 | offset: 8,
213 | type: this.TYPE.FLOAT
214 | },
215 | PY: {
216 | offset: 12,
217 | type: this.TYPE.FLOAT
218 | },
219 | PZ: {
220 | offset: 16,
221 | type: this.TYPE.FLOAT
222 | },
223 | HEIGHT: {
224 | offset: 68,
225 | type: this.TYPE.FLOAT
226 | }
227 | }
228 | },
229 | 2048: {
230 | NAME: "IMU",
231 | length: 120,
232 | postprocess: {
233 | ANGLE: {
234 | func: false,
235 | }
236 | },
237 | fields: {
238 | ACC_X: {
239 | offset: 20,
240 | type: this.TYPE.FLOAT
241 | },
242 | ACC_Y: {
243 | offset: 24,
244 | type: this.TYPE.FLOAT
245 | },
246 | ACC_Z: {
247 | offset: 28,
248 | type: this.TYPE.FLOAT
249 | },
250 | GYRO_X: {
251 | offset: 32,
252 | type: this.TYPE.FLOAT
253 | },
254 | GYRO_Y: {
255 | offset: 36,
256 | type: this.TYPE.FLOAT
257 | },
258 | GYRO_Z: {
259 | offset: 40,
260 | type: this.TYPE.FLOAT
261 | },
262 | PRESS: {
263 | offset: 44,
264 | type: this.TYPE.FLOAT
265 | },
266 | Q_W: {
267 | offset: 48,
268 | type: this.TYPE.FLOAT
269 | },
270 | Q_X: {
271 | offset: 52,
272 | type: this.TYPE.FLOAT
273 | },
274 | Q_Y: {
275 | offset: 56,
276 | type: this.TYPE.FLOAT
277 | },
278 | Q_Z: {
279 | offset: 60,
280 | type: this.TYPE.FLOAT
281 | },
282 | ag_x: {
283 | offset: 64,
284 | type: this.TYPE.FLOAT
285 | },
286 | ag_y: {
287 | offset: 68,
288 | type: this.TYPE.FLOAT
289 | },
290 | ag_z: {
291 | offset: 72,
292 | type: this.TYPE.FLOAT
293 | },
294 | vg_x: {
295 | offset: 76,
296 | type: this.TYPE.FLOAT
297 | },
298 | vg_y: {
299 | offset: 80,
300 | type: this.TYPE.FLOAT
301 | },
302 | vg_z: {
303 | offset: 84,
304 | type: this.TYPE.FLOAT
305 | },
306 | gb_x: {
307 | offset: 88,
308 | type: this.TYPE.FLOAT
309 | },
310 | gb_y: {
311 | offset: 92,
312 | type: this.TYPE.FLOAT
313 | },
314 | gb_z: {
315 | offset: 96,
316 | type: this.TYPE.FLOAT
317 | },
318 | m_x: {
319 | offset: 100,
320 | type: this.TYPE.SHORT
321 | },
322 | m_y: {
323 | offset: 102,
324 | type: this.TYPE.SHORT
325 | },
326 | m_z: {
327 | offset: 104,
328 | type: this.TYPE.SHORT
329 | },
330 | temp_x: {
331 | offset: 106,
332 | type: this.TYPE.SHORT
333 | },
334 | temp_y: {
335 | offset: 108,
336 | type: this.TYPE.SHORT
337 | },
338 | temp_z: {
339 | offset: 110,
340 | type: this.TYPE.SHORT
341 | },
342 | }
343 | },
344 | }
345 | },
346 | smart_video: {
347 | TELLO_SMART_VIDEO_STOP: 0x00,
348 | TELLO_SMART_VIDEO_START: 0x01,
349 | TELLO_SMART_VIDEO_360: 0x01,
350 | TELLO_SMART_VIDEO_CIRCLE: 0x02,
351 | TELLO_SMART_VIDEO_UP_OUT: 0x03,
352 | },
353 | TBL_CRC16: [
354 | 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
355 | 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
356 | 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
357 | 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
358 | 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
359 | 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
360 | 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
361 | 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
362 | 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
363 | 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
364 | 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
365 | 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
366 | 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
367 | 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
368 | 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
369 | 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
370 | ],
371 | TBL_CRC8: [
372 | 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41,
373 | 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc,
374 | 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0, 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62,
375 | 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, 0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff,
376 | 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, 0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07,
377 | 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a,
378 | 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24,
379 | 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9,
380 | 0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f, 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd,
381 | 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50,
382 | 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee,
383 | 0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73,
384 | 0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b,
385 | 0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16,
386 | 0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8,
387 | 0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35,
388 | ]
389 | }
390 |
391 |
392 | this.seqID = 0;
393 |
394 | this.stickData = {
395 | roll : 0,
396 | pitch : 0,
397 | throttle : 0,
398 | yaw : 0,
399 | fast:0
400 | };
401 | /*
402 | stick data is float -1,+1
403 | MODE 2
404 |
405 | throlle (lY) pitch (Ry)
406 | | |
407 | ---+--- yaw (lX) ---+--- roll (Rx)
408 | | |
409 |
410 |
411 | */
412 | this.rcCtr = 0;
413 | this.statusCtr = 0;
414 | this.video_stream_file = false;
415 | this.failsafe = 0;
416 | this.armed = false;
417 |
418 | this._calcCRC16 = function (buf, size) {
419 | var i = 0
420 | var seed = 0x3692
421 | while (size > 0) {
422 | seed = this.tello.TBL_CRC16[(seed ^ buf[i]) & 0xff] ^ (seed >> 8)
423 | i++
424 | size--
425 | };
426 | return seed
427 | }
428 |
429 | this._calcCRC8 = function (buf, size) {
430 | var i = 0
431 | var seed = 0x77
432 | while (size > 0) {
433 | seed = this.tello.TBL_CRC8[(seed ^ buf[i]) & 0xff]
434 | i++
435 | size--
436 | }
437 | return seed
438 | }
439 |
440 | this._buildPacket = function (pacType, cmdID, seq, data, data_len) {
441 | var size = 11 + (data_len?data_len:(data?data.length:0));
442 | var bb = Buffer.alloc(size)
443 | bb.writeUInt8(0xCC,0)
444 | bb.writeUInt16LE(size << 3,1)
445 | var crc8 = this._calcCRC8([...bb], 3)
446 | bb.writeUInt8(crc8,3);
447 | bb.writeUInt8(pacType,4);
448 | bb.writeUInt16LE(cmdID,5);
449 | bb.writeUInt16LE(seq,7);
450 | if (data && !data_len) {
451 | if (Buffer.isBuffer(data)) {
452 | // TODO all should be buffer!!!
453 | data.copy(bb, 9);
454 | } else {
455 | bb.write(data,9);
456 | }
457 | } else if (data) {
458 | for (var i=0; i= 11) {
472 | var mark = bb.readUInt8(0);
473 | if (mark == 0xCC) { // start of packet
474 | var size = bb.readUInt16LE(1) >> 3;
475 | var crc8 = bb.readUInt8(3);
476 | var calcCRC8 = this._calcCRC8([...bb], 3);
477 | if (crc8 != calcCRC8) {
478 | console.log('wrong CRC8',crc8, calcCRC8); // TODO error handler
479 | }
480 | var pacType = bb.readUInt8(4);
481 | var cmdID = bb.readUInt16LE(5);
482 | this.seqID = bb.readUInt16LE(7);
483 | var dataSize = size - 11;
484 | var data = false;
485 | var crc16 = bb.readUInt16LE(size - 2);
486 | var calcCRC16 = this._calcCRC16([...bb], size - 2);
487 | if (crc16 != calcCRC16) {
488 | console.log ('wrong CRC16', crc16, calcCRC16); // TODO error handler
489 | }
490 | //console.log("package", mark, pacType, cmdID, dataSize);
491 | if (cmdID == this.tello.commands.TELLO_CMD_WIFI_SIGNAL) {
492 | this.status.wifi.signal = bb.readUInt8(9);
493 | this.status.wifi.interference = bb.readUInt8(10);
494 |
495 | } else if (cmdID == this.tello.commands.TELLO_CMD_LIGHT_STRENGTH) {
496 | this.status.light = bb.readUInt8(9);
497 |
498 | } else if (cmdID == this.tello.commands.TELLO_CMD_STATUS) {
499 | this.parseStatus(bb);
500 |
501 | } else if (cmdID == this.tello.commands.TELLO_CMD_LOG_HEADER_WRITE) { // log header, we need request log
502 | var payload = Buffer.alloc(3);
503 | payload[1]=bb[9];
504 | payload[2]=bb[10];
505 | this._sendCmd(0x50, this.tello.commands.TELLO_CMD_LOG_HEADER_WRITE, payload, 3);
506 |
507 | } else if (cmdID == this.tello.commands.TELLO_CMD_LOG_DATA_WRITE) {
508 | try {
509 | this.parseLog (bb, size);
510 | } catch (e) {
511 | console.log("Log parse error", e, bb, size);
512 | }
513 |
514 | } else {
515 | console.log("DBG", "unknown CMD", cmdID);
516 | }
517 | } else {
518 | if (mark == 0x63) {
519 | var ack = Buffer.alloc(11);
520 | ack.write('conn_ack:');
521 | ack.writeUInt16LE(this.tello.video.port,9);
522 | if (ack.toString() == bb.toString()) {
523 | cmdID = this.tello.commands.TELLO_CMD_CONN_ACK;
524 | this.initVideo();
525 | } else {
526 | console.log('wrong video port !!', ack, bb); // TODO error handler
527 | }
528 | } else {
529 | console.log ('wrong mark !! ', mark); // TODO error handler
530 | }
531 | }
532 | } else {
533 | console.log('wrong packet', bb);
534 | }
535 | return cmdID
536 | }
537 |
538 | this._b = function (b) {
539 | return parseInt(b, 10).toString(2);
540 | }
541 |
542 | this._sendCmd = function(pacType, cmdID, data, data_len) {
543 | var bb = false
544 | var pp = false
545 | var payload = false
546 | var out = false
547 | var seq = 0
548 | var len = 0
549 | var ts = new Date();
550 |
551 | if (cmdID == this.tello.commands.TELLO_CMD_CONN) {
552 | out = Buffer.alloc(11)
553 | out.write('conn_req:')
554 | out.writeUInt16LE(this.tello.video.port,9);
555 | this.seqID++
556 | } else if (cmdID == this.tello.commands.TELLO_CMD_STICK) {
557 | // https://github.com/hanyazou/TelloPy/blob/develop-0.6.0/tellopy/_internal/tello.py
558 | bb = Buffer.alloc(11)
559 |
560 | // TODO... this string manipulation is soooo slow!
561 | var tmp_roll = "00000000000"+(parseInt(1024 + 660 * this.stickData.roll) & 0x07ff).toString(2); tmp_roll=tmp_roll.substr(-11);
562 | var tmp_pitch = "00000000000"+(parseInt(1024 + 660 * this.stickData.pitch) & 0x07ff).toString(2); tmp_pitch=tmp_pitch.substr(-11);
563 | var tmp_throttle = "00000000000"+(parseInt(1024 + 660 * this.stickData.throttle) & 0x07ff).toString(2); tmp_throttle=tmp_throttle.substr(-11);
564 | var tmp_yaw = "00000000000"+(parseInt(1024 + 660 * this.stickData.yaw) & 0x07ff).toString(2); tmp_yaw=tmp_yaw.substr(-11);
565 | //var tmp_boost = "00000000000"+(parseInt(1024 + 660 * 0)).toString(2);
566 |
567 | // well... JS is 32 bit only and for this thing we need 64bit (11*4 bit + 4 more for 48bit value)... lets do this as strings
568 | // !!!!string should be 48bits (chars)!!!!
569 | var rc = "0000"+tmp_yaw.toString(2)+tmp_throttle.toString(2)+tmp_pitch.toString(2)+tmp_roll.toString(2);
570 |
571 | bb.writeUInt8(parseInt(rc.substr( -8,8),2), 0);
572 | bb.writeUInt8(parseInt(rc.substr(-16,8),2), 1);
573 | bb.writeUInt8(parseInt(rc.substr(-24,8),2), 2);
574 | bb.writeUInt8(parseInt(rc.substr(-32,8),2), 3);
575 | bb.writeUInt8(parseInt(rc.substr(-40,8),2), 4);
576 | bb.writeUInt8(parseInt(rc.substr(-48,8),2), 5);
577 |
578 | bb.writeUInt8(ts.getHours(),6);
579 | bb.writeUInt8(ts.getMinutes(),7);
580 | bb.writeUInt8(ts.getSeconds(),8);
581 | bb.writeUInt16LE(ts.getMilliseconds()*1000 & 0xffff,9);
582 | //console.log("DBG", [tmp_roll.length, tmp_pitch.length, tmp_throttle.length, tmp_yaw.length], rc, rc.length, bb);
583 | //console.log("DBG", rc, '=>', rc.substr(-48,8), rc.substr(-40,8), rc.substr(-32,8), rc.substr(-24,8), rc.substr(-16,8), rc.substr(-8,8));
584 | seq = 0
585 |
586 | } else if (cmdID == this.tello.commands.TELLO_CMD_DATE_TIME) {
587 | seq = parseInt(this.seqID)
588 | bb = Buffer.alloc(15)
589 | bb.writeUInt8(0x00,0)
590 | bb.writeUInt16LE(ts.getYear(),1)
591 | bb.writeUInt16LE(ts.getMonth(),3)
592 | bb.writeUInt16LE(ts.getDay(),5)
593 | bb.writeUInt16LE(ts.getHours(),7)
594 | bb.writeUInt16LE(ts.getMinutes(),9)
595 | bb.writeUInt16LE(ts.getSeconds(),11)
596 | bb.writeUInt16LE(ts.getMilliseconds()*1000 & 0xffff,13)
597 | this.seqID++
598 | } else if (cmdID == this.tello.commands.TELLO_CMD_REQ_VIDEO_SPS_PPS) {
599 | seq = 0
600 | } else {
601 | seq = parseInt(this.seqID)
602 | this.seqID++
603 | }
604 |
605 | if (bb) {
606 | payload = bb;
607 | } else {
608 | payload = data;
609 | }
610 |
611 | if (!out) out = this._buildPacket(pacType, cmdID, seq, payload, data_len)
612 |
613 | /*if (cmdID != this.tello.commands.TELLO_CMD_STICK) {
614 | console.log("SEND:", cmdID, bb, payload.length, out); // TODO DEBUG handler
615 | }*/
616 | this.tello_control.send(out,0,out.length, this.tello.control.port, this.tello.control.host, function(err, bytes) {
617 | // TODO error handler
618 | //if (err) throw err;
619 | //console.log('UDP message sent', bytes,err);
620 | //client.close();)
621 | });
622 | }
623 |
624 | this._cmdRX =function (msg, addr) {
625 | var data = Buffer.from(msg);
626 | var payload = false;
627 |
628 | var size = msg.length;
629 |
630 | var cmdID = this._parsePacket(data);
631 | //console.log("command = ", cmdID);
632 | var payload = data.slice(9,size);
633 |
634 | if (cmdID == this.tello.commands.TELLO_CMD_CONN_ACK) {
635 | console.log("TELLO_CMD_CONN_ACK")
636 | console.log('Connection successful!');
637 | } else if (cmdID == this.tello.commands.TELLO_CMD_DATE_TIME) {
638 | console.log("TELLO_CMD_DATE_TIME")
639 | this._sendCmd(0x50, this.tello.commands.TELLO_CMD_DATE_TIME, false)
640 | } else if (cmdID == this.tello.commands.TELLO_CMD_STATUS) {
641 | //console.log("TELLO_CMD_STATUS", this.statusCtr);
642 | if (this.statusCtr == 3) {
643 | this._sendCmd(0x60, this.tello.commands.TELLO_CMD_REQ_VIDEO_SPS_PPS, false)
644 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_VERSION_STRING, false)
645 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_SET_VIDEO_BIT_RATE, false)
646 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_ALT_LIMIT, false)
647 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_LOW_BATT_THRESHOLD, false)
648 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_ATT_ANGLE, false)
649 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_REGION, false)
650 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_SET_EV, 0x00,1);
651 | }
652 | this.statusCtr++
653 | } else if (cmdID == this.tello.commands.TELLO_CMD_VERSION_STRING) {
654 | console.log("TELLO_CMD_VERSION_STRING")
655 | if (size >= 42) {
656 | this.status.version = data.slice(10,30).toString();
657 | console.log ('Version:', this.status.version);
658 | }
659 | } else if (cmdID == this.tello.commands.TELLO_CMD_SMART_VIDEO_START) {
660 | console.log("TELLO_CMD_SMART_VIDEO_START")
661 | if (payload.length > 0) {
662 | console.log('smart video start');
663 | }
664 | } else if (cmdID == this.tello.commands.TELLO_CMD_ALT_LIMIT) {
665 | console.log("TELLO_CMD_ALT_LIMIT")
666 | if (payload.length > 0) {
667 | //payload.get_ULInt8() # 0x00
668 | this.status.altitude.max = payload.readUInt16LE(1)
669 | console.log ('Altitude limit:',this.status.altitude.max, 'meters');
670 |
671 | // TODO
672 | //if height != self.NEW_ALT_LIMIT:
673 | // print 'set new alt limit : {0:2d} meter'.format(self.NEW_ALT_LIMIT)
674 | // self._sendCmd(0x68, self.TELLO_CMD_SET_ALT_LIMIT, bytearray([self.NEW_ALT_LIMIT & 0xff, (self.NEW_ALT_LIMIT >> 8) & 0xff]));
675 | }
676 |
677 | } else if (cmdID == this.tello.commands.TELLO_CMD_SMART_VIDEO_STATUS) {
678 | console.log("TELLO_CMD_SMART_VIDEO_STATUS")
679 | if (payload.length > 0) {
680 | var resp = payload.readUInt8()
681 | var dummy = resp & 0x07
682 | var start = (resp >> 3) & 0x03
683 | var mode = (resp >> 5) & 0x07
684 | console.log ('smart video status - mode:, start:', mode, start);
685 | this._sendCmd(0x50, this.tello.commands.TELLO_CMD_SMART_VIDEO_STATUS, 0x00);
686 | }
687 | }
688 | }
689 |
690 |
691 | this._timerTask = function() {
692 | this._sendCmd(0x60, this.tello.commands.TELLO_CMD_STICK, false);
693 | this.rcCtr++;
694 |
695 | // every 0.5 seconds increase failsafe value
696 | if (this.rcCtr % 25 == 0) {
697 | this.failsafe++;
698 | }
699 |
700 | if (this.failsafe>2) {
701 | // no data more than 0.5*2 seconds, set failsafe
702 | if (this.armed == true) {
703 | console.log("FAILSAFE!!!")
704 | this.tello_cmd({cmd: { cmd: "land", value: 0 }});
705 | }
706 | // set sticks data to default (middle)
707 | this.stickData = {
708 | roll : 0,
709 | pitch : 0,
710 | throttle : 0,
711 | yaw : 0,
712 | fast:0
713 | };
714 |
715 | }
716 |
717 | //every 1sec as this task run every 20ms, so 50*20 = 1000 = 1 sec
718 | if (this.rcCtr % 50 == 0) {
719 | this._sendCmd(0x60, this.tello.commands.TELLO_CMD_REQ_VIDEO_SPS_PPS, false)
720 | }
721 | }
722 |
723 |
724 | this.tello_cmd = function (data) {
725 | // JSON with command
726 | /*
727 | {
728 | cmd: {
729 | cmd: "photo" | "ev" | "quality.photo",
730 | value: ??? | see below | ???
731 | }
732 | }
733 |
734 | EV:
735 | -3.0, -2.7, -2.3, -2.0, -1.7, -1.3, -1.0, -0.7, -0.3, 0, 0.3, 0.7, 1.0, 1.3, 1.7, 2.0, 2.3, 2.7, 3.0
736 | */
737 | //console.log("TELLO_CMD:", data);
738 | if (data.cmd.cmd == 'ev') {
739 | console.log("TELLO_CMD SET_EV:", this.EV[data.cmd.value]);
740 | var payload = Buffer.alloc(1);
741 | payload[0]=this.EV[data.cmd.value];
742 | this._sendCmd(0x48, this.tello.commands.TELLO_CMD_SET_EV, payload, 1);
743 | }
744 | if (data.cmd.cmd == 'takeoff') {
745 | this.armed = true; // TODO check logs insted of this, NOT SAFE!!!
746 | console.log("TELLO_CMD_TAKEOFF");
747 | this._sendCmd(0x68, this.tello.commands.TELLO_CMD_TAKEOFF, false);
748 | }
749 | if (data.cmd.cmd == 'land') {
750 | this.armed = false; // TODO check logs insted of this, NOT SAFE!!!
751 | console.log("TELLO_CMD_LANDING");
752 | var payload = Buffer.alloc(1);
753 | payload[0]=0;
754 | this._sendCmd(0x68, this.tello.commands.TELLO_CMD_LANDING, payload, 1);
755 | }
756 | if (data.cmd.cmd == 'stick') {
757 | this.stickData.roll = data.cmd.value.roll; if (this.stickData.roll>1 || this.stickData.roll<-1) this.stickData.roll = 0;
758 | this.stickData.pitch = data.cmd.value.pitch; if (this.stickData.pitch>1 || this.stickData.pitch<-1) this.stickData.pitch = 0;
759 | this.stickData.throttle = data.cmd.value.throttle; if (this.stickData.throttle>1 || this.stickData.throttle<-1) this.stickData.throttle = 0;
760 | this.stickData.yaw = data.cmd.value.yaw; if (this.stickData.yaw>1 || this.stickData.yaw<-1) this.stickData.yaw = 0;
761 | }
762 |
763 | if (data.cmd.cmd == 'ping') {
764 | this.failsafe = 0;
765 | }
766 | }
767 |
768 |
769 | this.init = function () {
770 |
771 | if (this.save_video_path!==false) {
772 | this.video_stream_file = fs.createWriteStream(this.save_video_path+"/"+((new Date()).getTime())+'.video.h264');
773 | }
774 |
775 | this.tello_control.on('error', function (err){
776 | console.log(`tello_control error:\n${err.stack}`);
777 | this.tello_control.close();
778 | }.bind(this));
779 |
780 | this.tello_control.on('message', function (msg, rinfo) {
781 | this._cmdRX(msg,rinfo);
782 | }.bind(this));
783 |
784 | this.tello_control.on('listening', function () {
785 | var tello_address = this.tello_control.address();
786 | console.log(`tello_control listening ${tello_address.address}:${tello_address.port}`);
787 | }.bind(this));
788 |
789 | this._sendCmd(0, this.tello.commands.TELLO_CMD_CONN);
790 |
791 | setInterval(function () { this._timerTask(); }.bind(this),20);
792 |
793 | this.tello_video.on('error', function (err) {
794 | console.log(`tello_video error:\n${err.stack}`);
795 | setTimeout(function() { this.initVideo(); }.bind(this),1000);
796 | }.bind(this));
797 |
798 | this.tello_video.on('message', function (msg, rinfo) {
799 | if (this.save_video_path!==false) {
800 | this.video_stream_file.write(msg.slice(2));
801 | }
802 | if (this.tello_video_output!=false) {
803 | this.tello_video_output(msg.slice(2)); // video feed callback
804 | }
805 | }.bind(this));
806 |
807 | this.tello_video.on('listening', function () {
808 | var tello_video_address = this.tello_video.address();
809 | console.log(`tello_video listening ${tello_video_address.address}:${tello_video_address.port}`);
810 | }.bind(this));
811 | }
812 |
813 | this.initVideo = function() {
814 | this.tello_video.bind(this.tello.video.port);
815 | }
816 |
817 | this.parseStatus = function (bb) {
818 | //flight data msg (https://github.com/hanyazou/TelloPy/blob/master/tellopy/_internal/protocol.py) 0x56
819 | // just skip first 9 bytes
820 | this.status.speed.vertical = bb.readInt16LE(9);
821 | this.status.speed.north = bb.readInt16LE(11);
822 | this.status.speed.east = bb.readInt16LE(13);
823 | this.status.speed.ground = bb.readInt16LE(15);
824 | this.status.fly_time.time = bb.readInt16LE(17);
825 |
826 | var tmp = bb.readInt8(19);
827 | this.status.state.imu.state = (tmp >> 0) & 0x1;
828 | this.status.state.pressure = (tmp >> 1) & 0x1;
829 | this.status.state.down_visual = (tmp >> 2) & 0x1;
830 | this.status.state.power = (tmp >> 3) & 0x1;
831 | this.status.state.battery.state = (tmp >> 4) & 0x1;
832 | this.status.state.gravity = (tmp >> 5) & 0x1;
833 | this.status.state.wind = (tmp >> 7) & 0x1;
834 |
835 | this.status.state.imu.calibration = bb.readInt8(20);
836 | this.status.state.battery.percentage = bb.readInt8(21);
837 | this.status.state.battery.left = bb.readInt16LE(22);
838 | this.status.state.battery.voltage = bb.readInt16LE(24);
839 |
840 | tmp = bb.readInt8(26);
841 | this.status.state.em.sky = (tmp >> 0) & 0x1;
842 | this.status.state.em.ground = (tmp >> 1) & 0x1;
843 | this.status.state.em.open = (tmp >> 2) & 0x1;
844 | this.status.state.drone_hover = (tmp >> 3) & 0x1;
845 | this.status.state.outage_recording = (tmp >> 4) & 0x1;
846 | this.status.state.battery.low = (tmp >> 5) & 0x1;
847 | this.status.state.battery.lower = (tmp >> 6) & 0x1;
848 | this.status.state.factory_mode = (tmp >> 7) & 0x1;
849 |
850 | this.status.state.fly_mode = bb.readInt8(27);
851 | this.status.state.throw_fly_timer = bb.readInt8(28);
852 | this.status.state.camera = bb.readInt8(29);
853 | this.status.state.em.state = bb.readInt8(30);
854 |
855 | tmp = bb.readInt8(31);
856 | this.status.state.front.in = (tmp >> 0) & 0x1;
857 | this.status.state.front.out = (tmp >> 1) & 0x1;
858 | this.status.state.front.lsc = (tmp >> 2) & 0x1;
859 |
860 | this.status.state.temperature_height = bb.readInt8(32);
861 | //console.log("state", this.status);
862 | }
863 |
864 | this.parseLog = function (bb, size) {
865 | // https://github.com/Kragrathea/TelloLib/blob/master/TelloLib/TelloLog.cs
866 | // https://github.com/SMerrony/tello/blob/master/flog.go
867 | var logPosition = 0;
868 | var logFieldName = "";
869 | var logObject = false;
870 | while (logPosition < (size-2)) {
871 | if (bb.readUInt8(logPosition) != this.tello.log.SEPARATOR) {
872 | logPosition++;
873 | continue;
874 | }
875 | var len = bb.readUInt8(logPosition + 1); // TODO, could be: RangeError: Index out of range
876 | if (bb.readUInt8(logPosition + 2) != 0) {
877 | logPosition++;
878 | break;
879 | }
880 | var recSpecId = bb.readUInt16LE(logPosition + 4);
881 | var xorBuf = Buffer.alloc(256);
882 | var xorValue = bb[logPosition + 6];
883 | if (typeof this.tello.log.LOGREC[recSpecId] != 'undefined') {
884 | //console.log("DBG", " found:", this.tello.log.LOGREC[recSpecId].NAME);
885 | for (var i = 0; i < len; i++) { // Decrypt payload
886 | xorBuf[i] = (bb[logPosition + i] ^ xorValue);
887 | }
888 | var baseOffset = 10;
889 | for (var i in this.tello.log.LOGREC[recSpecId].fields) { if (typeof this.tello.log.LOGREC[recSpecId].fields[i] == 'object') {
890 | logObject = this.tello.log.LOGREC[recSpecId].fields[i];
891 | logFieldName = this.tello.log.LOGREC[recSpecId].NAME+"."+i;
892 | switch (logObject.type) {
893 | case this.TYPE.FLOAT:
894 | this.status[logFieldName] = xorBuf.readFloatLE(baseOffset + logObject.offset);
895 | break;
896 | case this.TYPE.SHORT:
897 | this.status[logFieldName] = xorBuf.readInt16LE(baseOffset + logObject.offset);
898 | break;
899 | case this.TYPE.BYTE:
900 | this.status[logFieldName] = xorBuf[baseOffset + logObject.offset];
901 | break;
902 | default:
903 | console.log("DBG", "unknown type", logObject.type);
904 | }
905 | }}
906 | if (typeof this.tello.log.LOGREC[recSpecId].postprocess == 'object') {
907 | for (var i in this.tello.log.LOGREC[recSpecId].postprocess) {
908 | if (typeof this.tello.log.LOGREC[recSpecId].postprocess[i].func == 'function') {
909 | this.tello.log.LOGREC[recSpecId].postprocess[i].func();
910 | }
911 | }
912 | }
913 | //console.log("STATUS:", this.status);
914 | this.sendLog();
915 | } else {
916 | //console.log("DBG", "unknown log type", recSpecId, "datasize = ", dataSize);
917 | }
918 | logPosition += len;
919 | }
920 | }
921 | this.sendLog = function () {
922 | // prepare telemetry output based on configuration
923 | var output = {};
924 | if (this.tello_telemetry_config !== false) {
925 | for (var i in this.tello_telemetry_config) {
926 | if (typeof this.status[this.tello_telemetry_config[i]] != 'undefined') {
927 | output[i] = this.status[this.tello_telemetry_config[i]];
928 | }
929 | }
930 | this.tello_telemetry_output(JSON.stringify(output));
931 | }
932 | }
933 |
934 | this.QtoDeg = function () {
935 | // https://github.com/SMerrony/tello/blob/master/flog.go
936 | try {
937 | var sqX = Math.pow(this.status['IMU.Q_X'],2);
938 | var sqY = Math.pow(this.status['IMU.Q_Y'],2);
939 | var sqZ = Math.pow(this.status['IMU.Q_Z'],2);
940 |
941 | var sinR = 2 * (this.status['IMU.Q_W'] * this.status['IMU.Q_X'] + this.status['IMU.Q_Y'] * this.status['IMU.Q_Z']);
942 | var cosR = 1 - 2 * (sqX + sqY);
943 | this.status['IMU.ANGLE_ROLL'] = parseFloat(Math.atan2(sinR, cosR) / this.DEG);
944 |
945 | var sinP = 2 * (this.status['IMU.Q_W'] * this.status['IMU.Q_Y'] - this.status['IMU.Q_Z'] * this.status['IMU.Q_X']);
946 | if (sinP > 1) {
947 | sinP = 1;
948 | } else if (sinP < -1) {
949 | sinP = -1;
950 | }
951 | this.status['IMU.ANGLE_PITCH'] = parseFloat(Math.asin(sinP) / this.DEG);
952 |
953 | var sinY = 2 * (this.status['IMU.Q_W'] * this.status['IMU.Q_Z'] + this.status['IMU.Q_X'] * this.status['IMU.Q_Y']);
954 | var cosY = 1 - 2 * (sqY + sqZ);
955 | this.status['IMU.ANGLE_YAW'] = parseFloat(Math.atan2(sinY, cosY) / this.DEG);
956 | } catch (e) {
957 | console.log("ERROR","QtoDeg", e);
958 | }
959 | }
960 | this.tello.log.LOGREC[2048].postprocess.ANGLE.func = function () { this.QtoDeg(); }.bind(this);
961 | }
962 |
963 | module.exports = nodetello;
964 |
--------------------------------------------------------------------------------
/public_html/Decoder.js:
--------------------------------------------------------------------------------
1 | // universal module definition
2 | (function (root, factory) {
3 | if (typeof define === 'function' && define.amd) {
4 | // AMD. Register as an anonymous module.
5 | define([], factory);
6 | } else if (typeof exports === 'object') {
7 | // Node. Does not work with strict CommonJS, but
8 | // only CommonJS-like environments that support module.exports,
9 | // like Node.
10 | module.exports = factory();
11 | } else {
12 | // Browser globals (root is window)
13 | root.Decoder = factory();
14 | }
15 | }(this, function () {
16 |
17 | var global;
18 |
19 | function initglobal(){
20 | global = this;
21 | if (!global){
22 | if (typeof window != "undefined"){
23 | global = window;
24 | }else if (typeof self != "undefined"){
25 | global = self;
26 | };
27 | };
28 | };
29 | initglobal();
30 |
31 |
32 | function error(message) {
33 | console.error(message);
34 | console.trace();
35 | };
36 |
37 |
38 | function assert(condition, message) {
39 | if (!condition) {
40 | error(message);
41 | };
42 | };
43 |
44 |
45 |
46 |
47 | var getModule = function(par_broadwayOnHeadersDecoded, par_broadwayOnPictureDecoded){
48 |
49 |
50 | /*var ModuleX = {
51 | 'print': function(text) { console.log('stdout: ' + text); },
52 | 'printErr': function(text) { console.log('stderr: ' + text); }
53 | };*/
54 |
55 |
56 | /*
57 |
58 | The reason why this is all packed into one file is that this file can also function as worker.
59 | you can integrate the file into your build system and provide the original file to be loaded into a worker.
60 |
61 | */
62 |
63 | //var Module = (function(){
64 |
65 |
66 | var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}Module["arguments"]=[];Module["thisProgram"]="./this.program";Module["quit"]=(function(status,toThrow){throw toThrow});Module["preRun"]=[];Module["postRun"]=[];var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;if(Module["ENVIRONMENT"]){if(Module["ENVIRONMENT"]==="WEB"){ENVIRONMENT_IS_WEB=true}else if(Module["ENVIRONMENT"]==="WORKER"){ENVIRONMENT_IS_WORKER=true}else if(Module["ENVIRONMENT"]==="NODE"){ENVIRONMENT_IS_NODE=true}else if(Module["ENVIRONMENT"]==="SHELL"){ENVIRONMENT_IS_SHELL=true}else{throw new Error("Module['ENVIRONMENT'] value is not valid. must be one of: WEB|WORKER|NODE|SHELL.")}}else{ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof null==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER}if(ENVIRONMENT_IS_NODE){var nodeFS;var nodePath;Module["read"]=function shell_read(filename,binary){var ret;if(!nodeFS)nodeFS=(null)("fs");if(!nodePath)nodePath=(null)("path");filename=nodePath["normalize"](filename);ret=nodeFS["readFileSync"](filename);return binary?ret:ret.toString()};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}Module["arguments"]=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",(function(ex){if(!(ex instanceof ExitStatus)){throw ex}}));process["on"]("unhandledRejection",(function(reason,p){process["exit"](1)}));Module["inspect"]=(function(){return"[Emscripten Module object]"})}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){Module["read"]=function shell_read(f){return read(f)}}Module["readBinary"]=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof quit==="function"){Module["quit"]=(function(status,toThrow){quit(status)})}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){Module["readBinary"]=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}Module["readAsync"]=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)};Module["setWindowTitle"]=(function(title){document.title=title})}else{throw new Error("not compiled for this environment")}Module["print"]=typeof console!=="undefined"?console.log.bind(console):typeof print!=="undefined"?print:null;Module["printErr"]=typeof printErr!=="undefined"?printErr:typeof console!=="undefined"&&console.warn.bind(console)||Module["print"];Module.print=Module["print"];Module.printErr=Module["printErr"];for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=undefined;var STACK_ALIGN=16;function staticAlloc(size){assert(!staticSealed);var ret=STATICTOP;STATICTOP=STATICTOP+size+15&-16;return ret}function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;var ret=size=Math.ceil(size/factor)*factor;return ret}var asm2wasmImports={"f64-rem":(function(x,y){return x%y}),"debugger":(function(){debugger})};var functionPointers=new Array(0);var GLOBAL_BASE=1024;var ABORT=0;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}function Pointer_stringify(ptr,length){if(length===0||!ptr)return"";var hasUtf=0;var t;var i=0;while(1){t=HEAPU8[ptr+i>>0];hasUtf|=t;if(t==0&&!length)break;i++;if(length&&i==length)break}if(!length)length=i;var ret="";if(hasUtf<128){var MAX_CHUNK=1024;var curr;while(length>0){curr=String.fromCharCode.apply(String,HEAPU8.subarray(ptr,ptr+Math.min(length,MAX_CHUNK)));ret=ret?ret+curr:curr;ptr+=MAX_CHUNK;length-=MAX_CHUNK}return ret}return UTF8ToString(ptr)}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(u8Array,idx){var endPtr=idx;while(u8Array[endPtr])++endPtr;if(endPtr-idx>16&&u8Array.subarray&&UTF8Decoder){return UTF8Decoder.decode(u8Array.subarray(idx,endPtr))}else{var u0,u1,u2,u3,u4,u5;var str="";while(1){u0=u8Array[idx++];if(!u0)return str;if(!(u0&128)){str+=String.fromCharCode(u0);continue}u1=u8Array[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}u2=u8Array[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u3=u8Array[idx++]&63;if((u0&248)==240){u0=(u0&7)<<18|u1<<12|u2<<6|u3}else{u4=u8Array[idx++]&63;if((u0&252)==248){u0=(u0&3)<<24|u1<<18|u2<<12|u3<<6|u4}else{u5=u8Array[idx++]&63;u0=(u0&1)<<30|u1<<24|u2<<18|u3<<12|u4<<6|u5}}}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}}}function UTF8ToString(ptr){return UTF8ArrayToString(HEAPU8,ptr)}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;var WASM_PAGE_SIZE=65536;var ASMJS_PAGE_SIZE=16777216;function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBuffer(buf){Module["buffer"]=buffer=buf}function updateGlobalBufferViews(){Module["HEAP8"]=HEAP8=new Int8Array(buffer);Module["HEAP16"]=HEAP16=new Int16Array(buffer);Module["HEAP32"]=HEAP32=new Int32Array(buffer);Module["HEAPU8"]=HEAPU8=new Uint8Array(buffer);Module["HEAPU16"]=HEAPU16=new Uint16Array(buffer);Module["HEAPU32"]=HEAPU32=new Uint32Array(buffer);Module["HEAPF32"]=HEAPF32=new Float32Array(buffer);Module["HEAPF64"]=HEAPF64=new Float64Array(buffer)}var STATIC_BASE,STATICTOP,staticSealed;var STACK_BASE,STACKTOP,STACK_MAX;var DYNAMIC_BASE,DYNAMICTOP_PTR;STATIC_BASE=STATICTOP=STACK_BASE=STACKTOP=STACK_MAX=DYNAMIC_BASE=DYNAMICTOP_PTR=0;staticSealed=false;function abortOnCannotGrowMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+TOTAL_MEMORY+", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ")}function enlargeMemory(){abortOnCannotGrowMemory()}var TOTAL_STACK=Module["TOTAL_STACK"]||5242880;var TOTAL_MEMORY=Module["TOTAL_MEMORY"]||52428800;if(TOTAL_MEMORY0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Module["dynCall_v"](func)}else{Module["dynCall_vi"](func,callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){if(runtimeInitialized)return;runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__);runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var Math_abs=Math.abs;var Math_cos=Math.cos;var Math_sin=Math.sin;var Math_tan=Math.tan;var Math_acos=Math.acos;var Math_asin=Math.asin;var Math_atan=Math.atan;var Math_atan2=Math.atan2;var Math_exp=Math.exp;var Math_log=Math.log;var Math_sqrt=Math.sqrt;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_pow=Math.pow;var Math_imul=Math.imul;var Math_fround=Math.fround;var Math_round=Math.round;var Math_min=Math.min;var Math_max=Math.max;var Math_clz32=Math.clz32;var Math_trunc=Math.trunc;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return String.prototype.startsWith?filename.startsWith(dataURIPrefix):filename.indexOf(dataURIPrefix)===0}function integrateWasmJS(){var wasmTextFile="avc.wast";var wasmBinaryFile="avc.wasm";var asmjsCodeFile="avc.temp.asm.js";if(typeof Module["locateFile"]==="function"){if(!isDataURI(wasmTextFile)){wasmTextFile=Module["locateFile"](wasmTextFile)}if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=Module["locateFile"](wasmBinaryFile)}if(!isDataURI(asmjsCodeFile)){asmjsCodeFile=Module["locateFile"](asmjsCodeFile)}}var wasmPageSize=64*1024;var info={"global":null,"env":null,"asm2wasm":asm2wasmImports,"parent":Module};var exports=null;function mergeMemory(newBuffer){var oldBuffer=Module["buffer"];if(newBuffer.byteLength>2];return ret}),getStr:(function(){var ret=Pointer_stringify(SYSCALLS.get());return ret}),get64:(function(){var low=SYSCALLS.get(),high=SYSCALLS.get();if(low>=0)assert(high===0);else assert(high===-1);return low}),getZero:(function(){assert(SYSCALLS.get()===0)})};function ___syscall140(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),offset_high=SYSCALLS.get(),offset_low=SYSCALLS.get(),result=SYSCALLS.get(),whence=SYSCALLS.get();var offset=offset_low;FS.llseek(stream,offset,whence);HEAP32[result>>2]=stream.position;if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall146(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.get(),iov=SYSCALLS.get(),iovcnt=SYSCALLS.get();var ret=0;if(!___syscall146.buffers){___syscall146.buffers=[null,[],[]];___syscall146.printChar=(function(stream,curr){var buffer=___syscall146.buffers[stream];assert(buffer);if(curr===0||curr===10){(stream===1?Module["print"]:Module["printErr"])(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}})}for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;j>2]=value;return value}DYNAMICTOP_PTR=staticAlloc(4);STACK_BASE=STACKTOP=alignMemory(STATICTOP);STACK_MAX=STACK_BASE+TOTAL_STACK;DYNAMIC_BASE=alignMemory(STACK_MAX);HEAP32[DYNAMICTOP_PTR>>2]=DYNAMIC_BASE;staticSealed=true;Module["wasmTableSize"]=10;Module["wasmMaxTableSize"]=10;Module.asmGlobalArg={};Module.asmLibraryArg={"abort":abort,"enlargeMemory":enlargeMemory,"getTotalMemory":getTotalMemory,"abortOnCannotGrowMemory":abortOnCannotGrowMemory,"___setErrNo":___setErrNo,"___syscall140":___syscall140,"___syscall146":___syscall146,"___syscall54":___syscall54,"___syscall6":___syscall6,"_broadwayOnHeadersDecoded":_broadwayOnHeadersDecoded,"_broadwayOnPictureDecoded":_broadwayOnPictureDecoded,"_emscripten_memcpy_big":_emscripten_memcpy_big,"DYNAMICTOP_PTR":DYNAMICTOP_PTR,"STACKTOP":STACKTOP};var asm=Module["asm"](Module.asmGlobalArg,Module.asmLibraryArg,buffer);Module["asm"]=asm;var _broadwayCreateStream=Module["_broadwayCreateStream"]=(function(){return Module["asm"]["_broadwayCreateStream"].apply(null,arguments)});var _broadwayExit=Module["_broadwayExit"]=(function(){return Module["asm"]["_broadwayExit"].apply(null,arguments)});var _broadwayGetMajorVersion=Module["_broadwayGetMajorVersion"]=(function(){return Module["asm"]["_broadwayGetMajorVersion"].apply(null,arguments)});var _broadwayGetMinorVersion=Module["_broadwayGetMinorVersion"]=(function(){return Module["asm"]["_broadwayGetMinorVersion"].apply(null,arguments)});var _broadwayInit=Module["_broadwayInit"]=(function(){return Module["asm"]["_broadwayInit"].apply(null,arguments)});var _broadwayPlayStream=Module["_broadwayPlayStream"]=(function(){return Module["asm"]["_broadwayPlayStream"].apply(null,arguments)});Module["asm"]=asm;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}ExitStatus.prototype=new Error;ExitStatus.prototype.constructor=ExitStatus;var initialStackTop;dependenciesFulfilled=function runCaller(){if(!Module["calledRun"])run();if(!Module["calledRun"])dependenciesFulfilled=runCaller};function run(args){args=args||Module["arguments"];if(runDependencies>0){return}preRun();if(runDependencies>0)return;if(Module["calledRun"])return;function doRun(){if(Module["calledRun"])return;Module["calledRun"]=true;if(ABORT)return;ensureInitRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout((function(){setTimeout((function(){Module["setStatus"]("")}),1);doRun()}),1)}else{doRun()}}Module["run"]=run;function exit(status,implicit){if(implicit&&Module["noExitRuntime"]&&status===0){return}if(Module["noExitRuntime"]){}else{ABORT=true;EXITSTATUS=status;STACKTOP=initialStackTop;exitRuntime();if(Module["onExit"])Module["onExit"](status)}if(ENVIRONMENT_IS_NODE){process["exit"](status)}Module["quit"](status,new ExitStatus(status))}Module["exit"]=exit;function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}if(what!==undefined){Module.print(what);Module.printErr(what);what=JSON.stringify(what)}else{what=""}ABORT=true;EXITSTATUS=1;throw"abort("+what+"). Build with -s ASSERTIONS=1 for more info."}Module["abort"]=abort;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}Module["noExitRuntime"]=true;run()
67 |
68 |
69 |
70 | // return Module;
71 | //})();
72 |
73 | var resultModule;
74 | if (typeof global !== "undefined"){
75 | if (global.Module){
76 | resultModule = global.Module;
77 | };
78 | };
79 | if (typeof Module != "undefined"){
80 | resultModule = Module;
81 | };
82 |
83 | resultModule._broadwayOnHeadersDecoded = par_broadwayOnHeadersDecoded;
84 | resultModule._broadwayOnPictureDecoded = par_broadwayOnPictureDecoded;
85 |
86 | var moduleIsReady = false;
87 | var cbFun;
88 | var moduleReady = function(){
89 | moduleIsReady = true;
90 | if (cbFun){
91 | cbFun(resultModule);
92 | }
93 | };
94 |
95 | resultModule.onRuntimeInitialized = function(){
96 | moduleReady(resultModule);
97 | };
98 | return function(callback){
99 | if (moduleIsReady){
100 | callback(resultModule);
101 | }else{
102 | cbFun = callback;
103 | };
104 | };
105 | };
106 |
107 | return (function(){
108 | "use strict";
109 |
110 |
111 | var nowValue = function(){
112 | return (new Date()).getTime();
113 | };
114 |
115 | if (typeof performance != "undefined"){
116 | if (performance.now){
117 | nowValue = function(){
118 | return performance.now();
119 | };
120 | };
121 | };
122 |
123 |
124 | var Decoder = function(parOptions){
125 | this.options = parOptions || {};
126 |
127 | this.now = nowValue;
128 |
129 | var asmInstance;
130 |
131 | var fakeWindow = {
132 | };
133 |
134 | var toU8Array;
135 | var toU32Array;
136 |
137 | var onPicFun = function ($buffer, width, height) {
138 | var buffer = this.pictureBuffers[$buffer];
139 | if (!buffer) {
140 | buffer = this.pictureBuffers[$buffer] = toU8Array($buffer, (width * height * 3) / 2);
141 | };
142 |
143 | var infos;
144 | var doInfo = false;
145 | if (this.infoAr.length){
146 | doInfo = true;
147 | infos = this.infoAr;
148 | };
149 | this.infoAr = [];
150 |
151 | if (this.options.rgb){
152 | if (!asmInstance){
153 | asmInstance = getAsm(width, height);
154 | };
155 | asmInstance.inp.set(buffer);
156 | asmInstance.doit();
157 |
158 | var copyU8 = new Uint8Array(asmInstance.outSize);
159 | copyU8.set( asmInstance.out );
160 |
161 | if (doInfo){
162 | infos[0].finishDecoding = nowValue();
163 | };
164 |
165 | this.onPictureDecoded(copyU8, width, height, infos);
166 | return;
167 |
168 | };
169 |
170 | if (doInfo){
171 | infos[0].finishDecoding = nowValue();
172 | };
173 | this.onPictureDecoded(buffer, width, height, infos);
174 | }.bind(this);
175 |
176 | var ignore = false;
177 |
178 | if (this.options.sliceMode){
179 | onPicFun = function ($buffer, width, height, $sliceInfo) {
180 | if (ignore){
181 | return;
182 | };
183 | var buffer = this.pictureBuffers[$buffer];
184 | if (!buffer) {
185 | buffer = this.pictureBuffers[$buffer] = toU8Array($buffer, (width * height * 3) / 2);
186 | };
187 | var sliceInfo = this.pictureBuffers[$sliceInfo];
188 | if (!sliceInfo) {
189 | sliceInfo = this.pictureBuffers[$sliceInfo] = toU32Array($sliceInfo, 18);
190 | };
191 |
192 | var infos;
193 | var doInfo = false;
194 | if (this.infoAr.length){
195 | doInfo = true;
196 | infos = this.infoAr;
197 | };
198 | this.infoAr = [];
199 |
200 | /*if (this.options.rgb){
201 |
202 | no rgb in slice mode
203 |
204 | };*/
205 |
206 | infos[0].finishDecoding = nowValue();
207 | var sliceInfoAr = [];
208 | for (var i = 0; i < 20; ++i){
209 | sliceInfoAr.push(sliceInfo[i]);
210 | };
211 | infos[0].sliceInfoAr = sliceInfoAr;
212 |
213 | this.onPictureDecoded(buffer, width, height, infos);
214 | }.bind(this);
215 | };
216 |
217 | var ModuleCallback = getModule.apply(fakeWindow, [function () {
218 | }, onPicFun]);
219 |
220 |
221 | var MAX_STREAM_BUFFER_LENGTH = 1024 * 1024;
222 |
223 | var instance = this;
224 | this.onPictureDecoded = function (buffer, width, height, infos) {
225 |
226 | };
227 |
228 | this.onDecoderReady = function(){};
229 |
230 | var bufferedCalls = [];
231 | this.decode = function decode(typedAr, parInfo, copyDoneFun) {
232 | bufferedCalls.push([typedAr, parInfo, copyDoneFun]);
233 | };
234 |
235 | ModuleCallback(function(Module){
236 | var HEAP8 = Module.HEAP8;
237 | var HEAPU8 = Module.HEAPU8;
238 | var HEAP16 = Module.HEAP16;
239 | var HEAP32 = Module.HEAP32;
240 | // from old constructor
241 | Module._broadwayInit();
242 |
243 | /**
244 | * Creates a typed array from a HEAP8 pointer.
245 | */
246 | toU8Array = function(ptr, length) {
247 | return HEAPU8.subarray(ptr, ptr + length);
248 | };
249 | toU32Array = function(ptr, length) {
250 | //var tmp = HEAPU8.subarray(ptr, ptr + (length * 4));
251 | return new Uint32Array(HEAPU8.buffer, ptr, length);
252 | };
253 | instance.streamBuffer = toU8Array(Module._broadwayCreateStream(MAX_STREAM_BUFFER_LENGTH), MAX_STREAM_BUFFER_LENGTH);
254 | instance.pictureBuffers = {};
255 | // collect extra infos that are provided with the nal units
256 | instance.infoAr = [];
257 |
258 | /**
259 | * Decodes a stream buffer. This may be one single (unframed) NAL unit without the
260 | * start code, or a sequence of NAL units with framing start code prefixes. This
261 | * function overwrites stream buffer allocated by the codec with the supplied buffer.
262 | */
263 |
264 | var sliceNum = 0;
265 | if (instance.options.sliceMode){
266 | sliceNum = instance.options.sliceNum;
267 |
268 | instance.decode = function decode(typedAr, parInfo, copyDoneFun) {
269 | instance.infoAr.push(parInfo);
270 | parInfo.startDecoding = nowValue();
271 | var nals = parInfo.nals;
272 | var i;
273 | if (!nals){
274 | nals = [];
275 | parInfo.nals = nals;
276 | var l = typedAr.length;
277 | var foundSomething = false;
278 | var lastFound = 0;
279 | var lastStart = 0;
280 | for (i = 0; i < l; ++i){
281 | if (typedAr[i] === 1){
282 | if (
283 | typedAr[i - 1] === 0 &&
284 | typedAr[i - 2] === 0
285 | ){
286 | var startPos = i - 2;
287 | if (typedAr[i - 3] === 0){
288 | startPos = i - 3;
289 | };
290 | // its a nal;
291 | if (foundSomething){
292 | nals.push({
293 | offset: lastFound,
294 | end: startPos,
295 | type: typedAr[lastStart] & 31
296 | });
297 | };
298 | lastFound = startPos;
299 | lastStart = startPos + 3;
300 | if (typedAr[i - 3] === 0){
301 | lastStart = startPos + 4;
302 | };
303 | foundSomething = true;
304 | };
305 | };
306 | };
307 | if (foundSomething){
308 | nals.push({
309 | offset: lastFound,
310 | end: i,
311 | type: typedAr[lastStart] & 31
312 | });
313 | };
314 | };
315 |
316 | var currentSlice = 0;
317 | var playAr;
318 | var offset = 0;
319 | for (i = 0; i < nals.length; ++i){
320 | if (nals[i].type === 1 || nals[i].type === 5){
321 | if (currentSlice === sliceNum){
322 | playAr = typedAr.subarray(nals[i].offset, nals[i].end);
323 | instance.streamBuffer[offset] = 0;
324 | offset += 1;
325 | instance.streamBuffer.set(playAr, offset);
326 | offset += playAr.length;
327 | };
328 | currentSlice += 1;
329 | }else{
330 | playAr = typedAr.subarray(nals[i].offset, nals[i].end);
331 | instance.streamBuffer[offset] = 0;
332 | offset += 1;
333 | instance.streamBuffer.set(playAr, offset);
334 | offset += playAr.length;
335 | Module._broadwayPlayStream(offset);
336 | offset = 0;
337 | };
338 | };
339 | copyDoneFun();
340 | Module._broadwayPlayStream(offset);
341 | };
342 |
343 | }else{
344 | instance.decode = function decode(typedAr, parInfo) {
345 | // console.info("Decoding: " + buffer.length);
346 | // collect infos
347 | if (parInfo){
348 | instance.infoAr.push(parInfo);
349 | parInfo.startDecoding = nowValue();
350 | };
351 |
352 | instance.streamBuffer.set(typedAr);
353 | Module._broadwayPlayStream(typedAr.length);
354 | };
355 | };
356 |
357 | if (bufferedCalls.length){
358 | var bi = 0;
359 | for (bi = 0; bi < bufferedCalls.length; ++bi){
360 | instance.decode(bufferedCalls[bi][0], bufferedCalls[bi][1], bufferedCalls[bi][2]);
361 | };
362 | bufferedCalls = [];
363 | };
364 |
365 | instance.onDecoderReady(instance);
366 |
367 | });
368 |
369 |
370 | };
371 |
372 |
373 | Decoder.prototype = {
374 |
375 | };
376 |
377 |
378 |
379 |
380 | /*
381 |
382 | asm.js implementation of a yuv to rgb convertor
383 | provided by @soliton4
384 |
385 | based on
386 | http://www.wordsaretoys.com/2013/10/18/making-yuv-conversion-a-little-faster/
387 |
388 | */
389 |
390 |
391 | // factory to create asm.js yuv -> rgb convertor for a given resolution
392 | var asmInstances = {};
393 | var getAsm = function(parWidth, parHeight){
394 | var idStr = "" + parWidth + "x" + parHeight;
395 | if (asmInstances[idStr]){
396 | return asmInstances[idStr];
397 | };
398 |
399 | var lumaSize = parWidth * parHeight;
400 | var chromaSize = (lumaSize|0) >> 2;
401 |
402 | var inpSize = lumaSize + chromaSize + chromaSize;
403 | var outSize = parWidth * parHeight * 4;
404 | var cacheSize = Math.pow(2, 24) * 4;
405 | var size = inpSize + outSize + cacheSize;
406 |
407 | var chunkSize = Math.pow(2, 24);
408 | var heapSize = chunkSize;
409 | while (heapSize < size){
410 | heapSize += chunkSize;
411 | };
412 | var heap = new ArrayBuffer(heapSize);
413 |
414 | var res = asmFactory(global, {}, heap);
415 | res.init(parWidth, parHeight);
416 | asmInstances[idStr] = res;
417 |
418 | res.heap = heap;
419 | res.out = new Uint8Array(heap, 0, outSize);
420 | res.inp = new Uint8Array(heap, outSize, inpSize);
421 | res.outSize = outSize;
422 |
423 | return res;
424 | };
425 |
426 |
427 | function asmFactory(stdlib, foreign, heap) {
428 | "use asm";
429 |
430 | var imul = stdlib.Math.imul;
431 | var min = stdlib.Math.min;
432 | var max = stdlib.Math.max;
433 | var pow = stdlib.Math.pow;
434 | var out = new stdlib.Uint8Array(heap);
435 | var out32 = new stdlib.Uint32Array(heap);
436 | var inp = new stdlib.Uint8Array(heap);
437 | var mem = new stdlib.Uint8Array(heap);
438 | var mem32 = new stdlib.Uint32Array(heap);
439 |
440 | // for double algo
441 | /*var vt = 1.370705;
442 | var gt = 0.698001;
443 | var gt2 = 0.337633;
444 | var bt = 1.732446;*/
445 |
446 | var width = 0;
447 | var height = 0;
448 | var lumaSize = 0;
449 | var chromaSize = 0;
450 | var inpSize = 0;
451 | var outSize = 0;
452 |
453 | var inpStart = 0;
454 | var outStart = 0;
455 |
456 | var widthFour = 0;
457 |
458 | var cacheStart = 0;
459 |
460 |
461 | function init(parWidth, parHeight){
462 | parWidth = parWidth|0;
463 | parHeight = parHeight|0;
464 |
465 | var i = 0;
466 | var s = 0;
467 |
468 | width = parWidth;
469 | widthFour = imul(parWidth, 4)|0;
470 | height = parHeight;
471 | lumaSize = imul(width|0, height|0)|0;
472 | chromaSize = (lumaSize|0) >> 2;
473 | outSize = imul(imul(width, height)|0, 4)|0;
474 | inpSize = ((lumaSize + chromaSize)|0 + chromaSize)|0;
475 |
476 | outStart = 0;
477 | inpStart = (outStart + outSize)|0;
478 | cacheStart = (inpStart + inpSize)|0;
479 |
480 | // initializing memory (to be on the safe side)
481 | s = ~~(+pow(+2, +24));
482 | s = imul(s, 4)|0;
483 |
484 | for (i = 0|0; ((i|0) < (s|0))|0; i = (i + 4)|0){
485 | mem32[((cacheStart + i)|0) >> 2] = 0;
486 | };
487 | };
488 |
489 | function doit(){
490 | var ystart = 0;
491 | var ustart = 0;
492 | var vstart = 0;
493 |
494 | var y = 0;
495 | var yn = 0;
496 | var u = 0;
497 | var v = 0;
498 |
499 | var o = 0;
500 |
501 | var line = 0;
502 | var col = 0;
503 |
504 | var usave = 0;
505 | var vsave = 0;
506 |
507 | var ostart = 0;
508 | var cacheAdr = 0;
509 |
510 | ostart = outStart|0;
511 |
512 | ystart = inpStart|0;
513 | ustart = (ystart + lumaSize|0)|0;
514 | vstart = (ustart + chromaSize)|0;
515 |
516 | for (line = 0; (line|0) < (height|0); line = (line + 2)|0){
517 | usave = ustart;
518 | vsave = vstart;
519 | for (col = 0; (col|0) < (width|0); col = (col + 2)|0){
520 | y = inp[ystart >> 0]|0;
521 | yn = inp[((ystart + width)|0) >> 0]|0;
522 |
523 | u = inp[ustart >> 0]|0;
524 | v = inp[vstart >> 0]|0;
525 |
526 | cacheAdr = (((((y << 16)|0) + ((u << 8)|0))|0) + v)|0;
527 | o = mem32[((cacheStart + cacheAdr)|0) >> 2]|0;
528 | if (o){}else{
529 | o = yuv2rgbcalc(y,u,v)|0;
530 | mem32[((cacheStart + cacheAdr)|0) >> 2] = o|0;
531 | };
532 | mem32[ostart >> 2] = o;
533 |
534 | cacheAdr = (((((yn << 16)|0) + ((u << 8)|0))|0) + v)|0;
535 | o = mem32[((cacheStart + cacheAdr)|0) >> 2]|0;
536 | if (o){}else{
537 | o = yuv2rgbcalc(yn,u,v)|0;
538 | mem32[((cacheStart + cacheAdr)|0) >> 2] = o|0;
539 | };
540 | mem32[((ostart + widthFour)|0) >> 2] = o;
541 |
542 | //yuv2rgb5(y, u, v, ostart);
543 | //yuv2rgb5(yn, u, v, (ostart + widthFour)|0);
544 | ostart = (ostart + 4)|0;
545 |
546 | // next step only for y. u and v stay the same
547 | ystart = (ystart + 1)|0;
548 | y = inp[ystart >> 0]|0;
549 | yn = inp[((ystart + width)|0) >> 0]|0;
550 |
551 | //yuv2rgb5(y, u, v, ostart);
552 | cacheAdr = (((((y << 16)|0) + ((u << 8)|0))|0) + v)|0;
553 | o = mem32[((cacheStart + cacheAdr)|0) >> 2]|0;
554 | if (o){}else{
555 | o = yuv2rgbcalc(y,u,v)|0;
556 | mem32[((cacheStart + cacheAdr)|0) >> 2] = o|0;
557 | };
558 | mem32[ostart >> 2] = o;
559 |
560 | //yuv2rgb5(yn, u, v, (ostart + widthFour)|0);
561 | cacheAdr = (((((yn << 16)|0) + ((u << 8)|0))|0) + v)|0;
562 | o = mem32[((cacheStart + cacheAdr)|0) >> 2]|0;
563 | if (o){}else{
564 | o = yuv2rgbcalc(yn,u,v)|0;
565 | mem32[((cacheStart + cacheAdr)|0) >> 2] = o|0;
566 | };
567 | mem32[((ostart + widthFour)|0) >> 2] = o;
568 | ostart = (ostart + 4)|0;
569 |
570 | //all positions inc 1
571 |
572 | ystart = (ystart + 1)|0;
573 | ustart = (ustart + 1)|0;
574 | vstart = (vstart + 1)|0;
575 | };
576 | ostart = (ostart + widthFour)|0;
577 | ystart = (ystart + width)|0;
578 |
579 | };
580 |
581 | };
582 |
583 | function yuv2rgbcalc(y, u, v){
584 | y = y|0;
585 | u = u|0;
586 | v = v|0;
587 |
588 | var r = 0;
589 | var g = 0;
590 | var b = 0;
591 |
592 | var o = 0;
593 |
594 | var a0 = 0;
595 | var a1 = 0;
596 | var a2 = 0;
597 | var a3 = 0;
598 | var a4 = 0;
599 |
600 | a0 = imul(1192, (y - 16)|0)|0;
601 | a1 = imul(1634, (v - 128)|0)|0;
602 | a2 = imul(832, (v - 128)|0)|0;
603 | a3 = imul(400, (u - 128)|0)|0;
604 | a4 = imul(2066, (u - 128)|0)|0;
605 |
606 | r = (((a0 + a1)|0) >> 10)|0;
607 | g = (((((a0 - a2)|0) - a3)|0) >> 10)|0;
608 | b = (((a0 + a4)|0) >> 10)|0;
609 |
610 | if ((((r & 255)|0) != (r|0))|0){
611 | r = min(255, max(0, r|0)|0)|0;
612 | };
613 | if ((((g & 255)|0) != (g|0))|0){
614 | g = min(255, max(0, g|0)|0)|0;
615 | };
616 | if ((((b & 255)|0) != (b|0))|0){
617 | b = min(255, max(0, b|0)|0)|0;
618 | };
619 |
620 | o = 255;
621 | o = (o << 8)|0;
622 | o = (o + b)|0;
623 | o = (o << 8)|0;
624 | o = (o + g)|0;
625 | o = (o << 8)|0;
626 | o = (o + r)|0;
627 |
628 | return o|0;
629 |
630 | };
631 |
632 |
633 |
634 | return {
635 | init: init,
636 | doit: doit
637 | };
638 | };
639 |
640 |
641 | /*
642 | potential worker initialization
643 |
644 | */
645 |
646 |
647 | if (typeof self != "undefined"){
648 | var isWorker = false;
649 | var decoder;
650 | var reuseMemory = false;
651 | var sliceMode = false;
652 | var sliceNum = 0;
653 | var sliceCnt = 0;
654 | var lastSliceNum = 0;
655 | var sliceInfoAr;
656 | var lastBuf;
657 | var awaiting = 0;
658 | var pile = [];
659 | var startDecoding;
660 | var finishDecoding;
661 | var timeDecoding;
662 |
663 | var memAr = [];
664 | var getMem = function(length){
665 | if (memAr.length){
666 | var u = memAr.shift();
667 | while (u && u.byteLength !== length){
668 | u = memAr.shift();
669 | };
670 | if (u){
671 | return u;
672 | };
673 | };
674 | return new ArrayBuffer(length);
675 | };
676 |
677 | var copySlice = function(source, target, infoAr, width, height){
678 |
679 | var length = width * height;
680 | var length4 = length / 4
681 | var plane2 = length;
682 | var plane3 = length + length4;
683 |
684 | var copy16 = function(parBegin, parEnd){
685 | var i = 0;
686 | for (i = 0; i < 16; ++i){
687 | var begin = parBegin + (width * i);
688 | var end = parEnd + (width * i)
689 | target.set(source.subarray(begin, end), begin);
690 | };
691 | };
692 | var copy8 = function(parBegin, parEnd){
693 | var i = 0;
694 | for (i = 0; i < 8; ++i){
695 | var begin = parBegin + ((width / 2) * i);
696 | var end = parEnd + ((width / 2) * i)
697 | target.set(source.subarray(begin, end), begin);
698 | };
699 | };
700 | var copyChunk = function(begin, end){
701 | target.set(source.subarray(begin, end), begin);
702 | };
703 |
704 | var begin = infoAr[0];
705 | var end = infoAr[1];
706 | if (end > 0){
707 | copy16(begin, end);
708 | copy8(infoAr[2], infoAr[3]);
709 | copy8(infoAr[4], infoAr[5]);
710 | };
711 | begin = infoAr[6];
712 | end = infoAr[7];
713 | if (end > 0){
714 | copy16(begin, end);
715 | copy8(infoAr[8], infoAr[9]);
716 | copy8(infoAr[10], infoAr[11]);
717 | };
718 |
719 | begin = infoAr[12];
720 | end = infoAr[15];
721 | if (end > 0){
722 | copyChunk(begin, end);
723 | copyChunk(infoAr[13], infoAr[16]);
724 | copyChunk(infoAr[14], infoAr[17]);
725 | };
726 |
727 | };
728 |
729 | var sliceMsgFun = function(){};
730 |
731 | var setSliceCnt = function(parSliceCnt){
732 | sliceCnt = parSliceCnt;
733 | lastSliceNum = sliceCnt - 1;
734 | };
735 |
736 |
737 | self.addEventListener('message', function(e) {
738 |
739 | if (isWorker){
740 | if (reuseMemory){
741 | if (e.data.reuse){
742 | memAr.push(e.data.reuse);
743 | };
744 | };
745 | if (e.data.buf){
746 | if (sliceMode && awaiting !== 0){
747 | pile.push(e.data);
748 | }else{
749 | decoder.decode(
750 | new Uint8Array(e.data.buf, e.data.offset || 0, e.data.length),
751 | e.data.info,
752 | function(){
753 | if (sliceMode && sliceNum !== lastSliceNum){
754 | postMessage(e.data, [e.data.buf]);
755 | };
756 | }
757 | );
758 | };
759 | return;
760 | };
761 |
762 | if (e.data.slice){
763 | // update ref pic
764 | var copyStart = nowValue();
765 | copySlice(new Uint8Array(e.data.slice), lastBuf, e.data.infos[0].sliceInfoAr, e.data.width, e.data.height);
766 | // is it the one? then we need to update it
767 | if (e.data.theOne){
768 | copySlice(lastBuf, new Uint8Array(e.data.slice), sliceInfoAr, e.data.width, e.data.height);
769 | if (timeDecoding > e.data.infos[0].timeDecoding){
770 | e.data.infos[0].timeDecoding = timeDecoding;
771 | };
772 | e.data.infos[0].timeCopy += (nowValue() - copyStart);
773 | };
774 | // move on
775 | postMessage(e.data, [e.data.slice]);
776 |
777 | // next frame in the pipe?
778 | awaiting -= 1;
779 | if (awaiting === 0 && pile.length){
780 | var data = pile.shift();
781 | decoder.decode(
782 | new Uint8Array(data.buf, data.offset || 0, data.length),
783 | data.info,
784 | function(){
785 | if (sliceMode && sliceNum !== lastSliceNum){
786 | postMessage(data, [data.buf]);
787 | };
788 | }
789 | );
790 | };
791 | return;
792 | };
793 |
794 | if (e.data.setSliceCnt){
795 | setSliceCnt(e.data.sliceCnt);
796 | return;
797 | };
798 |
799 | }else{
800 | if (e.data && e.data.type === "Broadway.js - Worker init"){
801 | isWorker = true;
802 | decoder = new Decoder(e.data.options);
803 |
804 | if (e.data.options.sliceMode){
805 | reuseMemory = true;
806 | sliceMode = true;
807 | sliceNum = e.data.options.sliceNum;
808 | setSliceCnt(e.data.options.sliceCnt);
809 |
810 | decoder.onPictureDecoded = function (buffer, width, height, infos) {
811 |
812 | // buffer needs to be copied because we give up ownership
813 | var copyU8 = new Uint8Array(getMem(buffer.length));
814 | copySlice(buffer, copyU8, infos[0].sliceInfoAr, width, height);
815 |
816 | startDecoding = infos[0].startDecoding;
817 | finishDecoding = infos[0].finishDecoding;
818 | timeDecoding = finishDecoding - startDecoding;
819 | infos[0].timeDecoding = timeDecoding;
820 | infos[0].timeCopy = 0;
821 |
822 | postMessage({
823 | slice: copyU8.buffer,
824 | sliceNum: sliceNum,
825 | width: width,
826 | height: height,
827 | infos: infos
828 | }, [copyU8.buffer]); // 2nd parameter is used to indicate transfer of ownership
829 |
830 | awaiting = sliceCnt - 1;
831 |
832 | lastBuf = buffer;
833 | sliceInfoAr = infos[0].sliceInfoAr;
834 |
835 | };
836 |
837 | }else if (e.data.options.reuseMemory){
838 | reuseMemory = true;
839 | decoder.onPictureDecoded = function (buffer, width, height, infos) {
840 |
841 | // buffer needs to be copied because we give up ownership
842 | var copyU8 = new Uint8Array(getMem(buffer.length));
843 | copyU8.set( buffer, 0, buffer.length );
844 |
845 | postMessage({
846 | buf: copyU8.buffer,
847 | length: buffer.length,
848 | width: width,
849 | height: height,
850 | infos: infos
851 | }, [copyU8.buffer]); // 2nd parameter is used to indicate transfer of ownership
852 |
853 | };
854 |
855 | }else{
856 | decoder.onPictureDecoded = function (buffer, width, height, infos) {
857 | if (buffer) {
858 | buffer = new Uint8Array(buffer);
859 | };
860 |
861 | // buffer needs to be copied because we give up ownership
862 | var copyU8 = new Uint8Array(buffer.length);
863 | copyU8.set( buffer, 0, buffer.length );
864 |
865 | postMessage({
866 | buf: copyU8.buffer,
867 | length: buffer.length,
868 | width: width,
869 | height: height,
870 | infos: infos
871 | }, [copyU8.buffer]); // 2nd parameter is used to indicate transfer of ownership
872 |
873 | };
874 | };
875 | postMessage({ consoleLog: "broadway worker initialized" });
876 | };
877 | };
878 |
879 |
880 | }, false);
881 | };
882 |
883 | Decoder.nowValue = nowValue;
884 |
885 | return Decoder;
886 |
887 | })();
888 |
889 |
890 | }));
891 |
892 |
--------------------------------------------------------------------------------