C#实现将音频PCM数据封装成wav文件
前言
之前实现了《C++ 将音频PCM数据封装成wav文件》,最近将其改成了C#版本。使用C#实现录音功能时还是需要写wav文件的,直接用C#实现也是比较简单的,这样可以免去了不必要的依赖。
一、如何实现
首先需要构造wav头部,wav文件音频信息全部保存在头部,我们要做的就是在PCM数据的前面加入wav头,并且记录PCM的相关参数。
1.定义头结构
只定义PCM格式的wav文件头,包含3部分riff、format、data。需要使用[StructLayout(LayoutKind.Sequential)]描述结构体,确保内存连续。
WAV头部
//WAV头部结构-PCM格式 [StructLayout(LayoutKind.Sequential)] struct WavPCMFileHeader { RIFF riff=new RIFF(); Format format = new Format(); Data data = new Data(); public WavPCMFileHeader() { } }
RTTF部分
[StructLayout(LayoutKind.Sequential)] struct RIFF { byte r = (byte)'R'; byte i = (byte)'I'; byte f = (byte)'F'; byte t = (byte)'F'; public uint fileLength = 0; byte w = (byte)'W'; byte a = (byte)'A'; byte v = (byte)'V'; byte e = (byte)'E'; public RIFF() { } }
Format部分
[StructLayout(LayoutKind.Sequential)] struct Format { byte f = (byte)'f'; byte m = (byte)'m'; byte t = (byte)'t'; byte s = (byte)' '; public uint blockSize = 16; public ushort formatTag=0; public ushort channels = 0; public uint samplesPerSec = 0; public uint avgBytesPerSec = 0; public ushort blockAlign = 0; public ushort bitsPerSample = 0; public Format() { } }
Data部分
[StructLayout(LayoutKind.Sequential)] struct Data { byte d = (byte)'d'; byte a = (byte)'a'; byte t = (byte)'t'; byte a2 = (byte)'a'; public uint dataLength=0; public Data() { } }
2.预留头部空间
创建文件时预留头部空间
_stream = File.Open(fileName, FileMode.Create); _stream!.Seek(Marshal.SizeOf<WavPCMFileHeader>(), SeekOrigin.Begin);
3.写入PCM数据
写入数据
_stream!.Write(data);
4.写入头部信息
关闭文件时,回到起始位置写入头部信息
//写入头部信息 _stream!.Seek(0, SeekOrigin.Begin); WavPCMFileHeader h = new WavPCMFileHeader(_channels, _sampleRate, _bitsPerSample, (uint)(_stream.Length - Marshal.SizeOf<WavPCMFileHeader>())); _stream!.Write(StructToBytes(h)); _stream!.Close(); _stream = null;
二、完整代码
.net6.0WavWriter.cs
using System.Runtime.InteropServices; /************************************************************************ * @Project: AC::WavWriter * @Decription: wav文件写入工具 * @Verision: v1.0.0.0 * @Author: Xin Nie * @Create: 2023/10/8 09:27:00 * @LastUpdate: 2023/10/8 18:28:00 ************************************************************************ * Copyright @ 2025. All rights reserved. ************************************************************************/ namespace AC { /// <summary> /// wav写入工具,目前只支持pcm格式 /// </summary> public class WavWriter:IDisposable { ushort _channels; uint _sampleRate; ushort _bitsPerSample; FileStream? _stream; /// <summary> /// 创建对象 /// </summary> /// <param name="fileName">文件名</param> /// <param name="channels">声道数</param> /// <param name="sampleRate">采样率,单位hz</param> /// <param name="bitsPerSample">位深</param> public static WavWriter Create(string fileName, ushort channels, uint sampleRate, ushort bitsPerSample) { return new WavWriter(fileName, channels, sampleRate, bitsPerSample); } /// <summary> /// 构造方法 /// </summary> /// <param name="fileName">文件名</param> /// <param name="channels">声道数</param> /// <param name="sampleRate">采样率,单位hz</param> /// <param name="bitsPerSample">位深</param> WavWriter(string fileName, ushort channels, uint sampleRate, ushort bitsPerSample) { _stream = File.Open(fileName, FileMode.Create); _channels = channels; _sampleRate = sampleRate; _bitsPerSample = bitsPerSample; _stream!.Seek(Marshal.SizeOf<WavPCMFileHeader>(), SeekOrigin.Begin); } /// <summary> /// 写入PCM数据 /// </summary> /// <param name="data">PCM数据</param> public void Write(byte[] data) { _stream!.Write(data); } /// <summary> /// 写入PCM数据 /// </summary> /// <param name="stream">PCM数据</param> public void Write(Stream stream) { stream.CopyTo(_stream!); } /// <summary> /// 关闭文件 /// </summary> public void Close() { //写入头部信息 _stream!.Seek(0, SeekOrigin.Begin); WavPCMFileHeader h = new WavPCMFileHeader(_channels, _sampleRate, _bitsPerSample, (uint)(_stream.Length - Marshal.SizeOf<WavPCMFileHeader>())); _stream!.Write(StructToBytes(h)); _stream!.Close(); _stream = null; } public void Dispose() { Close(); } static byte[] StructToBytes<T>(T obj) { int size = Marshal.SizeOf(typeof(T)); IntPtr bufferPtr = Marshal.AllocHGlobal(size); try { Marshal.StructureToPtr(obj!, bufferPtr, false); byte[] bytes = new byte[size]; Marshal.Copy(bufferPtr, bytes, 0, size); return bytes; } catch (Exception ex) { throw new Exception("Error in StructToBytes ! " + ex.Message); } finally { Marshal.FreeHGlobal(bufferPtr); } } } //WAV头部结构-PCM格式 [StructLayout(LayoutKind.Sequential)] struct WavPCMFileHeader { [StructLayout(LayoutKind.Sequential)] struct RIFF { byte r = (byte)'R'; byte i = (byte)'I'; byte f = (byte)'F'; byte t = (byte)'F'; public uint fileLength = 0; byte w = (byte)'W'; byte a = (byte)'A'; byte v = (byte)'V'; byte e = (byte)'E'; public RIFF() { } } [StructLayout(LayoutKind.Sequential)] struct Format { byte f = (byte)'f'; byte m = (byte)'m'; byte t = (byte)'t'; byte s = (byte)' '; public uint blockSize = 16; public ushort formatTag=0; public ushort channels = 0; public uint samplesPerSec = 0; public uint avgBytesPerSec = 0; public ushort blockAlign = 0; public ushort bitsPerSample = 0; public Format() { } } [StructLayout(LayoutKind.Sequential)] struct Data { byte d = (byte)'d'; byte a = (byte)'a'; byte t = (byte)'t'; byte a2 = (byte)'a'; public uint dataLength=0; public Data() { } } RIFF riff=new RIFF(); Format format = new Format(); Data data = new Data(); public WavPCMFileHeader() { } public WavPCMFileHeader(ushort nCh, uint nSampleRate, ushort bitsPerSample, uint dataSize) { riff.fileLength = (uint)(36 + dataSize); format.formatTag = 1; format.channels = nCh; format.samplesPerSec = nSampleRate; format.avgBytesPerSec = nSampleRate * nCh * bitsPerSample / 8; format.blockAlign = (ushort)(nCh * bitsPerSample / 8); format.bitsPerSample = bitsPerSample; data.dataLength = dataSize; } }; }
三、使用示例
using AC; try { using (var ww = WavWriter.Create("test.wav", 2, 44100, 16)) { byte[]data; //获取PCM数据 //略 //获取PCM数据-end //写入PCM数据 ww.Write(data); } } catch (Exception e) { Console.WriteLine(e.Message); }
总结
PCM封装成wav还是相对较简单的,只要了解wav头结构,然后自定义其头结构,然后再进行一定的测试,就可以实现这样一个功能。
以上就是C#实现将音频PCM数据封装成wav文件的详细内容,更多关于C# PCM数据封装成wav的资料请关注脚本之家其它相关文章!
最新评论