Ответ 1
Microsoft создала стандарт, который охватывает до 18 каналов. Согласно им, файл wav должен иметь специальный мета-фрагмент (в разделе "Расширяемый формат" ), который указывает "канал" mask "(dwChannelMask
). Это поле длиной 4 байта (a uint
), которое содержит соответствующие биты каждого присутствующего канала, поэтому указывает, какой из 18 каналов используется в файле.
Макет основного канала
Ниже приведен MCL, то есть порядок, в котором существующие каналы должны чередоваться, вместе с битовым значением для каждого канала. Если канал отсутствует, следующий канал, который там, "упадет" на место отсутствующего канала, и вместо него будет использоваться его порядковый номер, но никогда не будет битового значения. (Значения бит уникальны для каждого канала независимо от существования канала),
Order | Bit | Channel
1. 0x1 Front Left
2. 0x2 Front Right
3. 0x4 Front Center
4. 0x8 Low Frequency (LFE)
5. 0x10 Back Left (Surround Back Left)
6. 0x20 Back Right (Surround Back Right)
7. 0x40 Front Left of Center
8. 0x80 Front Right of Center
9. 0x100 Back Center
10. 0x200 Side Left (Surround Left)
11. 0x400 Side Right (Surround Right)
12. 0x800 Top Center
13. 0x1000 Top Front Left
14. 0x2000 Top Front Center
15. 0x4000 Top Front Right
16. 0x8000 Top Back Left
17. 0x10000 Top Back Center
18. 0x20000 Top Back Right
Например, если маска канала 0x63F
(1599), это означает, что файл содержит 8 каналов (FL, FR, FC, LFE, BL, BR, SL и SR).
Чтение и проверка маска канала
Чтобы получить маску, вам нужно будет прочитать 40 th 41 st 42 nd и 43 rd (предполагая базовый индекс 0, и вы читаете стандартный заголовок wav). Например,
var bytes = new byte[50];
using (var stream = new FileStream("filepath...", FileMode.Open))
{
stream.Read(bytes, 0, 50);
}
var speakerMask = BitConverter.ToUInt32(new[] { bytes[40], bytes[41], bytes[42], bytes[43] }, 0);
Затем вам нужно проверить, действительно ли нужный канал существует. Для этого я предложил бы создать enum
(определенный с помощью [Flags]
), который содержит все каналы (и их соответствующие значения).
[Flags]
public enum Channels : uint
{
FrontLeft = 0x1,
FrontRight = 0x2,
FrontCenter = 0x4,
Lfe = 0x8,
BackLeft = 0x10,
BackRight = 0x20,
FrontLeftOfCenter = 0x40,
FrontRightOfCenter = 0x80,
BackCenter = 0x100,
SideLeft = 0x200,
SideRight = 0x400,
TopCenter = 0x800,
TopFrontLeft = 0x1000,
TopFrontCenter = 0x2000,
TopFrontRight = 0x4000,
TopBackLeft = 0x8000,
TopBackCenter = 0x10000,
TopBackRight = 0x20000
}
И наконец, проверьте, если канал присутствует.
Что делать, если маска канала не существует?
Создай сам! На основе количества файлов в канале вам нужно будет либо догадаться, какие каналы используются, либо просто слепо следовать MCL. В приведенном ниже фрагменте кода мы делаем немного того и другого,
public static uint GetSpeakerMask(int channelCount)
{
// Assume setup of: FL, FR, FC, LFE, BL, BR, SL & SR. Otherwise MCL will use: FL, FR, FC, LFE, BL, BR, FLoC & FRoC.
if (channelCount == 8)
{
return 0x63F;
}
// Otherwise follow MCL.
uint mask = 0;
var channels = Enum.GetValues(typeof(Channels)).Cast<uint>().ToArray();
for (var i = 0; i < channelCount; i++)
{
mask += channels[i];
}
return mask;
}
Извлечение образцов
Чтобы на самом деле читать образцы определенного канала, вы делаете то же самое, что и файл, являющийся стереофоническим, то есть вы увеличиваете счетчик циклов по размеру кадра (в байтах).
frameSize = (bitDepth / 8) * channelCount
Вам также необходимо компенсировать свой начальный индекс цикла. Здесь ситуация усложняется, так как вам нужно начать считывать данные из номера заказа канала на основе существующих каналов, глубины байтов по времени.
Что я имею в виду "на основе существующих каналов"? Ну, вам нужно переназначить номер заказа существующих каналов с 1, увеличивая порядок для каждого присутствующего канала. Например, маска канала 0x63F
указывает, что используются каналы FL, FR, FC, LFE, BL, BR, SL и SR, поэтому новые порядковые номера каналов для соответствующих каналов будут выглядеть так (обратите внимание, бит значения не изменяются и не должны меняться),
Order | Bit | Channel
1. 0x1 Front Left
2. 0x2 Front Right
3. 0x4 Front Center
4. 0x8 Low Frequency (LFE)
5. 0x10 Back Left (Surround Back Left)
6. 0x20 Back Right (Surround Back Right)
7. 0x200 Side Left (Surround Left)
8. 0x400 Side Right (Surround Right)
Вы заметите, что FLOC, FRoC и BC отсутствуют, поэтому каналы SL и SR "выпадают" в следующие самые низкие доступные порядковые номера, вместо того, чтобы использовать порядок по умолчанию SL и SR (10, 11).
Подведение итогов
Итак, чтобы прочитать байты одного канала, вам нужно сделать что-то похожее на это,
// This code will only return the bytes of a particular channel. It up to you to convert the bytes to actual samples.
public static byte[] GetChannelBytes(byte[] audioBytes, uint speakerMask, Channels channelToRead, int bitDepth, uint sampleStartIndex, uint sampleEndIndex)
{
var channels = FindExistingChannels(speakerMask);
var ch = GetChannelNumber(channelToRead, channels);
var byteDepth = bitDepth / 8;
var chOffset = ch * byteDepth;
var frameBytes = byteDepth * channels.Length;
var startByteIncIndex = sampleStartIndex * byteDepth * channels.Length;
var endByteIncIndex = sampleEndIndex * byteDepth * channels.Length;
var outputBytesCount = endByteIncIndex - startByteIncIndex;
var outputBytes = new byte[outputBytesCount / channels.Length];
var i = 0;
startByteIncIndex += chOffset;
for (var j = startByteIncIndex; j < endByteIncIndex; j += frameBytes)
{
for (var k = j; k < j + byteDepth; k++)
{
outputBytes[i] = audioBytes[(k - startByteIncIndex) + chOffset];
i++;
}
}
return outputBytes;
}
private static Channels[] FindExistingChannels(uint speakerMask)
{
var foundChannels = new List<Channels>();
foreach (var ch in Enum.GetValues(typeof(Channels)))
{
if ((speakerMask & (uint)ch) == (uint)ch)
{
foundChannels.Add((Channels)ch);
}
}
return foundChannels.ToArray();
}
private static int GetChannelNumber(Channels input, Channels[] existingChannels)
{
for (var i = 0; i < existingChannels.Length; i++)
{
if (existingChannels[i] == input)
{
return i;
}
}
return -1;
}