Как сделать конвертацию 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[] я получаю полный список данных, как на скриншоте:
Но, несмотря на это, я все равно слышу прерывистый звук и вдобавок слышу шум ...
Для проверки я попытался записать все эти байты в файл .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 шт):
Я не знаю, в каком формате записаны данные, если у вас два канала, попробую наугад вот что:
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;
}
