org.jaudiolibs.audioservers.jack.JackAudioServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of audioservers-jack Show documentation
Show all versions of audioservers-jack Show documentation
>JACK based implementation of the AudioServer API using JNAJack
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2020 Neil C Smith.
*
* This code 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.1 of the License,
* or (at your option) any later version.
*
* This code 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 work; if not, see http://www.gnu.org/licenses/
*
*/
package org.jaudiolibs.audioservers.jack;
import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jaudiolibs.audioservers.AudioClient;
import org.jaudiolibs.audioservers.AudioConfiguration;
import org.jaudiolibs.audioservers.AudioServer;
import org.jaudiolibs.audioservers.ext.ClientID;
import org.jaudiolibs.audioservers.ext.Connections;
import org.jaudiolibs.jnajack.Jack;
import org.jaudiolibs.jnajack.JackClient;
import org.jaudiolibs.jnajack.JackException;
import org.jaudiolibs.jnajack.JackOptions;
import org.jaudiolibs.jnajack.JackPort;
import org.jaudiolibs.jnajack.JackPortFlags;
import org.jaudiolibs.jnajack.JackPortType;
import org.jaudiolibs.jnajack.JackProcessCallback;
import org.jaudiolibs.jnajack.JackShutdownCallback;
import org.jaudiolibs.jnajack.JackStatus;
/**
* Implementation of AudioServer using Jack (via JNAJack)
*/
public class JackAudioServer implements AudioServer {
private final static Logger LOG = Logger.getLogger(JackAudioServer.class.getName());
private enum State {
New, Initialising, Active, Closing, Terminated
};
private final ClientID clientID;
private final AudioClient client;
private final AtomicReference state;
private final Connections connections;
private AudioConfiguration context;
private Jack jack;
private JackClient jackclient;
private JackPort[] inputPorts;
private List inputBuffers;
private JackPort[] outputPorts;
private List outputBuffers;
JackAudioServer(
ClientID id,
Connections connections,
AudioConfiguration ctxt,
AudioClient client) {
this.clientID = id;
this.connections = connections;
this.context = ctxt;
this.client = client;
state = new AtomicReference<>(State.New);
}
@Override
public void run() throws Exception {
if (!state.compareAndSet(State.New, State.Initialising)) {
throw new IllegalStateException();
}
try {
initialise();
} catch (Exception ex) {
state.set(State.Terminated);
closeAll();
client.shutdown();
throw ex;
}
if (state.compareAndSet(State.Initialising, State.Active)) {
runImpl();
}
closeAll();
client.shutdown();
state.set(State.Terminated);
}
private void initialise() throws Exception {
jack = Jack.getInstance();
EnumSet options
= (connections.isConnectInputs() || connections.isConnectOutputs())
? EnumSet.noneOf(JackOptions.class)
: EnumSet.of(JackOptions.JackNoStartServer);
EnumSet status = EnumSet.noneOf(JackStatus.class);
try {
jackclient = jack.openClient(clientID.getIdentifier(), options, status);
} catch (JackException ex) {
LOG.log(Level.FINE, "Exception creating JACK client\nStatus set\n{0}", status);
throw ex;
}
LOG.log(Level.FINE, "JACK client created\nStatus set\n{0}", status);
int count = context.getInputChannelCount();
inputPorts = new JackPort[count];
inputBuffers = Arrays.asList(new FloatBuffer[count]);
for (int i = 0; i < count; i++) {
inputPorts[i] = jackclient.registerPort("Input_" + (i + 1),
JackPortType.AUDIO, JackPortFlags.JackPortIsInput);
}
count = context.getOutputChannelCount();
outputPorts = new JackPort[count];
outputBuffers = Arrays.asList(new FloatBuffer[count]);
for (int i = 0; i < count; i++) {
outputPorts[i] = jackclient.registerPort("Output_" + (i + 1),
JackPortType.AUDIO, JackPortFlags.JackPortIsOutput);
}
}
private void runImpl() {
try {
// make sure context is correct.
ClientID id = clientID;
String actualID = jackclient.getName();
if (!id.getIdentifier().equals(actualID)) {
id = new ClientID(actualID);
}
context = new AudioConfiguration(jackclient.getSampleRate(),
inputPorts.length,
outputPorts.length,
jackclient.getBufferSize(),
id,
connections,
jackclient);
LOG.log(Level.FINE, "Configuring AudioClient\n{0}", context);
client.configure(context);
jackclient.setProcessCallback(new Callback());
jackclient.onShutdown(new ShutDownHook());
jackclient.activate();
if (connections.isConnectInputs()) {
connectInputs();
}
if (connections.isConnectOutputs()) {
connectOutputs();
}
while (state.get() == State.Active) {
Thread.sleep(100); // @TODO switch to wait()
}
} catch (Exception ex) {
LOG.log(Level.SEVERE, "", ex);
shutdown();
}
}
private void connectInputs() {
try {
String[] ins = jack.getPorts(jackclient, null, JackPortType.AUDIO,
EnumSet.of(JackPortFlags.JackPortIsOutput, JackPortFlags.JackPortIsPhysical));
int inCount = Math.min(ins.length, inputPorts.length);
for (int i = 0; i < inCount; i++) {
jack.connect(jackclient, ins[i], inputPorts[i].getName());
}
} catch (JackException ex) {
LOG.log(Level.SEVERE, "", ex);
}
}
private void connectOutputs() {
try {
String[] outs = jack.getPorts(jackclient, null, JackPortType.AUDIO,
EnumSet.of(JackPortFlags.JackPortIsInput, JackPortFlags.JackPortIsPhysical));
int outCount = Math.min(outs.length, outputPorts.length);
for (int i = 0; i < outCount; i++) {
jack.connect(jackclient, outputPorts[i].getName(), outs[i]);
}
} catch (JackException ex) {
LOG.log(Level.SEVERE, "", ex);
}
}
private void processBuffers(int nframes) {
for (int i = 0; i < inputPorts.length; i++) {
inputBuffers.set(i, inputPorts[i].getFloatBuffer());
}
for (int i = 0; i < outputPorts.length; i++) {
outputBuffers.set(i, outputPorts[i].getFloatBuffer());
}
client.process(System.nanoTime(), inputBuffers, outputBuffers, nframes);
}
private class Callback implements JackProcessCallback {
@Override
public boolean process(JackClient client, int nframes) {
if (state.get() != State.Active) {
return false;
} else {
try {
processBuffers(nframes);
return true;
} catch (Exception ex) {
shutdown();
return false;
}
}
}
}
private class ShutDownHook implements JackShutdownCallback {
@Override
public void clientShutdown(JackClient client) {
shutdown();
}
}
@Override
public AudioConfiguration getAudioContext() {
return context;
}
@Override
public boolean isActive() {
State st = state.get();
return (st == State.Active || st == State.Closing);
}
@Override
public void shutdown() {
State st;
do {
st = state.get();
if (st == State.Terminated || st == State.Closing) {
break;
}
} while (!state.compareAndSet(st, State.Closing));
}
private void closeAll() {
try {
jackclient.close();
} catch (Throwable t) {
LOG.log(Level.WARNING, "", t);
}
}
}