C#实现将音频PCM数据封装成wav文件

 更新时间:2023年10月10日 13:57:37   作者:CodeOfCC  
这篇文章主要为大家详细介绍了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的资料请关注脚本之家其它相关文章!

相关文章

  • 解析C#中[],List,Array,ArrayList的区别及应用

    解析C#中[],List,Array,ArrayList的区别及应用

    本篇文章主要是对C#中[],List,Array,ArrayList的区别及应用进行了详细的介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • C#创建快捷方式文件和硬链接文件的方法

    C#创建快捷方式文件和硬链接文件的方法

    平常我们最常window桌面上点击的左下角带小箭头的文件就是快捷方式了,大家都很熟悉它,在Windows系统中,硬链接(Hard Link)是一种文件系统中的连接方式,本文给大家介绍了C#创建快捷方式文件和硬链接文件的方法,需要的朋友可以参考下
    2024-11-11
  • C#基于简单工厂模式实现的计算器功能示例

    C#基于简单工厂模式实现的计算器功能示例

    这篇文章主要介绍了C#基于简单工厂模式实现的计算器功能,结合简单实例形式分析了C#使用工厂模式的数值运算相关操作技巧,需要的朋友可以参考下
    2017-11-11
  • 详解C#使用AD(Active Directory)验证内网用户名密码

    详解C#使用AD(Active Directory)验证内网用户名密码

    这篇文章主要介绍了详解C#使用AD(Active Directory)验证内网用户名密码的相关资料,希望通过本文能帮助到大家,让大家实现这样的功能,需要的朋友可以参考下
    2017-10-10
  • C#通过子窗体刷新父窗体的实现方法

    C#通过子窗体刷新父窗体的实现方法

    在一些软件,比如,进销存管理系统中添加销售单信息时,每个销售单都可能对应多种商品,而且在向销售单中添加商品时,一般都是在新弹出的窗体中选择商品,这时就涉及通过子窗体刷新父窗体的问题,本文给大家介绍了C#通过子窗体刷新父窗体的实现方法,需要的朋友可以参考下
    2024-04-04
  • C#实现批量给图片添加水印的示例代码

    C#实现批量给图片添加水印的示例代码

    这篇文章主要为大家详细介绍了如何利用C#实现批量给图片添加水印的功能,文中的示例代码讲解详细,对我们学习C#有一定的帮助,感兴趣的小伙伴可以了解一下
    2022-12-12
  • C#实现创建桌面快捷方式与添加网页到收藏夹的示例

    C#实现创建桌面快捷方式与添加网页到收藏夹的示例

    本文是介绍了c#通过纯代码创建快捷方式与添加网页到收藏夹,非常具有实用价值,有需要的朋友可以来了解一下。
    2016-10-10
  • C# ComboBox的联动操作(三层架构)

    C# ComboBox的联动操作(三层架构)

    这篇文章主要介绍了C# ComboBox的联动操作(三层架构),根据下拉框的变化使得下拉框绑定对应值,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • C#调用halcon实现使用鼠标滚轮对图片进行缩放显示

    C#调用halcon实现使用鼠标滚轮对图片进行缩放显示

    这篇文章主要为大家详细介绍了C#如何调用halcon实现使用鼠标滚轮对图片进行缩放显示,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • C#封装的常用文件操作类实例

    C#封装的常用文件操作类实例

    这篇文章主要介绍了C#封装的常用文件操作类,实例分析了C#文件操作的技巧,涉及C#的读写、复制、删除等常用操作,非常具有实用价值,需要的朋友可以参考下
    2015-03-03

最新评论