首页 > 解决方案 > 在 C# 中将双精度数组写入波形文件

问题描述

我正在尝试使用 c# 从头开始​​编写波形文件。我设法毫无问题地编写了 16 位样本。但是当谈到 24 位时,显然所有的赌注都没有了。我尝试了各种将 int 转换为 3 字节数组的方法,我将继续将其写入数据块 LLLRRR(作为 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;
        }

这将返回一个数组,我稍后使用它来写入数据块,如下所示

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 的情况下尝试上述方法,它就会失败。波是一堆废话。(标题格式正确)。

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

为清晰起见进行了编辑。

标签: c#arrayswav

解决方案


没有int24- 你需要自己做。 for/switch也是一种反模式。

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 ),它生成一个15 秒的 44.1kHz 24 位立体声 wav 文件,左声道为 440 Hz,右声道为 1 kHz

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);
        }
    }
}

生成:

Adobe Audition 的屏幕截图放大了样本视图和频谱视图,显示了 0 dB 的纯 1 kHz 和 440 Hz 音调


推荐阅读