这看起来很有趣,所以我开发了一个简单的应用程序:
- 创建两秒纯音 (440Hz A) 的样本。
- 将它们转换为 WAV 文件格式的字节数组。
- 通过将字节数组传递给 PlaySound API 来播放声音。
- 还包括将 WAV 数据保存到 WAV 文件的代码。
您可以轻松更改采样率、音调频率和采样持续时间。该代码非常丑陋且空间效率低下,但它可以工作。以下是一个完整的命令行应用程序:
使用系统;
使用 System.Diagnostics;
使用 System.IO;
使用 System.Runtime.InteropServices;
命名空间 playwav
{
课堂节目
{
[DllImport("winmm.dll", EntryPoint = "PlaySound", SetLastError = true)]
private extern static int PlaySound(byte[] wavData, IntPtr hModule, PlaySoundFlags flags);
//#define SND_SYNC 0x0000 /* 同步播放(默认) */
//#define SND_ASYNC 0x0001 /* 异步播放 */
//#define SND_NODEFAULT 0x0002 /* 如果没有找到声音,则静音 (!default) */
//#define SND_MEMORY 0x0004 /* pszSound 指向一个内存文件 */
//#define SND_LOOP 0x0008 /* 循环播放声音直到下一个 sndPlaySound */
//#define SND_NOSTOP 0x0010 /* 不停止任何当前播放的声音 */
//#define SND_NOWAIT 0x00002000L /* 如果驱动程序忙则不要等待 */
//#define SND_ALIAS 0x00010000L /* name 是一个注册表别名 */
//#define SND_ALIAS_ID 0x00110000L /* 别名是预定义的 ID */
//#define SND_FILENAME 0x00020000L /* name 是文件名 */
//#define SND_RESOURCE 0x00040004L /* name 是资源名称或原子 */
枚举 PlaySoundFlags
{
SND_SYNC = 0x0000,
SND_ASYNC = 0x0001,
SND_MEMORY = 0x0004
}
// 播放出现在字节数组中的 wav 文件
静态无效 PlayWav(byte[] wav)
{
PlaySound(wav, System.IntPtr.Zero, PlaySoundFlags.SND_MEMORY | PlaySoundFlags.SND_SYNC);
}
static byte[] ConvertSamplesToWavFileFormat(short[] left, short[] right, int sampleRate)
{
Debug.Assert(left.Length == right.Length);
常量 int channelCount = 2;
int sampleSize = sizeof(short) * channelCount * left.Length;
整数总大小 = 12 + 24 + 8 + 样本大小;
字节[] wav = 新字节[totalSize];
诠释 b = 0;
// RIFF 头
wav[b++] = (byte)'R';
wav[b++] = (byte)'I';
wav[b++] = (byte)'F';
wav[b++] = (byte)'F';
int chunkSize = totalSize - 8;
wav[b++] = (byte)(chunkSize & 0xff);
wav[b++] = (byte)((chunkSize >> 8) & 0xff);
wav[b++] = (byte)((chunkSize >> 16) & 0xff);
wav[b++] = (byte)((chunkSize >> 24) & 0xff);
wav[b++] = (byte)'W';
wav[b++] = (byte)'A';
wav[b++] = (byte)'V';
wav[b++] = (byte)'E';
// 格式化标题
wav[b++] = (byte)'f';
wav[b++] = (byte)'m';
wav[b++] = (byte)'t';
wav[b++] = (字节)' ';
wav[b++] = 16;
wav[b++] = 0;
wav[b++] = 0;
wav[b++] = 0; // 块大小
wav[b++] = 1;
wav[b++] = 0; // 压缩代码
wav[b++] = 频道计数;
wav[b++] = 0; // 通道数
wav[b++] = (byte)(sampleRate & 0xff);
wav[b++] = (byte)((sampleRate >> 8) & 0xff);
wav[b++] = (byte)((sampleRate >> 16) & 0xff);
wav[b++] = (byte)((sampleRate >> 24) & 0xff);
int byteRate = sampleRate * channelCount * sizeof(short); // 所有通道的字节速率
wav[b++] = (byte)(byteRate & 0xff);
wav[b++] = (byte)((byteRate >> 8) & 0xff);
wav[b++] = (byte)((byteRate >> 16) & 0xff);
wav[b++] = (byte)((byteRate >> 24) & 0xff);
wav[b++] = channelCount * sizeof(short);
wav[b++] = 0; // 块对齐(每个样本的字节数)
wav[b++] = sizeof(short) * 8;
wav[b++] = 0; // 每个样本的位数
// 数据块头
wav[b++] = (byte)'d';
wav[b++] = (byte)'a';
wav[b++] = (byte)'t';
wav[b++] = (byte)'a';
wav[b++] = (byte)(sampleSize & 0xff);
wav[b++] = (byte)((sampleSize >> 8) & 0xff);
wav[b++] = (byte)((sampleSize >> 16) & 0xff);
wav[b++] = (byte)((sampleSize >> 24) & 0xff);
Debug.Assert(b == 44);
for (int s = 0; s != left.Length; ++s)
{
wav[b++] = (byte)(left[s] & 0xff);
wav[b++] = (byte)(((ushort)left[s] >> 8) & 0xff);
wav[b++] = (byte)(right[s] & 0xff);
wav[b++] = (byte)(((ushort)right[s] >> 8) & 0xff);
}
Debug.Assert(b == totalSize);
返回 wav;
}
// 创建一个简单的正弦波
static void CreateSamples(out short[] left, out short[] right, int sampleRate)
{
const double middleC = 261.626;
常量双标准A = 440;
常数倍频=标准A;
整数计数 = 采样率 * 2;// 两秒
左=新短[计数];
对 = 新的短 [count];
for (int i = 0; i != count; ++i)
{
双 t = (双) i / 采样率;// 这个样本的时间,以秒为单位
短 s = (short)Math.Floor(Math.Sin(t * 2 * Math.PI * 频率) * short.MaxValue);
左[i] = s;
对[i] = s;
}
}
静态无效主要(字符串 [] 参数)
{
短 [] 左;
短 [] 对;
整数采样率 = 44100;
CreateSamples(左出,右出,sampleRate);
byte[] wav = ConvertSamplesToWavFileFormat(left, right, sampleRate);
播放Wav(wav);
/*
// 将数据写入 wav 文件
使用 (FileStream fs = new FileStream(@"C:\documents and settings\carlos\desktop\a440stereo.wav", FileMode.Create))
{
fs.Write(wav, 0, wav.Length);
}
*/
}
}
}