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

文章目录

  • 前言
  • 一、如何实现?
    • 1.定义头结构
    • 2.预留头部空间
    • 3.写入PCM数据
    • 4.写入头部信息
  • 二、完整代码
  • 三、使用示例
  • 总结


前言

之前实现了《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.0
WavWriter.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头结构,然后自定义其头结构,然后再进行一定的测试,就可以实现这样一个功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/103584.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FPGA project : flash_secter_erase

flash的指定扇区擦除实验。 先发写指令&#xff0c;再进入写锁存周期等待500ns&#xff0c;进入写扇区擦除指令&#xff0c;然后写扇区地址&#xff0c;页地址&#xff0c;字节地址。即可完成扇区擦除。 模块框图&#xff1a; 时序图&#xff1a; 代码&#xff1a; module…

部署zabbix代理服务器、部署zabbix高可用集群

目录 部署zabbix代理服务器 1、环境设置 2、设置 zabbix 的下载源&#xff0c;安装 zabbix-proxy 3、配置Mariadb yum源&#xff0c;并下载marisdb数据库 4.、启动数据库&#xff0c;并初始化数据库 5、登录数据库&#xff0c;创建数据库并指定字符集&#xff0c;并进行…

面试复盘(待删)

泛型的理解 官方文档里是这么写的 In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one. Thi…

WSL VScode连接文件后无法修改(修改报错)

权限问题 usrname:用户名 dirpath:要修改的文件夹路径 sudo chown -R usrname /dirpath

【配置vscode编写c或c++程序,并在外部控制台输出程序和解决中文乱码问题!!!】

配置vscode编写c或c程序&#xff0c;并在外部控制台输出程序和解决中文乱码问题&#xff01;&#xff01;&#xff01; 1、点击扩展&#xff0c;添加C语言所需的插件&#xff08;点击安装&#xff0c;安装完成后点击设置标志安装另一个版本&#xff0c;注意一定要安装1.8.4版本…

计算机视觉(Computer Vision, CV)是什么?

什么是计算机视觉 近年来&#xff0c;计算机视觉 (Computer Vision&#xff0c;简称CV) 不断普及&#xff0c;已成为人工智能 (AI) 增长最快的领域之一。计算机视觉致力于使计算机能够识别和理解图像和视频中的物体和人。 计算机视觉应用程序使用来自传感设备、人工智能、机器…

ffmpeg中AVCodecContext和AVCodec的关系分析

怎么理解AVCodecContext和AVCodec的关系 AVCodecContext和AVCodec是FFmpeg库中两个相关的结构体&#xff0c;它们在音视频编解码中扮演着不同的角色。 AVCodecContext&#xff1a;是编解码器上下文结构体&#xff0c;用于存储音视频编解码器的参数和状态信息。它包含了进行音视…

hive 知识总结

​编辑 社区公告教程下载分享问答JD 登 录 注册 01 hive 介绍与安装 1 hive介绍与原理分析 Hive是一个基于Hadoop的开源数据仓库工具&#xff0c;用于存储和处理海量结构化数据。它是Facebook 2008年8月开源的一个数据仓库框架&#xff0c;提供了类似于SQL语法的HQL&#xf…

考古:MFC界面的自适应缩放(代码示例)

MFC窗体的控件的自适应缩放早期VS开发环境是不支持的&#xff0c;后来VS开发环境提供了支持但也简单&#xff0c;或者固定的缩放比例不符合要求。我一向坚持一个理念&#xff1a;“不支持缩放的窗口不是好窗口”&#xff0c;所以需要有一个自定义的缩放处理。机制不复杂&#x…

2023年中国渔业研究报告

第一章 行业概况 1.1 定义 渔业&#xff0c;作为全球经济的重要支柱之一&#xff0c;其核心活动包括捕捞、水产养殖、产品加工与销售等。其不仅是食物安全的重要保障&#xff0c;还是许多沿海和内陆地区经济发展的重要动力。 首先&#xff0c;捕捞活动是渔业的基础。通过海洋…

以太坊 CALL 数据解析【ETH】

文章目录 前言代码前言 当我们通过 jsonrpc CALL 获取到数据时,不可读,怎么办? 这里直接给大家一个工具类 代码 package trace// author JavaPub shiyuwangimport ("encoding/json""fmt""io/ioutil""net/http""strings&qu…

Web后端开发登录校验及JWT令牌,过滤器,拦截器详解

如果用户名正确则成功进入 登录功能 代码 Controller Service Mapper 结果 若登录成功结果如下: 如果登录失败,结果如下 登录校验 为什么需要登录校验 有时再未登录情况下, 我们也可以直接访问部门管理, 员工管理等功能 因此我们需要一个登录校验操作, 只有确认用户登录…

Pymol做B因子图

分子动力学模拟结束后&#xff0c;获得蛋白的平均结构&#xff0c; 比如获得的平均结构为WT-average.pdb 然后将平均结构导入到Pymol 中&#xff0c;可以得到B因子图。 gmx rmsf -f md_0_100_noPBC.xtc -s md_0_100.tpr -o rmsf-per-residue.xvg -ox average.pdb -oq bfactors…

有哪些免费的PPT模板网站,推荐这6个PPT模板免费下载网站!

混迹职场的打工人&#xff0c;或是还在校园的学生党&#xff0c;在日常的工作汇报或课程作业中&#xff0c;必然少不了PPT的影子&#xff0c;而每当提到做PPT&#xff0c;许多人首先会想到&#xff1a;有哪些免费的PPT模板下载网站&#xff1f; 本着辛苦自己&#xff0c;造福所…

编程助手成为编程高手,帮您正则调试

官方下载地址&#xff1a;安果移动 视频演示地址&#xff1a;编程助手-正则调试与面试题&#xff0c;升职加薪不是梦_哔哩哔哩_bilibili 编程助手成为编程高手&#xff0c;帮您正则调试 软件介绍版本号 1.0.2更新日期 2023-10-11 找工作不敢谈薪资&#xff1f;总觉得公司欠我…

React常用hooks总结

React Hooks react常用hooks React Hooks React Hooks是React16.8版本新增的特性&#xff0c;它允许你在不编写class的情况下使用state以及生命周期等特性。 在React中&#xff0c;组件的创建方式有两种&#xff1a;类组件和纯函数组件。 然而&#xff0c;函数组件没有状态…

线性回归模型进行特征重要性分析

目的 线性回归是很常用的模型&#xff1b;在局部可解释性上也经常用到。 数据归一化 归一化通常是为了确保不同特征之间的数值范围差异不会对线性模型的训练产生过大的影响。在某些情况下&#xff0c;特征归一化可以提高模型的性能&#xff0c;但并不是所有情况下都需要进行归一…

Java基础面试-final

final&#xff08;最终的&#xff09; 修饰类&#xff1a;表示类不可被继承修饰方法&#xff1a;表示方法不可被子类覆盖&#xff0c;但是可以重载修饰变量&#xff1a;表示变量一旦被赋值就不可以更改它的值 修饰成员变量 如果final修饰的是类变量&#xff0c;只能在静态初始…

Magica Cloth 使用方法笔记

Magica Cloth 使用方法笔记 效果展示&#xff1a; 参考资料&#xff1a; 1、官方使用文档链接&#xff1a; インストールガイド – Magica Soft 2、鱼儿效果案例&#xff1a; https://www.patreon.com/posts/69459293 3、插件工具链接&#xff1a;版本() 目录&#xff1a…

理解线程池源码 【C++】面试高频考点

理解线程池 C 文章目录 理解线程池 C程序源码知识点emplace_back 和 push_back有什么区别&#xff1f;互斥锁 mutexcondition_variablestd::move()函数bind()函数join 函数 线程池的原理就是管理一个任务队列和一个工作线程队列。 工作线程不断的从任务队列取任务&#xff0c;然…