java如何通过位移位将Android的AudioRecord创建的16位音频转换为12位音频?
我正在尝试将16位音频转换为12位音频。然而,我对这种转换非常缺乏经验,并且相信我的方法可能不正确或有缺陷
作为下面代码片段的上下文,该用例是一个Android应用程序,用户可以对其说话,音频被传输到IoT设备以立即播放。IoT设备要求音频为单声道12位、8k采样率、小尾端、无符号,存储在前12位(0-11)和最后4位(12-15)中的数据为零。音频数据需要以1000字节的数据包形式接收
通过使用AudioRecord在Android应用程序中创建音频。其实例化如下:
int bufferSize = 1000;
this.audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC,
8000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize
);
在while循环中,音频记录由1000字节的数据包读取,并根据用例中的规范进行修改。不确定该部分是否相关,但为了完整性:
byte[] buffer = new byte[1000];
audioRecord.read(buffer, 0, buffer.length);
byte[] modifiedBytes = convert16BitTo12Bit(buffer);
然后修改后的字节被发送到设备
下面是修改字节的方法。基本上,为了符合规范,我移动每个16位集合中的位(抛出最低有效值4),并在最后四个点上添加零。我是通过位集来实现的
/**
* Takes a byte array presented as 16 bit audio and converts it to 12 bit audio through bit
* manipulation. Packets must be of 1000 bytes or no manipulation will occur and the input
* will be immediately returned.
*/
private byte[] convert16BitTo12Bit(byte[] input) {
if (input.length == 1000) {
for (int i = 0; i < input.length; i += 2) {
Log.d(TAG, "convert16BitTo12Bit: pass #" + (i / 2));
byte[] chunk = new byte[2];
System.arraycopy(input, i, chunk, 0, 2);
if (!isEmptyByteArray(chunk)) {
byte[] modifiedBytes = convertChunk(chunk);
System.arraycopy(
modifiedBytes,
0,
input,
i,
modifiedBytes.length
);
}
}
return input;
}
Log.d(TAG, "convert16BitTo12Bit: Failed - input is not 1000 in length; it is " + input.length);
return input;
}
/**
* Converts 2 bytes 16 bit audio into 12 bit audio. If the input is not 2 bytes, the input
* will be returned without manipulation.
*/
private byte[] convertChunk(byte[] chunk) {
if (chunk.length == 2) {
BitSet bitSet = BitSet.valueOf(chunk);
Log.d(TAG, "convertChunk: bitSet starts as " + bitSet.toString());
modifyBitSet(bitSet);
Log.d(TAG, "convertChunk: bitSet ends as " + bitSet.toString());
return bitSet.toByteArray();
}
Log.d(TAG, "convertChunk: Failed = chunk is not 2 in length; it is " + chunk.length);
return chunk;
}
/**
* Removes the first four bits and shifts the rest to leave the final four bits as 0.
*/
private void modifyBitSet(BitSet bitSet) {
for (int i = 4; i < bitSet.length(); i++) {
bitSet.set(i - 4, bitSet.get(i));
}
if (bitSet.length() > 8) {
bitSet.clear(12, 16);
} else {
bitSet.clear(4, 8);
}
}
/**
* Returns true if the byte array input contains all zero bits.
*/
private boolean isEmptyByteArray(byte[] input) {
BitSet bitSet = BitSet.valueOf(input);
return bitSet.isEmpty();
}
不幸的是,这种方法产生的结果不理想。音频非常嘈杂,很难听懂有人在说什么(但你可以听到有人在说话)
我也一直在玩,只是把字节保存到一个文件中,然后通过AudioTrack在Android上播放。我注意到,如果我只删除前四位,不移动任何内容,音频实际上听起来很好,如下所示:
private void modifyBitSet(BitSet bitSet) {
bitSet.clear(0, 4);
}
然而,当通过设备播放时,听起来更糟,我甚至不认为我能辨认出任何单词
显然,我的方法在这里行不通中心问题是,如果最后四位必须为零,如何将16位块转换为12位音频并保持音频质量?此外,考虑到我使用AudioRecord获取音频的更大方法,前面问题的解决方案是否适合此用例强>
请让我知道,如果有什么我可以提供澄清这些问题和我的意图
# 1 楼答案
是的,当然,没有别的办法了,是吗
这是我现在可以迅速解决的问题。当然还没有完全测试过。仅在输入2和4字节时测试。我把它留给你去测试
解释
用法
使用这种方法非常简单
# 2 楼答案
我发现了一种可以产生清晰音频的解决方案。首先,重要的是重新说明用例的要求,即12位无符号单声道音频,该音频将由设备以小端字节的形式以1000字节的数据包读取
问题中描述的音频记录的初始化和配置很好
从AudioRecord读取1000字节的音频后,可以将其放入ByteBuffer并定义为little endian进行修改,然后放入ShortBuffer在16位级别进行操作:
接下来,在一个循环中,选取每个短字符,并将其修改为12位无符号:
这可以通过将16位的四个空格向右移位,将其转换为12位有符号空格来实现。然后,要转换为无符号,请添加2048。为了安全起见,我们还根据设备的要求屏蔽最低有效的四位,但考虑到移位和加法,任何位实际上都不应该保留在那里:
如果希望将12位返回到16位,则对每个短路执行相反的操作(减去2048并向左移动四个空格)