Windows(C#)音频开发-Windows Core Audio(WASAPI)

本文的例子使用NAudio.CoreAudioApi实现,全部为C#代码

以下仅为个人理解,不一定都对,勿信~

阅读这个文档,最好具备C++知识,因为会用到Marshal命名空间进行指针操作

一、关于Windows Core Audio

Windows Core Audio API 是一种非常底层的音频API,上层应用为DirectSound或者WaveXXX接口等,WASAPI是其中的一部分。

Windows Core Audio API 构成

Multimedia Device API(MMDevice):表示系统中的音频设备节点(Audio Device Endpoint),Mmdeviceapi.h

Windows Audio Session API(WASAPI):用来操作音频设备节点之间的音频流(Audio Stream),Audioclient.h,Audiopolicy.h,Audiosessiontypes.h

DeviceTopology API:用来处理音频设备节点拓扑结构。可以设计音频设备节点之间的数据流向,Devicetopology.h

EndpointVolume API:当音频设备节点为声卡独占模式(Exclusive-mode)时,使用该API对Volume进行操作,Endpointvolume.h

以上头文件位置,XXXX\Windows SDK\include,由于效率问题,这些接口以COM形式出现,而不是.net或者.net freamwork

我的电脑中,通过VS安装Windows SDK后,头文件位置为C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0\um

一些常见C#库都对其有映射,BASS.Net,NAudio。

以上接口调用顺序:

CoCreateInstance -> IMMDeviceEnumerator -> IMMDevice -> WASAPI DTAPI EVAPI

各种例子,Microsoft Learn: Build skills that open doors in your careerWindows SDK and emulator archive | Microsoft Developer

二、用户模式下的音频组件 User-Mode Audio Components

User Client:QQ音乐

User Mode:Core Audio(Audio Endpoint)、Direct Sound(High-Level API)

Kernel Mode:驱动(Audio Driver)、声音硬件(Audio Adapter)

Audio Endpoint Device:音频设备节点,对声音硬件的抽象,常见的声音硬件有喇叭、耳机、话筒、麦克风

三、Audio Endpoint Device

Audio Endpoint:表示一个声音设备

Audio Adapter:一个声音设备处理声音的流程,如A/D,D/A,输出,输入

MMDvice API用来处理和Audio Endpoint相关的操作。

实现方式:

C++

使用CoCreateInstance -> IMMDeviceEnumerator -> 其他API,包括:

IMMDevice、IMMEndpoint、IMMNotificationClient(热插拔设备)

NAudio

MMDeviceEnumerator -> MMDevice

获取到MMDevice后,调用Activate方法激活设备,随后调用WASAPI,DTAPI,EVAPI

获取到Audio Device后,就可以创建AudioClient实例进行WASAPI操作了

四、Audio Session

Audio Stream:音频流,可以为Recording 或者 Playing

Audio Session:每一个Audio Stream都对应一个Audio Stream,每一个Audio Session都对应一个Audio Device

与上文提到的IMMNotificationClient,对应Session也有事件处理接口IAudioSessionEvents,这些接口主要处理状态变化、音量变化、开始Session、结束Session等和声音处理相关的事件

获取到Audio Device后可以通过AudioSessionManager来确认系统中有哪些Session

五、Audio Stream

获取到Audio Device后,如果Activate成功,就可以得到AudioClient实例

通过AudioClient实例不仅可以获取到Audio Session,还可获取到Audio Stream

Audio Stream就是实际的音频数据,通过WASAPI对其进行操作

六、Audio Render Client 一个播放音频的例子

// 使用NAudio.WaveFileReader读取wav文件
// 使用FileStream也可以,不过需要自己解析Wave Chunk
// 例子中使用的wav文件为48000,32bit,2-channel,PCM
// 因此每一帧的大小 frame = 32bit / 8 * 2 = 8 bytes
var reader = new WaveFileReader(odl.FileName);

// 每次读取48000帧,也就是1秒钟
// 每帧大小为4bytes
int frame = 48000;
var bytes = new Byte[frame * 8];
reader.Read(bytes, 0, frame * 8);

// 以下为Core Audio部分,使用的是NAudio.CoreAudioAPI的C#映射

// 获取Core Audio Device遍历接口
var enumer = new NAudio.CoreAudioApi.MMDeviceEnumerator();

// 获取默认的Audio Device,Render表示输出设备,Multimedia表示多媒体设备
// 备注:Capture是录音设备,Console是交互设备,Communication是通讯设备
var device = enumer.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);

// 读取文件的WAVE配置,并初始化给输出设备
var format = reader.WaveFormat;

// Audio Device只是表示声音设备,并不是真正处理声音的抽象
// Audio Client才是处理声音的抽象
var audioClient = device.AudioClient;
audioClient.Initialize(
     AudioClientShareMode.Shared,
     AudioClientStreamFlags.None,
     10000000,
     0,
     format,
     Guid.Empty);

// 获取输出设备每次可以填充的音频缓冲区大小
// 单位是Frame,Frame与Byte的对应关系要根据WaveFormat计算
var bufferSize = audioClient.BufferSize;

// 获取输出设备
var renderclient = audioClient.AudioRenderClient;

// 填充第一帧音频输出缓冲区
// 由于Core Audio是COM接口,要用Marshal.Copy完成指针内存拷贝
var pData = renderclient.GetBuffer(frame);
Marshal.Copy(bytes, 0, pData, frame * 8);
renderclient.ReleaseBuffer(bufferSize, AudioClientBufferFlags.None);

// 开始播放音频
audioClient.Start();

// 以下为循环填充,否则只播放一次缓冲区数据
var sec = 1;

var stop = false;
while (!stop)
{
    Thread.Sleep(1000);
    reader.Read(bytes, 0, frame * 8);
    pData = renderclient.GetBuffer(frame);
    Marshal.Copy(bytes, 0, pData, frame * 8);
    renderclient.ReleaseBuffer(bufferSize, AudioClientBufferFlags.None);
}

audioClient.Stop();

renderclient.Dispose();
renderclient = null;
audioClient.Dispose();
audioClient = null;
device.Dispose();
device = null;

七、Audio Capture 一个录音的例子

// 与Audio Render部分的逻辑相同

var device = this.captureComboBox.SelectedItem as NAudio.CoreAudioApi.MMDevice;
var format = device.AudioClient.MixFormat;
var writer = new WaveFileWriter("capture.wav", format);
var audioClient = device.AudioClient;
audioClient.Initialize(
     AudioClientShareMode.Shared,
     AudioClientStreamFlags.AutoConvertPcm,
     10000000, // sec
     0,
     format,
     Guid.Empty);
var bufferSize = audioClient.BufferSize;
var captureclient = audioClient.AudioCaptureClient;

var sec = 1;

audioClient.Start();

while (!stop)
{
    // 0 表示没有录音
    var packageSize = captureclient.GetNextPacketSize();
    while (packageSize != 0)
    {
        int numFramestoRead = 0;
        AudioClientBufferFlags flags = AudioClientBufferFlags.None;
        IntPtr package = captureclient.GetBuffer(out numFramestoRead, out flags);
        captureclient.ReleaseBuffer(numFramestoRead);
        var length = numFramestoRead * 8;
        var buffer = new byte[length];
        Marshal.Copy(package, buffer, 0, length);
        writer.Write(buffer, 0, length);
        packageSize = captureclient.GetNextPacketSize();
    }
}

writer.Close();
writer.Dispose();
writer = null;

audioClient.Stop();
captureclient.Dispose();
captureclient = null;
audioClient.Dispose();
audioClient = null;
device.Dispose();
device = null;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值