Search code examples
c#audionaudiopcm

Mixing 16-bit stereo PCM audio


I'm trying to create a 16-bit PCM version of NAudio's MixingWaveProvider32 that operates on 16-bit PCM samples instead of 32-bit floats.

Each 16-bit stereo sample is packed in a byte array like so...

Byte 0 Byte 1 Byte 2 Byte 3
Channel 1 (Left) Lo Channel 1 Hi Channel 2 (Right) Lo Channel 2 Hi

The two bytes per channel are interpreted as signed integers, so the minimum value is short.MinValue, the max is short.MaxValue. I don't think you can simply add the byte values to each other.

I've written some very long-handed code (see below) but I am convinced there is a more performant way of doing this.

I'd be really grateful for any help :-)

static void Main(string[] args)
{
    // setup some input data
    byte[] b1 = { 0x1, 0x0, 0x2, 0x0, 0x3, 0x0, 0x4, 0x0 };
    byte[] b2 = new byte[b1.Length];

    Array.Copy(b1, b2, b1.Length);

    byte[] result = new byte[b1.Length];

    Console.WriteLine("b1");
    b1.DumpPcm();

    Console.WriteLine();

    Console.WriteLine("b2");
    b2.DumpPcm();

    for (int i = 0; i < b1.Length; i += 4)
    {
        short l1 = BitConverter.ToInt16(b1, i);
        short r1 = BitConverter.ToInt16(b1, i + 2);

        short l2 = BitConverter.ToInt16(b2, i);
        short r2 = BitConverter.ToInt16(b2, i + 2);

        byte[] resl = BitConverter.GetBytes(l1 + l2);
        byte[] resr = BitConverter.GetBytes(r1 + r2);

        result[i] = resl[0];
        result[i + 1] = resl[1];
        result[i + 2] = resr[0];
        result[i + 3] = resr[1];
    }

    Console.WriteLine();
    Console.WriteLine("Result...");

    result.DumpPcm();

    Console.ReadLine();
}

Solution

  • You could always use unsafe code, this should be significantly faster since you save a bunch of method calls and object allocations:

            // setup some input data
            byte[] b1 = {0x1, 0x0, 0x2, 0x0, 0x3, 0x0, 0x4, 0x0};
            byte[] b2 = new byte[b1.Length];
            Array.Copy(b1, b2, b1.Length);
            byte[] result = new byte[b1.Length];
    
            fixed (byte* b1Ptr = b1)
            {
                fixed (byte* b2Ptr = b2)
                {
                    fixed (byte* rPtr = result)
                    {
                        var s1Ptr = (short*) b1Ptr;
                        var s2Ptr = (short*) b2Ptr;
                        var srPtr = (short*) rPtr;
                        var length = b1.Length / 2;
                        for (int i = 0; i < length; i++)
                        {
                            var v = s1Ptr[i] + s2Ptr[i];
                            srPtr[i] = (short) v;
                            Console.WriteLine($"{s1Ptr[i]} + {s2Ptr[i]} -> {srPtr[i]}");
                        }
                    }
                }
            }
    

    Note that summing values might cause overflow. You should probably either average the two samples, or clamp the result to avoid this.