Java 如何转换使用Android创建的16位音频';通过位移位将音频记录转换为12位音频?

Java 如何转换使用Android创建的16位音频';通过位移位将音频记录转换为12位音频?,java,android,audio,bit-manipulation,audiorecord,Java,Android,Audio,Bit Manipulation,Audiorecord,我正在尝试将16位音频转换为12位音频。然而,我对这种转换非常缺乏经验,并且相信我的方法可能不正确或有缺陷 作为下面代码片段的上下文,该用例是一个Android应用程序,用户可以对其说话,音频被传输到IoT设备以立即播放。IoT设备要求音频为单声道12位、8k采样率、小尾端、无符号,存储在前12位(0-11)和最后4位(12-15)中的数据为零。音频数据需要以1000字节的数据包形式接收 通过使用AudioRecord在Android应用程序中创建音频。其实例化如下: int bufferSiz

我正在尝试将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();
    }
然而,当通过设备播放时,听起来更糟,我甚至不认为我能辨认出任何单词

显然,我的方法在这里行不通中心问题是,如果最后四位必须为零,如何将16位块转换为12位音频并保持音频质量?此外,考虑到我使用AudioRecord获取音频的更大方法,上述问题的解决方案是否适合此用例?

请让我知道,如果有什么我可以提供澄清这些问题和我的意图

考虑到音频是16位,但必须更改为12位,结尾有四个零,因此必须在某个地方抛出四位

是的,当然,没有别的办法了,是吗

这是我现在可以迅速解决的问题。当然还没有完全测试过。仅在输入2和4字节时测试。我把它留给你去测试

    //Reminder :: Convert as many as possible.
    //Reminder :: To calculate the required size for store: 
    //if((bytes.length & 1) == 0) Math.round((bytes.length * 6) / 8F) : Math.round(((bytes.length - 1) * 6) / 8F).
    //Return :: Amount of converted bytes.
    public static final int convert16BitTo12Bit(final byte[] bytes, final byte[] store) 
    {
        final int size = bytes.length;
        int storeIndex = 0;
        //Copy the first 2 bytes into store.
        store[storeIndex++] = bytes[0]; store[storeIndex] = bytes[1];
        if(size < 4) {
               store[storeIndex] = (byte)(store[storeIndex] & 0xF0);
               return 2;
                }
        final int result;
        final byte tmp;
        //  11111111 11110000 00000000 00000000
        //+              11111111 11110000      (<< 12)
        //= 11111111 11111111 11111111 00000000 (1)
        //-----------------------------------------
        //  11111111 00000000 00000000 00000000 (1)
        //+          11111111 11110000          (<< 16)
        //= 11111111 11111111 11110000 00000000 (2)
        //-----------------------------------------
        //  11110000 00000000 00000000 00000000 (2)
        //+     1111 11111111 0000              (<< 20)
        //= 11111111 11111111 00000000 00000000 (3)
        //-----------------------------------------
        //  00000000 00000000 00000000 00000000 (3)
        //+ 11111111 11110000                   (<< 24)
        //= 11111111 11110000 00000000 00000000
        for(int i=2, shiftBits = 12; i < size; i += 2) {
            if(shiftBits == 24) {
                //Copy 2 bytes from bytes[] into store[] and move on.
                store[storeIndex] = bytes[i];
                //Never store byte 0 (Garbage).
                tmp = (byte)(bytes[i + 1] & 0xF0); //Bit order: 11110000.
                if(tmp != 0) store[++storeIndex] = tmp;
                shiftBits = 12; //Reset
            } else if(shiftBits == 20) {
                result = ((store[storeIndex - 1] << 24) | ((store[storeIndex] & 0xFF) << 16))
                    | (((bytes[i] & 0xFF) << 20) | ((bytes[i + 1] & 0xFF) << 12));
                store[storeIndex] = (byte)((result >> 24) & 0xFF);
                tmp = (byte)((result >> 16) & 0xFF);
                //Never store byte 0 (Garbage).
                if(tmp != 0) store[++storeIndex] = tmp;
                shiftBits = 24;
            } else if(shiftBits == 16) {
                result = ((store[storeIndex - 1] << 24) | ((store[storeIndex] & 0xFF) << 16))
                    | (((bytes[i] & 0xFF) << 16) | ((bytes[i + 1] & 0xFF) << 8));
                store[storeIndex] = (byte)((result >> 16) & 0xFF);
                tmp = (byte)((result >> 8) & 0xF0);
                //Never store byte 0 (Garbage).
                if(tmp != 0) store[++storeIndex] = tmp;
                shiftBits = 20;
            } else {
                result = ((store[storeIndex - 1] << 24) | ((store[storeIndex] & 0xFF) << 16))
                    | (((bytes[i] & 0xFF) << 12) | ((bytes[i + 1] & 0xFF) << 4));
                store[storeIndex] = (byte)((result >> 16) & 0xFF);
                tmp = (byte)((result >> 8) & 0xFF);
                //Never store byte 0 (Garbage).
                if(tmp != 0) store[++storeIndex] = tmp;
                shiftBits = 16;
            }
        }
        return ++storeIndex;
    }
  • 中间的管道或竖条是将我们刚刚创建的两个整数合并为一个
用法

使用这种方法非常简单

    byte[] buffer = new byte[1000];
    byte[] store;
    if((buffer.length & 1) == 0) { //Even.
        store = new byte[Math.round((bytes.length * 6) / 8F)];
    } else { //Odd.
        store = new byte[Math.round(((bytes.length - 1) * 6) / 8F)]; 
    }
    audioRecord.read(buffer, 0, buffer.length);
    int convertedByteSize = convert16BitTo12Bit(buffer, store);
    System.out.println("size: " + convertedByteSize);

你确定你只需清除前4位,而不需要将12位彼此靠近
1111111110000 1111111111110000
-->
11111111111111111111111111110000000
?@Darkman位修改对我来说都是很新的;我以前从来没有做过。。。所以我真的不确定什么才是理想的。我的经营理念是,最低有效的四位应该被删除,以保持最佳的音频质量。只要我不移动任何东西,这实际上是有效的。因此,换档过程中出现了一些问题。你能在这里阐明你的方法吗?您如何连接示例中的位?考虑到音频是16位的,但必须更改为12位,最后是4个零,因此必须在某个地方抛出4位;这声音听不懂。一个关于存储大小的问题来澄清意图:在“用法”代码块中,存储总是相同的大小,因为缓冲区总是在前一行的1000处开始。在该位置调用代码是故意的还是错误的?存储区的长度始终为750字节,而且可能数组的最后一个块(100+字节)总是完全为零。实际上,它不是从1000开始的。1000是名为
buffer
的数组的大小,您希望传递给
audioRecord
。它将始终从索引0处的
缓冲区
转换字节。750字节-是的,1000个16位字节到12位字节需要多少空间。例如:完整的4字节16位需要4个字节
[11111111 11111111][11111111 111111111]
,而不是12位
[11111111 1111][11111111]
需要3个字节。这类似于24位与8位深度的图像,后者的大小更大。我建议您尝试更改
bitSet.clear(12,16)
位集。清除(8,12)
位集。清除(4,8)
位集。清除(0,4)
看看会发生什么。如果我现在理解正确,这个答案遗漏了问题中的零填充。这种方法是将四个字节中的两组16位合并为三个字节中的两组12位。因此,至少转换正在发生。但是,数据需要表示为16位中的12位(最后4位为0,因此4个字节中的12位
[11111111 11110000][11111111 11110000]
。这就是为什么我对750的大小感到困惑的原因。我们该怎么做呢?750字节的结果是否应该通过其他方法进行零填充和移位?我不知道如何编辑注释,但我的意思是“但是,[在您的三字节示例中]数据需要表示为16位中的[两组]12位(最后四位为0,因此4个字节中有12位…”
result = ((store[storeIndex - 1] << 24) | ((store[storeIndex] & 0xFF) << 16))
                    | (((bytes[i] & 0xFF) << 20) | ((bytes[i + 1] & 0xFF) << 12));
((store[storeIndex - 1] << 24) | ((store[storeIndex] & 0xFF) << 16))
(((bytes[i] & 0xFF) << 20) | ((bytes[i + 1] & 0xFF) << 12));
(...) | (...)
    byte[] buffer = new byte[1000];
    byte[] store;
    if((buffer.length & 1) == 0) { //Even.
        store = new byte[Math.round((bytes.length * 6) / 8F)];
    } else { //Odd.
        store = new byte[Math.round(((bytes.length - 1) * 6) / 8F)]; 
    }
    audioRecord.read(buffer, 0, buffer.length);
    int convertedByteSize = convert16BitTo12Bit(buffer, store);
    System.out.println("size: " + convertedByteSize);