在 C# 中将双数组写入 wave 文件

Writing a double array to a wave file in c#

我正在尝试使用 c# 从头开始​​编写 wave 文件。我设法毫无问题地编写了 16 位样本。但是当谈到 24 位时,显然所有的赌注都落空了。 我尝试了多种将 int 转换为 3 字节数组的方法,我将继续写入数据块 L-L-L-R-R-R(因为它是 24 位立体声 PCM wav)。

对于 16 位部分,我用它来生成样本:

//numberOfBytes = 2 - for 16bit. slice = something like 2*Math.Pi*frequency/samplerate

    private static byte[,] BuildByteWave(double slice, int numberOfBytes=2)
                {
                    double dataPt = 0;
                    byte[,] output = new byte[Convert.ToInt32(Samples),numberOfBytes];

            for (int i = 0; i < Samples; i++)
            {
                dataPt = Math.Sin(i * slice) * Settings.Amplitude;
                int data = Convert.ToInt32(dataPt * Settings.Volume * 32767);
                for (int j = 0; j < numberOfBytes; j++)
                {
                    output[i, j] = ExtractByte(data, j);
                }

            }
            return output;
        }

这个returns一个数组,我后来用它来写入数据块,就像这样

writer.WriteByte(samples[1][0]); //write to the left channel
writer.WriteByte(samples[1][1]); //write to the left channel
writer.WriteByte(samples[2][0]); //now to the second channel
writer.WriteByte(samples[2][1]); //and yet again.

其中1和2代表某个正弦波。 但是,如果我用 numberOfBytes = 3 尝试上面的操作,它就会失败。波就是一串non-sense。 (header 格式正确)。

我知道我需要将 int32 转换为 int24 并且我需要 "pad" 样本,但我在任何地方都找不到具体的 24 位教程。 你能给我指明正确的方向吗?

为清楚起见进行了编辑。

没有 int24 - 你需要自己做。 for/switch 也有点像 anti-pattern。

int[] samples = /* samples scaled to +/- 8388607 (0x7f`ffff) */;
byte[] data = new byte[samples.Length * 3];

for (int i = 0, j = 0; i < samples.Length; i++, j += 3)
{
    // WAV is little endian
    data[j + 0] = (byte)((i >>  0) & 0xff);
    data[j + 1] = (byte)((i >>  8) & 0xff);
    data[j + 2] = (byte)((i >> 16) & 0xff);
}

// data now has the 24-bit samples.

例如,这里有一个程序 (Github) which generates a 15 second 44.1kHz 24-bit stereo wav file with 440 Hz in the left channel and 1 kHz in the right channel:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace WavGeneratorDemo
{
    class Program
    {
        const int INT24_MAX = 0x7f_ffff;

        static void Main(string[] args)
        {
            const int sampleRate = 44100;
            const int lengthInSeconds = 15 /* sec */;
            const int channels = 2;
            const double channelSamplesPerSecond = sampleRate * channels;
            var samples = new double[lengthInSeconds * sampleRate * channels];

            // Left is 440 Hz sine wave
            FillWithSineWave(samples, channels, channelSamplesPerSecond, 0 /* Left */, 440 /* Hz */);
            // Right is 1 kHz sine wave
            FillWithSineWave(samples, channels, channelSamplesPerSecond, 1 /* Right */, 1000 /* Hz */);

            WriteWavFile(samples, sampleRate, channels, "out.wav");
        }

        private static void WriteWavFile(double[] samples, uint sampleRate, ushort channels, string fileName)
        {
            using (var wavFile = File.OpenWrite(fileName))
            {
                const int chunkHeaderSize = 8,
                    waveHeaderSize = 4,
                    fmtChunkSize = 16;
                uint samplesByteLength = (uint)samples.Length * 3u;

                // RIFF header
                wavFile.WriteAscii("RIFF");
                wavFile.WriteLittleEndianUInt32(
                    waveHeaderSize
                    + chunkHeaderSize + fmtChunkSize
                    + chunkHeaderSize + samplesByteLength);
                wavFile.WriteAscii("WAVE");

                // fmt header
                wavFile.WriteAscii("fmt ");
                wavFile.WriteLittleEndianUInt32(fmtChunkSize);
                wavFile.WriteLittleEndianUInt16(1);               // AudioFormat = PCM
                wavFile.WriteLittleEndianUInt16(channels);
                wavFile.WriteLittleEndianUInt32(sampleRate);
                wavFile.WriteLittleEndianUInt32(sampleRate * channels);
                wavFile.WriteLittleEndianUInt16((ushort)(3 * channels));    // Block Align (stride)
                wavFile.WriteLittleEndianUInt16(24);              // Bits per sample

                // samples data
                wavFile.WriteAscii("data");
                wavFile.WriteLittleEndianUInt32(samplesByteLength);
                for (int i = 0; i < samples.Length; i++)
                {
                    var scaledValue = DoubleToInt24(samples[i]);
                    wavFile.WriteLittleEndianInt24(scaledValue);
                }
            }
        }

        private static void FillWithSineWave(double[] samples, int channels, double channelSamplesPerSecond, int channelNo, double freq)
        {
            for (int i = channelNo; i < samples.Length; i += channels)
            {
                var t = (i - channelNo) / channelSamplesPerSecond;
                samples[i] = Math.Sin(t * (freq * Math.PI * 2));
            }
        }

        private static int DoubleToInt24(double value)
        {
            if (value < -1 || value > 1)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

            return (int)(value * INT24_MAX);
        }
    }

    static class StreamExtensions
    {
        public static void WriteAscii(this Stream s, string str) => s.Write(Encoding.ASCII.GetBytes(str));

        public static void WriteLittleEndianUInt32(this Stream s, UInt32 i)
        {
            var b = new byte[4];
            b[0] = (byte)((i >> 0) & 0xff);
            b[1] = (byte)((i >> 8) & 0xff);
            b[2] = (byte)((i >> 16) & 0xff);
            b[3] = (byte)((i >> 24) & 0xff);
            s.Write(b);
        }

        public static void WriteLittleEndianInt24(this Stream s, Int32 i)
        {
            var b = new byte[3];
            b[0] = (byte)((i >> 0) & 0xff);
            b[1] = (byte)((i >> 8) & 0xff);
            b[2] = (byte)((i >> 16) & 0xff);
            s.Write(b);
        }

        public static void WriteLittleEndianUInt16(this Stream s, UInt16 i)
        {
            var b = new byte[2];
            b[0] = (byte)((i >> 0) & 0xff);
            b[1] = (byte)((i >> 8) & 0xff);
            s.Write(b);
        }
    }
}

生成: