最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#利用waveIn實現(xiàn)聲音采集

 更新時間:2023年10月24日 16:14:38   作者:CodeOfCC  
wimm這種基于win32 api的庫,完全可以直接用C#去調(diào)用,將依賴減少到最小,所以本文小編就來和大家介紹一下C#如何使用waveIn實現(xiàn)聲音采集,感興趣的小伙伴可以了解下

前言

之前實現(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進(jìn)行數(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ù)到達(dá)事件參數(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ù)到達(dá)事件
        /// </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>
        /// 當(dāng)前設(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為默認(rèn)設(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è)備下標(biāo)和聲音格式

// 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é)

實現(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)用簡單而且沒有額外依賴。

以上就是C#利用waveIn實現(xiàn)聲音采集的詳細(xì)內(nèi)容,更多關(guān)于C# waveIn聲音采集的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 如何解決hash沖突

    如何解決hash沖突

    上篇文章 為什么哈希存取比較快?使用它需要付出什么代價 只是簡單介紹了使用hash所帶來的利與弊。并未涉及hash的技術(shù)細(xì)節(jié),本文則著重學(xué)習(xí)一下如何解決哈希編址的沖突問題。
    2016-06-06
  • C#查找對象在ArrayList中出現(xiàn)位置的方法

    C#查找對象在ArrayList中出現(xiàn)位置的方法

    這篇文章主要介紹了C#查找對象在ArrayList中出現(xiàn)位置的方法,涉及C#中IndexOf方法的使用技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • c#實現(xiàn)漢諾塔問題示例

    c#實現(xiàn)漢諾塔問題示例

    這篇文章主要介紹了c#實現(xiàn)漢諾塔問題示例,需要的朋友可以參考下
    2014-04-04
  • C# Unicode編碼解碼的實現(xiàn)

    C# Unicode編碼解碼的實現(xiàn)

    本文主要介紹了C# Unicode編碼解碼的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 使用C#正則表達(dá)式獲取必應(yīng)每日圖片地址

    使用C#正則表達(dá)式獲取必應(yīng)每日圖片地址

    這篇文章主要介紹了使用C#正則表達(dá)式獲取必應(yīng)每日圖片地址的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • C# Csv實現(xiàn)基本的讀寫和轉(zhuǎn)換DataTable

    C# Csv實現(xiàn)基本的讀寫和轉(zhuǎn)換DataTable

    本文主要介紹了C# Csv實現(xiàn)基本的讀寫和轉(zhuǎn)換DataTable,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • C#前端驗證和后臺驗證代碼實例

    C#前端驗證和后臺驗證代碼實例

    這篇文章主要介紹了C#前端驗證和后臺驗證代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-07-07
  • C#預(yù)定義的基礎(chǔ)類型轉(zhuǎn)換

    C#預(yù)定義的基礎(chǔ)類型轉(zhuǎn)換

    這篇文章介紹了C#預(yù)定義的基礎(chǔ)類型轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • C#(.net)中按字節(jié)數(shù)截取字符串最后出現(xiàn)亂碼問題的解決

    C#(.net)中按字節(jié)數(shù)截取字符串最后出現(xiàn)亂碼問題的解決

    這篇文章主要給大家介紹了關(guān)于C#(.net)中按字節(jié)數(shù)截取字符串最后出現(xiàn)亂碼問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • c#高效的線程安全隊列ConcurrentQueue<T>的實現(xiàn)

    c#高效的線程安全隊列ConcurrentQueue<T>的實現(xiàn)

    這篇文章主要介紹了c#高效的線程安全隊列ConcurrentQueue<T>的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評論

竹北市| 九江县| 惠水县| 阳泉市| 新昌县| 达拉特旗| 土默特右旗| 周口市| 南昌市| 彰化市| 滦平县| 仪陇县| 镇原县| 兴隆县| 本溪市| 当涂县| 左贡县| 临沧市| 鄯善县| 南投县| 尖扎县| 栾城县| 侯马市| 苗栗县| 镇安县| 浦北县| 东兰县| 池州市| 绍兴县| 承德县| 英德市| 青浦区| 辛集市| 城口县| 磐安县| 雅安市| 宣城市| 潮州市| 喀什市| 荣成市| 金昌市|