分类
标签
.NET 9008 adb android apt asp.net ASP.NET Core audio bug C c++ C++ chrome cmd csharp CSharp css css3 debian debug dotnet dotnet Framework dpkg GDI&GDI+ gdi&gdi+ golang graphics html html5 http java javascript json kali linux linux mci microsoft minimap MSBuild mysql OpenCV PInvoke powershell python QQ rust shell speex sql tutorial ubuntu ui unity vb.net visual studio Visual Studio web Web win32 winapi windows winform WinForm wpf WPF xaml xfce 列表 刷机 前端 加密 反射 反编译 可视化 图像处理 多线程 字符串 安卓 实例 局域网 幻影坦克 库 开发语言 异步 微信 手册 手机 接口 摘要 救砖 数字签名 数字证书 数字音频 数据库 桌面程序 游戏 游戏引擎 源码 爬虫 玩游戏 电脑硬件 笔记 算法 类库 线性代数 编程语言 网络 脚本语言 计算机图形学 计算机基础 设计模式 语音编解码 运维 进制 面向对象编程 音频 音频编码解码
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/