Как сделать конвертацию sound byte[] -> float[] для двухканального звука?

У меня есть конвертер, который я использую для одноканального звука, и он отлично работает:

public static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
{
    int x = sizeof(Int16);
    int convertedSize = source.Length / x;
    float[] data = new float[convertedSize];
    Int16 maxValue = Int16.MaxValue;

    for (int i = 0; i < convertedSize; i++)
    {
        int offset = i * x;
        data[i] = (float)BitConverter.ToInt16(source, offset) / maxValue;
        ++i;
    }

    return data;
}

Но я обнаружил, что этот подход не работает для двухканального (стерео) звука, я слышу прерывистый звук, как будто пытаюсь воспроизвести два канала, но у одного из них есть звук, а у другого нет, но он звучит без шума просто. Насколько я понимаю это происходит, потому что метод делит source на 2, так как мне нужен только один канал. Следуя этой логике, чтобы получить звуковые данные для двух каналов, я отредактировал этот метод следующим образом

public static float[] Convert16BitByteArrayToAudioClipData(byte[] source)
{
    float[] data = new float[source.Length];
    Int16 maxValue = Int16.MaxValue;

    for (int i = 0; i < data.Length - 1; i++)
    {
        data[i] = (float)BitConverter.ToInt16(source, i) / maxValue;
    }

    return data;
}

Я добавил data.Length - 1, потому что иначе я получаю эту ошибку:

ArgumentException: Destination array is not long enough to copy all the items in the collection.

В результате конвертируем byte[] -> float[] я получаю полный список данных, как на скриншоте:

enter image description here

Но, несмотря на это, я все равно слышу прерывистый звук и вдобавок слышу шум ...

Для проверки я попытался записать все эти байты в файл .wav, чтобы проверить, не повреждены ли звуковые байты, результат - нет. После записи байтов в файл я могу успешно воспроизвести файл с помощью стандартного win player. .

В итоге я почти уверен, что проблема в методе коневертации, что то там я делаю не так.

В чем возможная проблема? Почему это работает для одного канала и не работает для двух?

EDIT

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

    private void SaveWav(
        List<byte[]> soundSamples,
        long dataSize,
        ushort numberChannels,
        uint samplerate)
    {
        FileStream f = new FileStream("D:\\buffer\\out\\test.wav", FileMode.Create);
        BinaryWriter wr = new BinaryWriter(f);

        //    ***Write header
        wr.Write(Encoding.ASCII.GetBytes("RIFF"));
        wr.Write((System.Int32)(dataSize + 44 /*size of header*/));
        wr.Write(Encoding.ASCII.GetBytes("WAVE"));
        wr.Write(Encoding.ASCII.GetBytes("fmt "));
        wr.Write((System.Int32)16);
        wr.Write((System.Int16)1);
        wr.Write((System.Int16)numberChannels);
        wr.Write((System.Int32)samplerate);
        wr.Write((System.Int32)((samplerate * 16 /*BitsPerSample */ * numberChannels) / 8));
        wr.Write((System.Int16)(16 /*BitsPerSample */ * numberChannels / 8));
        wr.Write((System.Int16)16 /*BitsPerSample */);
        wr.Write(Encoding.ASCII.GetBytes("data"));
        wr.Write((System.Int32)dataSize);
        //    ***End of header

        //Combine all samples in one strip
        for (int i = 0; i < soundSamples.Count; i++)
        {
            wr.Write(soundSamples[i]);
        }
    }

Для примера загрузил файл сюда который записываю с помощью этого метода - https://drive.google.com/file/d/1tOJ3J_N6bZVJr0hKXh9HO-5DE5eZg6L7/view?usp=sharing

Так же насчет воспроизведения - я использую юнити и компонент который называется AudioSource

Вот собственно код который загружает float для воспроизведения

    public override void InitAudio(AudioSource audioPlayer)
    {
        this.audioPlayer = audioPlayer;
        if (HasAudio())
        {
            audioReader.LoadFromMemory(sound_data, sound_data_size);
            audioReader.GetCharacteristics(ref mAudioSampleRate, ref bitsPerSample, ref mAudioChannels, ref mAudioLengthSamples);
            audioReader.GetDecodedBytesAndSamplesCount(ref decodedBytesNumber, ref samplesNumber);
            if (audioClip == null && audioPlayer != null)
            {
                audioClip = AudioClip.Create("AudioVoice",
                    (int) decodedBytesNumber,
                    (int)mAudioChannels,
                    (int)(mAudioSampleRate),
                    false);

                audioPlayer.clip = audioClip;
            }

            int nSamples = 0;
            for (int i = 0; i < samplesNumber; i++)
            {
                byte[] data = null;
                if (audioReader.GetAudioByteData(i, ref data) && data != null)
                {
                    float[] pcmData = AudioReader.Convert16BitByteArrayToAudioClipData(data);

                    if (pcmData != null && pcmData.Length > 0)
                    {
                        audioPlayer.clip.SetData(pcmData, nSamples);
                        nSamples += pcmData.Length;
                    }
                }
            }
        }
    }

Ответы (1 шт):

Автор решения: VladD

Я не знаю, в каком формате записаны данные, если у вас два канала, попробую наугад вот что:

const int numberOfChannels = 2;
const int Int16InBytes = 2;

short maxValue = short.MaxValue;

var channels = new float[numberOfChannels][];
for (int i = 0; i < numberOfChannels; i++)
    channels[i] = new float[source.Length / Int16InBytes / numberOfChannels];

for (int wordNo = 0; wordNo < source.Length / Int16InBytes; wordNo++)
{
    var channelNo = wordNo % numberOfChannels;
    var indexInChannel = wordNo / numberOfChannels;
    var channel = channels[channelNo];
    float value = (float)BitConverter.ToInt16(source, wordNo * Int16InBytes) / maxValue;
    channel[indexInChannel] = value;
}

Можно попробовать переделать с новомодными Span'ами для преобразования типов «на месте»:

const int numberOfChannels = 2;
short maxValue = short.MaxValue;

Span<short> sourceAsShorts = MemoryMarshal.Cast<byte, short>(source);

var channels = new float[numberOfChannels][];
for (int i = 0; i < numberOfChannels; i++)
    channels[i] = new float[sourceAsShorts.Length / numberOfChannels];

for (int wordNo = 0; wordNo < sourceAsShorts.Length; wordNo++)
{
    var channelNo = wordNo % numberOfChannels;
    var indexInChannel = wordNo / numberOfChannels;
    var channel = channels[channelNo];
    float value = (float)sourceAsShorts[wordNo] / maxValue;
    channel[indexInChannel] = value;
}
→ Ссылка