当前位置: 首页 > news >正文

博客自助建站国际财经新闻

博客自助建站,国际财经新闻,如何建设网站兴田德润可信赖,中国域名后缀是什么文章目录 前言一、需要的对象及方法二、整体流程三、关键实现1、使用Thread开启线程2、TaskCompletionSource实现异步3、将指针封装为Stream 四、完整代码1.接口2.具体实现 五、使用示例方式一方式二 总结 前言 之前实现了《C 使用waveIn实现声音采集》#xff0c;后来C#项目… 文章目录 前言一、需要的对象及方法二、整体流程三、关键实现1、使用Thread开启线程2、TaskCompletionSource实现异步3、将指针封装为Stream 四、完整代码1.接口2.具体实现 五、使用示例方式一方式二 总结 前言 之前实现了《C 使用waveIn实现声音采集》后来C#项目也有此功能的需求直接调用C封装的dll是可以的。但是wimm这种基于win32 api的库完全可以直接用C#去调用将依赖减少到最小。 一、需要的对象及方法 参考《C 使用waveIn实现声音采集》此处不再赘述。 二、整体流程 参考《C 使用waveIn实现声音采集》此处不再赘述。 三、关键实现 此处讲一些与C#相关的点。 1、使用Thread开启线程 笔者一开是实现是使用Task开启线程由于Task基于线程池可以提高资源利用率但是这也出现了一些问题。由于录制需要在子线程开启消息循环多次重复调用录制时有概率打开同一个线程就有可能收到上一个录制的数据消息造成非法内存的读取问题。目前没找到销毁线程中消息循环的方法只有通过结束线程的方式结束消息循环。所以使用Thread开启线程才能够解决问题。 _thread new Thread(() { _CollectThread();});_thread.Start(); 2、TaskCompletionSource实现异步 因为C#支持async、await机制这样就可以直接去掉开始和停止两个回调使用异步实现开始和停止方法。 /// summary /// 开始采集,Start和Stop需要成对使用await可变成同步式真正开始采集才会返回。 /// 失败会抛出异常可通过ContinueWith或await获取异常。 /// /summary public async TaskTask Start(); /// summary /// 停止采集,直接调用是异步可await等待真正停止 /// 此方法是有可能抛异常的采集过程中出现的异常会在此方法中抛出 /// /summary public async TaskTask Stop();调用方式 await wic.Start(); //此行是采集真正开始的时机 await wic.Stop(); //此行是已经停止的时机由于使用了Thread开启线程所以我们需要使用其他方式生成Task在Thread结束后触发Task完成。用过flutter的朋友应该知道这种情况使用Completer就可以C#中对应Dart的Completer就是TaskCompletionSource。 示例代码如下 public async TaskTask Start() {TaskCompletionSource? tcsStartnew TaskCompletionSource(); ;_tcs new TaskCompletionSource();_thread new Thread(() { _CollectThread(tcsStart); _tcs.SetResult();/*线程结束触发完成*/ });_thread.Start(); //等待开始完成的信号 await tcsStart.Task;return Task.CompletedTask; } void _CollectThread(TaskCompletionSource tcsStart){while(GetMessage(out msg)!0){//接收到Wimm开始消息触发完成tcsStart.SetResult();//接收到Wimm结束消息退出循环结束线程} }public async TaskTask Stop(){if (_thread ! null){ //发送消息结束线程_exitFlag true;PostThreadMessage(_threadId, MM_WIM_CLOSE); //等待线程结束await _tcs!.Task;_tcs null;_thread null;}return Task.CompletedTask;}3、将指针封装为Stream 通过Wimm采集的音频数据是指针的形式如果需要转为byte[]这需要使用Marshall进行数据拷贝为了避免拷贝数据形式不能是byte[]数组。直接提供指针又不方便使用笔者采用了Stream的方式提供数据而且文件流直接支持Stream写入。C#本身有个UmanagedMemoryStream可以支持读取指针的数据但是需要unsafe上下文这显然是没必要的有unsafe上下文直接通过地址读取数据即可或者将此功能放dll单独设置unsafe对外提供Stream也不便于管理。最好的方式还是自己实现一个Stream用于读取指针数据。 完整代码如下 using System.Runtime.InteropServices; namespace AC {/// summary/// 用于读取指针数据的流内部不会管理指针/// 由于.net库提供的UnmanagedMemoryStream需要unsafe上下文所以直接自己封装一个类似功能的stream避开unsafe的使用。/// /summaryclass UMemoryStream : Stream{public override bool CanRead _access FileAccess.Read || _access FileAccess.ReadWrite;public override bool CanSeek true;public override bool CanWrite _access FileAccess.Write || _access FileAccess.ReadWrite;public override long Length _len;public override long Position { get; set; } 0;FileAccess _access;nint _ptr;nint _len;/// summary/// 构造方法/// /summary/// param nameptr数据地址/param/// param namelen数据长度/param/// param nameaccess/parampublic UMemoryStream(nint ptr, int len, FileAccess access){_ptr ptr;_len len;_access access;}public override void Flush(){throw new NotSupportedException();}public override int Read(byte[] buffer, int offset, int count){if (_ptr 0)throw new ObjectDisposedException(ToString());if (!CanRead)throw new NotSupportedException();var leftCount _len - Position;if (count leftCount){count (int)leftCount;}if (count 0){Marshal.Copy(_ptr (nint)Position, buffer, offset, count);Position count;}return count;}public override long Seek(long offset, SeekOrigin origin){switch (origin){case SeekOrigin.Begin:Position offset;break;case SeekOrigin.Current:Position offset;break;case SeekOrigin.End:Position _len - offset;break;}return Position;}public override void SetLength(long value){throw new NotSupportedException();}public override void Write(byte[] buffer, int offset, int count){if (_ptr 0)throw new ObjectDisposedException(ToString());if (!CanWrite)throw new NotSupportedException();var leftCount _len - Position;if (count leftCount){count (int)leftCount;}if (count 0){Marshal.Copy(buffer, offset, _ptr (nint)Position, count);Position count;}else { throw new ArgumentOutOfRangeException(); }}public override void Close(){_ptr 0;}} }四、完整代码 将采集功能封装成一个通用工具方便在任意地方使用。 1.接口 接口设计如下 using System.Runtime.InteropServices; using static AC.Winmm; using static AC.User32; using static AC.Kernel32;/************************************************************************ * Project: AC::WaveInCollector * Decription: 音频采集工具 * Verision: v1.0.0.0 * Author: Xin Nie * Create: 2023/10/8 09:27:00 * LastUpdate: 2023/10/24 11:34:00 ************************************************************************ * Copyright 2025. All rights reserved. ************************************************************************/ namespace AC {/// summary/// 声音采集对象/// /summary/// summary/// 声音采集对象///这是一个功能完整声音采集对象所有接口通过了测试。///非线程安全所有方法需确保在单线程中调用即比如Start和Stop不能在两个线程中同时调用。/// /summarypublic class WaveInCollector : IAsyncDisposable{/// summary/// 数据到达事件参数/// /summarypublic class DataArrivedEventArgs : EventArgs{/// summary/// 声音格式/// /summarypublic SampleFormat Format { set; get; }/// summary/// 声音数据流,为了减少数据拷贝次数将非托管内存封装成流的形式提供只读生命周期为回调方法内。/// /summarypublic Stream Stream { set; get; }}/// summary/// 采集数据到达事件/// /summarypublic event EventHandlerDataArrivedEventArgs? DataArrived;/// summary/// 采集速率单位次/秒/// 此属性会影响每次输出数据的大小/// 开始采集前设置有效/// /summarypublic int Frequency { set; get; } 50;/// summary/// 声音格式/// /summarypublic SampleFormat Format { private set; get; }/// summary/// 当前设备/// /summarypublic AudioDevice Device { private set; get; }/// summary/// 枚举可用的声音采集设备/// /summarypublic static IEnumerableAudioDevice AvailableDevices { get; }/// summary/// 构造方法/// /summary/// param namedevice音频设备不能为空/param/// param nameSampleFormat声音格式/parampublic WaveInCollector(AudioDevice device, SampleFormat sf);/// summary/// 构造方法/// 如果系统没有任何设备则会抛出异常/// /summary/// param namedeviceId声音设备Id0为默认设备/param/// param nameSampleFormat声音格式/parampublic WaveInCollector(uint deviceId, SampleFormat sf) : this(GetWaveInDeviceById(deviceId)!, sf) { }/// summary/// 开始采集,Start和Stop需要成对使用await可变成同步式真正开始采集才会返回。/// 失败会抛出异常可通过ContinueWith或await获取异常。/// /summary public async TaskTask Start();/// summary/// 停止采集,直接调用是异步可await等待真正停止/// 此方法是有可能抛异常的采集过程中出现的异常会在此方法中抛出/// /summarypublic async TaskTask Stop();}/// summary/// 声音格式/// /summarypublic class SampleFormat{/// summary/// 声道数/// /summarypublic ushort Channels { set; get; }/// summary/// 采样率/// /summarypublic uint SampleRate { set; get; }/// summary/// 位深/// /summarypublic ushort BitsPerSample { set; get; }}/// summary/// 音频设备/// /summarypublic class AudioDevice{/// summary/// 设备Id/// /summarypublic uint Id { set; get; }/// summary/// 设备名称/// /summarypublic string Name { set; get; } ;/// summary/// 声道数/// /summarypublic int Channels { set; get; }/// summary/// 支持的格式/// /summarypublic IEnumerableSampleFormat SupportedFormats { set; get; }} }2.具体实现 vs2022 .net6.0 项目所有win api通过dllimport引入没有任意额外依赖。 注winmm不能识别dshow虚拟设备请根据需要下载资源。 之后上传 五、使用示例 采集声音并保存为wav文件其中的WavWriter对象参考《C# 将音频PCM数据封装成wav文件》 方式一 获取可用设备并采集 // See https://aka.ms/new-console-template for more information using AC; try {//获取可用的音频设备var device WaveInCollector.AvailableDevices.First();//创建wav文件using (var ww WavWriter.Create(test.wav, device.SupportedFormats!.First().Channels, device.SupportedFormats!.First().SampleRate, device.SupportedFormats!.First().BitsPerSample)){//初始化录制对象await using (var wic new WaveInCollector(device.Id, device.SupportedFormats!.First())){//由于api限制设备名称不一定全。长度最大32。Console.WriteLine(设备名称 wic.Device.Name);Console.WriteLine(声音格式Chanels wic.Format.Channels SampleRate wic.Format.SampleRate BitsPerSample wic.Format.BitsPerSample);Console.WriteLine(开始录制);//注册录制事件wic.DataArrived (s, e) {Console.WriteLine(接收数据长度 e.Stream.Length);//写入文件ww.Write(e.Stream);};//开始录制await wic.Start();//录制10s结束await Task.Delay(10000);Console.WriteLine(录制完成);}} } catch (Exception e) {Console.WriteLine(e.Message); }方式二 指定设备下标和声音格式 // See https://aka.ms/new-console-template for more information using AC; try {//创建wav文件using (var ww WavWriter.Create(test.wav, 2, 44100, 16)){//初始化录制对象await using (var wic new WaveInCollector(0, new SampleFormat() { Channels 2, SampleRate 44100, BitsPerSample 16 })){//由于api限制设备名称不一定全。长度最大32。Console.WriteLine(设备名称 wic.Device.Name);Console.WriteLine(声音格式Chanels wic.Format.Channels SampleRate wic.Format.SampleRate BitsPerSample wic.Format.BitsPerSample);Console.WriteLine(开始录制);//注册录制事件wic.DataArrived (s, e) {Console.WriteLine(接收数据长度 e.Stream.Length);//写入文件ww.Write(e.Stream);};//开始录制await wic.Start();//录制10s结束await Task.Delay(10000);Console.WriteLine(录制完成);}} } catch (Exception e) {Console.WriteLine(e.Message); }效果预览 总结 以上就是今天要讲的内容实现waveIn声音采集虽然核心部分和C一样但是对于接口的设计以及调用流程都有很大的不同尤其是C#的异步可以简化调用使得接口变得很简洁而且通过disposable又可以和using配合省去Stop的调用。但唯一比较麻烦的地方就是内存的互操作尤其是音频数据缓存的读取和写入在非unsafe的环境下会多一次拷贝。总的来说这个功能在C#中实现还是有用的调用简单而且没有额外依赖。
http://www.hkea.cn/news/14336120/

相关文章:

  • 自己提供域名做网站如何开发属于自己的小程序
  • 家装行业网站建设网站开发的报告书
  • 四川平台网站建设设计优质高职院校建设专题网站
  • 有关网站建设的视频云安区学校网站建设统计表
  • 做公众号试卷的网站淮南网站建设公司
  • 深圳宝安网站制作公司外贸网站设计设计注意事项
  • 创建网站流程图怎么找外包公司
  • 企业网站模板中文 产品列表泉州企业网站建设
  • 网站建设还流行吗品牌设计流程
  • 北京中兴时代网站建设专业团队建设实施方案
  • 网站建设制作定制wordpress展示页面
  • 网站和数据库微信运营专员
  • 网站建立的方式是什么张家港建设局门户网站
  • 做公司网站,哪个程序用的多保定百度推广排名
  • 用jsp怎么做的购物网站wordpress站点设置使用期限
  • 电子商城开发网站开发四个常见的网络营销方式
  • 网站建设前台功能设计与实现开网站怎么开
  • 网站服务器无法访问邢台网站建设平台
  • 论坛门户网站开发保定网站建设推广
  • 品牌设计网站怎么做wordpress小工具使用
  • 建站用哪个模板好如何快速推广自己的网站
  • 阜蒙县建设学校网站是什么做网页的软件h
  • 泉州做网站便宜做网络优化哪家公司比较好
  • 北京代理网站备案深圳网站推广哪家好
  • 软件开发公司在哪里企业seo解决方案
  • 建设 政务数据共享网站网站搭建公司哪家好
  • flask做的网站如何上传文件东阿网站制作
  • 网站建设运营有限公司网站国内服务器租用
  • 网站开发经济可行性最有效的app推广方式有哪些
  • 网页网站公司如何做备份广西最优秀的品牌网站建设公司