中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

南陽做網(wǎng)站seo的收錄網(wǎng)站的平臺有哪些

南陽做網(wǎng)站seo的,收錄網(wǎng)站的平臺有哪些,成都旅游發(fā)朋友圈的精美句子,廣州微商城公司文章目錄 前言一、需要的對象及方法二、整體流程三、關(guān)鍵實現(xiàn)1、使用Thread開啟線程2、TaskCompletionSource實現(xiàn)異步3、將指針封裝為Stream 四、完整代碼1.接口2.具體實現(xiàn) 五、使用示例方式一方式二 總結(jié) 前言 之前實現(xiàn)了《C 使用waveIn實現(xiàn)聲音采集》,后來C#項目…

文章目錄

  • 前言
  • 一、需要的對象及方法
  • 二、整體流程
  • 三、關(guān)鍵實現(xiàn)
    • 1、使用Thread開啟線程
    • 2、TaskCompletionSource實現(xiàn)異步
    • 3、將指針封裝為Stream
  • 四、完整代碼
    • 1.接口
    • 2.具體實現(xiàn)
  • 五、使用示例
    • 方式一
    • 方式二
  • 總結(jié)


前言

之前實現(xiàn)了《C++ 使用waveIn實現(xiàn)聲音采集》,后來C#項目也有此功能的需求,直接調(diào)用C++封裝的dll是可以的。但是wimm這種基于win32 api的庫,完全可以直接用C#去調(diào)用,將依賴減少到最小。


一、需要的對象及方法

參考《C++ 使用waveIn實現(xiàn)聲音采集》,此處不再贅述。


二、整體流程

參考《C++ 使用waveIn實現(xiàn)聲音采集》,此處不再贅述。


三、關(guān)鍵實現(xiàn)

此處講一些與C#相關(guān)的點。

1、使用Thread開啟線程

筆者一開是實現(xiàn)是使用Task開啟線程,由于Task基于線程池可以提高資源利用率,但是這也出現(xiàn)了一些問題。由于錄制需要在子線程開啟消息循環(huán),多次重復(fù)調(diào)用錄制時,有概率打開同一個線程,就有可能收到上一個錄制的數(shù)據(jù)消息,造成非法內(nèi)存的讀取問題。目前沒找到銷毀線程中消息循環(huán)的方法,只有通過結(jié)束線程的方式結(jié)束消息循環(huán)。所以使用Thread開啟線程,才能夠解決問題。

 _thread = new Thread(() => { _CollectThread();});_thread.Start();      

2、TaskCompletionSource實現(xiàn)異步

因為C#支持async、await機制,這樣就可以直接去掉開始和停止兩個回調(diào),使用異步實現(xiàn)開始和停止方法。

/// <summary>
/// 開始采集,Start和Stop需要成對使用,await可變成同步式,真正開始采集才會返回。
/// 失敗會拋出異常,可通過ContinueWith或await獲取異常。
/// </summary>		
public async Task<Task> Start();
/// <summary>
/// 停止采集,直接調(diào)用是異步,可await等待真正停止
/// 此方法是有可能拋異常的,采集過程中出現(xiàn)的異常,會在此方法中拋出
/// </summary>
public async Task<Task> Stop();

調(diào)用方式

await wic.Start();
//此行是采集真正開始的時機
await wic.Stop();
//此行是已經(jīng)停止的時機

由于使用了Thread開啟線程,所以我們需要使用其他方式生成Task,在Thread結(jié)束后觸發(fā)Task完成。用過flutter的朋友應(yīng)該知道這種情況使用Completer就可以,C#中對應(yīng)Dart的Completer就是TaskCompletionSource。
示例代碼如下

public async Task<Task> Start()
{TaskCompletionSource? tcsStart=new TaskCompletionSource(); ;_tcs = new TaskCompletionSource();_thread = new Thread(() => { _CollectThread(tcsStart); _tcs.SetResult();/*線程結(jié)束觸發(fā)完成*/ });_thread.Start();     //等待開始完成的信號 await tcsStart.Task;return Task.CompletedTask;
}
void _CollectThread(TaskCompletionSource tcsStart){while(GetMessage(out msg)!=0){//接收到Wimm開始消息,觸發(fā)完成tcsStart.SetResult();//接收到Wimm結(jié)束消息退出循環(huán)結(jié)束線程}   
}public async Task<Task> Stop(){if (_thread != null){ //發(fā)送消息結(jié)束線程_exitFlag = true;PostThreadMessage(_threadId, MM_WIM_CLOSE); //等待線程結(jié)束await _tcs!.Task;_tcs = null;_thread = null;}return Task.CompletedTask;}

3、將指針封裝為Stream

通過Wimm采集的音頻數(shù)據(jù)是指針的形式,如果需要轉(zhuǎn)為byte[]這需要使用Marshall進行數(shù)據(jù)拷貝,為了避免拷貝,數(shù)據(jù)形式不能是byte[]數(shù)組。直接提供指針又不方便使用,筆者采用了Stream的方式提供數(shù)據(jù),而且文件流直接支持Stream寫入。C#本身有個UmanagedMemoryStream可以支持讀取指針的數(shù)據(jù),但是需要unsafe上下文,這顯然是沒必要的(有unsafe上下文,直接通過地址讀取數(shù)據(jù)即可,或者將此功能放dll單獨設(shè)置unsafe對外提供Stream也不便于管理)。最好的方式還是自己實現(xiàn)一個Stream用于讀取指針數(shù)據(jù)。
完整代碼如下:

using System.Runtime.InteropServices;
namespace AC
{/// <summary>/// 用于讀取指針數(shù)據(jù)的流,內(nèi)部不會管理指針/// 由于.net庫提供的UnmanagedMemoryStream需要unsafe上下文,所以直接自己封裝一個類似功能的stream避開unsafe的使用。/// </summary>class 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>/// 構(gòu)造方法/// </summary>/// <param name="ptr">數(shù)據(jù)地址</param>/// <param name="len">數(shù)據(jù)長度</param>/// <param name="access"></param>public 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.接口

接口設(shè)計如下:

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>/// 聲音采集對象///這是一個功能完整聲音采集對象,所有接口通過了測試。///非線程安全,所有方法需確保在單線程中調(diào)用,即比如:Start和Stop不能在兩個線程中同時調(diào)用。/// </summary>public class WaveInCollector : IAsyncDisposable{/// <summary>/// 數(shù)據(jù)到達事件參數(shù)/// </summary>public class DataArrivedEventArgs : EventArgs{/// <summary>/// 聲音格式/// </summary>public SampleFormat Format { set; get; }/// <summary>/// 聲音數(shù)據(jù)流,為了減少數(shù)據(jù)拷貝次數(shù),將非托管內(nèi)存封裝成流的形式提供,只讀,生命周期為回調(diào)方法內(nèi)。/// </summary>public Stream Stream { set; get; }}/// <summary>/// 采集數(shù)據(jù)到達事件/// </summary>public event EventHandler<DataArrivedEventArgs>? DataArrived;/// <summary>/// 采集速率單位:次/秒/// 此屬性會影響每次輸出數(shù)據(jù)的大小/// 開始采集前設(shè)置有效/// </summary>public int Frequency { set; get; } = 50;/// <summary>/// 聲音格式/// </summary>public SampleFormat Format { private set; get; }/// <summary>/// 當前設(shè)備/// </summary>public AudioDevice Device { private set; get; }/// <summary>/// 枚舉可用的聲音采集設(shè)備/// </summary>public static IEnumerable<AudioDevice> AvailableDevices { get; }/// <summary>/// 構(gòu)造方法/// </summary>/// <param name="device">音頻設(shè)備,不能為空</param>/// <param name="SampleFormat">聲音格式</param>public WaveInCollector(AudioDevice device, SampleFormat sf);/// <summary>/// 構(gòu)造方法/// 如果系統(tǒng)沒有任何設(shè)備則會拋出異常/// </summary>/// <param name="deviceId">聲音設(shè)備Id,0為默認設(shè)備</param>/// <param name="SampleFormat">聲音格式</param>public WaveInCollector(uint deviceId, SampleFormat sf) : this(GetWaveInDeviceById(deviceId)!, sf) { }/// <summary>/// 開始采集,Start和Stop需要成對使用,await可變成同步式,真正開始采集才會返回。/// 失敗會拋出異常,可通過ContinueWith或await獲取異常。/// </summary>		public async Task<Task> Start();/// <summary>/// 停止采集,直接調(diào)用是異步,可await等待真正停止/// 此方法是有可能拋異常的,采集過程中出現(xiàn)的異常,會在此方法中拋出/// </summary>public async Task<Task> Stop();}/// <summary>/// 聲音格式/// </summary>public class SampleFormat{/// <summary>/// 聲道數(shù)/// </summary>public ushort Channels { set; get; }/// <summary>/// 采樣率/// </summary>public uint SampleRate { set; get; }/// <summary>/// 位深/// </summary>public ushort BitsPerSample { set; get; }}/// <summary>/// 音頻設(shè)備/// </summary>public class AudioDevice{/// <summary>/// 設(shè)備Id/// </summary>public uint Id { set; get; }/// <summary>/// 設(shè)備名稱/// </summary>public string Name { set; get; } = "";/// <summary>/// 聲道數(shù)/// </summary>public int Channels { set; get; }/// <summary>/// 支持的格式/// </summary>public IEnumerable<SampleFormat> SupportedFormats { set; get; }}
}

2.具體實現(xiàn)

vs2022 .net6.0 項目,所有win api通過dllimport引入,沒有任意額外依賴。
注:winmm不能識別dshow虛擬設(shè)備,請根據(jù)需要下載資源。
之后上傳


五、使用示例

采集聲音并保存為wav文件,其中的WavWriter對象參考《C# 將音頻PCM數(shù)據(jù)封裝成wav文件》

方式一

獲取可用設(shè)備并采集

// See https://aka.ms/new-console-template for more information
using AC;
try
{//獲取可用的音頻設(shè)備var device = WaveInCollector.AvailableDevices.First();//創(chuàng)建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限制設(shè)備名稱不一定全。長度最大32。Console.WriteLine("設(shè)備名稱:" + 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("接收數(shù)據(jù)長度" + e.Stream.Length);//寫入文件ww.Write(e.Stream);};//開始錄制await wic.Start();//錄制10s結(jié)束await Task.Delay(10000);Console.WriteLine("錄制完成");}}
}
catch (Exception e)
{Console.WriteLine(e.Message);
}

方式二

指定設(shè)備下標和聲音格式

// See https://aka.ms/new-console-template for more information
using AC;
try
{//創(chuàng)建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限制設(shè)備名稱不一定全。長度最大32。Console.WriteLine("設(shè)備名稱:" + 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("接收數(shù)據(jù)長度" + e.Stream.Length);//寫入文件ww.Write(e.Stream);};//開始錄制await wic.Start();//錄制10s結(jié)束await Task.Delay(10000);Console.WriteLine("錄制完成");}}
}
catch (Exception e)
{Console.WriteLine(e.Message);
}

效果預(yù)覽
在這里插入圖片描述


總結(jié)

以上就是今天要講的內(nèi)容,實現(xiàn)waveIn聲音采集雖然核心部分和C++一樣,但是對于接口的設(shè)計以及調(diào)用流程都有很大的不同,尤其是C#的異步可以簡化調(diào)用,使得接口變得很簡潔,而且通過disposable又可以和using配合省去Stop的調(diào)用。但唯一比較麻煩的地方就是內(nèi)存的互操作,尤其是音頻數(shù)據(jù)緩存的讀取和寫入,在非unsafe的環(huán)境下會多一次拷貝??偟膩碚f,這個功能在C#中實現(xiàn)還是有用的,調(diào)用簡單而且沒有額外依賴。

http://www.risenshineclean.com/news/6373.html

相關(guān)文章:

  • 網(wǎng)站建設(shè)網(wǎng)頁制seo研究中心超逸seo
  • 丹陽網(wǎng)站優(yōu)化廣東省疫情最新
  • 網(wǎng)站變灰色代碼web網(wǎng)站模板
  • 買公司 網(wǎng)站建設(shè)搜索seo優(yōu)化
  • 國外域名怎么購買windows優(yōu)化大師收費
  • 有域名 有固定ip怎么做網(wǎng)站進入百度搜索網(wǎng)站
  • 網(wǎng)站開發(fā)軟件系統(tǒng)阿里云域名注冊流程
  • 網(wǎng)站指向ip列表是什么全國最好的廣告公司加盟
  • 網(wǎng)站運維是做什么的所有關(guān)鍵詞
  • 如何搭建免費網(wǎng)站seo推廣營銷公司
  • 購物網(wǎng)站做推廣銅仁搜狗推廣
  • 做網(wǎng)站只用php不用html馮耀宗seo視頻教程
  • 廈門建設(shè)銀行網(wǎng)站百度引擎搜索
  • 建網(wǎng)站的重要性網(wǎng)絡(luò)營銷服務(wù)的內(nèi)容
  • 騰訊短鏈接生成seo褲子的關(guān)鍵詞首頁排名有哪些
  • 做買衣服的網(wǎng)站友情鏈接互換網(wǎng)站
  • app外包流程濰坊自動seo
  • 成都制作網(wǎng)站寧波seo企業(yè)網(wǎng)絡(luò)推廣
  • 購物網(wǎng)站建設(shè)思維導(dǎo)圖百度app官方正式版
  • php免費網(wǎng)站系統(tǒng)電商seo是什么意思
  • 免費網(wǎng)站代碼下載百度號碼
  • WordPress網(wǎng)站打不開nginx百度一下了你就知道官網(wǎng)
  • 做介紹美食網(wǎng)站的菜單的企業(yè)seo整站優(yōu)化方案
  • 咋做網(wǎng)站代碼背景圖seo怎樣優(yōu)化網(wǎng)站
  • 網(wǎng)站解析查詢?nèi)W(wǎng)營銷渠道
  • 淮南發(fā)布seo網(wǎng)站優(yōu)化培訓(xùn)公司
  • pa66用途障車做網(wǎng)站百度應(yīng)用商店下載安裝
  • 東莞公司網(wǎng)站開發(fā)武漢網(wǎng)站設(shè)計十年樂云seo
  • 個性化定制網(wǎng)站公司宣傳軟文
  • 三好街做網(wǎng)站的公司百度搜索網(wǎng)站優(yōu)化