blob: 9e346f81653c2e416bb4324ca998107c38e64550 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2022-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 { Bitstream as BS } from 'gpaccore'
import { Sys as sys } from 'gpaccore'
import * as evg from 'evg'
filter.pids = [];
const SAMPLING_NONE=0;
const SAMPLING_422=1;
const SAMPLING_420=2;
const SAMPLING_411=3;
const INTERLEAVE_COMPONENT=0;
const INTERLEAVE_PIXEL=1;
const INTERLEAVE_MIXED=2;
const INTERLEAVE_ROW=3;
const INTERLEAVE_TILE=4;
const INTERLEAVE_MULTIY=5;
//metadata
filter.set_name("uncvg");
filter.set_class_hint(GF_FS_CLASS_MM_IO);
filter.set_desc("Uncompressed Video Generator");
filter.set_version("1.0");
filter.set_author("GPAC team - (c) Telecom ParisTech 2023 - license LGPL v2");
filter.set_help("This filter provides generation of test images for ISO/IEC 23001-17\n"
+"Generated pixels can be:\n"
+"- a pattern of colors in columns, or in rectangles if [-sq]() is set, using the specified palette.\n"
+"- an image source if [-img]() is used.\n"
+"\n"
+"Colors specified in the palette can be default GPAC colors (see gpac -h colors), 0xRRGGBB or 0xAARRGGBB\n"
+"\n"
+"When generating video, the pixels are shifted to the left at every frame\n"
+"\n"
+"Components are described as N[bpc][+k] with\n"
+"- N: component type, one of M(mono), Y, U, V, R, G, B, A, d(depth), disp, p(palette), f(filterArray), x (pad)\n"
+"- bpc: bits per component value, default is 8. Non-integer values must be one of\n"
+" - sft: floating-point value on 16 bits\n"
+" - flt: floating-point value on 32 bits\n"
+" - dbl: floating-point value on 64 bits\n"
+" - dblx: floating-point value on 128 bits\n"
+" - cps: complex value as two floats on 16 bits each\n"
+" - cpf: complex value as two floats on 32 bits each\n"
+" - cpd: complex value as two floats on 64 bits each\n"
+" - cpx: complex value as two floats on 128 bits each\n"
+"- k: force component alignment on k bytes, default is 0 (no alignment)\n"
);
filter.set_arg({ name: "vsize", desc: "width and height of output image", type: GF_PROP_VEC2, def: "128x128"} );
filter.set_arg({ name: "c", desc: "image components", type: GF_PROP_STRING_LIST, def: ["R8", "G8", "B8"]} );
filter.set_arg({ name: "tiles", desc: "number of horizontal and vertical tiles", type: GF_PROP_VEC2, def: "1x1"} );
filter.set_arg({ name: "interleave", desc: `interleave type
- comp: component-based interleaving (planar modes)
- pix: pixel-based interleaving (packed modes)
- mix: pixel-based interleaving for UV (semi-planar modes)
- row: row-based interleaving
- tile: tile-component interleaving
- multi: pixel-based interleaving (packed modes) for sub-sampled modes`, type: GF_PROP_UINT, def: "pix", minmax_enum: "comp|pix|mix|row|tile|multi"} );
filter.set_arg({ name: "sampling", desc: `sampling types
- none: no sub-sampling
- 422: YUV 4:2:2 sub-sampling
- 420: YUV 4:2:0 sub-sampling
- 411: YUV 4:1:1 sub-sampling`, type: GF_PROP_UINT, def: "none", minmax_enum: "none|422|420|411"} );
filter.set_arg({ name: "block_size", desc: "block size in bytes", type: GF_PROP_UINT, def: "0"} );
filter.set_arg({ name: "pad_lsb", desc: "padded bits are at LSB in the block", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "ble", desc: "block is little endian", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "br", desc: "block has reversed components", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "pixel_size", desc: "size of pixel in bytes", type: GF_PROP_UINT, def: "0"} );
filter.set_arg({ name: "row_align", desc: "row alignment in bytes", type: GF_PROP_UINT, def: "0"} );
filter.set_arg({ name: "tile_align", desc: "tile alignment in bytes", type: GF_PROP_UINT, def: "0"} );
filter.set_arg({ name: "cle", desc: "byte-aligned components are little endian", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "img", desc: "use specified image as input instead of RGB generation", type: GF_PROP_STRING, def: ""} );
filter.set_arg({ name: "asize", desc: "use input image size", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "pal", desc: "default palette for color generation", type: GF_PROP_STRING_LIST, def: ["red", "green", "blue", "white", "black", "yellow", "cyan", "grey", "orange", "violet"]} );
filter.set_arg({ name: "fa", desc: "bayer-like filter - only 2x2 on R,G,B components is supported", type: GF_PROP_STRING_LIST, def: ["B", "G", "G", "R"]} );
filter.set_arg({ name: "bpm", desc: "set sensor bad pixel map as a list of cN (broken column), rM (broken row) or NxM (single pixel)", type: GF_PROP_STRING_LIST, def: []} );
filter.set_arg({ name: "fps", desc: "frame rate to generate - using 0 will trigger item muxing", type: GF_PROP_FRACTION, def: "25/1"} );
filter.set_arg({ name: "dur", desc: "duration to generate - using 0 will trigger item muxing", type: GF_PROP_FRACTION, def: "1/1"} );
filter.set_arg({ name: "cloc", desc: "set chroma location type", type: GF_PROP_UINT, def: "-1", minmax_enum: "-1,6"} );
filter.set_arg({ name: "stereo", desc: "dump a stereo image", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "sq", desc: "generate square patterns instead of columns", type: GF_PROP_FLOAT, def: "0"} );
filter.set_arg({ name: "shade", desc: "shade pixels from black at bottom to full intensity at top", type: GF_PROP_BOOL, def: "false"} );
filter.set_arg({ name: "scpt", desc: "use single color per tile, first tile using the first color in palette", type: GF_PROP_BOOL, def: "false"} );
let components = [];
let tx = null;
let tx_width, tx_height;
let col_width;
let out_size=0;
let cts=0;
let max_cts=0;
let pid;
let pixel = [];
let min_bits=0;
let pix_len;
let r_y_idx = -1;
let g_u_idx = -1;
let b_v_idx = -1;
let a_idx = -1;
let d_idx = -1;
let is_playing=false;
let use_palette=false;
let use_fa=false;
let fa_width=2;
let fa_height=2;
let fa=[];
let palette_alpha=false;
filter.set_cap({id: "StreamType", value: "Video", output: true} );
filter.set_cap({id: "CodecID", value: "raw", output: true} );
filter.initialize = function()
{
/*create a texture from a local file*/
if (this.img != "") {
tx = new evg.Texture(this.img);
tx_width = tx.width;
tx_height = tx.height;
if (this.asize) {
this.vsize.x = tx.width;
this.vsize.y = tx.height;
}
}
col_width = Math.ceil(filter.vsize.x / filter.pal.length);
while (col_width%4) col_width++;
if (filter.stereo && (filter.vsize.x%2))
filter.vsize.x++;
setup_uncv();
pid = this.new_pid();
pid.set_prop('StreamType', 'Visual');
pid.set_prop('CodecID', 'uncv');
pid.set_prop('Width', this.vsize.x);
pid.set_prop('Height', this.vsize.y);
pid.set_prop('Timescale', filter.fps.n ? filter.fps.n : 1000);
pid.set_prop('PixelFormat', 'uncv');
if (!filter.fps.n || !filter.fps.d || !filter.dur.n || !filter.dur.d) {
max_cts = 1;
pid.set_prop('ItemID', 1);
} else {
if (filter.fps.n && filter.fps.d)
pid.set_prop('FPS', filter.fps);
max_cts = filter.dur.n * filter.fps.n / filter.dur.d / filter.fps.d;
}
//write dec config as a box array
let bs = new BS();
let size, pos = 0;
//component definition
bs.put_u32(0);
bs.put_4cc("cmpd");
if (use_palette || use_fa) {
if (palette_alpha)
bs.put_u32(components.length+4);
else
bs.put_u32(components.length+3);
}
else
bs.put_u32(components.length);
for (let i=0; i<components.length; i++) {
let c = components[i];
bs.put_u16(c.type);
}
if (use_palette || use_fa) {
bs.put_u16(4); //R
bs.put_u16(5); //G
bs.put_u16(6); //B
if (palette_alpha)
bs.put_u16(7); //A
}
size = bs.pos;
bs.pos = 0;
bs.put_u32(size);
bs.pos = size;
//uncC
pos = bs.pos;
bs.put_u32(0);
bs.put_4cc("uncC");
bs.put_u32(0); //version+flags
bs.put_u32(0); //profile
bs.put_u32(components.length);
for (let i=0; i<components.length; i++) {
let c = components[i];
bs.put_u16(i); //index
bs.put_u8(c.bits-1); //nb_bits_minus_one
bs.put_u8(c.mode); //component_format
bs.put_u8(c.align); //component_align_size
}
bs.put_u8(filter.sampling);
bs.put_u8(filter.interleave);
bs.put_u8(filter.block_size);
bs.put_bits(filter.cle, 1); //components_little_endian
if (filter.block_size) {
bs.put_bits(filter.pad_lsb ? 1 : 0, 1); //pad_lsb
bs.put_bits(filter.ble ? 1 : 0, 1); //block_little_endian
bs.put_bits(filter.br ? 1 : 0, 1); //block_reversed
} else {
bs.put_bits(0, 3);
}
bs.put_bits(0, 1); //pad_unknown
bs.put_bits(0, 3); //reserved
bs.put_u32(filter.pixel_size);
bs.put_u32(filter.row_align);
bs.put_u32(filter.tile_align);
bs.put_u32(filter.tiles.x-1);
bs.put_u32(filter.tiles.y-1);
//write size
size = bs.pos - pos;
bs.pos = pos;
bs.put_u32(size);
bs.pos = size + pos;
if (use_palette) {
pos = bs.pos;
bs.put_u32(0);
bs.put_4cc("cpal");
bs.put_u32(0); //version 0 no flags
bs.put_u16(palette_alpha ? 4 : 3); //nb_comp, only (A)RGB palette 8 bits int for now
bs.put_u32(components.length); //R idx
bs.put_u8(7); //bits minus 1
bs.put_u8(0); //format
bs.put_u32(components.length+1); //G idx
bs.put_u8(7); //bits minus 1
bs.put_u8(0); //format
bs.put_u32(components.length+2); //B idx
bs.put_u8(7); //bits minus 1
bs.put_u8(0); //format
if (palette_alpha) {
bs.put_u32(components.length+3); //A idx
bs.put_u8(7); //bits minus 1
bs.put_u8(0); //format
}
bs.put_u32(filter.pal.length);
for (let i=0; i<filter.pal.length; i++) {
let col = filter.pal[i];
bs.put_u8( sys.color_component(col, 1) * 255);
bs.put_u8( sys.color_component(col, 2) * 255 );
bs.put_u8( sys.color_component(col, 3) * 255 );
if (palette_alpha)
bs.put_u8( sys.color_component(col, 0) * 255 );
}
//write size
let size = bs.pos - pos;
bs.pos = pos;
bs.put_u32(size);
bs.pos = size + pos;
}
if (use_fa) {
pos = bs.pos;
bs.put_u32(0);
bs.put_4cc("cpat");
bs.put_u32(0); //version 0 no flags
bs.put_u16(fa_width); //pattern width
bs.put_u16(fa_height); //pattern height
for (let i=0; i<fa_height; i++) {
for (let j=0; j<fa_width; j++) {
let ctype = fa[i*fa_height + j];
//ctype is 4->6 (R->B), so index us ctype-4 + components.length
bs.put_u32(ctype-4 + components.length);
bs.put_float(1.0);
}
}
//write size
let size = bs.pos - pos;
bs.pos = pos;
bs.put_u32(size);
bs.pos = size + pos;
}
let bad_rows=[];
let bad_cols=[];
let bad_pix=[];
let use_sbpm=false;
for (let i=0; i<filter.bpm.length; i++) {
let c = filter.bpm[i];
if (c.indexOf('r')==0) {
bad_rows.push( parseInt(c.slice(1) ) );
use_sbpm=true;
}
else if (c.indexOf('c')==0) {
bad_cols.push( parseInt(c.slice(1) ) );
use_sbpm=true;
}
else {
let s = c.indexOf('x');
if (s>0) {
let x = parseInt(c.slice(0, s) );
let y = parseInt(c.slice(s+1) );
bad_pix.push({x: x, y: y});
use_sbpm=true;
}
}
}
if (use_sbpm) {
pos = bs.pos;
bs.put_u32(0);
bs.put_4cc("sbpm");
bs.put_u32(0); //version 0 no flags
bs.put_u32(0); //component count, applies to all
bs.put_u8(0); //correction applied + reserved7=0
bs.put_u32(bad_rows.length);
bs.put_u32(bad_cols.length);
bs.put_u32(bad_pix.length);
for (let i=0; i<bad_rows.length; i++) {
bs.put_u32(bad_rows[i]);
}
for (let i=0; i<bad_cols.length; i++) {
bs.put_u32(bad_cols[i]);
}
for (let i=0; i<bad_pix.length; i++) {
bs.put_u32(bad_pix[i].y); //bad row
bs.put_u32(bad_pix[i].x); //bad col
}
//write size
let size = bs.pos - pos;
bs.pos = pos;
bs.put_u32(size);
bs.pos = size + pos;
}
if ((filter.cloc>=0) && gen_yuv) {
bs.put_u32(13);
bs.put_4cc("cloc");
bs.put_u32(0); //version 0 no flags
bs.put_u8(filter.cloc);
}
if (filter.stereo) {
bs.put_u32(14);
bs.put_4cc("fpac");
bs.put_u32(0); //version 0 no flags
bs.put_bits(3, 4); //video_frame_packing side by side
bs.put_bits(1, 4); //PackedContentInterpretationType left is left view
bs.put_bits(0, 1); //QuincunxSamplingFlag
bs.put_bits(0, 7); //reserved
}
/* todo - write if needed
NUC
polarization
reference level
disparity info
depth mapping info
*/
pid.set_prop('DecoderConfig', bs.get_content());
/*todo - set field interlace sample group if needed*/
}
let blocksize_bits;
let gen_yuv;
function line_size(comp_bits)
{
let clen = comp_bits.length;
let size=0;
if (!filter.block_size) {
for (let j=0; j<tile_width; j++) {
for (let c=0; c<clen; c++) {
let cbits;
if (comp_bits[c].align) {
cbits = 8*comp_bits[c].align;
while (size%8) size++;
} else {
cbits = comp_bits[c].bits;
}
size += cbits;
}
}
while (size % 8) size++;
return size / 8;
}
//block size, figure out how many blocks
let nb_bits = 0;
for (let i=0; i<tile_width; i++) {
for (let c=0; c<clen; c++) {
let cbits;
if (comp_bits[c].align) {
cbits = 8*comp_bits[c].align;
while (size%8) size++;
} else {
cbits = comp_bits[c].bits;
}
if (nb_bits + cbits > blocksize_bits) {
nb_bits=0;
size += filter.block_size;
//if first comp in block is first comp, pattern is done
if (c==0) {
let nb_pix = i;
let nb_blocks = 1;
while ((nb_blocks+1) * nb_pix < tile_width) nb_blocks++;
size *= nb_blocks;
//jump to last non-full pattern
i += nb_pix * (nb_blocks-1);
}
}
nb_bits += cbits;
}
}
if (nb_bits)
size += filter.block_size;
return size;
}
let tile_width, tile_height;
let row_align_bytes, tile_align_bytes, subsample_x;
const comp_types = [
{ type: 8, name: "depth", alias: "d"},
{ type: 9, name: "disp"},
{ type: 10, name: "pal", alias: "p"},
{ type: 11, name: "fa", alias: "f"},
{ type: 12, name: "pad", alias: "x"},
//do not change order of these types otherwise we conflict with 'u', 'v' and 'g'
{ type: 0, name: "mono", alias: "m"},
{ type: 1, name: "y"},
{ type: 2, name: "u"},
{ type: 3, name: "v"},
{ type: 4, name: "r"},
{ type: 5, name: "g"},
{ type: 6, name: "b"},
{ type: 7, name: "a"},
];
let comp_le_buf = null;
let comp_le_bs = null;
let comp_le_view = null;
let has_a=false;
let has_depth=false;
let has_disp=false;
function setup_uncv()
{
components.length=0;
if (filter.c.length==0)
throw "At least one component must be specified";
let max_align_size=0;
let has_rgb=false;
for (let i=0; i<filter.pal.length; i++) {
if (sys.color_component(filter.pal[i], 0) < 1.0) {
palette_alpha = true;
break;
}
}
for (let i=0; i<filter.c.length; i++) {
let depth=0;
let fmt=0;
let c_str = filter.c[i].toLowerCase();
let type=0;
for (let j=0; j<comp_types.length; j++) {
let match=0;
if (c_str.startsWith(comp_types[j].name)) {
match = comp_types[j].name.length;
}
else if (!match && (typeof comp_types[j].alias == 'string') && c_str.startsWith(comp_types[j].alias)) {
match = comp_types[j].alias.length;
}
if (match) {
type = comp_types[j].type + 1;
if (typeof comp_types[j].fmt != 'undefined')
fmt = comp_types[j].fmt;
c_str = c_str.slice(match);
break;
}
}
if (!type) {
throw "Invalid component declaration "+c_str;
}
type--;
let align_size=0;
let ws = c_str.indexOf('+');
if (ws>0) {
let a_str = c_str.slice(ws + 1);
align_size = parseInt(a_str);
c_str = c_str.slice(0, ws);
if (align_size>max_align_size) max_align_size = align_size;
}
if (c_str=='') { fmt=0; depth = 8; }
else if (c_str=='flt') {fmt = 1; depth=32; }
else if (c_str=='sft') {fmt = 1; depth=16; }
else if (c_str=='dbl') {fmt = 1; depth=64; }
else if (c_str=='dbx') {fmt = 1; depth=128; }
else if (c_str=='cps') {fmt = 2; depth=32; }
else if (c_str=='cpf') {fmt = 2; depth=64; }
else if (c_str=='cpd') {fmt = 2; depth=128; }
else if (c_str=='cpx') {fmt = 2; depth=256; }
else if (!isNaN(c_str)) { fmt=0; depth = parseInt(c_str); }
if (!depth) {
throw "Invalid component declaration "+c_str;
}
if (fmt==2) {
throw "Complex types not supported yet";
}
if (align_size && (align_size*8<depth)) {
throw "Align size must be greater than component bit depth";
}
components.push({type: type, bits: depth, mode: fmt, align: align_size});
if (type==10) use_palette=true;
else if (type==11) use_fa=true;
else if ((type>=4) && (type<=6)) {
has_rgb = true;
}
else if (type==7) {
palette_alpha = false;
has_a=true;
}
else if (type==8) has_depth = true;
else if (type==9) has_disp = true;
}
if (use_palette && has_rgb) {
throw "Cannot use R,G or B component with palette (already RGB)"+c_str;
}
if (use_fa) {
if (has_rgb) {
throw "Cannot use R,G or B component with filter array (already RGB)"+c_str;
}
if (use_palette) {
throw "Cannot use palette with filter array"+c_str;
}
if (filter.fa.length != 4) {
throw "Filter array must be 2x2 size, got "+ JSON.stringify(filter.fa);
}
fa=[];
for (let i=0; i<filter.fa.length; i++) {
let c_str = filter.fa[i].toLowerCase();
let type=0;
if (c_str=='r') type=4;
else if (c_str=='g') type=5;
else if (c_str=='b') type=6;
else {
throw "Only RGB component types supported in filter array";
}
fa.push(type);
}
}
pixel = [];
for (let i=0; i<components.length; i++) {
pixel.push(0);
}
pix_len = pixel.length;
blocksize_bits = 8 * filter.block_size;
if (!filter.vsize.x || !filter.vsize.y)
throw "Image size shall not be 0";
if (!filter.tiles.x || !filter.tiles.y)
throw "Image tiling shall not be 0";
if (filter.vsize.x % filter.tiles.x)
throw "Image width not a multiple of num horizontal tiles";
if (filter.vsize.y % filter.tiles.y)
throw "Image height not a multiple of num vertical tiles";
if (filter.interleave==INTERLEAVE_MULTIY) {
let nb_y=0, nb_u=0, nb_v=0;
for (let i=0; i<components.length; i++) {
let comp = components[i];
if (comp.type==1) nb_y++;
if (comp.type==2) nb_u++;
if (comp.type==3) nb_v++;
}
if ((nb_u!=1) && (nb_v!=1))
throw "Component pattern must have 1 U/Cb and 1 V/Cr";
if (filter.sampling==SAMPLING_422) {
if (nb_y != 2)
throw "Component pattern must have 2 Y, not " + nb_y;
}
else if (filter.sampling==SAMPLING_411) {
if (nb_y != 4)
throw "Component pattern must have 4 Y, not " + nb_y;
}
else {
throw "Unsupported interleave type for sampling type " +filter.sampling;
}
if (components.length % 2)
throw "Component pattern must have an even number of components";
}
tile_width = filter.vsize.x / filter.tiles.x;
tile_height = filter.vsize.y / filter.tiles.y;
if (filter.sampling) {
switch (filter.interleave) {
case INTERLEAVE_COMPONENT:
case INTERLEAVE_MIXED:
case INTERLEAVE_MULTIY:
break;
default:
if (filter.sampling==SAMPLING_420)
throw "Sub-sampling must use interleave mode 0 or 2";
else
throw "Sub-sampling must use interleave mode 0, 2 or 5";
break;
}
if (filter.sampling==SAMPLING_420) {
if (tile_width%2)
throw "Tile width must be a multiple of 2 for YUV 420 sampling";
if (tile_height%2)
throw "Tile height must be a multiple of 2 for YUV 420 sampling";
}
else if (filter.sampling==SAMPLING_422) {
if (tile_width%2)
throw "Tile width must be a multiple of 2 for YUV 422 sampling";
}
else if (filter.sampling==SAMPLING_411) {
if (tile_width%4)
throw "Tile width must be a multiple of 4 for YUV 411 sampling";
}
}
let num_tiles = filter.tiles.x * filter.tiles.y;
row_align_bytes = tile_align_bytes = 0;
if ((filter.interleave==INTERLEAVE_TILE) && (num_tiles==1)) {
throw "Tile-Component interleaving requires more than one tile";
}
gen_yuv = false;
let nb_y=0;
for (let i=0; i<components.length; i++) {
let comp = components[i];
if (comp.type==1) { nb_y++; gen_yuv = true; }
else if ((comp.type==2) || (comp.type==3)) gen_yuv = true;
comp.row_align_size = 0;
comp.tile_align_size = 0;
}
if ((nb_y>1) && (filter.interleave!=INTERLEAVE_MULTIY))
print(GF_LOG_WARNING, 'Multiple Y component but interleaving is not 5, undefined results');
if ((filter.interleave==INTERLEAVE_COMPONENT) || (filter.interleave==INTERLEAVE_ROW) || (filter.interleave==INTERLEAVE_TILE)) {
out_size = 0;
let tile_size=0;
filter.write_pixel = writepix_interleave_0;
//write component-interleave (planar), get the size of each component
for (let i=0; i<components.length; i++) {
let comp = components[i];
comp.line_size = line_size([ {bits: comp.bits, align: comp.align} ] );
if ((comp.type==2) || (comp.type==3)) {
if ((filter.sampling==SAMPLING_422) || (filter.sampling==SAMPLING_420))
comp.line_size /= 2;
else if (filter.sampling==SAMPLING_411)
comp.line_size /= 4;
}
if (filter.row_align) {
while (comp.line_size % filter.row_align) {
comp.line_size++;
}
comp.row_align_size = comp.line_size;
}
comp.plane_size = comp.line_size * tile_height;
//for tile-component mode, tile align is on the plane size
if (filter.tile_align && (filter.interleave==INTERLEAVE_TILE)) {
while (comp.plane_size % filter.tile_align) {
comp.plane_size++;
}
comp.tile_align_size = comp.plane_size;
}
if ((comp.type==2) || (comp.type==3)) {
if (filter.sampling==SAMPLING_420)
comp.plane_size /= 2;
}
tile_size += comp.plane_size;
}
//except for tile-component mode, tile align is on cumulated size of all components in tile
if (filter.interleave!=INTERLEAVE_TILE) {
if (filter.tile_align) {
while (tile_size % filter.tile_align) {
tile_size++;
}
tile_align_bytes = tile_size;
} else {
tile_align_bytes = tile_size;
}
}
out_size += tile_size * num_tiles;
if (gen_yuv) {
if (filter.sampling==SAMPLING_420)
filter.write_pixel = writepix_interleave_0_420;
else if (filter.sampling==SAMPLING_422)
filter.write_pixel = writepix_interleave_0_422;
else if (filter.sampling==SAMPLING_411)
filter.write_pixel = writepix_interleave_0_411;
}
} else if (filter.interleave==INTERLEAVE_MIXED) {
out_size = 0;
if (!filter.sampling || (filter.sampling>SAMPLING_411))
throw "bad sampling type for mixed interleave mode";
if (filter.row_align)
throw "row align not supported in interleave=2 mode";
if (filter.tile_align)
throw "tile align not supported in interleave=2 mode";
let uv_found=false;
//write component-interleave (planar), get the size of each component
for (let i=0; i<components.length; i++) {
let comp = components[i];
comp.line_size = line_size([ {bits: comp.bits, align: comp.align} ] );
if ((comp.type==2) || (comp.type==3)) {
if (uv_found) {
comp.line_size = 0;
comp.plane_size = 0;
continue;
}
uv_found = true;
}
comp.plane_size = comp.line_size * tile_height;
if ((comp.type==2) || (comp.type==3)) {
if (filter.sampling==SAMPLING_420)
comp.plane_size /= 2;
}
out_size += comp.plane_size * num_tiles;
}
if (filter.sampling==SAMPLING_420)
filter.write_pixel = writepix_interleave_2_420;
else if (filter.sampling==SAMPLING_422)
filter.write_pixel = writepix_interleave_2_422;
else if (filter.sampling==SAMPLING_411)
filter.write_pixel = writepix_interleave_2_411;
} else if ((filter.interleave==INTERLEAVE_PIXEL) || (filter.interleave==INTERLEAVE_MULTIY)) {
let comp_bits=[];
for (let i=0; i<components.length; i++) {
let comp = components[i];
comp_bits.push( {bits: comp.bits, align: comp.align} );
comp.line_size = 0;
comp.plane_size = 0;
}
let lsize = line_size( comp_bits );
if (filter.pixel_size) {
let lsize2 = tile_width * filter.pixel_size;
if (lsize2<lsize) {
throw "Invalid pixel size, smaller than required bits";
}
lsize = lsize2;
}
if (filter.row_align) {
while (lsize % filter.row_align) {
lsize++;
}
row_align_bytes = lsize;
}
let psize = lsize * tile_height;
tile_align_bytes = psize;
if (filter.tile_align) {
while (psize % filter.tile_align) {
psize++;
}
tile_align_bytes = psize;
}
out_size = psize * num_tiles;
filter.write_pixel = writepix_interleave_1;
if (filter.interleave==INTERLEAVE_MULTIY) {
if (filter.sampling==SAMPLING_422) subsample_x = 2;
else if (filter.sampling==SAMPLING_411) subsample_x = 4;
filter.write_pixel = writepix_interleave_multiy;
}
} else {
throw "Unsupported interleave type";
}
for (let i=0; i<components.length; i++) {
let comp = components[i];
let nbb = comp.bits;
comp.max_val = 1;
//palette is an index - for other types we normalize between 0 and 1
if (comp.type != 10) {
while (nbb) {
comp.max_val *= 2;
nbb--;
}
comp.max_val--;
}
if (!min_bits || (min_bits>comp.bits))
min_bits = comp.bits;
//map monochrome, Y or R to first comp
if ((comp.type==0) || (comp.type==1) || (comp.type==4)) {
if (r_y_idx<0) r_y_idx = i;
comp.base_idx = r_y_idx;
}
else if ((comp.type==2) || (comp.type==5)) {
if (g_u_idx<0) g_u_idx = i;
comp.base_idx = g_u_idx;
}
else if ((comp.type==3) || (comp.type==6)) {
if (b_v_idx<0) b_v_idx = i;
comp.base_idx = b_v_idx;
}
//alpha
else if (comp.type==7) {
if (a_idx<0) a_idx = i;
comp.base_idx = a_idx;
}
//depth or disp
else if ((comp.type==8) || (comp.type==9)) {
if (d_idx<0) d_idx = i;
comp.base_idx = d_idx;
}
//palette
else if (comp.type==10) {
if (r_y_idx<0) r_y_idx = i;
comp.base_idx = r_y_idx;
}
//filter array
else if (comp.type==11) {
if (r_y_idx<0) r_y_idx = i;
comp.base_idx = r_y_idx;
}
else {
comp.base_idx = -1;
}
}
//print('Components setup: ' + JSON.stringify(components));
if (filter.cle && max_align_size) {
comp_le_buf = new ArrayBuffer(max_align_size);
comp_le_bs = new BS(comp_le_buf, true);
comp_le_view = new Uint8Array(comp_le_buf);
}
}
function get_pixel(i, j)
{
i -= cts * filter.vsize.x / max_cts;
if (i<0) i += filter.vsize.x;
if (filter.stereo && (i>=filter.vsize.x/2))
i-=filter.vsize.x/2;
let r=0, g=0, b=0;
let a=1;
let d=0;
if (tx) {
let t_x = Math.floor(i * tx.width / filter.vsize.x);
if (filter.stereo) t_x *= 2;
let t_y = Math.floor(j * tx.height / filter.vsize.y);
let tx_p = tx.get_pixel(t_x, t_y);
r = tx_p.r;
g = tx_p.g;
b = tx_p.b;
if (d_idx>=0)
d = tx_p.a;
else
a = tx_p.a;
} else {
let tidx;
if (filter.scpt) {
let tile_x = Math.floor(i / tile_width);
let tile_y = Math.floor(j / tile_height);
tidx = (tile_x * filter.tiles.x + tile_y) % filter.pal.length;
} else {
tidx = Math.floor(i / col_width);
if (filter.sq!=0) {
let ridx = Math.floor(filter.sq*j / col_width);
tidx += ridx;
while (tidx>=filter.pal.length)
tidx -= filter.pal.length;
while (tidx<0)
tidx += filter.pal.length;
}
}
if (use_palette) {
pixel[r_y_idx] = tidx;
return;
}
let scale = 1 - j/filter.vsize.y;
let col = filter.pal[tidx];
r = sys.color_component(col, 1);
g = sys.color_component(col, 2);
b = sys.color_component(col, 3);
if (a_idx>=0) {
if ((d_idx<0) && palette_alpha)
a = sys.color_component(col, 0);
else
a = scale;
} else if (filter.shade) {
r *= scale;
g *= scale;
b *= scale;
}
if (d_idx>=0) {
if (palette_alpha)
d = sys.color_component(col, 0);
else
d = scale;
}
}
if (gen_yuv) {
let y = 0.299*r + 0.587 * g + 0.114 * b;
let cb = -0.169*r - 0.331*g + 0.499*b + 0.5;
let cr = 0.499*r - 0.418*g - 0.0813*b + 0.5;
r = y;
g = cb;
b = cr;
}
else if (use_fa) {
let p_x = i % fa_width;
let p_y = j % fa_height;
let type = fa[p_x + p_y*fa_height];
if (type==5) r = g;
else if (type==6) r = b;
}
if (r_y_idx>=0) pixel[r_y_idx] = r;
if (g_u_idx>=0) pixel[g_u_idx] = g;
if (b_v_idx>=0) pixel[b_v_idx] = b;
if (a_idx>=0) pixel[a_idx] = a;
if (d_idx>=0) pixel[d_idx] = d;
}
//array of bitstream wrapper objects, one if pixel-interleave, one per comp if component-interleave (planar)
let bs_array = [];
function setup_block(bsw)
{
if (!blocksize_bits) return;
bsw.next_block_idx = 0;
bsw.nb_bits_block = 0;
bsw.block_comps = [];
if (filter.ble) {
bsw.le_buf = new ArrayBuffer(filter.block_size);
bsw.le_bs = new BS(bsw.le_buf, true);
bsw.le_view = new Uint8Array(bsw.le_buf);
}
let max_comp = Math.ceil(blocksize_bits / min_bits);
for (let i=0; i<max_comp; i++) {
bsw.block_comps.push({val: 0, component: null});
}
}
let tile_start_offset = 0;
function start_frame(data)
{
bs_array.length=0;
tile_start_offset=0;
//component interleave
if ((filter.interleave==INTERLEAVE_PIXEL) || (filter.interleave==INTERLEAVE_MULTIY)) {
let bsw = {};
bsw.bs = new BS(data, true);
setup_block(bsw);
bs_array.push(bsw);
bsw.init_offset = 0;
bsw.line_start_pos = 0;
bsw.row_align_size = row_align_bytes;
bsw.tile_align_size = 0;
bsw.tile_size = tile_align_bytes;
return;
}
//all other are planar modes
let offset = 0;
let tile_size=0;
let comp_row_size=0;
for (let i=0; i<components.length; i++) {
let comp = components[i];
let bsw = {};
bsw.bs = new BS(data, true);
bsw.bs.pos = offset;
bsw.init_offset = offset;
bsw.tile_start_pos = offset;
bsw.line_start_pos = offset;
bsw.row_align_size = comp.row_align_size;
bsw.tile_align_size = 0;
setup_block(bsw);
//row-interleave, first row of next comp starts after first row of this comp
if (filter.interleave==INTERLEAVE_ROW)
offset += comp.line_size;
//tile-interleave, first row of next comp starts after last value of last tile of this comp
else if (filter.interleave==INTERLEAVE_TILE) {
offset += comp.plane_size * filter.tiles.x * filter.tiles.y;
bsw.tile_align_size = comp.tile_align_size;
}
//otherwise next comp starts after last value for this comp
else
offset += comp.plane_size;
comp_row_size += comp.line_size;
tile_size += comp.plane_size;
bsw.plane_size = comp.plane_size;
bs_array.push(bsw);
//tile size is per comp in this mode, not for the cumulated size
if (filter.interleave==INTERLEAVE_TILE)
bsw.tile_size = bsw.plane_size;
}
if (tile_align_bytes)
tile_size = tile_align_bytes;
bs_array.forEach(bsw => {
bsw.comp_row_size = comp_row_size;
if (filter.interleave!=INTERLEAVE_TILE)
bsw.tile_size = tile_size;
});
}
function start_tile(tile_x, tile_y)
{
if ((filter.tiles.x==1) && (filter.tiles.y==1)) return;
tile_start_offset = tile_align_bytes * (tile_x + filter.tiles.x * tile_y);
bs_array.forEach(bsw => {
let offs = tile_align_bytes ? tile_start_offset : (bsw.tile_size * (tile_x + filter.tiles.x * tile_y));
offs += bsw.init_offset;
bsw.bs.pos = offs;
bsw.tile_start_pos = offs;
bsw.line_start_pos = offs;
});
}
function end_tile()
{
let max_pos=0;
let max_bs=null;
bs_array.forEach (bsw => {
//per comp tile alignment, only in INTERLEAVE_TILE mode
if (bsw.tile_align_size) {
let remain = bsw.bs.pos - bsw.tile_start_pos;
while (remain<bsw.tile_align_size) {
bsw.bs.put_u8(0);
remain++;
}
}
if (max_pos<bsw.bs.pos) {
max_pos = bsw.bs.pos;
max_bs = bsw.bs;
}
});
if (tile_align_bytes) {
let remain = max_pos - tile_start_offset;
if (remain<0) throw "Internal error in tile padding: remainder is negative " + remain;
if (remain>tile_align_bytes) throw "Internal error in tile padding: remainder " + remain + " is greater than tile size " + tile_align_bytes + ' tile start ' + tile_start_offset;
while (remain<tile_align_bytes) {
max_bs.put_u8(0);
remain++;
}
}
}
function end_line(last_line_in_tile)
{
bs_array.forEach (bsw => {
if (filter.block_size && bsw.next_block_idx) {
flush_block(bsw);
}
//rows of tiles shall be byte-aligned at the end of the row
bsw.bs.align();
if (bsw.row_align_size) {
let remain = bsw.bs.pos - bsw.line_start_pos;
while (remain<bsw.row_align_size) {
bsw.bs.put_u8(0);
remain++;
}
}
//not last line of tile, update offsets
//for last line we don't update, so that padding can be properly computed. We update the line offset at the beginning of next tile
if (!last_line_in_tile) {
//row interleave, seek to next row start
if (filter.interleave==INTERLEAVE_ROW) {
bsw.bs.pos = bsw.line_start_pos + bsw.comp_row_size;
}
bsw.line_start_pos = bsw.bs.pos;
}
});
}
function write_val(bs, val, comp)
{
if (comp.align) bs.align();
if (comp.mode==0) {
if (comp.align) {
if (comp_le_buf) {
comp_le_bs.pos = 0;
comp_le_bs.put_bits(val * comp.max_val, 8*comp.align);
for (let i=comp.align; i>0; i--) {
bs.put_u8(comp_le_view[i-1]);
}
} else {
bs.put_bits(val * comp.max_val, 8*comp.align);
}
}
else
bs.put_bits(val * comp.max_val, comp.bits);
}
else if (comp.mode==1) {
if (comp.bits==32)
bs.put_float(val);
else if (comp.bits==64)
bs.put_double(val);
}
}
function flush_block(bsw)
{
let i, bits=0;
for (i=0; i<bsw.next_block_idx; i++) {
let comp = bsw.block_comps[i].component;
if (!comp) break;
if (comp.align) {
while (bits % 8) bits++;
bits += comp.align*8;
} else {
bits += comp.bits;
}
}
let pad = blocksize_bits - bits;
if (pad<0)
throw "Wrong block size " + blocksize_bits + " vs " + bits + " nb comps " + bsw.next_block_idx;
if (filter.ble) {
let bs_le = bsw.le_bs;
bs_le.pos = 0;
if (!filter.pad_lsb)
bs_le.put_bits(0, pad);
for (i=0; i<bsw.next_block_idx; i++) {
//check block reverse
let b_idx = filter.br ? (bsw.next_block_idx - i - 1) : i;
let cval = bsw.block_comps[b_idx];
write_val(bs_le, cval.val, cval.component);
cval.component = null;
}
if (filter.pad_lsb)
bs_le.put_bits(0, pad);
for (i=filter.block_size; i>0; i--) {
bsw.bs.put_u8(bsw.le_view[i-1]);
}
} else {
if (!filter.pad_lsb)
bsw.bs.put_bits(0, pad);
//big endian, no block reverse
for (i=0; i<bsw.next_block_idx; i++) {
let cval = bsw.block_comps[i];
if (!cval.component) break;
write_val(bsw.bs, cval.val, cval.component);
cval.component = null;
}
if (filter.pad_lsb)
bsw.bs.put_bits(0, pad);
}
bsw.nb_bits_block=0;
bsw.next_block_idx = 0;
}
function push_val(bsw, c, comp)
{
if (filter.block_size) {
if (comp.align) {
while (bsw.nb_bits_block % 8)
bsw.nb_bits_block++;
}
let cbits = comp.align ? (comp.align*8) : comp.bits;
if (bsw.nb_bits_block + cbits > blocksize_bits) {
flush_block(bsw);
}
bsw.block_comps[bsw.next_block_idx].val = c;
bsw.block_comps[bsw.next_block_idx].component = comp;
bsw.next_block_idx++;
bsw.nb_bits_block += cbits;
} else {
write_val(bsw.bs, c, comp);
}
}
function writepix_interleave_0(x, y)
{
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
push_val(bs_array[i], c, comp);
}
}
function writepix_interleave_0_420(x, y)
{
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
if ((comp.type==2) || (comp.type==3)) {
if (x%2) continue;
if (y%2) continue;
}
push_val(bs_array[i], c, comp);
}
}
function writepix_interleave_0_422(x, y)
{
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
if ((comp.type==2) || (comp.type==3)) {
if (x%2) continue;
}
push_val(bs_array[i], c, comp);
}
}
function writepix_interleave_0_411(x, y)
{
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
if ((comp.type==2) || (comp.type==3)) {
if (x%4) continue;
}
push_val(bs_array[i], c, comp);
}
}
function writepix_interleave_1(x, y)
{
let bsw = bs_array[0];
let psize = bsw.bs.pos;
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
push_val(bsw, c, comp);
}
if (filter.pixel_size) {
if (filter.block_size) {
if (bsw.next_block_idx) {
flush_block(bsw);
}
}
else
bsw.bs.align();
psize = bsw.bs.pos - psize;
if (psize>filter.pixel_size) {
throw "Invalid pixel_size " + filter.pixel_size + ", less than total size of components " + psize;
}
while (psize<filter.pixel_size) {
bsw.bs.put_u8(0);
psize++;
}
}
}
function writepix_interleave_2_420(x, y)
{
let bs_uv=null;
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
let bs = bs_array[i];
if ((comp.type==2) || (comp.type==3)) {
if (x%2) continue;
if (y%2) continue;
if (!bs_uv) bs_uv = bs;
bs = bs_uv;
}
push_val(bs, c, comp);
}
}
function writepix_interleave_2_422(x, y)
{
let bs_uv=null;
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
let bs = bs_array[i];
if ((comp.type==2) || (comp.type==3)) {
if (x%2) continue;
if (!bs_uv) bs_uv = bs;
bs = bs_uv;
}
push_val(bs, c, comp);
}
}
function writepix_interleave_2_411(x, y)
{
let bs_uv=null;
for (let i=0; i<components.length; i++) {
let comp = components[i];
let c = (pix_len>i) ? pixel[i] : 0;
let bs = bs_array[i];
if ((comp.type==2) || (comp.type==3)) {
if (x%4) continue;
if (!bs_uv) bs_uv = bs;
bs = bs_uv;
}
push_val(bs, c, comp);
}
}
function writepix_interleave_multiy(x, y)
{
if (! (x % subsample_x)) {
for (let i=0; i<components.length; i++) {
let comp = components[i];
comp.stored = false;
if (comp.type==2) comp.store_val = pixel[g_u_idx];
else if (comp.type==3) comp.store_val = pixel[b_v_idx];
}
}
let types=[];
let bsw = bs_array[0];
let psize = bsw.bs.pos;
for (let i=0; i<components.length; i++) {
let comp = components[i];
if (comp.stored) continue;
//we already wrote a component of that type, we need next pixel
if (types.includes(comp.type) ) break;
let c;
if ((comp.type==2) || (comp.type==3)) {
c = comp.store_val;
} else if (comp.base_idx>=0) {
c = pixel[comp.base_idx];
} else {
c = 0;
}
push_val(bsw, c, comp);
comp.stored = true;
types.push(comp.type);
}
if (filter.pixel_size) {
if (filter.block_size) {
if (bsw.next_block_idx)
flush_block(bws)
}
else
bsw.bs.align();
psize = bsw.bs.pos - psize;
if (psize>filter.pixel_size) {
throw "Invalid pixel_size " + filter.pixel_size + ", less than total size of components " + psize;
}
while (psize<filter.pixel_size) {
bsw.bs.put_u8(0);
psize++;
}
}
}
filter.process = function()
{
if (!is_playing)
return GF_EOS;
if (cts >= max_cts) {
return GF_EOS;
}
// try {
let pck = pid.new_packet(out_size);
start_frame(pck.data);
for (let t_j=0; t_j<filter.tiles.y; t_j++) {
for (let t_i=0; t_i<filter.tiles.x; t_i++) {
let t_y = t_j * tile_height;
let t_x = t_i * tile_width;
start_tile(t_i, t_j);
for (let j=0; j<tile_height; j++) {
for (let i=0; i<tile_width; i++) {
get_pixel(t_x + i, t_y + j);
this.write_pixel(t_x + i, t_y + j);
}
end_line((j+1==tile_height) ? true : false);
}
end_tile();
}
}
/*set packet properties and send it*/
pck.cts = cts;
pck.sap = GF_FILTER_SAP_1;
pck.send();
cts ++;
if (cts == max_cts) {
pid.eos = true;
return GF_EOS;
}
print('Frame done ' + cts + '/' + max_cts + '\r');
return GF_OK;
/* } catch (e) {
print('Error writing frame ' + e);
is_playing=false;
return GF_EOS;
}
*/
}
filter.process_event = function(pid, evt)
{
if (evt.type == GF_FEVT_STOP) {
is_playing = false;
}
else if (evt.type == GF_FEVT_PLAY) {
is_playing = true;
}
}