AudioRecord & AudioTrack 似乎不适用于 32 位编码
AudioRecord & AudioTrack do not seem to work with 32-bit encoding
我目前正在从事一个项目,尝试使用 websockets 将音频从一台设备实时流式传输到另一台设备。为此,我正在尝试实现一个适用于浏览器 android 和 ios 的 'cross platform' 实现。我的目标是录制和播放各种格式的 PCM 音频。浏览器 (chrome & firefox) 生成的 PCM 具有 32 位编码,我尝试在 android phone 上播放。仅供参考 here 是项目。
在 android 我正在用 AudioRecord and streaming the raw pcm over a websocket to another device. And similarly I play it using AudioTrack 录音。如果我使用 16 位编码、采样率 44100Hz 和 2 个通道,一切正常。但是它似乎不适用于 32 位编码。来自浏览器(32 位)的录音无法播放,即使我确实交错了频道等。同样,当我尝试使用 32 位编码在 android 上录音时,它不会产生任何声音,因为它没有在浏览器上播放任何内容。
我尝试在 android 上播放 32 位编码的 wav 文件,效果很好。不过不知道系统有没有后台降采样
我的目标是尽可能避免down/up-sampling,因为我想实现低延迟。
我在网上找不到任何解决方案,这是一个常见问题吗?我在这里遗漏了什么吗?
使用 32 位编码 write
方法的结果 returns AudioTrack.ERROR_INVALID_OPERATION
int result = audioTrack.write(buffer, 0, buffer.length, AudioTrack.WRITE_BLOCKING);
if(result == AudioTrack.ERROR_BAD_VALUE){
System.out.println("ERROR: bad value");
}else if (result == AudioTrack.ERROR_DEAD_OBJECT){
System.out.println("ERROR: dead object");
}else if (result == AudioTrack.ERROR_INVALID_OPERATION){
System.out.println("ERROR: invalid operation");
}else if (result == AudioTrack.ERROR){
System.out.println("ERROR: ??");
}else{
System.out.println("Successfully written to buffer!");
}
录制音频的实现:
public class AudioStream {
private AudioStreamMetadata metadata = AudioStreamMetadata.getDefault();
...
public void start() {
...
new Thread(() -> {
socket.send("started");
socket.send(metadata.toString());
while (!hasStopped) {
float[] data = new float[metadata.getBufferSize()];
recorder.read(data, 0, data.length, AudioRecord.READ_BLOCKING);
byte[] output = new byte[data.length * metadata.getBytesPerSample()];
ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(data);
socket.send(ByteString.of(output));
}
}).start();
}
private void initRecorder() {
int min = AudioRecord.getMinBufferSize(metadata.getSampleRate(), metadata.getChannels(true), metadata.getEncoding());
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, metadata.getSampleRate(),
metadata.getChannels(true), metadata.getEncoding(), min);
}
}
AudioStreamMetadataClass:
public class AudioStreamMetadata {
public static final int DEFAULT_SAMPLE_RATE = 44100;
public static final int DEFAULT_CHANNELS = 2;
public static final int DEFAULT_ENCODING = 32;
public static final int DEFAULT_BUFFER_SIZE = 6144*4;
...
public AudioStreamMetadata(int sampleRate, int bufferSize, int channels, int encoding) {
this.sampleRate = sampleRate;
this.bufferSize = bufferSize;
this.channels = channels;
this.encoding = encoding;
this.bytesPerSample = encoding / 8;
this.bufferSizeInBytes = bufferSize * bytesPerSample;
}
//getters
public int getChannels(boolean in) {
if(channels == 1){
return in? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_OUT_MONO;
}else if(channels == 2){
return in? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_OUT_STEREO;
}else{
return 0;
}
}
public int getEncoding() {
if(encoding == 8){
return AudioFormat.ENCODING_PCM_8BIT;
}else if(encoding == 16){
return AudioFormat.ENCODING_PCM_16BIT;
}else if(encoding == 32){
return AudioFormat.ENCODING_PCM_FLOAT;
}else{
return 0;
}
}
public static AudioStreamMetadata getDefault(){
return new AudioStreamMetadata(DEFAULT_SAMPLE_RATE, DEFAULT_BUFFER_SIZE, DEFAULT_CHANNELS, DEFAULT_ENCODING);
}
}
要设置缓冲区大小,请使用以下代码:
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channels, encoding);
我假设 AudioTrack
能够处理 write()
中的不同数据类型,因为我使用正确的配置对其进行了初始化。但是,初始化为 8 位编码的 AudioTrack
仅接受 byte
,接受 byte
和 short
的 16 位编码,但 AudioTrack
作为32 浮点位编码只接受 float
。
我从套接字接收数据作为 byte[]
,我需要将其转换为 float[]
.
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
byte[] buffer = bytes.toByteArray();
FloatBuffer fb = ByteBuffer.wrap(buffer).asFloatBuffer();
float[] out = new float[fb.capacity()];
fb.get(out);
int result = audioTrack.write(out, 0, out.length, AudioTrack.WRITE_BLOCKING);
}
在某些情况下,播放32位浮点PCM时,我们应该先改变字节顺序。
FloatBuffer fb = buffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
我目前正在从事一个项目,尝试使用 websockets 将音频从一台设备实时流式传输到另一台设备。为此,我正在尝试实现一个适用于浏览器 android 和 ios 的 'cross platform' 实现。我的目标是录制和播放各种格式的 PCM 音频。浏览器 (chrome & firefox) 生成的 PCM 具有 32 位编码,我尝试在 android phone 上播放。仅供参考 here 是项目。
在 android 我正在用 AudioRecord and streaming the raw pcm over a websocket to another device. And similarly I play it using AudioTrack 录音。如果我使用 16 位编码、采样率 44100Hz 和 2 个通道,一切正常。但是它似乎不适用于 32 位编码。来自浏览器(32 位)的录音无法播放,即使我确实交错了频道等。同样,当我尝试使用 32 位编码在 android 上录音时,它不会产生任何声音,因为它没有在浏览器上播放任何内容。
我尝试在 android 上播放 32 位编码的 wav 文件,效果很好。不过不知道系统有没有后台降采样
我的目标是尽可能避免down/up-sampling,因为我想实现低延迟。
我在网上找不到任何解决方案,这是一个常见问题吗?我在这里遗漏了什么吗?
使用 32 位编码 write
方法的结果 returns AudioTrack.ERROR_INVALID_OPERATION
int result = audioTrack.write(buffer, 0, buffer.length, AudioTrack.WRITE_BLOCKING);
if(result == AudioTrack.ERROR_BAD_VALUE){
System.out.println("ERROR: bad value");
}else if (result == AudioTrack.ERROR_DEAD_OBJECT){
System.out.println("ERROR: dead object");
}else if (result == AudioTrack.ERROR_INVALID_OPERATION){
System.out.println("ERROR: invalid operation");
}else if (result == AudioTrack.ERROR){
System.out.println("ERROR: ??");
}else{
System.out.println("Successfully written to buffer!");
}
录制音频的实现:
public class AudioStream {
private AudioStreamMetadata metadata = AudioStreamMetadata.getDefault();
...
public void start() {
...
new Thread(() -> {
socket.send("started");
socket.send(metadata.toString());
while (!hasStopped) {
float[] data = new float[metadata.getBufferSize()];
recorder.read(data, 0, data.length, AudioRecord.READ_BLOCKING);
byte[] output = new byte[data.length * metadata.getBytesPerSample()];
ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(data);
socket.send(ByteString.of(output));
}
}).start();
}
private void initRecorder() {
int min = AudioRecord.getMinBufferSize(metadata.getSampleRate(), metadata.getChannels(true), metadata.getEncoding());
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, metadata.getSampleRate(),
metadata.getChannels(true), metadata.getEncoding(), min);
}
}
AudioStreamMetadataClass:
public class AudioStreamMetadata {
public static final int DEFAULT_SAMPLE_RATE = 44100;
public static final int DEFAULT_CHANNELS = 2;
public static final int DEFAULT_ENCODING = 32;
public static final int DEFAULT_BUFFER_SIZE = 6144*4;
...
public AudioStreamMetadata(int sampleRate, int bufferSize, int channels, int encoding) {
this.sampleRate = sampleRate;
this.bufferSize = bufferSize;
this.channels = channels;
this.encoding = encoding;
this.bytesPerSample = encoding / 8;
this.bufferSizeInBytes = bufferSize * bytesPerSample;
}
//getters
public int getChannels(boolean in) {
if(channels == 1){
return in? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_OUT_MONO;
}else if(channels == 2){
return in? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_OUT_STEREO;
}else{
return 0;
}
}
public int getEncoding() {
if(encoding == 8){
return AudioFormat.ENCODING_PCM_8BIT;
}else if(encoding == 16){
return AudioFormat.ENCODING_PCM_16BIT;
}else if(encoding == 32){
return AudioFormat.ENCODING_PCM_FLOAT;
}else{
return 0;
}
}
public static AudioStreamMetadata getDefault(){
return new AudioStreamMetadata(DEFAULT_SAMPLE_RATE, DEFAULT_BUFFER_SIZE, DEFAULT_CHANNELS, DEFAULT_ENCODING);
}
}
要设置缓冲区大小,请使用以下代码:
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channels, encoding);
我假设 AudioTrack
能够处理 write()
中的不同数据类型,因为我使用正确的配置对其进行了初始化。但是,初始化为 8 位编码的 AudioTrack
仅接受 byte
,接受 byte
和 short
的 16 位编码,但 AudioTrack
作为32 浮点位编码只接受 float
。
我从套接字接收数据作为 byte[]
,我需要将其转换为 float[]
.
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
byte[] buffer = bytes.toByteArray();
FloatBuffer fb = ByteBuffer.wrap(buffer).asFloatBuffer();
float[] out = new float[fb.capacity()];
fb.get(out);
int result = audioTrack.write(out, 0, out.length, AudioTrack.WRITE_BLOCKING);
}
在某些情况下,播放32位浮点PCM时,我们应该先改变字节顺序。
FloatBuffer fb = buffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();