Подтвердить что ты не робот

Выберите выходную строку в java для восьмиканальной звуковой карты

edit: Теперь я использую Jack (Jack Audio Connection Kit). См. Ответ ниже.

У меня есть звуковая карта на моей малиновой Pi с 8 выходными каналами (четыре стереоканала), карта Octosound. Я хочу, чтобы выбрать один из каналов для маршрутизации звука. С помощью этого кода я печатаю информацию о звуковой карте:

mixers = AudioSystem.getMixerInfo();
    for (Mixer.Info mixerInfo : mixers) {
        logger.debug("\n");
        logger.debug("Found Mixer: " + mixerInfo);

        Mixer m = AudioSystem.getMixer(mixerInfo);

        Line.Info[] sourceLines = m.getSourceLineInfo();
        for (Line.Info li : sourceLines) {
            logger.debug("Found source line: " + li + " " + li.getClass());

            if (li instanceof Port.Info) {
                Port.Info portInfo = (Port.Info) li;
                logger.debug("port found " + portInfo.getName() + " is source " + portInfo.isSource());
                sourceDataLines.add(portInfo);
            }

        }

        Line.Info[] targetLines = m.getTargetLineInfo();

        for (Line.Info li : targetLines) {
            logger.debug("Found target line: " + li + " " + li.getClass());
            outputLines.add(li);

            if (li instanceof Port.Info) {
                Port.Info portInfo = (Port.Info) li;
                logger.debug("port found " + portInfo.getName() + " is source " + portInfo.isSource());
                outputPorts.add(portInfo);
            }
        }
    }


private void lineClose(int soundPort) throws LineUnavailableException {
    Port.Info lineInfo = outputPorts.get(soundPort);
    Line line = (Port) AudioSystem.getLine(lineInfo);
    line.close();
}

private void lineOpen(int l) throws LineUnavailableException {

    for (int i = 0; i < outputPorts.size(); i++) {
        Port.Info lineInfo = outputPorts.get(i);
        Line line = (Port) AudioSystem.getLine(lineInfo);
        if (l == i) {
            line.open();
        } else {
            line.close();
        }
    }
}

Это результат, который я получаю:

Found Mixer: audioinjectoroc [default], version 4.9.41-v7+
Found source line: interface SourceDataLine supporting 84 audio formats, and buffers of at least 32 bytes class com.sun.media.sound.DirectAudioDevice$DirectDLI
Found source line: interface Clip supporting 84 audio formats, and buffers of at least 32 bytes class com.sun.media.sound.DirectAudioDevice$DirectDLI
Found target line: interface TargetDataLine supporting 84 audio formats, and buffers of at least 32 bytes class com.sun.media.sound.DirectAudioDevice$DirectDLI

Found Mixer: audioinjectoroc [plughw:0,0], version 4.9.41-v7+
Found source line: interface SourceDataLine supporting 96 audio formats, and buffers of at least 32 bytes class com.sun.media.sound.DirectAudioDevice$DirectDLI
Found source line: interface Clip supporting 96 audio formats, and buffers of at least 32 bytes class com.sun.media.sound.DirectAudioDevice$DirectDLI
Found target line: interface TargetDataLine supporting 96 audio formats, and buffers of at least 32 bytes class com.sun.media.sound.DirectAudioDevice$DirectDLI

Found Mixer: Port audioinjectoroc [hw:0], version 4.9.41-v7+
Found source line: ADC1 source port class com.sun.media.sound.PortMixer$PortInfo
port found ADC1 is source true
Found source line: ADC2 source port class com.sun.media.sound.PortMixer$PortInfo
port found ADC2 is source true
Found source line: ADC3 source port class com.sun.media.sound.PortMixer$PortInfo
port found ADC3 is source true
Found target line: DAC1 target port class com.sun.media.sound.PortMixer$PortInfo
port found DAC1 is source false
Found target line: DAC2 target port class com.sun.media.sound.PortMixer$PortInfo
port found DAC2 is source false
Found target line: DAC3 target port class com.sun.media.sound.PortMixer$PortInfo
port found DAC3 is source false
Found target line: DAC4 target port class com.sun.media.sound.PortMixer$PortInfo
port found DAC4 is source false

Теперь это код, который я использую для вывода звука из wav файла:

   String path = soundDirectory + soundUrl;
   InputStream is = new FileInputStream(path);
   BufferedInputStream bis = new BufferedInputStream(is);
   AudioInputStream inputStream = AudioSystem.getAudioInputStream(bis);
   AudioFormat format = inputStream.getFormat();

   Mixer.Info mi = mixers[0];

   SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getSourceDataLine(format,mi);
   sourceDataLine.open(format);
   sourceDataLine.start();
   byte[] buf = new byte[1024];
   int bytesRead;
   while ((bytesRead = inputStream.read(buf)) != -1){
       sourceDataLine.write(buf, 0, bytesRead);
   }
   inputStream.close();

   sourceDataLine.drain();
   sourceDataLine.stop();
   sourceDataLine.close();

   lineClose(soundPort);

Я пробовал несколько вещей, но во всех случаях звук выходит из всех выходных.

4b9b3361

Ответ 1

Я нашел решение самостоятельно. Теперь я использую Jack (Jack Audio Connection Kit, см. здесь. Это немного хлопот, чтобы Джек играл на Малиновой Пи. Там хорошая информация здесь.

Я использую JnaJack с интерфейсом между Java и Jack.

Вы не можете запустить Jack on Raspbian из коробки. У Debian Wheezy был патч, но у Raspbian Jessie, похоже, нет этого. Поэтому вам нужно создать версию Jackd2, которая не использует DBus. Здесь объясняется, как построить Jackd2 без DBus. Там хватка: все, что вам нужно сделать, это удалить две строки, которые относятся к DBus. Все остальное, что они говорят вам об исправлении, по-прежнему исправлено в Raspbian по умолчанию, или так кажется. Эти строки необходимо заменить: после того, как вы загрузили источник, в debian/rules change

waf-configure-options += $(if $(filter linux,$(DEB_HOST_ARCH_OS)),--alsa --dbus) 
became
waf-configure-options += $(if $(filter linux,$(DEB_HOST_ARCH_OS)),--alsa)

dh_install -pjackd2 debian/tmp/usr/share/dbus-1/*
became
#dh_install -pjackd2 debian/tmp/usr/share/dbus-1/*

Я изменил пример SimpleAudioClient, найденный в источнике JnaJack:

public class SimpleAudioClient {

    private boolean autoconnect = true;
    private JackClient client;
    private Processor processor;
    private Callback callback;
    private ShutDownHook shutDownHook;
    private JackPort[] inputPorts;
    private JackPort[] outputPorts;
    private FloatBuffer[] inputBuffers;
    private FloatBuffer[] outputBuffers;
    private float samplerate;
    private int buffersize;
    private volatile boolean active;

    private Logger logger = Logger.getLogger(getClass().getSimpleName());
    private int channelNumber = 0;

    public SimpleAudioClient() throws JackException {

        Jack jack = Jack.getInstance();
        logger.debug("Jack instance " + jack.toString());
        EnumSet<JackOptions> options = EnumSet.of(JackOptions.JackNoStartServer);
        EnumSet<JackStatus> status = EnumSet.noneOf(JackStatus.class);
        try {
            client = jack.openClient("jna_jack", options, status);
        } catch (JackException ex) {
            System.out.println("ERROR : Status : " + status);
            throw ex;
        }

        String[] inputs = new String[0];
        inputPorts = new JackPort[inputs.length];
        EnumSet<JackPortFlags> flags = EnumSet.of(JackPortFlags.JackPortIsInput);
        for (int i = 0; i < inputs.length; i++) {
            //inputPorts[i] = client.registerPort(inputs[i], JackPortType.AUDIO, flags);
        }

        String[] outputs = new String[]{"playback_1", "playback_2", "playback_3", "playback_4", "playback_5", "playback_6", "playback_7", "playback_8"};
        outputPorts = new JackPort[outputs.length];
        flags = EnumSet.of(JackPortFlags.JackPortIsOutput);
        for (int i = 0; i < outputs.length; i++) {
            outputPorts[i] = client.registerPort(outputs[i], JackPortType.AUDIO, flags);
        }

        processor = new SineAudioSource();

        this.inputBuffers = new FloatBuffer[inputPorts.length];
        this.outputBuffers = new FloatBuffer[outputPorts.length];
        this.callback = new Callback();
        this.shutDownHook = new ShutDownHook();
        client.onShutdown(shutDownHook);

        for (JackPort port : inputPorts) {
            logger.debug("input port " + port.getType() + " " + port.getName());
        }

        for (JackPort port : outputPorts) {
            logger.debug("output port " + port.getType() + " " + port.getName());
        }
    }

    public void activate(int channelNr) throws JackException {

        this.channelNumber = channelNr;

        try {
            samplerate = client.getSampleRate();
            System.out.println("Sample rate = " + samplerate);
            buffersize = client.getBufferSize();
            System.out.println("Buffersize = " + buffersize);
            processor.setup(samplerate, buffersize);
            active = true;
            client.setProcessCallback(callback);
            client.activate();
            if (autoconnect) {
                doAutoconnect();
            }
        } catch (Exception ex) {
            active = false;
            throw new JackException("Could not activate Jack client");
        }
    }

    private void doAutoconnect() throws JackException {
        Jack jack = Jack.getInstance();
        String[] physical = jack.getPorts(client, null, JackPortType.AUDIO,
                EnumSet.of(JackPortFlags.JackPortIsInput, JackPortFlags.JackPortIsPhysical));
        int count = Math.min(outputPorts.length, physical.length);
        for (int i = 0; i < count; i++) {
            logger.debug("output port " + outputPorts[i].getName());
            jack.connect(client, outputPorts[i].getName(), physical[i]);
        }
        physical = jack.getPorts(client, null, JackPortType.AUDIO,
                EnumSet.of(JackPortFlags.JackPortIsOutput, JackPortFlags.JackPortIsPhysical));
        count = Math.min(inputPorts.length, physical.length);
        for (int i = 0; i < count; i++) {
            logger.debug("input port " + inputPorts[i].getName());
            //jack.connect(client, physical[i], inputPorts[i].getName());
        }
    }

    public void shutdown() {
        active = false;
        client.deactivate();
        client.close();
    }

    private void processBuffers(int nframes) {
        for (int i = 0; i < inputPorts.length; i++) {
            inputBuffers[i] = inputPorts[i].getFloatBuffer();
        }
        for (int i = 0; i < outputPorts.length; i++) {
            outputBuffers[i] = outputPorts[i].getFloatBuffer();
        }
        processor.process(channelNumber, inputBuffers, outputBuffers);
    }

    private class Callback implements JackProcessCallback {

        public boolean process(JackClient client,final int nframes) {

            if (!active) {
                return false;
            } else {
                try {
                    processBuffers(nframes);
                    return true;
                } catch (Exception ex) {
                    System.out.println("ERROR : " + ex);
                    active = false;
                    return false;
                }

            }
        }
    }

    private class ShutDownHook implements JackShutdownCallback {

        public void clientShutdown(JackClient client) {
            active = false;
            processor.shutdown();
        }
    }

    public static interface Processor {

        public void setup(float samplerate, int buffersize);

        public void process(int channelNumber, FloatBuffer[] inputs, FloatBuffer[] outputs);

        public void shutdown();
    }

    /**
     * Create a SimpleAudioClient.
     *
     * @return client
     * @throws org.jaudiolibs.jnajack.JackException
     */
    public static SimpleAudioClient create(
    ) throws JackException {

        return new SimpleAudioClient();
    }
}

Я изменил SineAudioClient из кода примера на это:

public class SineAudioSource implements SimpleAudioClient.Processor {

    private final static int TABLE_SIZE = 200;
    private int left_phase = 0;
    private int right_phase = 0;
    private float[] data;

    public void setup(float samplerate, int buffersize) {
        data = new float[TABLE_SIZE];
        for (int i = 0; i < TABLE_SIZE; i++) {
            data[i] = (float) (0.2 * Math.sin(((double) i / (double) TABLE_SIZE) * Math.PI * 2.0));
        }
    }

    public void process(int channelNumber, FloatBuffer[] inputs, FloatBuffer[] outputs) {

        FloatBuffer left = outputs[channelNumber];
        int size = left.capacity();
        for (int i = 0; i < size; i++) {
            left.put(i, data[left_phase]);
            left_phase += 2;
            right_phase += 3;
            if (left_phase >= TABLE_SIZE) {
                left_phase -= TABLE_SIZE;
            }
        }
    }

    public void shutdown() {
        System.out.println("Sine Audio Source shutdown");
    }
}

чтобы он воспроизводил синусоидальную волну в течение двух секунд в каждом из восьми каналов звуковой карты. Я не пробовал входные каналы (пока), я читал, что трудно заставить Джека работать на Raspbian, когда активируются вход и выход.

Я запускаю Jack до запуска моего приложения, команда запуска

/usr/bin/jackd -dalsa -dhw:audioinjectoroc -r48000 -p1024 -n2 -P &

Журнал, когда вы запускаете гнездо, должен показывать

creating alsa driver ... hw:audioinjectoroc|-|1024|2|48000|0|0|nomon|swmeter|-|32bit

где "audioinjector" - это имя звуковой карты. Если он показывает

...hw:audioinjectoroc|hw:audioinjectoroc|1024 ...

тогда у вас будут проблемы с подключением к нему.

Вы можете просмотреть настройки Джека с помощью QJackCtl, который вы можете запустить на своем малине Pi и получить доступ с X-сервера с другого компьютера. Мне не удалось запустить X Windows на Pi.

Если вы хотите играть в wav файлы через Jack, этот является хорошим примером того, как читать wav файл и подавать его на разъем,

Изменить: пример является хорошей отправной точкой, но вам нужно внести несколько изменений. Лучше всего открыть все порты, которые вы планируете использовать, вызвать client.activate(), а в JackCallback проложить каналы из вашего аудиофайла в соответствующие каналы на звуковой карте. Вы можете использовать qjackctl, чтобы узнать, что происходит в Джеке.