欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

c# 通過WinAPI播放PCM聲音

 更新時間:2020年12月10日 08:54:19   作者:天方  
這篇文章主要介紹了c# 通過WinAPI播放PCM聲音的方法,幫助大家更好的理解和使用c#編程語言,感興趣的朋友可以了解下

在Windows平臺上,播放PCM聲音使用的API通常有如下兩種。

  • waveOut and waveIn:傳統(tǒng)的音頻MMEAPI,也是使用的最多的
  • xAudio2:C++/COM API,主要針對游戲開發(fā),是DirectSound的基礎(chǔ)

在Windows Vista以后,推出了更加強大的WASAPI ,并用WASAPI封裝了MME以及DirectSound API。

對于前面的兩個API,在.net平臺下有如下封裝:

WSAPI可能由于更加復(fù)雜,沒有什么比較完善的封裝,codeproject上有篇文章介紹了如何簡單的封裝WSAPI: Recording and playing PCM audio on Windows 8 (VB)

最近一個項目中使用到了PCM文件的播放,本來想用NAudio實現(xiàn)的,但使用過程中發(fā)現(xiàn)它自己提供的BlockAlignReductionStream播放實時數(shù)據(jù)是效果不是蠻好(方法可以參考這篇文章),總是有一些卡頓的現(xiàn)象。

究其原因是其Buffer的機制,要求每次都填充滿buffer,對于文件播放這個不是問題,但對于實時pcm數(shù)據(jù),buffer過大播放的時候得不到足夠的數(shù)據(jù),buffer過小丟數(shù)據(jù)的情況。

于是,我便研究了一下微軟的MMEAPI,官方文檔:Using Waveform and Auxiliary Audio。發(fā)現(xiàn)MMEAPI也并不復(fù)雜,一個簡單的示例如下 

#include <Windows.h>
#include <stdio.h>
#pragma comment(lib, "winmm.lib")
 
int main()
{
  const int buf_size = 1024 * 1024 * 30;
  char* buf = new char[buf_size];
 
  FILE* thbgm; //文件
 
  fopen_s(&thbgm, R"(r:\re_sample.pcm)", "rb");
  fread(buf, sizeof(char), buf_size, thbgm); //預(yù)讀取文件
  fclose(thbgm);
 
  WAVEFORMATEX wfx = {0};
  wfx.wFormatTag = WAVE_FORMAT_PCM; //設(shè)置波形聲音的格式
  wfx.nChannels = 2;      //設(shè)置音頻文件的通道數(shù)量
  wfx.nSamplesPerSec = 44100; //設(shè)置每個聲道播放和記錄時的樣本頻率
  wfx.wBitsPerSample = 16;  //每隔采樣點所占的大小
 
  wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
  wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
 
  HANDLE wait = CreateEvent(NULL, 0, 0, NULL);
  HWAVEOUT hwo;
  waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)wait, 0L, CALLBACK_EVENT); //打開一個給定的波形音頻輸出裝置來進行回放
 
  int data_size = 20480;
  char* data_ptr = buf;
  WAVEHDR wh;
 
  while (data_ptr - buf < buf_size)
  {
    //這一部分需要特別注意的是在循環(huán)回來之后不能花太長的時間去做讀取數(shù)據(jù)之類的工作,不然在每個循環(huán)的間隙會有“噠噠”的噪音
    wh.lpData = data_ptr;
    wh.dwBufferLength = data_size;
    wh.dwFlags = 0L;
    wh.dwLoops = 1L;
 
    data_ptr += data_size;
 
    waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR)); //準備一個波形數(shù)據(jù)塊用于播放
    waveOutWrite(hwo, &wh, sizeof(WAVEHDR)); //在音頻媒體中播放第二個函數(shù)wh指定的數(shù)據(jù)
 
    WaitForSingleObject(wait, INFINITE); //等待
  }
  waveOutClose(hwo);
  CloseHandle(wait);
 
  
  return 0;
}

這里是首先預(yù)讀pcm文件到內(nèi)存,然后通過事件回調(diào)的方式同步寫入聲音數(shù)據(jù)。 整個播放過程大概也就用到了五六個API,主要過程如下:

設(shè)置音頻參數(shù)

音頻參數(shù)定義在一個WAVEFORMATEX對象中,這里只介紹PCM的設(shè)置方法,主要設(shè)置聲道數(shù)、采樣率、和采樣位數(shù)。

WAVEFORMATEX    wfx = { 0 };
wfx.wFormatTag = WAVE_FORMAT_PCM;    //設(shè)置波形聲音的格式
wfx.nChannels = 2;                    //設(shè)置音頻文件的道數(shù)量
wfx.nSamplesPerSec = 44100;            //設(shè)置每個聲道播放和記錄時的樣本頻率
wfx.wBitsPerSample = 16;            //每隔采樣點所占的大小

除此之外,還需要設(shè)置兩個參數(shù)nBlockAlign和nAvgBytesPerSec。對于PCM,它們的計算公式如下:

wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; 
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec; 

更多信息請參看MSDN文檔:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd757713(v=vs.85).aspx

打開音頻輸出

打開音頻輸出需要定義一個HWAVEOUT對象,它代表一個波形對象,通過waveOutOpen函數(shù)打開它。

HWAVEOUT hwo;
waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)wait, 0L, CALLBACK_EVENT); 

這個函數(shù)前三個參數(shù)分別是波形對象,輸出設(shè)備(WAVE_MAPPER為-1,表示默認輸出設(shè)備),音頻參數(shù)。 后面三個參數(shù)分別是回調(diào)相關(guān)參數(shù),因為音頻數(shù)據(jù)一次只寫入一小段,播放是由系統(tǒng)在另一個線程中進行的,當數(shù)據(jù)播放完成后,需要通過回調(diào)的方式通知寫入新數(shù)據(jù)。

MMEAPI支持多種回調(diào)方式。具體參看MSDN文檔: waveOutOpen function。具體常見的回調(diào)方式有如下幾種:

  • CALLBACK_NULL        不回調(diào),需要主動掌握寫入數(shù)據(jù)時機,常用于實時音頻流
  • CALLBACK_EVENT        需要數(shù)據(jù)時寫事件,在另外一個獨立的線程上等待該事件寫入數(shù)據(jù)
  • CALLBACK_FUNCTION        需要數(shù)據(jù)時執(zhí)行回調(diào)函數(shù),在回調(diào)函數(shù)中寫入數(shù)據(jù)

這里是示例通過事件的方式回調(diào)的

寫入音頻數(shù)據(jù)

音頻的播放操作是一個生產(chǎn)者消費者模型,調(diào)用waveOutOpen后,系統(tǒng)會在后臺啟動一個播放線程(WinForm程序也可以設(shè)置為使用UI線程)。當需要數(shù)據(jù)時,調(diào)用回調(diào)函數(shù),寫入相應(yīng)的數(shù)據(jù)。

首先定義一個WAVEHDR對象:

int data_size = 20480;
char* data_ptr = buf;
WAVEHDR wh;

每次寫入的操作過程如下:

wh.lpData = data_ptr;
wh.dwBufferLength = data_size;
wh.dwFlags = 0L;
wh.dwLoops = 1L;

data_ptr += data_size;

waveOutPrepareHeader(hwo, &wh, sizeof(WAVEHDR)); //準備一個波形數(shù)據(jù)塊用于播放
waveOutWrite(hwo, &wh, sizeof(WAVEHDR)); //在音頻媒體中播放第二個函數(shù)wh指定的數(shù)據(jù)

寫入主要是通過兩個函數(shù)waveOutPrepareHeader和waveOutWrite進行。這里有兩個地方需要注意

  1. 每次寫入data_size不要太小,太小了會出現(xiàn)聲音不流暢
  2. 從它調(diào)用回調(diào)到寫入的時間間隔不能過長,否則會出現(xiàn)聲音斷流而出現(xiàn)的噠噠聲。

這兩個地方的原因?qū)嶋H上都是一個,消費者線程沒有足夠的數(shù)據(jù)。要解決這個問題需要采取緩沖模型,對數(shù)據(jù)源預(yù)讀。

另外,寫入操作waveOutPrepareHeader和waveOutWrite這兩個函數(shù)是并不要求一定非要在等待通知后才執(zhí)行的,當寫入的速度和播放的速度不一致時,出現(xiàn)聲音快進會慢速播放現(xiàn)象。

關(guān)閉音頻輸出

關(guān)閉音頻輸出只需要使用接口即可。

waveOutClose(hwo);

.net接口封裝

了解各接口功能后,自己封裝一個也比較簡單了。用起來也方便多了。

 WinAPI封裝:

using HWAVEOUT = IntPtr;

  class winmm
  {
    [StructLayout(LayoutKind.Sequential)]
    public struct WAVEFORMATEX
    {
      /// <summary>
      /// 波形聲音的格式
      /// </summary>
      public WaveFormat wFormatTag;

      /// <summary>
      /// 音頻文件的通道數(shù)量
      /// </summary>
      public UInt16 nChannels; /* number of channels (i.e. mono, stereo...) */

      /// <summary>
      /// 采樣頻率
      /// </summary>
      public UInt32 nSamplesPerSec; /* sample rate */

      /// <summary>
      /// 每秒緩沖區(qū)
      /// </summary>
      public UInt32 nAvgBytesPerSec; /* for buffer estimation */


      public UInt16 nBlockAlign;  /* block size of data */
      public UInt16 wBitsPerSample; /* number of bits per sample of mono data */
      public UInt16 cbSize;     /* the count in bytes of the size of */
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WAVEHDR
    {
      /// <summary>
      /// 緩沖區(qū)指針
      /// </summary>
      public IntPtr lpData;

      /// <summary>
      /// 緩沖區(qū)長度
      /// </summary>
      public UInt32 dwBufferLength;
      public UInt32 dwBytesRecorded; /* used for input only */
      public IntPtr dwUser;     /* for client's use */

      /// <summary>
      /// 設(shè)置標志
      /// </summary>
      public UInt32 dwFlags; 

      /// <summary>
      /// 循環(huán)控制
      /// </summary>
      public UInt32 dwLoops; 

      /// <summary>
      /// 保留字段
      /// </summary>
      public IntPtr lpNext; 

      /// <summary>
      /// 保留字段
      /// </summary>
      public IntPtr reserved;
    }


    [Flags]
    public enum WaveOpenFlags
    {
      CALLBACK_NULL   = 0,
      CALLBACK_FUNCTION = 0x30000,
      CALLBACK_EVENT  = 0x50000,
      CallbackWindow  = 0x10000,
      CallbackThread  = 0x20000,
    }

    public enum WaveMessage
    {
      WIM_OPEN = 0x3BE,
      WIM_CLOSE = 0x3BF,
      WIM_DATA = 0x3C0,
      WOM_CLOSE = 0x3BC,
      WOM_DONE = 0x3BD,
      WOM_OPEN = 0x3BB
    }


    [Flags]
    public enum WaveHeaderFlags
    {
      WHDR_BEGINLOOP = 0x00000004,
      WHDR_DONE   = 0x00000001,
      WHDR_ENDLOOP  = 0x00000008,
      WHDR_INQUEUE  = 0x00000010,
      WHDR_PREPARED = 0x00000002
    }

    public enum WaveFormat : ushort
    {
      WAVE_FORMAT_PCM = 0x0001,
    }


    /// <summary>
    /// 默認設(shè)備
    /// </summary>
    public static IntPtr WAVE_MAPPER { get; } = (IntPtr)(-1);

    public delegate void WaveCallback(IntPtr hWaveOut, WaveMessage message, IntPtr dwInstance, WAVEHDR wavhdr,
                     IntPtr dwReserved);

    [DllImport("winmm.dll")]
    public static extern int waveOutOpen(out HWAVEOUT hWaveOut,  IntPtr uDeviceID, in WAVEFORMATEX lpFormat,
                       WaveCallback dwCallback, IntPtr dwInstance, WaveOpenFlags  dwFlags);

    [DllImport("winmm.dll")]
    public static extern int waveOutOpen(out HWAVEOUT hWaveOut,  IntPtr uDeviceID, in WAVEFORMATEX lpFormat,
                       IntPtr    dwCallback, IntPtr dwInstance, WaveOpenFlags  dwFlags);

    [DllImport("winmm.dll")]
    public static extern int waveOutSetVolume(HWAVEOUT hwo, ushort dwVolume);

    [DllImport("winmm.dll")]
    public static extern int waveOutClose(in HWAVEOUT hWaveOut);

    [DllImport("winmm.dll")]
    public static extern int waveOutPrepareHeader(HWAVEOUT hWaveOut, in WAVEHDR lpWaveOutHdr, int uSize);

    [DllImport("winmm.dll")]
    public static extern int waveOutUnprepareHeader(HWAVEOUT hWaveOut, in WAVEHDR lpWaveOutHdr, int uSize);

    [DllImport("winmm.dll")]
    public static extern int waveOutWrite(HWAVEOUT hWaveOut, in WAVEHDR lpWaveOutHdr, int uSize);
  }

  class kernel32
  {
    [DllImport("kernel32.dll")]
    public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);

    [DllImport("kernel32.dll")]
    public static extern int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);

    [DllImport("kernel32.dll")]
    public static extern bool CloseHandle(IntPtr hHandle);
  }

PCM播放器:

/// <summary>
  /// Pcm播放器
  /// </summary>
  public unsafe class PcmPlayer
  {
    /// <param name="channels">聲道數(shù)目</param>
    /// <param name="sampleRate">采樣頻率</param>
    /// <param name="sampleSize">采樣大小(bits)</param>
    public PcmPlayer(int channels, int sampleRate, int sampleSize)
    {
      _wfx = new winmm.WAVEFORMATEX
      {
        wFormatTag   = winmm.WaveFormat.WAVE_FORMAT_PCM,
        nChannels   = (ushort)channels,
        nSamplesPerSec = (ushort)sampleRate,
        wBitsPerSample = (ushort)sampleSize
      };

      _wfx.nBlockAlign   = (ushort)(_wfx.nChannels * _wfx.wBitsPerSample / 8);
      _wfx.nAvgBytesPerSec = _wfx.nBlockAlign * _wfx.nSamplesPerSec;
    }

    winmm.WAVEFORMATEX _wfx;
    IntPtr    _hwo;

    /// <summary>
    /// 以事件回調(diào)的方式打開設(shè)備
    /// </summary>
    /// <param name="waitEvent"></param>
    public void OpenEvent(IntPtr waitEvent)
    {
      winmm.waveOutOpen(out _hwo, winmm.WAVE_MAPPER, _wfx, waitEvent, IntPtr.Zero, winmm.WaveOpenFlags.CALLBACK_EVENT);
      Debug.Assert(_hwo != IntPtr.Zero);
    }

    public void OpenNone()
    {
      winmm.waveOutOpen(out _hwo, winmm.WAVE_MAPPER, _wfx, IntPtr.Zero, IntPtr.Zero, winmm.WaveOpenFlags.CALLBACK_NULL);
      Debug.Assert(_hwo != IntPtr.Zero);
    }


    winmm.WAVEHDR _wh;
    public void WriteData(ReadOnlyMemory<byte> buffer)
    {
      var hwnd = buffer.Pin();

      _wh.lpData     = (IntPtr)hwnd.Pointer;
      _wh.dwBufferLength = (uint)buffer.Length;
      _wh.dwFlags    = 0;
      _wh.dwLoops    = 1;

      winmm.waveOutPrepareHeader(_hwo, _wh, sizeof(winmm.WAVEHDR)); //準備一個波形數(shù)據(jù)塊用于播放
      winmm.waveOutWrite(_hwo, _wh, sizeof(winmm.WAVEHDR));     //在音頻媒體中播放第二個函數(shù)wh指定的數(shù)據(jù)
      hwnd.Dispose();
    }

    public void Dispose()
    {
      winmm.waveOutPrepareHeader(_hwo, _wh, sizeof(winmm.WAVEHDR));
      winmm.waveOutClose(_hwo);
      _hwo = IntPtr.Zero;
    }
  }

  public class WaitObject : IDisposable
  {

    public IntPtr Hwnd { get; set; }

    public WaitObject()
    {
      Hwnd = kernel32.CreateEvent(IntPtr.Zero, false, false, null);
    }

    public void Wait()
    {
      kernel32.WaitForSingleObject(Hwnd, -1);
    }

    public void Dispose()
    {
      kernel32.CloseHandle(Hwnd);
      Hwnd = IntPtr.Zero;
    }
  }

以上就是c# 通過WinAPI播放PCM聲音的詳細內(nèi)容,更多關(guān)于c# 播放PCM聲音的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • c#之圓形無標題欄橢圓窗體的實現(xiàn)詳解

    c#之圓形無標題欄橢圓窗體的實現(xiàn)詳解

    本篇文章是對c#中圓形無標題欄橢圓窗體的實現(xiàn)方法進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06
  • 使用C#實現(xiàn)讀取PDF中所有文本內(nèi)容

    使用C#實現(xiàn)讀取PDF中所有文本內(nèi)容

    這篇文章主要為大家詳細介紹了如何使用C#實現(xiàn)讀取PDF中所有文本內(nèi)容,文中的示例代碼簡潔易懂,具有一定的學(xué)習(xí)價值,有需要的小伙伴可以了解下
    2024-02-02
  • 深入理解C#中foreach遍歷的使用方法

    深入理解C#中foreach遍歷的使用方法

    在c#中通過foreach遍歷一個列表是經(jīng)常拿用的方法,使用起來也方便,下面這篇文章先給大家介紹了關(guān)于C#中foreach遍歷的使用方法,后面介紹了c#使用foreach注意的一些是,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-08-08
  • 淺談C#單例模式的實現(xiàn)和性能對比

    淺談C#單例模式的實現(xiàn)和性能對比

    這篇文章主要介紹了淺談C#單例模式的實現(xiàn)和性能對比的相關(guān)資料,詳細的介紹了6種實現(xiàn)方式,需要的朋友可以參考下
    2017-09-09
  • C# Ado.net實現(xiàn)讀取SQLServer數(shù)據(jù)庫存儲過程列表及參數(shù)信息示例

    C# Ado.net實現(xiàn)讀取SQLServer數(shù)據(jù)庫存儲過程列表及參數(shù)信息示例

    這篇文章主要介紹了C# Ado.net實現(xiàn)讀取SQLServer數(shù)據(jù)庫存儲過程列表及參數(shù)信息,結(jié)合實例形式總結(jié)分析了C#針對SQLServer數(shù)據(jù)庫存儲過程及參數(shù)信息的各種常見操作技巧,需要的朋友可以參考下
    2019-02-02
  • C#入門學(xué)習(xí)之集合、比較和轉(zhuǎn)換

    C#入門學(xué)習(xí)之集合、比較和轉(zhuǎn)換

    本文詳細講解了C#中的集合、比較和轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • C#向無窗口的進程發(fā)送消息

    C#向無窗口的進程發(fā)送消息

    這篇文章主要介紹了C#向無窗口的進程發(fā)送消息 的相關(guān)資料,需要的朋友可以參考下
    2016-05-05
  • C#圖表算法之無向圖

    C#圖表算法之無向圖

    這篇文章介紹了C#圖表算法之無向圖,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • Unity UGUI的RectMask2D遮罩組件的介紹使用

    Unity UGUI的RectMask2D遮罩組件的介紹使用

    這篇文章主要為大家介紹了Unity UGUI的RectMask2D遮罩組件的介紹使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • C#判斷本地文件是否處于打開狀態(tài)的方法

    C#判斷本地文件是否處于打開狀態(tài)的方法

    這篇文章主要介紹了C#判斷本地文件是否處于打開狀態(tài)的方法,涉及C#操作文件的技巧,非常具有實用價值,需要的朋友可以參考下
    2015-05-05

最新評論