Как объединить 8-битный и 16-битный файл PCM в отдельные аудиоканалы в Android

Я знаю, что было много подобных вопросов, и я прочитал многие из них, но я не мог заставить это работать. как я уже сказал в заголовке, у меня есть один 8-битный и один 16-битный монофонический файл PCM, которые я хочу объединить в один файл стереофонической волны, но в отдельных каналах, один в левом и один в правом канале.

До сих пор я делал следующее, но независимо от того, что я делаю, возникают две проблемы: 1- я не могу записать их в отдельные каналы в выходном файле. 2- один из файлов будет записан как шумы (будь то "RECORDER_BPP" 8 или 16)

мой код:

new Thread(new Runnable() {
        @Override
        public void run() {
            int channels = 2;
            RECORDER_BPP = 16;
            int RECORDER_SAMPLERATE = 44100;
            int minBufferSize = AudioTrack.getMinBufferSize(maxCaptureRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

            byte[] data = new byte[minBufferSize];
            long longSampleRate = RECORDER_SAMPLERATE;
            long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels/8;

            try {
                FileInputStream in1 = new FileInputStream(rawAudio1);
                FileInputStream in2 = new FileInputStream(rawAudio2);
                FileOutputStream out = new FileOutputStream(wavAudio);
                long totalAudioLen = in1.getChannel().size();
                long totalDataLen = totalAudioLen + 36;

                WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
                        longSampleRate, channels, byteRate);

                while(in1.read(data) != -1){
                    out.write(data);

                    //2nd channel ?! // unsuccessful
                    in2.read(data);
                    out.write(data);
                }

                in1.close();
                in2.close();
                out.close();
            } catch (Exception e) {
                Log.d("EEE-2ch", e.getMessage());
            }
        }
}).start();

и вот часть заголовка:

private void WriteWaveFileHeader(
        FileOutputStream out, long totalAudioLen,
        long totalDataLen, long longSampleRate, int channels,
        long byteRate) throws IOException {

    byte[] header = new byte[44];

    header[0] = 'R';  // RIFF/WAVE header
    header[1] = 'I';
    header[2] = 'F';
    header[3] = 'F';
    header[4] = (byte) (totalDataLen & 0xff);
    header[5] = (byte) ((totalDataLen >> 8) & 0xff);
    header[6] = (byte) ((totalDataLen >> 16) & 0xff);
    header[7] = (byte) ((totalDataLen >> 24) & 0xff);
    header[8] = 'W';
    header[9] = 'A';
    header[10] = 'V';
    header[11] = 'E';
    header[12] = 'f';  // 'fmt ' chunk
    header[13] = 'm';
    header[14] = 't';
    header[15] = ' ';
    header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    header[20] = 1;  // format = 1
    header[21] = 0;
    header[22] = (byte) channels;
    header[23] = 0;
    header[24] = (byte) (longSampleRate & 0xff);
    header[25] = (byte) ((longSampleRate >> 8) & 0xff);
    header[26] = (byte) ((longSampleRate >> 16) & 0xff);
    header[27] = (byte) ((longSampleRate >> 24) & 0xff);
    header[28] = (byte) (byteRate & 0xff);
    header[29] = (byte) ((byteRate >> 8) & 0xff);
    header[30] = (byte) ((byteRate >> 16) & 0xff);
    header[31] = (byte) ((byteRate >> 24) & 0xff);
    header[32] = (byte) (channels * RECORDER_BPP / 8);  // block align
    header[33] = 0;
    header[34] = (byte) RECORDER_BPP;  // bits per sample
    header[35] = 0;
    header[36] = 'd';
    header[37] = 'a';
    header[38] = 't';
    header[39] = 'a';
    header[40] = (byte) (totalAudioLen & 0xff);
    header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
    header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
    header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

    out.write(header, 0, 44);
}

Я ценю любую помощь, спасибо


person Lord Sepid    schedule 18.04.2016    source источник
comment
Стерео данные PCM чередуются (L R L R L R и т. д.), поэтому вам необходимо читать/записывать по одному сэмплу за раз. А для 8-битного семпла нужно применить масштабирование, если вы хотите сохранить его громкость.   -  person Michael    schedule 18.04.2016
comment
@Michael, спасибо, но не могли бы вы дать мне несколько примеров кода, как это сделать? Честно говоря, у меня нет опыта работы с аудиофайлами и я не совсем понял, что вы сказали. Благодарность   -  person Lord Sepid    schedule 18.04.2016


Ответы (2)


Выходные данные должны чередоваться, то есть вам нужно прочитать один образец из in1, записать его на выход, а затем прочитать один образец из in2 и записать его на выход. Поскольку in2 — это 8-битный звук, вам нужно прочитать 8-битный образец, а затем записать 16-битный.

byte[] data = new byte[2];
while(in1.read(data) != -1) { // read 2 bytes
    out.write(data);          // write 2 bytes (L)
    in2.read(data, 0, 1);     // read 1 byte
    data[1] = 0;              // set the other byte to zero
    // if this is the wrong byte order then do this
    // data[1] = data[0];
    // data[0] = 0;
    out.write(data);          // write 2 bytes (R)
}

Этот код предполагает, что in1 — это файл с 16-битным звуком. Если это не так, вам нужно немного переработать его.

person jaket    schedule 19.04.2016
comment
На самом деле он разделил левый и правый каналы, но канал, на который был записан 8-битный файл, был полным шумом. Кажется, это происходит потому, что я установил RECORDER_BPP=16 во время записи в файл wav. Я изменил порядок байтов, как вы сказали, но другой байт пуст и ничего не пишет. Я проверил файлы pcm, и они в порядке. Любая идея, как это исправить? - person Lord Sepid; 20.04.2016
comment
Я не совсем понимаю, что вы подразумеваете под пустым другим байтом. Что вам нужно сделать для второго канала, так это прочитать один байт, а затем записать его как два байта с помощью сдвига влево. Таким образом, если значение выборки в файле было 0x43, то вы записываете 0x4300 - вуаля, 8-битное значение превращается в 16-битное. - person jaket; 20.04.2016
comment
вы сказали, что если это неправильный порядок байтов, то сделайте это .... это на самом деле написало что-то в отличие от данных первой попытки [1] = 0, но это был просто шум. правда размер в 2 раза больше. если я преобразую его в волну, используя RECORDER_BPP=8, этот файл будет записан нормально, но в другой файл (16 бит) будет записан весь шум. что-то не так, но я не могу понять, что. - person Lord Sepid; 20.04.2016

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

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

byte[] data = new byte[minBufferSize];  // this is way too big for interlacing sample by sample.
while(in1.read(data) != -1){
    out.write(data);

    in2.read(data);  // <-- This could fail. 
    out.write(data);
}

Вот способ справиться с такого рода проблемами. Решение 1, без усиления 8-битного значения.

byte[] stereo_pair = new byte[4];  // a 16bit pcm stereo pair has only 4 bytes.
while (in1.read(stereo_pair, 0, 2)) // read two bytes from 16 bits pcm file.
{
    if (!in2.read(stereo_pair, 2, 1))  // read one byte from 8 bit file.
    {
        if (/*is unsigned 8 bit value*/)  // make it zero if error.
            stereo_pair[2] = 128;            
        else
            stereo_pair[2] = 0;
    }

    // we have to do some processing on that 8bit value.
    // If it's unsigned, we have to make it signed, then sign extend it.
    if (/*is unsigned 8 bit value*/)
    {
        stereo_pair[2] += 128; // this will offset the byte to make it signed
    }
    // we now have to sign extend the value
    if (stereo_pair[2] >= 127)
        stereo_pair[3] = 0xFF;
    else
        stereo_pair[3] = 0;

    // our stereo sample is ready to write.
    out.write(stereo_pair);
}

Решение 2, усиление 8-битного значения.

byte[] stereo_pair = new byte[4];  // a stereo pair has only 4 bytes.
while (in1.read(stereo_pair, 0, 2)) // read two bytes from 16 bits pcm file.
{
    // the only differences are that we read the byte at a different offset
    // in our stereo buffer, effectively multiplying the value by 256.
    // we also do not need to sign-extend the velue.

    if (!in2.read(stereo_pair, 3, 1))  // read one byte from 8 bit file.
    {
        if (/*is unsigned 8 bit value*/)  // make it zero if error.
            stereo_pair[3] = 128;            
        else
            stereo_pair[3] = 0;
    }

    // we have to do some processing on that 8bit value.
    // If it's unsigned, we have to make it signed
    if (/*is unsigned 8 bit value*/)
    {
        stereo_pair[3] += 128; // this will offset the byte to make it signed
    }

    stereo_pair[2] = 0; // I put this here for clarity, but it can be moved out of the loop.

    // our stereo sample is ready to write.
    out.write(stereo_pair);
}

Обратите внимание, что это не обрабатывает случай, когда файл in1 короче, чем файл in2...

person Michaël Roy    schedule 22.09.2019