来自套接字的“无限”音频输入流

14

我在从 Socket 创建 AudioInputStream 方面遇到了问题。以下是重要部分:

public class SoundStream extends Thread {
    private int port;
    private String IP;
    private Socket socket;

    private SoundObject soundObject;

    private OpenAL openAL;
    private Source source;

    private boolean run = true;

    public SoundStream(int port, String IP, SoundObject soundObject) {
        this.soundObject = soundObject;
        this.port = port;
        this.IP = IP;
    }

    public void run() {
        try {
            this.socket = new Socket(this.IP, this.port);
            this.openAL = new OpenAL();
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.mainCycleMethod();
    }

    private void mainCycleMethod() {
        while (run) {
            this.soundObject.blockAndWait();
            switch (this.soundObject.getAndResetEvent()) {
                case 0:
                    this.run = false;
                    this.close();
                    break;
                case 1:
                    this.setPitch();
                    break;
                case 2:
                    this.closeSource();
                    this.play();
                    break;
                case 3:
                    this.pause(true);
                    break;
                case 4:
                    this.pause(false);
                    break;
            }
        }
    }

    private BufferedInputStream getInputStream() throws Exception {
        return new BufferedInputStream(socket.getInputStream());
    }

    private void setPitch() {
        if(this.source != null) {
            try {
                this.source.setPitch(this.soundObject.getPitch());
            } catch (ALException e) {
                e.printStackTrace();
            }
        }
    }

    private void play() {
        try {
            AudioInputStream audioInputStream = new AudioInputStream(this.getInputStream(), this.soundObject.getAudioFormat(), AudioSystem.NOT_SPECIFIED);
//            AudioInputStream audioInputStream_tmp = AudioSystem.getAudioInputStream(this.getInputStream());
//            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(this.soundObject.getAudioFormat(), audioInputStream_tmp);
            this.source = openAL.createSource(audioInputStream);
            this.source.setGain(1f);
            this.source.play();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void close() {
        this.closeSource();
        this.openAL.close();
        try {
            this.socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void closeSource() {
        if(this.source!=null) {
            this.source.close();
        }
    }

    private void pause(boolean pause) {
        if(this.source != null) {
            try {
                if (pause) {
                    this.source.pause();
                } else {
                    this.source.play();
                }
            } catch (ALException ex) {
                ex.printStackTrace();
            }
        }
    }
}


public class SoundObject extends AbstractEventObject {
    public AudioFormat getAudioFormat() {
        boolean signed = false;
        //true,false
        boolean bigEndian = false;
        //true,false
        return new AudioFormat(this.frequency, this.bits, this.channels, signed, bigEndian);
    }
.
.
.
.
}

这段代码在这一行会抛出UnsupportedAudioFileException异常:

AudioInputStream audioInputStream_tmp = AudioSystem.getAudioInputStream(this.getInputStream());

但是当我使用这段代码时:

AudioInputStream audioInputStream = new AudioInputStream(this.getInputStream(), this.soundObject.getAudioFormat(), 100000);

它会在加载完那100000个采样帧到AudioInputStream之后才播放声音。当它播放完所有的100000个帧时,它就结束了。

我想如果我能在第一次AudioInputStream初始化期间直接将AudioFormat作为参数传递,我可能会解决这个问题,但似乎不可能。 我从服务器接收音频格式规范。

我认为一个可能的解决方案是创建一个数据行,我可以将其作为参数传递给AudioInputStream构造函数。然而,我不确定如何直接将数据从套接字传输到数据行。我知道一种使用无限循环的解决方案,它读取数据并将它们写入数据行中。但这似乎是浪费资源的。是否有更直接的方法?

我希望能够使用java-openAL库来解决这个问题,因为我需要改变速度,希望不必自己实现。

谢谢


1
作为第一步,你可以尝试使用AudioInputStream audioInputStream = new AudioInputStream(this.getInputStream(), this.soundObject.getAudioFormat(), AudioSystem.NOT_SPECIFIED);,然后看看会发生什么。 - Roman
这并没有解决我的问题,因为它被openAL.createSource(audioInputStream)方法阻塞了。它可能在等待整个InputStream完成。谢谢。 - Jan Beneš
openAL 是什么?你能展示完整的源代码吗(可能是 [mcve])? - Roman
我修改了我的帖子,现在有整个代码。openAL只是OpenAL类的初始化。@Roman - Jan Beneš
嗯 - 我会建议创建一个实现 Target DataLine 的类,看看能否解决问题。源和目标的命名有些奇怪... - Jan
显示剩余6条评论
1个回答

1

问题终于解决了。事实证明,java-openAL内置了流支持,但GitHub上的文档并没有提到,所以我一开始没注意到。在Source类中有一个createOutputStream方法,它返回OutputStream。你可以直接向OutputStream写入字节。

这里是我的代码:

在这个片段中,我初始化OpenAL:

public void run() {
    try {
        this.socket = new Socket(this.IP, this.port);
        this.openAL = new OpenAL();
    } catch (Exception ex) {
        Log.severe(ex.toString());
    }
    this.mainCycleMethod();
}

当InputStream可用时,这是我的播放方法:

private void play() {
    try {
        this.source = openAL.createSource();
        this.outputWriter = new OutputWriter(this.socket.getInputStream(), this.source, this.soundObject.getAudioFormat());
        this.source.setGain(1f);
        this.outputWriter.start();
    } catch (Exception ex) {
        Log.severe(ex.toString());
    }
}

您需要使用不带参数的createSource方法,它会返回新的Source实例。不要在source上调用play方法,因为它由createOutputStream方法返回的SourceOutputStream类实例处理。手动调用play方法没有问题,但当缓冲区为空时,我曾经有过不好的经历。基本上,当您开始将数据流传输到OpenAL时,它不会在以后开始播放。

这是我的OutputWriter代码,它负责将InputStream中的字节传递给OutputStream:

package cz.speechtech.sound;

import org.urish.openal.ALException;
import org.urish.openal.Source;

import javax.sound.sampled.AudioFormat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Created by honza on 16.12.15.
 */
public class OutputWriter extends Thread {
    private InputStream inputStream;
    private OutputStream outputStream;

    private int STREAMING_BUFFER_SIZE = 24000;
    private int NUMBER_OF_BUFFERS = 4;

    private boolean run = true;

    public OutputWriter(InputStream inputStream, Source source, AudioFormat audioFormat) {
        this.inputStream = inputStream;
        try {
            this.outputStream = source.createOutputStream(audioFormat, this.NUMBER_OF_BUFFERS, 1024);
        } catch (ALException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        byte[] buffer = new byte[this.STREAMING_BUFFER_SIZE];
        int i;
        try {
            Thread.sleep(1000); // Might cause problems
            while (this.run) {
                i = this.inputStream.read(buffer);
                if (i == -1) break;
                outputStream.write(buffer, 0, i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void stopRunning() {
        this.run = false;
        try {
            this.outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

祝你拥有愉快的一天。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接