我需要从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)...
。
但是,对于超过2个频道的文件,使用了什么顺序?
答案 0 :(得分:32)
Microsoft创建了一个standard,最多可覆盖18个频道。据他们说,wav文件需要special meta sub-chunk(在“可扩展格式”部分下)指定“通道掩码”(dwChannelMask
)。该字段长度为4个字节(一个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
}
如果频道存在,最后check。
自己创建一个!根据文件的通道数,您将不得不猜测使用了哪些通道,或者只是盲目地遵循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&amp;使用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&amp; SR通道“下拉”到下一个最低可用订单号,而不是使用SL&amp; SR的默认顺序(10,11)。
因此,要读取单个通道的字节,您需要执行与此类似的操作,
// This code will only return the bytes of a particular channel. It's 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;
}