Подтвердить что ты не робот

Чтение одного канала из многоканального wav файла

Мне нужно извлечь образцы одного канала из wav файла, который будет содержать до 12 (11,1-формат) каналов. Я знаю, что в обычных стереофайлах образцы чередуются, сначала слева, а затем вправо, например,

[1st L] [1st R] [2nd L] [2nd R]...

Итак, чтобы прочитать левый канал, я бы сделал это,

for (var i = 0; i < myByteArray.Length; i += (bitDepth / 8) * 2)
{
    // Get bytes and convert to actual samples.
}

И чтобы получить правильный канал, я просто выполнил for (var i = (bitDepth / 8)....

Но какой порядок используется для файлов с более чем двумя каналами?

4b9b3361

Ответ 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;
}