mirror of
https://github.com/gpac/gpac.git
synced 2026-01-12 00:05:22 +08:00
262 lines
6.7 KiB
JavaScript
262 lines
6.7 KiB
JavaScript
/*
|
|
* GPAC - Multimedia Framework C SDK
|
|
*
|
|
* Authors: Jean Le Feuvre
|
|
* Copyright (c) Telecom ParisTech 2020-2024
|
|
* All rights reserved
|
|
*
|
|
* This file is part of GPAC / AVGenerator filter
|
|
*
|
|
* GPAC is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* GPAC is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
import {WebGLContext} from 'webgl'
|
|
import {Texture, Matrix} from 'evg'
|
|
import { Sys as sys } from 'gpaccore'
|
|
|
|
|
|
//metadata
|
|
filter.set_name("glpush");
|
|
filter.set_class_hint(GF_FS_CLASS_AV);
|
|
filter.set_desc("GPU texture uploader");
|
|
filter.set_version("1.0");
|
|
filter.set_author("GPAC team");
|
|
filter.set_help("This filter pushes input video streams to GPU as OpenGL textures. It can be used to simulate hardware decoders dispatching OpenGL textures");
|
|
|
|
//raw video in and out
|
|
filter.set_cap({id: "StreamType", value: "Video", inout: true} );
|
|
filter.set_cap({id: "CodecID", value: "raw", inout: true} );
|
|
|
|
let gl=null;
|
|
filter.initialize = function() {
|
|
let gpac_help = sys.get_opt("temp", "gpac-help");
|
|
let gpac_doc = (sys.get_opt("temp", "gendoc") == "yes") ? true : false;
|
|
//don't initialize gl if doc gen or help
|
|
if (gpac_help || gpac_doc) return;
|
|
}
|
|
|
|
let pids=[];
|
|
|
|
function cleanup_texture(pid)
|
|
{
|
|
pid.o_textures.forEach( t => {
|
|
if (t.id) gl.deleteTexture(t.id);
|
|
|
|
});
|
|
pid.o_textures = [];
|
|
}
|
|
filter.configure_pid = function(pid)
|
|
{
|
|
//init gl only when configuring input - doing it at init may result in uninitialized GL
|
|
if (!gl) {
|
|
gl = new WebGLContext(16, 16);
|
|
if (!gl) return GF_SERVICE_ERROR;
|
|
}
|
|
|
|
if (typeof pid.o_pid == 'undefined') {
|
|
pid.o_pid = this.new_pid();
|
|
pid.o_pid.i_pid = pid;
|
|
pid.o_pid.wtf = "toto";
|
|
pid.o_w = 0;
|
|
pid.o_h = 0;
|
|
pid.o_pf = '';
|
|
pid.o_textures = [];
|
|
pid.frame_pending = false;
|
|
pid.cache_pck = null;
|
|
pids.push(pid);
|
|
}
|
|
pid.o_pid.copy_props(pid);
|
|
pid.o_pid.set_prop('Stride', null);
|
|
pid.o_pid.set_prop('StrideUV', null);
|
|
let i_w = pid.get_prop('Width');
|
|
let i_h = pid.get_prop('Height');
|
|
let i_pf = pid.get_prop('PixelFormat');
|
|
let i_stride = pid.get_prop('Stride');
|
|
let i_stride_uv = pid.get_prop('StrideUV');
|
|
//not ready yet
|
|
if (!i_w || !i_h || !i_pf) return;
|
|
//same config
|
|
if ((i_w == pid.o_w) && (i_h == pid.o_h) && (i_pf == pid.o_pf)) return;
|
|
|
|
pid.o_w = i_w;
|
|
pid.o_h = i_h;
|
|
pid.o_pf = i_pf;
|
|
cleanup_texture(pid);
|
|
print(GF_LOG_DEBUG, 'Configure pid ' + pid.o_w + 'x' + pid.o_h + '@' + pid.o_pf);
|
|
|
|
if (!i_stride) i_stride = i_w;
|
|
|
|
if ((i_pf == 'nv12') || (i_pf == 'nv21')) {
|
|
let tx = {};
|
|
tx.id=0;
|
|
tx.offset=0;
|
|
tx.size=i_stride*i_h;
|
|
tx.w=i_w;
|
|
tx.h=i_h;
|
|
tx.fmt = gl.LUMINANCE;
|
|
pid.o_textures.push(tx);
|
|
|
|
tx = {};
|
|
tx.id=0;
|
|
tx.offset=i_stride*i_h;
|
|
tx.size=i_stride*i_h/2;
|
|
tx.w=i_w/2;
|
|
tx.h=i_h/2;
|
|
tx.fmt = gl.LUMINANCE_ALPHA;
|
|
pid.o_textures.push(tx);
|
|
}
|
|
else if ((i_pf == 'yuv420') || (i_pf == 'yvu420')) {
|
|
if (!i_stride_uv) i_stride_uv = i_stride/2;
|
|
let tx = {};
|
|
tx.id=0;
|
|
tx.offset=0;
|
|
tx.size=i_stride*i_h;
|
|
tx.w=i_w;
|
|
tx.h=i_h;
|
|
tx.fmt = gl.LUMINANCE;
|
|
pid.o_textures.push(tx);
|
|
|
|
tx = {};
|
|
tx.offset=i_stride*i_h;
|
|
tx.size=i_stride_uv*i_h/2;
|
|
tx.id=0;
|
|
tx.w=i_w/2;
|
|
tx.h=i_h/2;
|
|
tx.fmt = gl.LUMINANCE;
|
|
pid.o_textures.push(tx);
|
|
|
|
tx = {};
|
|
tx.id=0;
|
|
tx.offset=i_stride*i_h + i_stride_uv*i_h/2;
|
|
tx.size=i_stride_uv*i_h/2;
|
|
tx.w=i_w/2;
|
|
tx.h=i_h/2;
|
|
tx.fmt = gl.LUMINANCE;
|
|
pid.o_textures.push(tx);
|
|
}
|
|
else if ((i_pf == 'rgba') || (i_pf == 'argb') || (i_pf == 'abgr') || (i_pf == 'bgra')
|
|
|| (i_pf == 'xrgb') || (i_pf == 'rgbx') || (i_pf == 'xbgr') || (i_pf == 'bgrx')
|
|
) {
|
|
let tx = {};
|
|
tx.id=0;
|
|
tx.offset=0;
|
|
tx.size=i_stride*i_h*4;
|
|
tx.w=i_w;
|
|
tx.h=i_h;
|
|
tx.fmt = gl.RGBA;
|
|
pid.o_textures.push(tx);
|
|
}
|
|
else if ((i_pf == 'rgb') || (i_pf == 'bgr')) {
|
|
let tx = {};
|
|
tx.id=0;
|
|
tx.offset=0;
|
|
tx.size=i_stride*i_h*3;
|
|
tx.w=i_w;
|
|
tx.h=i_h;
|
|
tx.fmt = gl.RGB;
|
|
pid.o_textures.push(tx);
|
|
}
|
|
else if ((i_pf == 'algr') || (i_pf == 'gral')) {
|
|
let tx = {};
|
|
tx.id=0;
|
|
tx.offset=0;
|
|
tx.size=i_stride*i_h*2;
|
|
tx.w=i_w;
|
|
tx.h=i_h;
|
|
tx.fmt = gl.LUMINANCE_ALPHA;
|
|
pid.o_textures.push(tx);
|
|
}
|
|
else if ((i_pf == 'grey')) {
|
|
let tx = {};
|
|
tx.id=0;
|
|
tx.offset=0;
|
|
tx.size=i_stride*i_h;
|
|
tx.w=i_w;
|
|
tx.h=i_h;
|
|
tx.fmt = gl.LUMINANCE;
|
|
pid.o_textures.push(tx);
|
|
}
|
|
else {
|
|
//todo
|
|
print(GF_LOG_ERROR, "Unsupported pixel format " + i_pf + " for GPU upload");
|
|
return GF_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
filter.remove_pid = function(pid)
|
|
{
|
|
cleanup_texture(pid);
|
|
pids.splice(pids.indexOf(pid), 1);
|
|
}
|
|
|
|
|
|
filter.process = function()
|
|
{
|
|
|
|
pids.forEach(pid => {
|
|
if (pid.frame_pending) return;
|
|
let ipck = pid.get_packet();
|
|
if (!ipck) {
|
|
if (pid.eos) pid.o_pid.eos = true;
|
|
return;
|
|
}
|
|
//frame is already in gpu, simply forward
|
|
if (ipck.frame_ifce_gl) {
|
|
pid.o_pid.forward(ipck);
|
|
pid.drop_packet();
|
|
return;
|
|
}
|
|
|
|
//frame is an interface, force fetching data (no per-plane access in JS)
|
|
let clone_pck = null;
|
|
if (ipck.frame_ifce) {
|
|
clone_pck = ipck.clone(pid.cache_pck);
|
|
ipck = clone_pck;
|
|
}
|
|
|
|
//upload all textures
|
|
pid.o_textures.forEach( (tx, index) => {
|
|
let ab = new Uint8Array(ipck.data, tx.offset, tx.size);
|
|
if (!tx.id) tx.id = gl.createTexture();
|
|
gl.bindTexture(gl.TEXTURE_2D, tx.id);
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, tx.fmt, tx.w, tx.h, 0, tx.fmt, gl.UNSIGNED_BYTE, ab);
|
|
});
|
|
|
|
//send new frame interface in blocking mode since the same set of textures is used for each packet
|
|
let opck = pid.o_pid.new_packet(
|
|
(pid, pck, plane_idx) => {
|
|
let i_pid = pid.i_pid;
|
|
if (plane_idx >= i_pid.o_textures.length) return null;
|
|
return {"id": i_pid.o_textures[plane_idx].id, 'fmt': gl.TEXTURE_2D};
|
|
},
|
|
(pid, pck) => {
|
|
let i_pid = pid.i_pid;
|
|
i_pid.frame_pending = false;
|
|
},
|
|
true
|
|
);
|
|
opck.copy_props(ipck);
|
|
pid.frame_pending = true;
|
|
opck.send();
|
|
pid.drop_packet();
|
|
if (clone_pck)
|
|
clone_pck.discard();
|
|
});
|
|
|
|
return GF_OK;
|
|
}
|
|
|