【问题标题】:Play back sound in Kotlin/Java directly from generated sound array直接从生成的声音数组播放 Kotlin/Java 中的声音
【发布时间】:2020-01-04 18:46:52
【问题描述】:

我正在寻找一种在 Kotlin/Java 中生成和播放声音的方法。我一直在搜索并尝试了不同的解决方案,但并不是很令人满意。 我不是在寻找可以让我将混响添加到现有声音的 Java Control 类,也不是在寻找让我进行 MIDI 排序的 javax.sound.midi 包。相反,我想通过以下方式从头开始构建声音作为声音向量/列表:

fun createSinWaveBuffer(freq: Double, ms: Int, sampleRate: Int = 44100): ByteArray {
    val samples = (ms * sampleRate / 1000)
    val output = ByteArray(samples)
    val period = sampleRate.toDouble() / freq
    for (i in output.indices) {
        val angle = 2.0 * Math.PI * i.toDouble() / period
        output[i] = (Math.sin(angle) * 127f).toByte()
    }
    //output.forEach { println(it) }
    return output
}

然后我想播放声音,并让扬声器的实际输出与发送到函数的输入参数相匹配,包括频率、长度等。当然,创建两个像这样具有不同频率的声音向量应该求和将它们放在一起或至少取平均值应该会导致两个音调同时播放。

这在matlab中很简单,如果你有一个向量y,像这样

t=0:1/samplerate:duration;
y=sin(2*pi*freq*t);

做事

sound(y,sampleRate)

虽然在 Java 中可能没有那么简单或干净的解决方案,但我仍然觉得应该可以播放自定义声音。

在这里和其他地方搜索了一下之后,这是我现在正在尝试的最干净的解决方案之一(即使它使用 sun.audio,其他建议也更加混乱):

import sun.audio.AudioPlayer
import sun.audio.AudioDataStream
import sun.audio.AudioData

private fun playsound(sound: ByteArray) {
    val audiodata = AudioData(sound)
    val audioStream = AudioDataStream(audiodata)
    AudioPlayer.player.start(audioStream)
}

但是 playsound(createSinWaveBuffer(440.0, 10000, 44100)) 在我的扬声器中听起来不正确。听起来波涛汹涌,不是 440 赫兹,不是纯正弦波,也不是十秒。 我错过了什么?

【问题讨论】:

  • 如果我对这个 API 的简单搜索有任何好处,那么我认为选择音频播放器不是一个维护良好的选项,它是面向未来的。不过我可能错了。
  • 您在寻找桌面解决方案还是 Android 解决方案?
  • 不要使用 sun.audio 类 - 它们不是官方 API 的一部分,在现代 Java 版本中根本不起作用。查看javax.sound.sampled 类。 javasound 标签信息有很多示例链接。
  • 一般情况下,不要使用sun 类。
  • @hendrik 一个桌面解决方案。如果它可以用任何标准的 java/kotlin 类来完成,那就太好了(我真的不明白为什么不能,因为它是你可以要求计算机做的最基本的事情之一),但是我猜嵌入式 c/c++ 文件或 .dll 库也可以。

标签: java audio kotlin signal-processing javasound


【解决方案1】:

首先,不要使用 sun 包。永远。

对于桌面,方法是生成数据,获取SourceDataLine,打开并启动线路,然后将数据写入其中。该行适合您选择生成的AudioFormat,这一点很重要。在这种情况下,8 位/采样和44,100 Hz 的采样率。

这是一个 Java 的工作示例,我相信您可以轻松地将其转换为 Kotlin。

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

public class ClipDemo {

    private static byte[] createSinWaveBuffer(final float freq, final int ms, final float sampleRate) {
        final int samples = (int)(ms * sampleRate / 1000);
        final byte[] output = new byte[samples];
        final float period = sampleRate / freq;
        for (int i=0; i<samples; i++) {
            final float angle = (float)(2f * Math.PI * i / period);
            output[i] = (byte) (Math.sin(angle) * 127f);
        }
        return output;
    }

    public static void main(String[] args) throws LineUnavailableException {
        final int rate = 44100;
        final byte[] sineBuffer = createSinWaveBuffer(440, 5000, rate);
        // describe the audio format you're using.
        // because its byte-based, it's 8 bit/sample and signed
        // if you use 2 bytes for one sample (CD quality), you need to pay more attention
        // to endianess and data encoding in your byte buffer
        final AudioFormat format = new AudioFormat(rate, 8, 1, true, true);
        final SourceDataLine line = AudioSystem.getSourceDataLine(format);
        // open the physical line, acquire system resources
        line.open(format);
        // start the line (... to your speaker)
        line.start();
        // write to the line (... to your speaker)
        // this call blocks.
        line.write(sineBuffer, 0, sineBuffer.length);
        // cleanup, i.e. close the line again (left out in this example)
    }
}

【讨论】:

  • SourceDataLine 也适用于流式传输和连续音频。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多