AudioRecord и AudioTrack не работают с 32-битным кодированием

В настоящее время я работаю над проектом, в котором я пытаюсь передавать аудио с одного устройства на другое в режиме реального времени с помощью веб-сокетов. Для этого я пытаюсь реализовать «кросс-платформенную» реализацию, которая работает с браузерами, Android и ios. Моя цель — записывать и воспроизводить звук PCM в различных форматах. PCM, созданный браузером (chrome и firefox), имеет 32-битную кодировку, которую я пытаюсь воспроизвести на телефоне Android. Просто для справки здесь находится проект.

На Android я записываю с помощью AudioRecord и передаю необработанный PCM через веб-сокет на другое устройство. Точно так же я играю с помощью AudioTrack. Все работает нормально, если я использую 16-битное кодирование, частоту дискретизации 44100 Гц и 2 канала. Однако, похоже, он не работает с 32-битной кодировкой. Запись из браузера (32-битная) не воспроизводится, хотя я чередовал каналы и т. д. И аналогично, когда я пытаюсь записать на андроид с использованием 32-битной кодировки, он не воспроизводит звук, так как ничего не воспроизводит в браузере.

Я пытался воспроизвести wav-файл на Android с 32-битной кодировкой, и он отлично работает. Однако я не знаю, выполняет ли система понижение частоты дискретизации в фоновом режиме.

Моя цель — максимально избежать понижения/повышения частоты дискретизации, так как я хочу добиться низкой задержки.

Я не смог найти какое-либо решение в Интернете, это распространенная проблема, я что-то упустил?

При 32-битном кодировании результат метода write возвращает 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);
    }
}

Класс AudioStreamMetadata:

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);
    }
}

person Merve Sahin    schedule 18.10.2019    source источник
comment
Какой уровень API вы используете?   -  person Squti    schedule 19.10.2019
comment
минимальный уровень API — 23, оба телефона Android удовлетворяют минимальному уровню API для 32-битного кодирования.   -  person Merve Sahin    schedule 19.10.2019
comment
Не могли бы вы вставить код вашего регистратора сюда?   -  person Squti    schedule 19.10.2019
comment
@Squti я добавил код   -  person Merve Sahin    schedule 19.10.2019
comment
спасибо это работает! @MerveSahin   -  person Hamza Üzümcü    schedule 14.01.2020


Ответы (3)


Я предположил, что AudioTrack сможет обрабатывать разные типы данных в write(), так как я инициализировал его с правильными конфигурациями. Однако AudioTrack, инициализированный как 8-битное кодирование, принимает только byte, как 16-битное кодирование, как byte, так и short, а 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);

    }
person Merve Sahin    schedule 18.10.2019

Чтобы установить размер буфера, используйте следующий код:

bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channels, encoding);
person Squti    schedule 18.10.2019

В некоторых случаях мы должны сначала изменить порядок байтов при воспроизведении 32-битного PCM с плавающей запятой.

FloatBuffer fb = buffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
person 王慧龙    schedule 08.07.2020