From 71ed35ae007ab68990a658c7560f695367a1dfc8 Mon Sep 17 00:00:00 2001 From: lxsang Date: Tue, 29 Dec 2020 20:55:34 +0000 Subject: [PATCH] use worker on remote camera to decode jpg image --- RemoteCamera/README.md | 5 ++- RemoteCamera/build/debug/README.md | 5 ++- RemoteCamera/build/debug/decoder.js | 42 +++++++++++++++++++ RemoteCamera/build/debug/main.js | 2 +- RemoteCamera/build/debug/package.json | 2 +- RemoteCamera/build/release/RemoteCamera.zip | Bin 5577 -> 7081 bytes RemoteCamera/coffees/main.coffee | 44 +++++++++++++------- RemoteCamera/js/decoder.js | 42 +++++++++++++++++++ RemoteCamera/package.json | 2 +- RemoteCamera/project.json | 2 +- packages.json | 2 +- 11 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 RemoteCamera/build/debug/decoder.js create mode 100644 RemoteCamera/js/decoder.js diff --git a/RemoteCamera/README.md b/RemoteCamera/README.md index a44b9db..baa0ace 100644 --- a/RemoteCamera/README.md +++ b/RemoteCamera/README.md @@ -3,4 +3,7 @@ Connect to a V4L2 camera on server via Antunnel. This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher** -on the server-side \ No newline at end of file +on the server-side + +## Change log +* v0.1.2-a: user worker for jpeg decoding \ No newline at end of file diff --git a/RemoteCamera/build/debug/README.md b/RemoteCamera/build/debug/README.md index a44b9db..baa0ace 100644 --- a/RemoteCamera/build/debug/README.md +++ b/RemoteCamera/build/debug/README.md @@ -3,4 +3,7 @@ Connect to a V4L2 camera on server via Antunnel. This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher** -on the server-side \ No newline at end of file +on the server-side + +## Change log +* v0.1.2-a: user worker for jpeg decoding \ No newline at end of file diff --git a/RemoteCamera/build/debug/decoder.js b/RemoteCamera/build/debug/decoder.js new file mode 100644 index 0000000..636d169 --- /dev/null +++ b/RemoteCamera/build/debug/decoder.js @@ -0,0 +1,42 @@ +decode = (arr) => +{ + if(!JpegImage) + { + console.error("libjpeg is not available"); + return; + } + let raw = new Uint8Array(arr); + let jpeg = new JpegImage(); + jpeg.parse(raw); + let data = jpeg.getData(jpeg.width, jpeg.height); + let msg = { + w: jpeg.width, + h: jpeg.height, + pixels: undefined + } + msg.pixels = new Uint8Array(msg.w*msg.h*4); + for(let j = 0; j < msg.h; j++) + { + for(let i = 0; i < msg.w; i++) + { + let index = j*msg.w*4 + i*4; + msg.pixels[index] = data[j*msg.w*3 + i*3]; + msg.pixels[index+1] = data[j*msg.w*3 + i*3 + 1]; + msg.pixels[index+2] = data[j*msg.w*3 + i*3 + 2]; + msg.pixels[index+3] = 255; + } + } + msg.pixels = msg.pixels.buffer; + postMessage(msg, [msg.pixels]); +} + +onmessage = (e) => { + if(e.data.libjpeg) + { + importScripts(e.data.libjpeg); + } + else + { + decode(e.data); + } +} \ No newline at end of file diff --git a/RemoteCamera/build/debug/main.js b/RemoteCamera/build/debug/main.js index 636b56a..83a37e3 100644 --- a/RemoteCamera/build/debug/main.js +++ b/RemoteCamera/build/debug/main.js @@ -1 +1 @@ -(function(){var t;(t=class extends this.OS.application.BaseApplication{constructor(t){super("RemoteCamera",t)}main(){var t,e,s;for(this.mute=!1,this.player=this.find("player"),this.qctl=this.find("qctl"),this.fpsctl=this.find("fpsctl"),this.cam_setting={w:640,h:480,fps:10,quality:60},t=[],e=s=5;s<=30;e=s+=5)t.push({text:""+e,value:e});return this.fpsctl.data=t,this.fpsctl.selected=this.cam_setting.fps/5-1,this.fpsctl.onlistselect=t=>{if(!this.mute)return this.cam_setting.fps=t.data.item.data.value,this.setCameraSetting()},this.qctl.value=this.cam_setting.quality,this.resoctl=this.find("resoctl"),this.resoctl.data=[{text:__("320x240"),mode:"qvga"},{text:__("640x480"),selected:!0,mode:"vga"},{text:__("800x600"),mode:"svga"},{text:__("1024x760"),mode:"hd"},{text:__("1920×1080"),mode:"fhd"}],this.resoctl.onlistselect=t=>{if(!this.mute){switch(t.data.item.data.mode){case"qvga":this.cam_setting.w=320,this.cam_setting.h=240;break;case"vga":this.cam_setting.w=640,this.cam_setting.h=480;break;case"svga":this.cam_setting.w=800,this.cam_setting.h=600;break;case"hd":this.cam_setting.w=1024,this.cam_setting.h=768;break;case"fhd":this.cam_setting.w=1920,this.cam_setting.h=1080}return this.setCameraSetting()}},this.qctl.onvaluechange=t=>{if(!this.mute)return this.cam_setting.quality=t.data,this.setCameraSetting()},Antunnel.tunnel?this.setting.channel?this.openSession():this.requestChannel():this.notify(__("Antunnel service is not available"))}requestChannel(){return this.openDialog("PromptDialog",{title:__("Enter camera channel"),label:__("Please enter camera channel name")}).then(t=>(this.setting.channel=t,this.openSession()))}menu(){return{text:"__(Option)",nodes:[{text:"__(Camera channel)"}],onchildselect:t=>this.requestChannel()}}openSession(){if(Antunnel&&this.setting.channel)return this.tunnel=Antunnel.tunnel,this.sub=new Antunnel.Subscriber(this.setting.channel),this.sub.onopen=()=>console.log("Subscribed to camera channel"),this.sub.onerror=t=>this.error(__("Error: {0}",new TextDecoder("utf-8").decode(t.data)),t),this.sub.onctrl=t=>{switch(this.cam_setting.w=Antunnel.Msg.int_from(t.data,0),this.cam_setting.h=Antunnel.Msg.int_from(t.data,2),this.cam_setting.fps=t.data[4],this.cam_setting.quality=t.data[5],this.mute=!0,this.qctl.value=this.cam_setting.quality,`${this.cam_setting.w}x${this.cam_setting.h}`){case"320x240":this.resoctl.selected=0;break;case"640x480":this.resoctl.selected=1;break;case"800x600":this.resoctl.selected=2;break;case"1024x768":this.resoctl.selected=3;break;case"1920x1080":this.resoctl.selected=4}return this.fpsctl.selected=this.cam_setting.fps/5-1,this.mute=!1},this.sub.onmessage=t=>{var e,s,i;return(i=new JpegImage).parse(t.data),e=this.player.getContext("2d"),this.player.width=i.width,this.player.height=i.height,s=e.getImageData(0,0,i.width,i.height),i.copyToImageData(s),e.putImageData(s,0,0)},this.sub.onclose=()=>(this.sub=void 0,this.notify(__("Unsubscribed to the camera service")),this.quit()),Antunnel.tunnel.subscribe(this.sub)}cleanup(){if(this.sub)return this.sub.close()}setCameraSetting(){var t;if(this.sub)return(t=new Uint8Array(6)).set(Antunnel.Msg.bytes_of(this.cam_setting.w),0),t.set(Antunnel.Msg.bytes_of(this.cam_setting.h),2),t[4]=this.cam_setting.fps,t[5]=this.cam_setting.quality,this.sub.send(Antunnel.Msg.CTRL,t)}}).singleton=!0,t.dependencies=["pkg://libjpeg/jpg.js"],this.OS.register("RemoteCamera",t)}).call(this); \ No newline at end of file +(function(){var t;(t=class extends this.OS.application.BaseApplication{constructor(t){super("RemoteCamera",t)}main(){var t,e,s;for(this.decoder=new Worker("pkg://RemoteCamera/decoder.js".asFileHandle().getlink()),this.decoder.onmessage=t=>this.paint(t.data),this.decoder.postMessage({libjpeg:"pkg://libjpeg/jpg.js".asFileHandle().getlink()}),this.mute=!1,this.player=this.find("player"),this.qctl=this.find("qctl"),this.fpsctl=this.find("fpsctl"),this.cam_setting={w:640,h:480,fps:10,quality:60},t=[],e=s=5;s<=30;e=s+=5)t.push({text:""+e,value:e});return this.fpsctl.data=t,this.fpsctl.selected=this.cam_setting.fps/5-1,this.fpsctl.onlistselect=t=>{if(!this.mute)return this.cam_setting.fps=t.data.item.data.value,this.setCameraSetting()},this.qctl.value=this.cam_setting.quality,this.resoctl=this.find("resoctl"),this.resoctl.data=[{text:__("320x240"),mode:"qvga"},{text:__("640x480"),selected:!0,mode:"vga"},{text:__("800x600"),mode:"svga"},{text:__("1024x760"),mode:"hd"},{text:__("1920×1080"),mode:"fhd"}],this.resoctl.onlistselect=t=>{if(!this.mute){switch(t.data.item.data.mode){case"qvga":this.cam_setting.w=320,this.cam_setting.h=240;break;case"vga":this.cam_setting.w=640,this.cam_setting.h=480;break;case"svga":this.cam_setting.w=800,this.cam_setting.h=600;break;case"hd":this.cam_setting.w=1024,this.cam_setting.h=768;break;case"fhd":this.cam_setting.w=1920,this.cam_setting.h=1080}return this.setCameraSetting()}},this.qctl.onvaluechange=t=>{if(!this.mute)return this.cam_setting.quality=t.data,this.setCameraSetting()},Antunnel.tunnel?this.setting.channel?this.openSession():this.requestChannel():this.notify(__("Antunnel service is not available"))}requestChannel(){return this.openDialog("PromptDialog",{title:__("Enter camera channel"),label:__("Please enter camera channel name")}).then(t=>(this.setting.channel=t,this.sub?(this.sub.onclose=t=>this.openSession(),this.sub.close()):this.openSession()))}paint(t){var e,s,i;return s=new Uint8Array(t.pixels),e=this.player.getContext("2d",{alpha:!1}),this.player.width=t.w,this.player.height=t.h,(i=e.createImageData(t.w,t.h)).data.set(s),e.putImageData(i,0,0)}menu(){return{text:"__(Option)",nodes:[{text:"__(Camera channel)"}],onchildselect:t=>this.requestChannel()}}openSession(){if(Antunnel&&this.setting.channel)return this.tunnel=Antunnel.tunnel,this.sub=new Antunnel.Subscriber(this.setting.channel),this.sub.onopen=()=>console.log("Subscribed to camera channel"),this.sub.onerror=t=>this.error(__("Error: {0}",new TextDecoder("utf-8").decode(t.data)),t),this.sub.onctrl=t=>{switch(this.cam_setting.w=Antunnel.Msg.int_from(t.data,0),this.cam_setting.h=Antunnel.Msg.int_from(t.data,2),this.cam_setting.fps=t.data[4],this.cam_setting.quality=t.data[5],this.mute=!0,this.qctl.value=this.cam_setting.quality,`${this.cam_setting.w}x${this.cam_setting.h}`){case"320x240":this.resoctl.selected=0;break;case"640x480":this.resoctl.selected=1;break;case"800x600":this.resoctl.selected=2;break;case"1024x768":this.resoctl.selected=3;break;case"1920x1080":this.resoctl.selected=4}return this.fpsctl.selected=this.cam_setting.fps/5-1,this.mute=!1},this.sub.onmessage=t=>{if(console.log("receive"),this.decoder)return this.decoder.postMessage(t.data.buffer,[t.data.buffer])},this.sub.onclose=()=>(this.sub=void 0,this.notify(__("Unsubscribed to the camera service")),this.quit()),Antunnel.tunnel.subscribe(this.sub)}cleanup(){if(this.sub&&this.sub.close(),this.decoder)return this.decoder.terminate()}setCameraSetting(){var t;if(this.sub)return(t=new Uint8Array(6)).set(Antunnel.Msg.bytes_of(this.cam_setting.w),0),t.set(Antunnel.Msg.bytes_of(this.cam_setting.h),2),t[4]=this.cam_setting.fps,t[5]=this.cam_setting.quality,this.sub.send(Antunnel.Msg.CTRL,t)}}).singleton=!0,this.OS.register("RemoteCamera",t)}).call(this); \ No newline at end of file diff --git a/RemoteCamera/build/debug/package.json b/RemoteCamera/build/debug/package.json index 406327f..ccefe36 100644 --- a/RemoteCamera/build/debug/package.json +++ b/RemoteCamera/build/debug/package.json @@ -7,7 +7,7 @@ "author": "", "email": "" }, - "version":"0.0.1-a", + "version":"0.1.2-a", "category":"Other", "iconclass":"fa fa-camera", "mimes":["none"], diff --git a/RemoteCamera/build/release/RemoteCamera.zip b/RemoteCamera/build/release/RemoteCamera.zip index 32aaaffe8ebf3850be3d6a740de4542ca41eb483..9294f8e087cdb5fdf40460f2b62ada1e7ca96016 100644 GIT binary patch delta 2066 zcmaJ?&2Jk;6nAQx!jhx~l2Teerm0kR?Pc>>QJlC46@@}nDG(4skq;C5#XHG*cQrGP zTSeBMIB-CK77iS^0iuEn$^{NwdgC9U_g>%#2M$O`@MdQ1wT;VIYwgVY{ocIq{keSS z#dBMm#fPkdTCK0Yc=PR_Hb1&MQz+b>`Q+SZNdxLp19&xr4tY1?2f*$5_@G%?Sa=Is zk%a5C1)MHqFmGSXdsJ+&0B+E*5rFM@O^^W#4{XPArO$-dgGONsgrH5R%IY;Oi75-E zExiVnGy^}=L&uSjHx1rC3fS&`49!aFO`m=Gf%kPC_xo?|olHb~_hnmOfNx$D)`Ljo zmqsy!+o+gDp`Bf-U>bxrS2S@5w#-4yU33?nZVSTpaXR^1vBCE2dK8K%056D|_B@9= zutS(1PLPv%q^%s629qdnx4pLq+}#=4*3zv5Nt*N<* z^tI`ird3gXTKXfsj|F-^{c!rn4a>T8iCp(-*aU(eSS50}=q-86FVG5U3*e-qE0yd; zoTx$}+I$u^hbsMed*;N%!%F*iQy<%D?et$o)RDe-=G!739-n&b^J3C#j1W=x{uWRC zQY;kIquQkxlYL>{NR`->bB9!~Sw|MZg@L~K2Hvl8;pihHP{Ljcfb$5ODywP21PLQa z=pkhRO?CrJ%bI2?j!B8xclBuil5pC=yH$q{)Sa@#02M?>!C?9Z*_m9)yqIzUHfkCm zHE1F!YSsX3V_kB3qpQ_50#l;{w3cWhDR;C}F+fA=`{HNEhVS;zO}Aho!v-XKEQE#@ z8Fx{4)=vNRSjjpi_3xKfGkdTCZ0$e77uU!>StlBRujTS+MtWdoz>KUX8r@kVtcO-v z$X!!ArJkX!w~)23ukVyrNtv+Hs)@e>sg>KlqwzH)s8P4mW36b`N-d`)<9Owz2QiVq zG{7E9Uj85Q@+dj!hufH3UQz7jD_3$8OTYdL zoS4-eKl*`8f0%uq42>ro3ySrUT#W&H;PG<4@yZhx$#ZDCnaVJb3iL20|N;2 zz)b)#y!jaz3KEmEfw2bkSbiS5L59-Os=ovn7>biKQgc)FGD>oD0=!w-Kw4RWP!wq3 IJP{BN0K_+}SpWb4 diff --git a/RemoteCamera/coffees/main.coffee b/RemoteCamera/coffees/main.coffee index 4fcc9ae..cc7c41c 100644 --- a/RemoteCamera/coffees/main.coffee +++ b/RemoteCamera/coffees/main.coffee @@ -3,6 +3,14 @@ class RemoteCamera extends this.OS.application.BaseApplication super "RemoteCamera", args main: () -> + + @decoder = new Worker("pkg://RemoteCamera/decoder.js".asFileHandle().getlink()) + + @decoder.onmessage = (e) => + @paint e.data + + @decoder.postMessage {libjpeg: "pkg://libjpeg/jpg.js".asFileHandle().getlink()} + @mute = false @player = @find "player" @@ -10,6 +18,8 @@ class RemoteCamera extends this.OS.application.BaseApplication @fpsctl = @find "fpsctl" + + @cam_setting = { w: 640, h: 480, @@ -97,7 +107,20 @@ class RemoteCamera extends this.OS.application.BaseApplication } .then (v) => @setting.channel = v - @openSession() + return @openSession() unless @sub + @sub.onclose = (e) => + @openSession() + @sub.close() + + paint: (msg) -> + # console.log msg + data = new Uint8Array msg.pixels + ctx = @player.getContext "2d", { alpha: false } + @player.width = msg.w + @player.height = msg.h + imgData = ctx.createImageData msg.w, msg.h + imgData.data.set data + ctx.putImageData imgData, 0, 0 menu: () -> { @@ -142,24 +165,19 @@ class RemoteCamera extends this.OS.application.BaseApplication @mute = false @sub.onmessage = (e) => - jpeg = new JpegImage() - jpeg.parse e.data - context = @player.getContext("2d") - @player.width = jpeg.width - @player.height = jpeg.height - #jpeg.copyToImageData(d) - imgData = context.getImageData(0,0,jpeg.width,jpeg.height) - jpeg.copyToImageData imgData - context.putImageData(imgData, 0, 0) + console.log("receive") + return unless @decoder + @decoder.postMessage e.data.buffer, [e.data.buffer] @sub.onclose = () => @sub = undefined @notify __("Unsubscribed to the camera service") - @quit() + return @quit() Antunnel.tunnel.subscribe @sub cleanup: () -> @sub.close() if @sub + @decoder.terminate() if @decoder setCameraSetting: () -> return unless @sub @@ -171,7 +189,5 @@ class RemoteCamera extends this.OS.application.BaseApplication @sub.send Antunnel.Msg.CTRL, arr RemoteCamera.singleton = true -RemoteCamera.dependencies = [ - "pkg://libjpeg/jpg.js" -] + this.OS.register "RemoteCamera", RemoteCamera \ No newline at end of file diff --git a/RemoteCamera/js/decoder.js b/RemoteCamera/js/decoder.js new file mode 100644 index 0000000..636d169 --- /dev/null +++ b/RemoteCamera/js/decoder.js @@ -0,0 +1,42 @@ +decode = (arr) => +{ + if(!JpegImage) + { + console.error("libjpeg is not available"); + return; + } + let raw = new Uint8Array(arr); + let jpeg = new JpegImage(); + jpeg.parse(raw); + let data = jpeg.getData(jpeg.width, jpeg.height); + let msg = { + w: jpeg.width, + h: jpeg.height, + pixels: undefined + } + msg.pixels = new Uint8Array(msg.w*msg.h*4); + for(let j = 0; j < msg.h; j++) + { + for(let i = 0; i < msg.w; i++) + { + let index = j*msg.w*4 + i*4; + msg.pixels[index] = data[j*msg.w*3 + i*3]; + msg.pixels[index+1] = data[j*msg.w*3 + i*3 + 1]; + msg.pixels[index+2] = data[j*msg.w*3 + i*3 + 2]; + msg.pixels[index+3] = 255; + } + } + msg.pixels = msg.pixels.buffer; + postMessage(msg, [msg.pixels]); +} + +onmessage = (e) => { + if(e.data.libjpeg) + { + importScripts(e.data.libjpeg); + } + else + { + decode(e.data); + } +} \ No newline at end of file diff --git a/RemoteCamera/package.json b/RemoteCamera/package.json index 406327f..ccefe36 100644 --- a/RemoteCamera/package.json +++ b/RemoteCamera/package.json @@ -7,7 +7,7 @@ "author": "", "email": "" }, - "version":"0.0.1-a", + "version":"0.1.2-a", "category":"Other", "iconclass":"fa fa-camera", "mimes":["none"], diff --git a/RemoteCamera/project.json b/RemoteCamera/project.json index 54687c9..edf59e3 100644 --- a/RemoteCamera/project.json +++ b/RemoteCamera/project.json @@ -3,5 +3,5 @@ "css": ["css/main.css"], "javascripts": [], "coffees": ["coffees/main.coffee"], - "copies": ["assets/scheme.html", "package.json", "README.md"] + "copies": ["assets/scheme.html", "js/decoder.js", "package.json", "README.md"] } \ No newline at end of file diff --git a/packages.json b/packages.json index c2fec8d..40a222a 100644 --- a/packages.json +++ b/packages.json @@ -205,7 +205,7 @@ "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/RemoteCamera/README.md", "category": "Other", "author": "", - "version": "0.0.1-a", + "version": "0.1.2-a", "dependencies": ["libjpeg@0.1.1-a","Antunnel@0.1.8-a"], "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/RemoteCamera/build/release/RemoteCamera.zip" },