Click here to Skip to main content
16,015,296 members
Articles / Programming Languages / C#
Tip/Trick

Convert byte[] to short[]

Rate me:
Please Sign up or sign in to vote.
2.50/5 (4 votes)
2 Aug 2024CPOL 3.8K   41
Converting byte[] to short[]. Can be used when dealing with raw audio samples.
I came across an old question, some people on here get a little butt hurt when you post on an old question, so I decided to post a tip/trick on how, in case someone is searching for the same.
C#
static unsafe short[] ToInt16Array(byte[] data)
{
    //the length of data should be an even number
    int length = data.Length >> 1;
    short[] array = new short[length];
    fixed(byte* ptr = data) 
    {
        short* value = (short*)ptr;
        for (int i = 0; i < length; i++, value++) 
        {
            array[i] = (short)*value;
        }
    }
    return array;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I do not claim to be wrong! I just rarely ever write.

Comments and Discussions

 
SuggestionMake it faster Pin
Hawkynt12-Aug-24 3:23
professionalHawkynt12-Aug-24 3:23 
After reviewing your code and reading through the existing comments, it’s clear you've received some valuable suggestions for improvement. However, I’d like to offer a different perspective. If your goal is to maximize speed for your audio-related conversion, I have an alternative method to suggest, along with some benchmarks comparing it to your current approach.

TL;DR the results:

Quote:
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.4651/22H2/2022Update)
11th Gen Intel Core i9-11950H 2.60GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.3.24204.13
[Host] : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI [AttachedDebugger]
DefaultJob : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method | ArraySize | Mean | Error | StdDev | Ratio | RatioSD |
|----------------- |---------- |---------------Hmmm | :| --------------Hmmm | :| --------------Hmmm | :| ------Hmmm | :| --------Hmmm | :|
| CharlesHenington | 0 | 2.867 ns | 0.0456 ns | 0.0426 ns | 1.00 | 0.02 |
| GeorgeSwan | 0 | 2.652 ns | 0.0568 ns | 0.0504 ns | 0.93 | 0.02 |
| BufferBlockCopy | 0 | 8.933 ns | 0.1486 ns | 0.1390 ns | 3.12 | 0.06 |
| Hawkynt | 0 | 3.266 ns | 0.0796 ns | 0.0745 ns | 1.14 | 0.03 |
| | | | | | | |
| CharlesHenington | 1 | 3.199 ns | 0.0697 ns | 0.0618 ns | 1.00 | 0.03 |
| GeorgeSwan | 1 | 3.586 ns | 0.0648 ns | 0.0541 ns | 1.12 | 0.03 |
| BufferBlockCopy | 1 | 8.980 ns | 0.1237 ns | 0.1157 ns | 2.81 | 0.06 |
| Hawkynt | 1 | 3.507 ns | 0.0621 ns | 0.0485 ns | 1.10 | 0.03 |
| | | | | | | |
| CharlesHenington | 2 | 3.825 ns | 0.0834 ns | 0.0780 ns | 1.00 | 0.03 |
| GeorgeSwan | 2 | 4.922 ns | 0.0659 ns | 0.0617 ns | 1.29 | 0.03 |
| BufferBlockCopy | 2 | 10.489 ns | 0.0780 ns | 0.0691 ns | 2.74 | 0.06 |
| Hawkynt | 2 | 4.224 ns | 0.0623 ns | 0.0583 ns | 1.10 | 0.03 |
| | | | | | | |
| CharlesHenington | 3 | 3.784 ns | 0.0722 ns | 0.0640 ns | 1.00 | 0.02 |
| GeorgeSwan | 3 | 5.972 ns | 0.1318 ns | 0.1233 ns | 1.58 | 0.04 |
| BufferBlockCopy | 3 | 9.868 ns | 0.1085 ns | 0.1015 ns | 2.61 | 0.05 |
| Hawkynt | 3 | 4.149 ns | 0.0634 ns | 0.0529 ns | 1.10 | 0.02 |
| | | | | | | |
| CharlesHenington | 4 | 4.031 ns | 0.0742 ns | 0.0658 ns | 1.00 | 0.02 |
| GeorgeSwan | 4 | 7.178 ns | 0.0815 ns | 0.0762 ns | 1.78 | 0.03 |
| BufferBlockCopy | 4 | 9.472 ns | 0.1750 ns | 0.1552 ns | 2.35 | 0.05 |
| Hawkynt | 4 | 4.297 ns | 0.1083 ns | 0.1064 ns | 1.07 | 0.03 |
| | | | | | | |
| CharlesHenington | 5 | 4.141 ns | 0.0904 ns | 0.0846 ns | 1.00 | 0.03 |
| GeorgeSwan | 5 | 8.544 ns | 0.1891 ns | 0.2322 ns | 2.06 | 0.07 |
| BufferBlockCopy | 5 | 9.545 ns | 0.1052 ns | 0.1638 ns | 2.31 | 0.06 |
| Hawkynt | 5 | 4.348 ns | 0.0959 ns | 0.1027 ns | 1.05 | 0.03 |
| | | | | | | |
| CharlesHenington | 6 | 4.524 ns | 0.0775 ns | 0.0605 ns | 1.00 | 0.02 |
| GeorgeSwan | 6 | 9.034 ns | 0.1142 ns | 0.1068 ns | 2.00 | 0.04 |
| BufferBlockCopy | 6 | 9.621 ns | 0.0818 ns | 0.0683 ns | 2.13 | 0.03 |
| Hawkynt | 6 | 4.387 ns | 0.0720 ns | 0.0673 ns | 0.97 | 0.02 |
| | | | | | | |
| CharlesHenington | 7 | 4.570 ns | 0.0924 ns | 0.0819 ns | 1.00 | 0.02 |
| GeorgeSwan | 7 | 10.324 ns | 0.1752 ns | 0.1463 ns | 2.26 | 0.05 |
| BufferBlockCopy | 7 | 9.814 ns | 0.1516 ns | 0.1266 ns | 2.15 | 0.05 |
| Hawkynt | 7 | 4.364 ns | 0.1031 ns | 0.1058 ns | 0.96 | 0.03 |
| | | | | | | |
| CharlesHenington | 8 | 5.040 ns | 0.1238 ns | 0.2200 ns | 1.00 | 0.06 |
| GeorgeSwan | 8 | 11.637 ns | 0.2525 ns | 0.2362 ns | 2.31 | 0.11 |
| BufferBlockCopy | 8 | 10.043 ns | 0.2073 ns | 0.2304 ns | 2.00 | 0.09 |
| Hawkynt | 8 | 4.239 ns | 0.0764 ns | 0.0678 ns | 0.84 | 0.04 |
| | | | | | | |
| CharlesHenington | 10 | 5.788 ns | 0.1234 ns | 0.1094 ns | 1.00 | 0.03 |
| GeorgeSwan | 10 | 13.647 ns | 0.1929 ns | 0.1710 ns | 2.36 | 0.05 |
| BufferBlockCopy | 10 | 9.911 ns | 0.1507 ns | 0.1409 ns | 1.71 | 0.04 |
| Hawkynt | 10 | 5.714 ns | 0.1106 ns | 0.1035 ns | 0.99 | 0.02 |
| | | | | | | |
| CharlesHenington | 12 | 6.068 ns | 0.1095 ns | 0.0971 ns | 1.00 | 0.02 |
| GeorgeSwan | 12 | 15.491 ns | 0.1899 ns | 0.1776 ns | 2.55 | 0.05 |
| BufferBlockCopy | 12 | 9.640 ns | 0.2152 ns | 0.2210 ns | 1.59 | 0.04 |
| Hawkynt | 12 | 5.674 ns | 0.0974 ns | 0.0911 ns | 0.94 | 0.02 |
| | | | | | | |
| CharlesHenington | 14 | 6.552 ns | 0.0894 ns | 0.0793 ns | 1.00 | 0.02 |
| GeorgeSwan | 14 | 17.905 ns | 0.3773 ns | 0.3529 ns | 2.73 | 0.06 |
| BufferBlockCopy | 14 | 9.831 ns | 0.1092 ns | 0.0912 ns | 1.50 | 0.02 |
| Hawkynt | 14 | 5.690 ns | 0.0979 ns | 0.0915 ns | 0.87 | 0.02 |
| | | | | | | |
| CharlesHenington | 16 | 7.161 ns | 0.1508 ns | 0.1411 ns | 1.00 | 0.03 |
| GeorgeSwan | 16 | 20.149 ns | 0.1629 ns | 0.1272 ns | 2.81 | 0.06 |
| BufferBlockCopy | 16 | 9.841 ns | 0.1662 ns | 0.1554 ns | 1.37 | 0.03 |
| Hawkynt | 16 | 6.122 ns | 0.1000 ns | 0.0886 ns | 0.86 | 0.02 |
| | | | | | | |
| CharlesHenington | 100 | 32.404 ns | 0.5817 ns | 0.5441 ns | 1.00 | 0.02 |
| GeorgeSwan | 100 | 119.744 ns | 1.0700 ns | 0.9485 ns | 3.70 | 0.07 |
| BufferBlockCopy | 100 | 13.340 ns | 0.1519 ns | 0.1346 ns | 0.41 | 0.01 |
| Hawkynt | 100 | 9.394 ns | 0.1396 ns | 0.1306 ns | 0.29 | 0.01 |
| | | | | | | |
| CharlesHenington | 3200 | 647.414 ns | 7.2307 ns | 6.7636 ns | 1.00 | 0.01 |
| GeorgeSwan | 3200 | 3,453.941 ns | 27.4155 ns | 25.6445 ns | 5.34 | 0.07 |
| BufferBlockCopy | 3200 | 132.376 ns | 0.9534 ns | 0.8918 ns | 0.20 | 0.00 |
| Hawkynt | 3200 | 119.747 ns | 2.3510 ns | 3.2958 ns | 0.18 | 0.01 |
| | | | | | | |
| CharlesHenington | 11025 | 2,172.936 ns | 25.2469 ns | 22.3807 ns | 1.00 | 0.01 |
| GeorgeSwan | 11025 | 11,824.237 ns | 85.0400 ns | 75.3857 ns | 5.44 | 0.06 |
| BufferBlockCopy | 11025 | 385.451 ns | 7.5880 ns | 6.7266 ns | 0.18 | 0.00 |
| Hawkynt | 11025 | 396.096 ns | 4.3619 ns | 4.4793 ns | 0.18 | 0.00 |
| | | | | | | |
| CharlesHenington | 22050 | 4,414.342 ns | 42.7922 ns | 40.0278 ns | 1.00 | 0.01 |
| GeorgeSwan | 22050 | 23,455.660 ns | 133.7755 ns | 118.5885 ns | 5.31 | 0.05 |
| BufferBlockCopy | 22050 | 805.459 ns | 14.1072 ns | 12.5056 ns | 0.18 | 0.00 |
| Hawkynt | 22050 | 836.101 ns | 16.6413 ns | 30.0077 ns | 0.19 | 0.01 |
| | | | | | | |
| CharlesHenington | 44100 | 8,741.491 ns | 125.3789 ns | 104.6971 ns | 1.00 | 0.02 |
| GeorgeSwan | 44100 | 45,600.153 ns | 383.3263 ns | 358.5637 ns | 5.22 | 0.07 |
| BufferBlockCopy | 44100 | 2,104.648 ns | 38.5773 ns | 36.0852 ns | 0.24 | 0.00 |
| Hawkynt | 44100 | 2,152.238 ns | 18.7546 ns | 15.6609 ns | 0.25 | 0.00 |
| | | | | | | |
| CharlesHenington | 48000 | 9,453.950 ns | 91.0283 ns | 80.6942 ns | 1.00 | 0.01 |
| GeorgeSwan | 48000 | 49,454.773 ns | 342.8557 ns | 320.7074 ns | 5.23 | 0.05 |
| BufferBlockCopy | 48000 | 2,340.245 ns | 42.8133 ns | 40.0476 ns | 0.25 | 0.00 |
| Hawkynt | 48000 | 2,322.503 ns | 27.2077 ns | 25.4501 ns | 0.25 | 0.00 |
| | | | | | | |
| CharlesHenington | 96000 | 43,552.606 ns | 300.8701 ns | 281.4341 ns | 1.00 | 0.01 |
| GeorgeSwan | 96000 | 128,840.415 ns | 1,081.0900 ns | 1,011.2522 ns | 2.96 | 0.03 |
| BufferBlockCopy | 96000 | 34,951.609 ns | 523.7410 ns | 489.9076 ns | 0.80 | 0.01 |
| Hawkynt | 96000 | 33,729.029 ns | 583.4537 ns | 545.7630 ns | 0.77 | 0.01 |
| | | | | | | |
| CharlesHenington | 192000 | 88,196.524 ns | 1,064.7532 ns | 995.9708 ns | 1.00 | 0.02 |
| GeorgeSwan | 192000 | 260,512.458 ns | 2,030.4894 ns | 1,899.3210 ns | 2.95 | 0.04 |
| BufferBlockCopy | 192000 | 69,638.689 ns | 1,202.5732 ns | 1,124.8878 ns | 0.79 | 0.02 |
| Hawkynt | 192000 | 66,311.854 ns | 1,296.6680 ns | 1,212.9040 ns | 0.75 | 0.02 |


And here the test-code:
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<BytesToShorts>();

public class BytesToShorts {
  
  private byte[] data;

  [Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 100, 3200, 11025, 22050, 44100, 48000, 96000, 192000)]
  public int ArraySize;

  [GlobalSetup]
  public void Setup() {
    this.data = new byte[this.ArraySize];
    new Random().NextBytes(this.data);
  }

  [Benchmark(Baseline = true)]
  public void CharlesHenington() {
    var result = Invoke(this.data);

    static unsafe short[] Invoke(byte[] data) {
      int length = data.Length / 2;
      short[] array = new short[length];
      fixed (byte* ptr = data) {
        short* value = (short*)ptr;
        for (int i = 0; i < length; i++, value++) {
          array[i] = (short)*value;
        }
      }

      return array;
    }
  }

  [Benchmark]
  public void GeorgeSwan() {
    var result = Invoke(this.data);
    
    static short[] Invoke(byte[] data) {
      //Calculate the required array size
      int arrayLength = (data.Length / 2) + data.Length % 2;
      short[] array = new short[arrayLength];
      for (int i = 0; i < data.Length; i++) {
        //When i%2 is 1 it's the high order byte of the short
        //So shift it to the left by 8 bits
        int shiftN = (i % 2) * 8;
        short shifted = (short)(data[i] << shiftN);
        //OR in the required byte.
        //use integer division arithmetic to index the correct array member
        array[i / 2] = (short)(array[i / 2] | shifted);
      }

      return array;
    }
  }

  [Benchmark]
  public void BufferBlockCopy() {
    var result = Invoke(this.data);
    static short[] Invoke(byte[] data) {
      var result = new short[data.Length / 2];
      Buffer.BlockCopy(data, 0, result, 0, result.Length * 2);
      return result;
    }
  }


  [StructLayout(LayoutKind.Explicit, Size = 64)]
  private struct Block64; 

  [Benchmark]
  public void Hawkynt() {
    var result = Invoke(this.data);
    
    static unsafe short[] Invoke(byte[] data) {
      int length = data.Length >> 1;
      short[] result = new short[length];
      fixed (byte* sourcePointer = data)
      fixed (short* targetPointer = result) {
        short* source = (short*)sourcePointer;
        short* target = targetPointer;

        for (;;)
          switch (length) {
            case 0:
              return result;
            case 1:
              *target = *source;
              return result;
            case 2:
              *(int*)target = *(int*)source;
              return result;
            case 3:
              *(int*)target = *(int*)source;
              target[2] = source[2];
              return result;
            case 4:
              *(long*)target = *(long*)source;
              return result;
            default: {

              while (length > 256) {
                *(Block64*)target = *(Block64*)source;
                ((Block64*)target)[1] = ((Block64*)source)[1];
                ((Block64*)target)[2] = ((Block64*)source)[2];
                ((Block64*)target)[3] = ((Block64*)source)[3];
                ((Block64*)target)[4] = ((Block64*)source)[4];
                ((Block64*)target)[5] = ((Block64*)source)[5];
                ((Block64*)target)[6] = ((Block64*)source)[6];
                ((Block64*)target)[7] = ((Block64*)source)[7];

                source += 256;
                target += 256;
                length -= 256;
              }

              while (length > 32) {
                *(Block64*)target = *(Block64*)source;
                source += 32;
                target += 32;
                length -= 32;
              }

              while (length >= 4) {
                *(long*)target = *(long*)source;
                source += 4;
                target += 4;
                length -= 4;
              }

              if (length == 0)
                return result;

              continue;
            }
          }
      }
    }
  }

}


So what you could use is basically this method:
static unsafe short[] ToInt16Array(byte[] data) {
  //the length of data should be an even number,
  //so we divide by 2 (=right shift 1) and ignore the last outstanding byte if any
  int length = data.Length >> 1;
  short[] result = new short[length];
  fixed (byte* sourcePointer = data)
  fixed (short* targetPointer = result) {
    short* source = (short*)sourcePointer;
    short* target = targetPointer;

    for (;;) // as long as there are bytes left
      // we don't include the check for length > 0, because the compiler would emit it in every iteration,
      // and we know better in the switch cases anyway
      switch (length) {
        case 0:
          // less than 2 bytes left - nothing to copy
          return result;
        case 1:
          // copy 2 bytes
          *target = *source;
          return result;
        case 2:
          // copy 4 bytes
          *(int*)target = *(int*)source;
          return result;
        case 3:
          // copy 4 bytes
          *(int*)target = *(int*)source;
          // copy the remaining 2 bytes
          target[2] = source[2];
          return result;
        case 4:
          // copy 8 bytes
          *(long*)target = *(long*)source;
          return result;
        default: {

          // copy in multiple of 512 bytes - for very large counts
          // We unroll the loop to avoid checking the loop condition.
          // By unrolling 8 times we save 7 out of 8 checks, so 87.5% less.
          // More would not be feasible, because the highest IL-opcode for loading an immediate is `ldc.i8`.
          while (length > 256) {
            *(Block64*)target = *(Block64*)source;
            ((Block64*)target)[1] = ((Block64*)source)[1];
            ((Block64*)target)[2] = ((Block64*)source)[2];
            ((Block64*)target)[3] = ((Block64*)source)[3];
            ((Block64*)target)[4] = ((Block64*)source)[4];
            ((Block64*)target)[5] = ((Block64*)source)[5];
            ((Block64*)target)[6] = ((Block64*)source)[6];
            ((Block64*)target)[7] = ((Block64*)source)[7];

            source += 256;
            target += 256;
            length -= 256;
          }

          // copy in multiple of 64 bytes - for large counts
          // This struct is needed so the jitter can emit optimal code to copy 64 bytes at once.
          // It could also generate code for other counts < 64 bytes but in our specific case that
          // would not help. By 2024 the jitter would not generate better code for counts > 64 but
          // instead use `rep mov[b/w/d/q]` which would improve our loop but leading to countless
          // structs being necessary for all different counts.
          while (length > 32) {
            *(Block64*)target = *(Block64*)source;
            source += 32;
            target += 32;
            length -= 32;
          }

          // copy in multiple of 8 bytes - for medium counts
          // This utilizes the largest register on x64 machines we have access to without using Vector<T>.
          while (length >= 4) {
            *(long*)target = *(long*)source;
            source += 4;
            target += 4;
            length -= 4;
          }

          // shortcut here to avoid re-entering the loop and doing the switch-table stuff again
          if (length == 0)
            return result;

          continue;
        }
      }
  }
}

Have a nice day Smile | :)
Try my C#-Extensions: https://github.com/Hawkynt/C--FrameworkExtensions

GeneralRe: Make it faster Pin
charles henington13-Aug-24 15:46
charles henington13-Aug-24 15:46 
SuggestionRe: Make it faster Pin
Hawkynt14-Aug-24 23:00
professionalHawkynt14-Aug-24 23:00 
QuestionJose Pin
Jose Hernandez 20248-Aug-24 18:18
Jose Hernandez 20248-Aug-24 18:18 
AnswerRe: Jose Pin
OriginalGriff8-Aug-24 18:19
mveOriginalGriff8-Aug-24 18:19 
SuggestionHave you tried Buffer.BlockCopy? Pin
Daniele Rota Nodari4-Aug-24 23:51
Daniele Rota Nodari4-Aug-24 23:51 
AnswerConvertAll! Pin
Sergey Alexandrovich Kryukov5-Aug-24 6:14
mvaSergey Alexandrovich Kryukov5-Aug-24 6:14 
GeneralRe: ConvertAll! Pin
charles henington5-Aug-24 7:02
charles henington5-Aug-24 7:02 
AnswerRe: ConvertAll! — timing Pin
Sergey Alexandrovich Kryukov5-Aug-24 10:34
mvaSergey Alexandrovich Kryukov5-Aug-24 10:34 
GeneralRe: ConvertAll! Pin
Daniele Rota Nodari5-Aug-24 8:03
Daniele Rota Nodari5-Aug-24 8:03 
AnswerRe: ConvertAll! — two reasons Pin
Sergey Alexandrovich Kryukov5-Aug-24 10:36
mvaSergey Alexandrovich Kryukov5-Aug-24 10:36 
GeneralRe: Have you tried Buffer.BlockCopy? Pin
George Swan6-Aug-24 3:39
mveGeorge Swan6-Aug-24 3:39 
GeneralRe: Have you tried Buffer.BlockCopy? Pin
Daniele Rota Nodari6-Aug-24 8:18
Daniele Rota Nodari6-Aug-24 8:18 
SuggestionPretty bad Pin
Sergey Alexandrovich Kryukov4-Aug-24 7:59
mvaSergey Alexandrovich Kryukov4-Aug-24 7:59 
GeneralRe: Pretty bad Pin
charles henington4-Aug-24 18:44
charles henington4-Aug-24 18:44 
GeneralRe: Pretty bad Pin
George Swan4-Aug-24 22:59
mveGeorge Swan4-Aug-24 22:59 
GeneralRe: Pretty bad Pin
charles henington5-Aug-24 7:06
charles henington5-Aug-24 7:06 
AnswerLet's sort it out Pin
Sergey Alexandrovich Kryukov5-Aug-24 6:09
mvaSergey Alexandrovich Kryukov5-Aug-24 6:09 
QuestionConvert Using A Binary Operator. Pin
George Swan3-Aug-24 7:44
mveGeorge Swan3-Aug-24 7:44 
AnswerRe: Convert Using A Binary Operator. Pin
charles henington3-Aug-24 18:26
charles henington3-Aug-24 18:26 
AnswerLearn first Pin
Sergey Alexandrovich Kryukov4-Aug-24 8:07
mvaSergey Alexandrovich Kryukov4-Aug-24 8:07 
AnswerStill not good enough Pin
Sergey Alexandrovich Kryukov4-Aug-24 8:02
mvaSergey Alexandrovich Kryukov4-Aug-24 8:02 
GeneralRe: Still not good enough Pin
George Swan4-Aug-24 10:51
mveGeorge Swan4-Aug-24 10:51 
AnswerRe: Still not good enough Pin
Sergey Alexandrovich Kryukov4-Aug-24 12:43
mvaSergey Alexandrovich Kryukov4-Aug-24 12:43 
GeneralRe: Still not good enough Pin
charles henington4-Aug-24 18:50
charles henington4-Aug-24 18:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.