use worker on remote camera to decode jpg image

This commit is contained in:
lxsang 2020-12-29 20:55:34 +00:00
parent 0a6f8c0f8c
commit 71ed35ae00
11 changed files with 127 additions and 21 deletions

View File

@ -4,3 +4,6 @@ Connect to a V4L2 camera on server via Antunnel.
This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher** This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher**
on the server-side on the server-side
## Change log
* v0.1.2-a: user worker for jpeg decoding

View File

@ -4,3 +4,6 @@ Connect to a V4L2 camera on server via Antunnel.
This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher** This application reauires the **tunel plugin** and the **ant-tunnel v4l2 publisher**
on the server-side on the server-side
## Change log
* v0.1.2-a: user worker for jpeg decoding

View File

@ -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);
}
}

View File

@ -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); (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);

View File

@ -7,7 +7,7 @@
"author": "", "author": "",
"email": "" "email": ""
}, },
"version":"0.0.1-a", "version":"0.1.2-a",
"category":"Other", "category":"Other",
"iconclass":"fa fa-camera", "iconclass":"fa fa-camera",
"mimes":["none"], "mimes":["none"],

View File

@ -3,6 +3,14 @@ class RemoteCamera extends this.OS.application.BaseApplication
super "RemoteCamera", args super "RemoteCamera", args
main: () -> 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 @mute = false
@player = @find "player" @player = @find "player"
@ -10,6 +18,8 @@ class RemoteCamera extends this.OS.application.BaseApplication
@fpsctl = @find "fpsctl" @fpsctl = @find "fpsctl"
@cam_setting = { @cam_setting = {
w: 640, w: 640,
h: 480, h: 480,
@ -97,7 +107,20 @@ class RemoteCamera extends this.OS.application.BaseApplication
} }
.then (v) => .then (v) =>
@setting.channel = v @setting.channel = v
return @openSession() unless @sub
@sub.onclose = (e) =>
@openSession() @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: () -> menu: () ->
{ {
@ -142,24 +165,19 @@ class RemoteCamera extends this.OS.application.BaseApplication
@mute = false @mute = false
@sub.onmessage = (e) => @sub.onmessage = (e) =>
jpeg = new JpegImage() console.log("receive")
jpeg.parse e.data return unless @decoder
context = @player.getContext("2d") @decoder.postMessage e.data.buffer, [e.data.buffer]
@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)
@sub.onclose = () => @sub.onclose = () =>
@sub = undefined @sub = undefined
@notify __("Unsubscribed to the camera service") @notify __("Unsubscribed to the camera service")
@quit() return @quit()
Antunnel.tunnel.subscribe @sub Antunnel.tunnel.subscribe @sub
cleanup: () -> cleanup: () ->
@sub.close() if @sub @sub.close() if @sub
@decoder.terminate() if @decoder
setCameraSetting: () -> setCameraSetting: () ->
return unless @sub return unless @sub
@ -171,7 +189,5 @@ class RemoteCamera extends this.OS.application.BaseApplication
@sub.send Antunnel.Msg.CTRL, arr @sub.send Antunnel.Msg.CTRL, arr
RemoteCamera.singleton = true RemoteCamera.singleton = true
RemoteCamera.dependencies = [
"pkg://libjpeg/jpg.js"
]
this.OS.register "RemoteCamera", RemoteCamera this.OS.register "RemoteCamera", RemoteCamera

View File

@ -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);
}
}

View File

@ -7,7 +7,7 @@
"author": "", "author": "",
"email": "" "email": ""
}, },
"version":"0.0.1-a", "version":"0.1.2-a",
"category":"Other", "category":"Other",
"iconclass":"fa fa-camera", "iconclass":"fa fa-camera",
"mimes":["none"], "mimes":["none"],

View File

@ -3,5 +3,5 @@
"css": ["css/main.css"], "css": ["css/main.css"],
"javascripts": [], "javascripts": [],
"coffees": ["coffees/main.coffee"], "coffees": ["coffees/main.coffee"],
"copies": ["assets/scheme.html", "package.json", "README.md"] "copies": ["assets/scheme.html", "js/decoder.js", "package.json", "README.md"]
} }

View File

@ -205,7 +205,7 @@
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/RemoteCamera/README.md", "description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/RemoteCamera/README.md",
"category": "Other", "category": "Other",
"author": "", "author": "",
"version": "0.0.1-a", "version": "0.1.2-a",
"dependencies": ["libjpeg@0.1.1-a","Antunnel@0.1.8-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" "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/RemoteCamera/build/release/RemoteCamera.zip"
}, },