【问题标题】:Get byte array of highest value获取最大值的字节数组
【发布时间】:2018-10-07 15:13:29
【问题描述】:

我有一个包含数百万字节的数组。这些字节是 int 值(Int16、Int24 或 Int32)。现在我想从一定数量的字节中获取具有最大 int 值的 x 字节。

为了更好地解释这一点,让我们想象一个包含 10 个条目的数组:

byte[] arr = {255, 10, 55, 60, 128, 90, 88, 66, 199, 56};

我会知道我们使用的是 In16、Int24 还是 Int32,所以对于这个示例,假设我们使用的是 Int16。这意味着,我们使用 2 个字节来表示一个 Int16。所以 Ints 包括:

{255, 10},
{55, 60},
{128, 90},
{88, 66},
{199, 56}

问题 1:因为这是音频处理所需要的,所以 1046 低于 -2096。所以有必要独立于消极性进行比较

问题2:因为这需要非常高效,将字节转换为 Ints 进行比较似乎效率低下,应该有其他方法。

这是起点:

    /// <summary>
    /// Gets the maximum value of a number of bytes representing Int-Values
/// </summary>
/// <returns>The channels.</returns>
/// <param name="leftChannel">Left channel.</param>
/// <param name="rightChannel">Right channel.</param>
/// <param name="bytesPerInt">Bytes per int. 2 bytes = Int16, 3 bytes = Int24, 4 bytes = Int32</param>
/// <param name="countBytesToCombine">The number of bytes to look for the highest value</param>
private (byte[] combinedLeft, byte[] combinedRight) CombineChannels(byte[] leftChannel, byte[] rightChannel, int bytesPerInt, int countBytesToCombine)
{

}

/// <summary>
/// Gets the highest byte[] value 
/// </summary>
/// <returns>The highest value. The size of the byte array is equal the bytesPerInt</returns>
/// <param name="bytes">A subarray of the given byte array of the upper method. The size of this array is equals countBytesToCombine</param>
/// <param name="bytesPerInt">The count of bytes representing an Int</param>
private byte[] GetHighestValue(byte[] bytes, int bytesPerInt)
{

}

编辑2

这是一个可行的解决方案,但对于每个通道执行 1400 万字节大约需要 2 秒,这太远了。

    /// <summary>
    /// Gets the maximum value of a number of bytes representing Int-Values
    /// </summary>
    /// <returns>The channels.</returns>
    /// <param name="leftChannel">Left channel.</param>
    /// <param name="rightChannel">Right channel.</param>
    /// <param name="bytesPerInt">Bytes per int. 2 bytes = Int16, 3 bytes = Int24, 4 bytes = Int32</param>
    /// <param name="countValuesToCombine">The number of bytes to look for the highest value</param>
    private (byte[] combinedLeft, byte[] combinedRight) CombineChannels(byte[] leftChannel, byte[] rightChannel, int bytesPerInt, int countValuesToCombine)
    {
        var cLeft = new List<byte>();
        var cRight = new List<byte>();

        for (int i = 0; i < leftChannel.Length; i += countValuesToCombine * bytesPerInt)
        {
            var arrLeft = SubArray(leftChannel, i, countValuesToCombine * bytesPerInt);
            var arrRight = SubArray(rightChannel, i, countValuesToCombine * bytesPerInt);

            cLeft.AddRange(GetHighestValue(arrLeft, bytesPerInt));
            cRight.AddRange(GetHighestValue(arrRight, bytesPerInt));
        }

        return (cLeft.ToArray(), cRight.ToArray());
    }

    /// <summary>
    /// Gets the highest byte[] value 
    /// </summary>
    /// <returns>The highest value.</returns>
    /// <param name="bytes">Bytes.</param>
    /// <param name="bytesPerInt">The count of bytes representing an Int</param>
    private byte[] GetHighestValue(byte[] bytes, int bytesPerInt)
    {
        byte[] bytesOfHighestValue = new byte[bytesPerInt];

        for (int i = 0; i < bytes.Length; i += bytesPerInt)
        {
            var arr = SubArray(bytes, i, bytesPerInt);

            if (IsValueHigher(arr, bytesOfHighestValue, bytesPerInt))
            {
                bytesOfHighestValue = arr;
            }
        }

        return bytesOfHighestValue;
    }

    private bool IsValueHigher(byte[] one, byte[] two, int bytesPerInt)
    {
        var o = ConvertToInt(one, bytesPerInt);
        var t = ConvertToInt(two, bytesPerInt);

        return Math.Abs(o) > Math.Abs(t);
    }

    private int ConvertToInt(byte[] bytes, int bytesPerInt)
    {
        switch (bytesPerInt)
        {
            case 2:
                return BitConverter.ToInt16(bytes, 0);
            case 3:
                return Int24.ToInt32(bytes, 0);
            case 4:
                return BitConverter.ToInt32(bytes, 0);
        }

        return 0;
    }

这很难解释,所以请在投票前询问是否有问题。

【问题讨论】:

  • 不应该 GetHighestValue 例如返回一个 Int16 吗?
  • 这个方法是一个例子,将在CombineChannels内部调用。如果没有性能滞后,这也可以返回 Int16 或其他。但我想,在转换和打开一个新的瓶颈之前,让我们返回代表这个 Int16 值的 2 个字节
  • 如果你想返回 byte[] 那么它将是 byte[][] 或 byte[][] 的列表,例如 byte[10] 将变成 List有五个项目。
  • 我想我明白你的问题是什么,但我不知道你在问什么。您是否尝试过自己解决问题?还是您只是在问如何从性能角度解决问题?
  • 我能够自己解决这个问题,但它的性能太差,我删除了代码,所以我需要一些帮助来使它在现实世界的场景中可用。所以是的,我需要性能方面的帮助。

标签: c# arrays audio byte


【解决方案1】:

好的,这是 4 字节整数的简单实现:

private static int GetHighestValue(byte[] data)
{
  if (data.Length % 4 != 0)
     throw new ArgumentException();

  var maximum = 0, maximumAbs = 0;
  for (var i = 0; i < data.Length; i+=4)
  {
    var current = BitConverter.ToInt32 (data, i);
    var currentAbs = Math.Abs(current);

    if (currentAbs > maximumAbs)
    {
      maximum = current;
      maximumAbs = currentAbs;
    }
  }

  return maximum;
}

在 100 万字节的 byte[] 上运行它,使用 Debug 编译大约需要 3 毫秒。

我不知道您的目标是哪种速度,但对于 99% 的情况,这应该没问题。


编辑:由于您更新了问题并在此处包含示例代码,因此是更新:

这些是我让你的代码比它需要的慢的地方:

  • 我们不需要在CombineChannels 的每次迭代中创建子数组。我们可以重写GetHighestValue,使其接受arrayoffsetamount作为参数。

  • 我们应该把它分成不同的字节大小,而不是只有一个CombineChannels 方法。例如CombineChannelsInt32CombineChannelsInt16 ... 这样方法本身可以将最大值存储为int32/int16/... 而无需在每次迭代时转换它们。

以下是我们最终会得到类似这样的方法:

(byte[] combinedLeft, byte[] combinedRight) CombineChannels(byte[] leftChannel, byte[] rightChannel, int bytesPerInt, int countValuesToCombine)
{
  switch(bytesPerInt)
  {
    case 2:
      return CombineChannelsInt16(leftChannel, rightChannel, countValuesToCombine);
    case 3:
      return CombineChannelsInt24(leftChannel, rightChannel, countValuesToCombine);
    case 4:
      return CombineChannelsInt32(leftChannel, rightChannel, countValuesToCombine);
  }
}

(byte[] combinedLeft, byte[] combinedRight) CombineChannelsInt16(byte[] leftChannel, byte[] rightChannel, int countValuesToCombine);
(byte[] combinedLeft, byte[] combinedRight) CombineChannelsInt24(byte[] leftChannel, byte[] rightChannel, int countValuesToCombine);
(byte[] combinedLeft, byte[] combinedRight) CombineChannelsInt32(byte[] leftChannel, byte[] rightChannel, int countValuesToCombine);

short GetHighestValueInt16(byte[] bytes, int offset, int amount);
Int24 GetHighestValueInt24(byte[] bytes, int offset, int amount);
int GetHighestValueInt32(byte[] bytes, int offset, int amount);

【讨论】:

    【解决方案2】:

    正如已经多次提到的,在你的阅读中避免使用“if”语句;只需单独创建一个读取Int16Int24Int32的函数,并提前选择使用哪一个。

    我个人会为此使用System.IO.BinaryReader;它已经包含了从流中读取整数的函数,并且与BitConverter 不同,它在技术上依赖于系统字节序,BinaryReader 实际上保证以小字节序读取值;它在 MSDN 规范中。

    这里是使用BinaryReader的基本功能,以Int32为例。在这个版本中,我让EndOfStreamException 负责最后的处理。他们说异常抛出/处理是一项相当繁重的操作,但在这种情况下,它取代了读取之间的大量检查,因此它可能是合理的。

    您可以通过将while (true) 替换为对流指针的实际检查来适应这一点。它要么只是根据输入字节数组的长度检查 ms.Position,要么跟踪您自己的变量中的位置,您在每一步中增加读取的字节数。

    public static Int32 GetHighestValueInt32(Byte[] bytes)
    {
        Int32 maxval = 0;
        try
        {
            using (MemoryStream ms = new MemoryStream(bytes))
            using (BinaryReader reader = new BinaryReader(ms))
            {
                while (true)
                {
                    // Clear highest bit so the value's always a positive Int32.
                    Int32 val = (Int32)(reader.ReadUInt32() & 0x7FFFFFFF);
                    if (val > maxval)
                        maxval = val;
                }
            }
        }
        catch (EndOfStreamException ex)
        {
            // Finished reading!
        }
        return maxval;
    }
    

    对于Int16,实际读取val的行应该简单地替换为

    Int16 val = (Int16)(reader.ReadUInt16() & 0x7FFF);
    

    并且maxval 和返回类型同样应该更改为Int16

    但是,BinaryReader 不能在本地读取Int24 流。但是解决方法并不难。您可以简单地使用 Int32 并将其下移 8 位,然后手动调整流指针以补偿两个额外的读取字节:

    while (true)
    {
        Int32 val = (Int32)((reader.ReadUInt32() >> 8) & 0x7FFFFF);
        ms.Position -= 2;
        if (val > maxval)
            maxval = val;
    }
    

    【讨论】:

      【解决方案3】:

      我做了一个返回最大索引的方法。它首先比较最高字节,当相等时比较低字节。使用更大的整数,它的工作速度更快。

      static int getMaxIndex(byte[] data, int byteLenght)
              {
                  int MaxIndex = 0;
                  int signMax = data[byteLenght - 1] >> 7;// get sign
                  for (int i = byteLenght; i < data.Length; i += byteLenght)
                  {
                      int step = byteLenght - 1;
                      int compResult = 0;
      
                      while (compResult == 0 && step > -1)
                      {
                          if (step == byteLenght -1)
                          {
                              int signData = data[i + step] >> 7;
      
                              compResult = signData - signMax;
                              if (compResult == 0) compResult = data[MaxIndex + step] & 127 - data[i + step] & 127;
                          }
                          else compResult = data[MaxIndex + step] - data[i + step];
                          if (compResult < 0)
                          {
                              MaxIndex = i;
                              signMax = data[MaxIndex + step] >> 7;
                          }
                          step--;
                      }
                  }
                  return MaxIndex;
              }
      

      【讨论】:

      • 这个方法与我前面提到的大于检查不匹配。 11111111 11111111 被解释为最大数,但实际上这是-1
      • 但你确实想考虑消极因素。您可以通过查看最高位来调整方法。
      猜你喜欢
      • 2021-02-21
      • 1970-01-01
      • 2013-01-19
      • 1970-01-01
      • 2014-05-21
      • 2018-05-21
      • 1970-01-01
      • 1970-01-01
      • 2018-07-19
      相关资源
      最近更新 更多