/* Phaneron - Clustered, accelerated and cloud-fit video server, pre-assembled and in kit form. Copyright (C) 2020 Streampunk Media Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import { clContext as nodenCLContext, OpenCLBuffer } from 'nodencl' import { ClJobs } from '../clJobQueue' import ImageProcess from './imageProcess' import YadifCl from './yadifCl' // eslint-disable-next-line prettier/prettier export type YadifMode = 'send_frame' | 'send_field' | 'send_frame_nospatial' | 'send_field_nospatial' export type YadifConfig = { mode: YadifMode; tff: boolean } export default class Yadif { private readonly clContext: nodenCLContext private readonly clJobs: ClJobs private readonly width: number private readonly height: number private readonly config: YadifConfig private readonly interlaced: boolean private readonly sendField: boolean private readonly skipSpatial: boolean private yadifCl: ImageProcess | null = null private in: OpenCLBuffer[] = [] private out: OpenCLBuffer | null = null constructor( clContext: nodenCLContext, clJobs: ClJobs, width: number, height: number, config: YadifConfig, interlaced: boolean ) { this.clContext = clContext this.clJobs = clJobs this.width = width this.height = height this.config = config this.interlaced = interlaced this.sendField = this.interlaced && (this.config.mode === 'send_field' || this.config.mode === 'send_field_nospatial') this.skipSpatial = this.config.mode === 'send_frame_nospatial' || this.config.mode === 'send_field_nospatial' } async init(): Promise { this.yadifCl = new ImageProcess( this.clContext, new YadifCl(this.width, this.height), this.clJobs ) await this.yadifCl.init() } private async makeOutput(isSecond: boolean): Promise { const numBytesRGBA = this.width * this.height * 4 * 4 this.out = await this.clContext.createBuffer( numBytesRGBA, 'readwrite', 'coarse', { width: this.width, height: this.height }, `yadif ${isSecond ? '2' : '1'}` ) } private async runYadif(isSecond: boolean, sourceID: string): Promise { if (!this.yadifCl) throw new Error('Yadif needs to be initialised') // make a copy of in array for async release const srcs = this.in.slice(0) srcs.forEach((s) => s.addRef()) const out = this.out as OpenCLBuffer // out.loadstamp = srcs[1].loadstamp out.timestamp = srcs[1].timestamp + (isSecond ? 1 : 0) await this.yadifCl.run( { prev: srcs[0], cur: srcs[1], next: srcs[2], parity: (this.config.tff ? 1 : 0) ^ (!isSecond ? 1 : 0), tff: this.config.tff, skipSpatial: this.skipSpatial, output: out }, { source: sourceID, timestamp: out.timestamp }, () => srcs.forEach((s) => s.release()) ) await this.clJobs.runQueue({ source: sourceID, timestamp: out.timestamp }) } async processFrame( input: OpenCLBuffer, outputs: Array, sourceID: string ): Promise { if (!this.interlaced) { outputs.push(input) return } this.in.push(input) if (this.in.length < 3) { // complete any processing queued for input so the sources are released await this.clJobs.runQueue({ source: sourceID, timestamp: input.timestamp }) return } if (this.in.length > 3) { const old = this.in.shift() old?.release() } await this.makeOutput(false) await this.runYadif(false, sourceID) outputs.push(this.out as OpenCLBuffer) if (this.sendField) { await this.makeOutput(true) await this.runYadif(true, sourceID) outputs.push(this.out as OpenCLBuffer) } } release(): void { this.in.forEach((i) => i.release()) } }