На этом изображении показаны импульсы при записи Firefox.

Это форма волны при записи Firefox, когда ничего не происходит. играет.

Эта форма сигнала отображается при записи с моего микрофона. Совершенно нормально, как и ожидалось.

Соответствующими файлами в моем исходном коде являются "Bolt.java", "PortAudioInputStream.java", "InputThread.java", а также все папки jni и исходный код c.
Следующий демонстрационный код полностью работает со всеми устройствами ввода, как и ожидалось.
package net.ellie.bolt;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.List;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import net.ellie.bolt.jni.portaudio.AudioInputStream;
import net.ellie.bolt.jni.portaudio.PortAudioJNI;
public class Main {
public static void main(String[] args) {
// try {
// Bolt.run();
// } catch (Exception e) {
// e.printStackTrace();
// }
final int durationSeconds = 5;
final int channelsPreferred = 2;
final double sampleRatePreferred = 48000.0;
PortAudioJNI pa = new PortAudioJNI();
int init = pa.initialize();
if (init != 0) {
System.err.println("Failed to initialize PortAudio (code=" + init + ")");
return;
}
try {
List devices = pa.enumerateDevices();
if (devices == null || devices.isEmpty()) {
System.err.println("No audio devices found");
return;
}
for (PortAudioJNI.DeviceInfo d : devices) {
System.out.printf("Device %d: %s (in: %d ch, out: %d ch, default SR: %.1f)\n",
d.index(), d.name(), d.maxInputChannels(), d.maxOutputChannels(), d.defaultSampleRate());
}
PortAudioJNI.DeviceInfo device = devices.get(27);
int channels = Math.min(device.maxInputChannels(), channelsPreferred);
double sampleRate = sampleRatePreferred;
if (!pa.isFormatSupported(device.index(), channels, sampleRate)) {
channels = Math.max(1, Math.min(device.maxInputChannels(), 1));
sampleRate = device.defaultSampleRate() > 0 ? device.defaultSampleRate() : sampleRatePreferred;
}
long framesPerBuffer = 256;
AudioInputStream inputStream = pa.openInputStream(device.index(), channels, sampleRate, framesPerBuffer);
try (inputStream) {
inputStream.start();
int totalFrames = (int) (sampleRate * durationSeconds);
int bytesPerSample = 2;
int bytesPerFrame = bytesPerSample * channels;
byte[] data = new byte[totalFrames * bytesPerFrame];
int bytesCaptured = 0;
while (bytesCaptured < data.length) {
int remaining = data.length - bytesCaptured;
int toRead = Math.max(bytesPerFrame, Math.min(remaining, bytesPerFrame * 1024));
int read = inputStream.read(data, bytesCaptured, toRead);
if (read 1.0f)
f = 1.0f;
if (f < -1.0f)
f = -1.0f;
int s;
if (f >= 0) {
s = (int) Math.round(f * 32767.0);
} else {
s = (int) Math.round(f * 32768.0);
}
out[bi++] = (byte) (s & 0xFF);
out[bi++] = (byte) ((s >>> 8) & 0xFF);
}
return out;
}
}
Вот PortAudioInputSource.java
package net.ellie.bolt.input.sources.real;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.ellie.bolt.contexts.PortAudioContext;
import net.ellie.bolt.input.CloseableInputSource;
import net.ellie.bolt.jni.portaudio.AudioInputStream;
import net.ellie.bolt.jni.portaudio.PortAudioJNI;
public class PortAudioInputSource implements CloseableInputSource {
private final Logger logger = LoggerFactory.getLogger(PortAudioInputSource.class);
private volatile boolean running = true;
private AudioInputStream audioInputStream;
private final int sampleRate;
private final int channelCount;
private final int framesPerBuffer;
private byte[] byteBuffer; // re-used between reads to avoid allocations
public PortAudioInputSource(int deviceIndex, int channels, double sampleRate, long framesPerBuffer) {
PortAudioJNI pa = PortAudioContext.getInstance().getPortAudioJNI();
PortAudioJNI.DeviceInfo device = null;
List devices = pa.enumerateDevices();
for (PortAudioJNI.DeviceInfo d : devices) {
if (d.index() == deviceIndex) {
device = d;
break;
}
}
if (device == null) {
throw new RuntimeException("Device not found: " + deviceIndex);
}
int maxInputChannels = device.maxInputChannels();
int actualChannels = channels;
if (channels == 1 && maxInputChannels < 1) {
actualChannels = Math.max(1, maxInputChannels);
logger.warn("Device {} doesn't support {} channels, using {} channels instead",
device.name(), channels, actualChannels);
}
this.sampleRate = (int) sampleRate;
this.channelCount = Math.max(1, actualChannels);
this.framesPerBuffer = (int) Math.max(1, framesPerBuffer);
logger.info("Opening PortAudio input stream with deviceIndex={}, channels={} (requested: {}), sampleRate={}, framesPerBuffer={}",
deviceIndex, this.channelCount, channels, sampleRate, framesPerBuffer);
audioInputStream = PortAudioContext.getInstance().getPortAudioJNI()
.openInputStream(deviceIndex, this.channelCount, sampleRate, framesPerBuffer);
this.byteBuffer = new byte[this.framesPerBuffer * this.channelCount * 2];
audioInputStream.start();
}
@Override
public int read(float[] buffer, int offset, int length) throws InterruptedException, IOException {
if (!running || audioInputStream == null) {
return 0;
}
int bytesToRead = Math.min(byteBuffer.length, length * 2 * channelCount);
int bytesRead = audioInputStream.read(byteBuffer, 0, bytesToRead);
if (bytesRead = byteBuffer.length) break;
int lo = byteBuffer[bi++] & 0xFF;
int hi = byteBuffer[bi++];
int sample = (hi = 0 ? (sample / 32767.0f) : (sample / 32768.0f);
sum += f;
channelsRead++;
}
if (channelsRead > 0) {
buffer[offset + i] = sum / channelsRead;
} else {
buffer[offset + i] = 0;
}
}
return framesToCopy;
}
@Override
public int getSampleRate() {
return sampleRate;
}
@Override
public void stop() {
running = false;
if (audioInputStream != null) {
audioInputStream.stop();
audioInputStream.close();
audioInputStream = null;
}
}
@Override
public String getName() {
return "PortAudio Input Source";
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean isComplex() {
return false;
}
@Override
public void close() {
stop();
}
}
Это файл InputThread.java
package net.ellie.bolt.input;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import net.ellie.bolt.config.Configuration;
import net.ellie.bolt.dsp.buffers.CircularFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InputThread implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(InputThread.class);
private final CloseableInputSource inputSource;
private final AtomicBoolean running = new AtomicBoolean(false);
private final CircularFloatBuffer buffer;
// Throttling state
private final int complexFactor;
private final int sampleRate; // samples per second for one channel; effective rate accounts for complexFactor
private long nextReadDeadlineNanos = 0L;
private final float[] readBuffer;
private Thread localInputThread = null;
public InputThread(CloseableInputSource inputSource, int sampleRate) {
this.inputSource = inputSource;
this.complexFactor = inputSource.isComplex() ? 2 : 1;
this.sampleRate = sampleRate;
int bufferSize = Configuration.getFftSize() * 32 * complexFactor; // TODO: figure out the correct size
this.buffer = new CircularFloatBuffer(bufferSize);
this.readBuffer = new float[4 * 16384 * complexFactor]; // TODO: maybe increase the size
}
public void start() {
running.set(true);
localInputThread = new Thread(this, "InputThread-" + inputSource.getName());
localInputThread.start();
}
@Override
public void run() {
logger.info("InputThread for {} started", inputSource.getName());
try {
nextReadDeadlineNanos = System.nanoTime();
while (running.get()) {
long now = System.nanoTime();
long waitNanos = nextReadDeadlineNanos - now;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1_000_000L;
int waitExtraNanos = (int)(waitNanos % 1_000_000L);
if (waitMillis > 0 || waitExtraNanos > 0) {
try {
Thread.sleep(waitMillis, waitExtraNanos);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
int samplesRead = inputSource.read(readBuffer, 0, readBuffer.length);
if (samplesRead > 0) {
buffer.write(readBuffer, 0, samplesRead);
double secondsForChunk = (double) samplesRead / (double) (sampleRate * complexFactor);
long nanosForChunk = (long) (secondsForChunk * 1_000_000_000L);
nextReadDeadlineNanos = System.nanoTime() + nanosForChunk;
} else {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
nextReadDeadlineNanos = System.nanoTime() + 1_000_000L;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (IOException e) {
e.printStackTrace();
} finally {
try { inputSource.close(); } catch (Exception ignored) {}
}
}
public void stop() {
running.set(false);
if (localInputThread != null) {
try {
localInputThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
inputSource.stop();
logger.info("InputThread for {} stopped", inputSource.getName());
}
public CircularFloatBuffer getBuffer() {
return buffer;
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... devices-an
Мобильная версия