/*
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 } from 'nodencl'
import { logging as ffmpegLogging } from 'beamcoder'
import { ClProcessJobs } from './clJobQueue'
import { start, processCommand } from './AMCP/server'
import { Commands } from './AMCP/commands'
import { Channel } from './channel'
import { BasicCmds } from './AMCP/basicCmds'
import { MixerCmds } from './AMCP/mixerCmds'
import { ConsumerRegistry } from './consumer/consumer'
import { ProducerRegistry } from './producer/producer'
import { ConsumerConfig, VideoFormats } from './config'
import readline from 'readline'
import { Osc, OscConfig } from './osc/osc'
import { Heads, HeadsConfig } from './heads/heads'
class Config {
private readonly videoFormats: VideoFormats
readonly consumers: ConsumerConfig[]
readonly oscConfig: OscConfig
readonly headsConfig: HeadsConfig
constructor() {
this.videoFormats = new VideoFormats()
this.consumers = [
{
format: this.videoFormats.get('1080i5000'),
devices: [
{ name: 'decklink', deviceIndex: 1, embeddedAudio: true }
// { name: 'screen', deviceIndex: 0 }
]
},
{
format: this.videoFormats.get('1080i5000'),
devices: [
// { name: 'decklink', deviceIndex: 2, embeddedAudio: true }
]
},
{
format: this.videoFormats.get('1080i5000'),
devices: [
// { name: 'decklink', deviceIndex: 3, embeddedAudio: true }
]
},
{
format: this.videoFormats.get('1080i5000'),
devices: [
// { name: 'decklink', deviceIndex: 4, embeddedAudio: true }
]
}
]
this.oscConfig = {
serverPort: 9876,
clientPort: 9877,
clientAddr: '192.168.1.141'
}
this.headsConfig = {
channel: 1,
controls: { load: '/1/push1', take: '/1/push3' },
url: 'heads.json'
}
}
}
const initialiseOpenCL = async (): Promise => {
const platformIndex = 0
const deviceIndex = 0
const clContext = new nodenCLContext({
platformIndex: platformIndex,
deviceIndex: deviceIndex,
overlapping: true
})
await clContext.initialise()
const platformInfo = clContext.getPlatformInfo()
console.log(
`OpenCL accelerator running on device from vendor '${platformInfo.vendor}', type '${platformInfo.devices[deviceIndex].type}'`
)
return clContext
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'AMCP> '
})
rl.on('line', async (input) => {
if (input === 'q' || input === 'Q') {
process.kill(process.pid, 'SIGTERM')
}
if (input !== '') {
console.log(`AMCP received: ${input}`)
const result = await processCommand(input.toUpperCase().match(/"[^"]+"|""|\S+/g))
console.log('AMCP returned:', result)
}
rl.prompt()
})
rl.on('SIGINT', () => {
process.kill(process.pid, 'SIGTERM')
})
console.log('\nWelcome to Phaneron\n')
const commands = new Commands()
export const channels: Channel[] = []
initialiseOpenCL()
.then(async (clContext) => {
const consReg = new ConsumerRegistry(clContext)
const prodReg = new ProducerRegistry(clContext)
const clProcessJobs = new ClProcessJobs(clContext)
const clJobs = clProcessJobs.getJobs()
const config = new Config()
let osc: Osc | undefined
if (config.oscConfig) osc = new Osc(config.oscConfig)
let numThreads = 4
const threadsStr = process.env.UV_THREADPOOL_SIZE
if (threadsStr) numThreads = +threadsStr
console.log(`Using ${numThreads} worker threads`)
config.consumers.forEach((consConfig, i) => {
try {
channels.push(
new Channel(clContext, `ch${i + 1}`, i + 1, consConfig, consReg, prodReg, clJobs)
)
} catch (err) {
console.log(`Error creating configured channel ${i + 1}: ${err.message}`)
}
})
if (channels.length === 0) console.error('Error: No channels found!!')
await Promise.all(channels.map((chan) => chan.initialise()))
if (osc && config.headsConfig) {
const hc = config.headsConfig
const heads = new Heads(osc, channels[hc.channel - 1], hc.controls)
if (hc.url) heads.loadSpec(hc.url)
}
commands.add(new BasicCmds(consReg, channels, clJobs).list())
commands.add(new MixerCmds(channels).list())
ffmpegLogging('warning')
// setInterval(() => clContext.logBuffers(), 2000)
})
.then(() => start(commands))
.then(console.log, console.error)
.then(() => rl.prompt())
.catch(console.error)