有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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获取音频的更大方法,前面问题的解决方案是否适合此用例

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


共 (2) 个答案

  1. # 1 楼答案

    Given that the audio is 16 bits but must be changed to 12 with four zeros at the end, four bits somewhere do have to be tossed.

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

    这是我现在可以迅速解决的问题。当然还没有完全测试过。仅在输入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;
        }
    

    解释

    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));
    
    • 后者用于具有不同位位置的2个当前字节
    (...) | (...)
    
    • 中间的管道或竖条是将我们刚刚创建的两个整数合并为一个

    用法

    使用这种方法非常简单

        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);
    
  2. # 2 楼答案

    我发现了一种可以产生清晰音频的解决方案。首先,重要的是重新说明用例的要求,即12位无符号单声道音频,该音频将由设备以小端字节的形式以1000字节的数据包读取

    问题中描述的音频记录的初始化和配置很好

    从AudioRecord读取1000字节的音频后,可以将其放入ByteBuffer并定义为little endian进行修改,然后放入ShortBuffer在16位级别进行操作:

            // Audio specifications of device is in little endian.
            ByteBuffer byteBuffer = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN);
            // Turn into a ShortBuffer so bitwise manipulation can occur on the 16 bit level.
            ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
    

    接下来,在一个循环中,选取每个短字符,并将其修改为12位无符号:

            for (int i = 0; i < shortBuffer.capacity(); i++) {
                short currentShort = shortBuffer.get(i);
                shortBuffer.put(i, convertShortTo12Bit(currentShort));
            }
    

    这可以通过将16位的四个空格向右移位,将其转换为12位有符号空格来实现。然后,要转换为无符号,请添加2048。为了安全起见,我们还根据设备的要求屏蔽最低有效的四位,但考虑到移位和加法,任何位实际上都不应该保留在那里:

        private static short convertShortTo12Bit(short input) {
            int inputAsInt = input;
            inputAsInt >>>= 4;
            inputAsInt += 2048;
            input = (short) (inputAsInt & 0B0000111111111111);
            return input;
        }
    

    如果希望将12位返回到16位,则对每个短路执行相反的操作(减去2048并向左移动四个空格)