743 字
4 分钟
[C#] 任意频率 PCM 正弦波生成与 WAV 格式文件保存

封装一个简单的 PcmBuilder, 以 IEnumerable<float> 的形式提供采样值:

public class PcmBuilder
{
private PcmBuilder() { }
public float SampleRate { get; set; } = 44100;
public float Frequency { get; set; } = 4000;
public float Amplitude { get; set; } = 1;
public PcmBuilder WithSampleRate(long sampleRate)
{
SampleRate = sampleRate;
return this;
}
public PcmBuilder WithFrequency(float frequency)
{
Frequency = frequency;
return this;
}
public PcmBuilder WithAmplitude(float amplitude)
{
Amplitude = amplitude;
return this;
}
public IEnumerable<float> Build()
{
float current = 0;
while (true)
{
yield return MathF.Sin(current / SampleRate * Frequency) * Amplitude;
current++;
}
}
public static PcmBuilder CreateNew() => new PcmBuilder();
}

使用它构建出 44100Hz 采样率, 8000Hz 的正弦波:

var pcm = PcmBuilder.CreateNew()
.WithSampleRate(44100)
.WithFrequency(8000)
.Build();

WAV 文件的头格式:

WAV 文件的头结构体定义:

public unsafe struct WaveHeader
{
private fixed byte _chunkId[4];
private uint _chunkSize;
private fixed byte _format[4];
private fixed byte _subChunk1Id[4];
private uint _subChunk1Size;
private WaveAudioFormat _audioFormat;
private ushort _numChannels;
private uint _sampleRate;
private uint _byteRate;
private ushort _blockAlign;
private ushort _bitsPerSample;
private fixed byte _subChunk2Id[4];
private uint _subChunk2Size;
public unsafe string ChunkId
{
get
{
fixed (byte* ptr = _chunkId)
{
return CreateString(ptr, 4);
}
}
set
{
fixed (byte* ptr = _chunkId)
{
FillString(ptr, value, 4);
}
}
}
public uint ChunkSize
{
get => _chunkSize;
set => _chunkSize = value;
}
public string Format
{
get
{
fixed (byte* ptr = _format)
{
return CreateString(ptr, 4);
}
}
set
{
fixed (byte* ptr = _format)
{
FillString(ptr, value, 4);
}
}
}
public string SubChunk1Id
{
get
{
fixed (byte* ptr = _subChunk1Id)
{
return CreateString(ptr, 4);
}
}
set
{
fixed (byte* ptr = _subChunk1Id)
{
FillString(ptr, value, 4);
}
}
}
public uint SubChunk1Size
{
get => _subChunk1Size;
set => _subChunk1Size = value;
}
public WaveAudioFormat AudioFormat
{
get => _audioFormat;
set => _audioFormat = value;
}
public ushort ChannelCount
{
get => _numChannels;
set => _numChannels = value;
}
public uint SampleRate
{
get => _sampleRate;
set => _sampleRate = value;
}
public uint ByteRate
{
get => _byteRate;
set => _byteRate = value;
}
public ushort BlockAlign
{
get => _blockAlign;
set => _blockAlign = value;
}
public ushort BitsPerSample
{
get => _bitsPerSample;
set => _bitsPerSample = value;
}
public string SubChunk2Id
{
get
{
fixed (byte* ptr = _subChunk2Id)
{
return CreateString(ptr, 4);
}
}
set
{
fixed (byte* ptr = _subChunk2Id)
{
FillString(ptr, value, 4);
}
}
}
public uint SubChunk2Size
{
get => _subChunk2Size;
set => _subChunk2Size = value;
}
public unsafe static WaveHeader Create(WaveAudioFormat audioFormat, ushort channelCount, uint sampleRate, ushort bitsPerSample, uint pcmDataSize)
{
return new WaveHeader()
{
ChunkId = "RIFF",
ChunkSize = (uint)(pcmDataSize + (sizeof(WaveHeader) - 8)),
Format = "WAVE",
SubChunk1Id = "fmt ",
SubChunk1Size = 16,
AudioFormat = audioFormat,
ChannelCount = channelCount,
SampleRate = sampleRate,
ByteRate = sampleRate * channelCount * bitsPerSample / 8,
BlockAlign = (ushort)(channelCount * bitsPerSample / 8),
BitsPerSample = bitsPerSample,
SubChunk2Id = "data",
SubChunk2Size = pcmDataSize
};
}
private static void FillString(byte* ptr, string value, int maxLength)
{
if (value.Length > maxLength)
{
throw new ArgumentException(nameof(value));
}
fixed (char* textPtr = value)
{
for (int i = 0; i < value.Length && i < maxLength; i++)
{
ptr[i] = (byte)textPtr[i];
}
}
}
private static string CreateString(byte* ptr, int maxLength)
{
StringBuilder sb = new StringBuilder(maxLength);
for (int i = 0; i < maxLength; i++)
{
if (ptr[i] == 0)
{
break;
}
sb.Append((char)ptr[i]);
}
return sb.ToString();
}
}
public enum WaveAudioFormat : ushort
{
None = 0,
PCM = 1,
}

使用它构建 PCM 格式, 单声道, 44100Hz 采样率, 32位深, 30 秒的音频 WAV 头:

var header = WaveHeader.Create(WaveAudioFormat.PCM, 1, 44100, 32, 44100 * 4 * 30);

创建文件, 并写入 WAV 头和 PCM 内容:

using var output = File.Create("output.wav");
using var outputWriter = new BinaryWriter(output);
var pcm = PcmBuilder.CreateNew()
.WithSampleRate(44100)
.WithFrequency(8000)
.Build();
var header = WaveHeader.Create(WaveAudioFormat.PCM, 1, 44100, 32, 44100 * 4 * 30);
// 写入 Header
unsafe
{
var headerByteSpan = new Span<byte>((byte*)&header, sizeof(WaveHeader));
outputWriter.Write(headerByteSpan);
}
// 获取 PCM 数据迭代器
var pcmEnumerator = pcm.GetEnumerator();
// 向文件写入 PCM
for (int i = 0; i < 44100 * 30; i++)
{
pcmEnumerator.MoveNext();
outputWriter.Write(pcmEnumerator.Current);
}
[C#] 任意频率 PCM 正弦波生成与 WAV 格式文件保存
https://slimenull.com/posts/202408191228/
作者
SlimeNull
发布于
2024-08-19
许可协议
CC BY-NC-SA 4.0