ASIO音频驱动开发指南

ASIO音频驱动开发指南

By 张佩

PDF下载地址:http://bbs.driverdevelop.com/read.php?tid-111697-keyword-asio.html 

目前来说,音频驱动开发是个相对窄小的技术范畴,因为生产厂家的相对集中和垄断,导致这个市场不是很火热。国内做过ASIO音频驱动的,更是少得可怜。我从百度上搜索ASIO关键字,得到的资料几乎都是同一份粗犷简介的拷贝,不仅笼统,而且简直浪费:无休止的复制是一种浪费,哪怕是在网络上。我不得不寻找更多的资料,可发布这个规范的厂商,德国的Steinberg公司的官方网站上的资料同样过于俭省,我能得到的只是一份称之为SDK的包,里面有ASIO示例代码和一份SDK文档——两者都很简明,简直是春秋笔法。我后来又在维基百科上找到了几份资源,却证明是很有用的。

我对所有能找到的资源进行学习,一段时间以来,小有收获。我愿意共享我的成绩,如果有后来者的话,但愿能给予力所能及的帮助。

1. ASIO的作用

关于这一点,你可以从网上得到很多说明。ASIO能达到的效果就是“多声道”和“低延迟(low latency)

低延迟的原理是尽量绕过WDM驱动框架中能导致声音延迟的模块,而把音频数据流发送到尽量底层的内核模块中。WDM驱动框架中,最突出的模块是KMixer驱动模块,它像一个中转站一样,把用户层的音频数据流做最后的整理——包括格式上的整理,多声道混音方面的整理——然后把整理后的最终数据发送给底层的驱动(内核流驱动)处理。

KMixer这里,所有的数据流都有至少50ms的延迟,或者更多——所谓延迟,是对数据流的转换、处理所花费的时间。延迟即是对时间的消耗,数据处理是一定要耗费时间的,但如果出现了过甚的消耗,就是“浪费”了。KMixer问题也就在此。因为不是所有的数据流都需要转换的,有些数据在传进来之前就已经是转换好的PCM格式了。但按照KMixer标准流程,这些数据流也必须KMixer里面走一,这就导致了无谓时间消耗,即过甚的延迟。

ASIO的原理其实很简单,即想办法避过KMixer。避过了KMixer,就避过了无谓的延迟,而把数据处理的时间尽可能地降到最低。ASIO整个的框架,一定是由两个部分组成:用户层+内核层。但我们平常讲ASIO驱动,实际上仅仅就用户层而言。这是有原因的。因为用户层接口统一,很容易给出标准定义。而内核驱动由于硬件接口的不同,PCIUSB1394,是不容易统一的。所以讲ASIO,只指用户层驱动。

但两个模块重要的部分,恰在于内核层的音频驱动。内核驱动必须提供大量可用的内核ASIO接口,供用户层驱动使用,并实现和硬件设备之间的内核数据I/O。用户层当然也提供了一系列的ASIO接口,这些接口供音频软件使用。用户层ASIO驱动的实现,大体上需要子类化ASIO SDK中提供的IASIO接口类,再做必要的扩展;虽然不容易,但相比较内核开发其难度要小很多。

所有的音频数据在发到ASIO驱动之前都已被处理成可用的PCM格式ASIO驱动避过其他内核模块,直接调用内核驱动接口,内核驱动则将数据直接发往声卡设备。由于避免了处理过程中的二道手,而实现低延迟。

两三年前,市面上能支持ASIO的声卡很贵很少一般只有专业人士才会买来用。现在却慢慢地多起来了,千元以内很容易就能买到,价格也算比较平民化

2. ASIO驱动实现(及例程)

再次说明:这里说的驱动,跟Windows内核驱动VxDWDMWDF完全是两码事。你简直就应该ASIO驱动看成是一个可被音频软件加载并调用的动态连接库(DLL我下面分几个小的模块讲解ASIO驱动的实现。

1)COM接口

2)驱动安装与卸载

ASIO驱动的用户层接口,以类或接口类的形式向调用者提供。因此在设计的时候,ASIO驱动被实现成COM组件的形式所以我们也必须提供标准的COM接口:

DllRegisterServer

DllUnregisterServer

DllCanUnloadNow

DllGetClassObject

我在这里不想介绍太多的关于COM的东西,否则就牵扯太多了我将介绍最贴ASIO的一些内容。上面的4个接口是ASIO驱动必须提供的

按照典型实现,DllRegisterServer接口函数中应该调用SDK函数RegisterAsioDriver,此函数把ASIO驱动注册到系统中下面是RegisterAsioDriver的函数声明:

LONG RegisterAsioDriver (

CLSID clsid,

char *szdllname,

char *szregname,

char *szasiodesc,

char*szthreadmodel)

你要提供一个Class ID,可以用工具生成以保证其唯一性。第二个参数是你生成的驱动模块的文件名(dll)。第三个是你想要注册的名字,可以任意指定喜欢的名字,不为空。第四个是描述字符串,任意指定,可以为空。

建议读者细心研读RegisterAsioDriver中的注册位置和信息,一定能对你有帮助的!起码你应该知道,注册表中ASIO驱动约定俗成的注册位置是哪里(HKEY_LOCAL_MACHINE/SOFTWARE/ASIO)。

DllUnregisterServer接口用来卸载COM组建,应该在其中调用ASIO SDK中提供的UnregisterAsioDriver函数,以清除掉系统中的注册信息

DllCanUnloadNow接口在用户调用DllUnregisterServer函数之前被调用,用以确认COM组件本身是否有外部引用存在,如果有,说明不能被立即卸载掉。

DllGetClassObject接口最为重要,用户程序调用它以取得一个可用的ASIO驱动接口,此ASIO驱动接口,亦即接口类的一个实例我们下面要讲到这样的一个接口类:ASIOSample

3)ASIO驱动类(ASIOSample

SDK中定义了一个极为重要的接口IASIO这个接口即ASIO驱动接口。外部音频软件并不需要知道ASIO驱动的内部实现,只要知道如何使用这些接口即可。音频软件需要正确地使用ASIO接口,而ASIO驱动负责正确地实现它们。IASIO的定义如下,我略做注释。

interface IASIO : public IUnknown

// 初始化。打开设备。

virtual ASIOBool init(void *sysHandle) = 0;

// ASIO驱动名

virtual void getDriverName(char *name) = 0; 

// 取得版本信息

virtual long getDriverVersion() = 0;

// 取得错误信息

virtual void getErrorMessage(char *string) = 0;

// 打开输入/输出端口,开始工作

virtual ASIOError start() = 0;

// 关闭输入/输出端口,Start相逆。

virtual ASIOError stop() = 0; 

// 取得输入和输出声道

virtual ASIOError getChannels(long *numInputChannels, 

long *numOutputChannels) = 0;

 

// 取得当前声卡设备的延迟:输入延迟,输出延迟。

// 这往往包括了硬件本身的处理延迟,故而因硬件设备而异。

virtual ASIOError getLatencies(long *inputLatency, 

long *outputLatency) = 0;

// 取得设备支持的数据缓冲信息,即支持最小的缓冲大小,允许的最大缓冲大小,

// 期望的合适大小,以及当缓冲长度变化时,其变化的粒度(granularity)。

// 这里需要说明的是,缓冲大小直接与处理延迟有关。缓冲越大,延迟越大。

// 所以缓冲可以无限大,不会影响处理,这里的最大值,只是一个容忍度罢了;

// 而最小值却与设备处理能力有关。如果设置得太小,设备无法快速地反应,

// 就会导致数据处理不流畅产生爆音等问题。最小值因设备而异,一般不小于32

// 有些设备则为64甚至128

virtual ASIOError getBufferSize(long *minSize, long *maxSize, 

long *preferredSize, long *granularity) = 0;

// 是否是合适的数据采样率ASIO驱动一般不在内部做采样转换,以免

// 增加不必要的延迟。而是告知应用软件,它所能支持的采样值有哪些。

// 这就和KMixer的处理方式极为不同。KMixer总是把音频软件发送过来的数据根据

// 设备当前采样情况进行转换。而ASIO驱动从来不这样做。

// 音频软件发送过来的音频数据,不管采样是否与当前设备相同,它都不做转换地

// 直接发往设备。

// 所以音频软件在开始播放音乐之前,总是要征询一下ASIO驱动,某采样值是否可用。

virtual ASIOError canSampleRate(ASIOSampleRate sampleRate) = 0; 

// 取得ASIO设备当前在使用的采样率

virtual ASIOError getSampleRate(ASIOSampleRate *sampleRate) = 0; 

// 在调用了canSampleRate并得到准确结果后,

// 音频软件调用此接口以设置设备的采样率

virtual ASIOError setSampleRate(ASIOSampleRate sampleRate) = 0; 

// 取得设备时钟。一般声卡设备的时钟有内部时钟和外部时钟两种。

// 内部时钟使用设备芯片自己的时钟,外部时钟用来完成多个音频设备间的同步,

// 外部时钟可通过SPDIF接口或光纤口传输。

virtual ASIOError getClockSources(ASIOClockSource *clocks, 

long *numSources) = 0; 

// 取得当前正在被设备使用的时钟

virtual ASIOError setClockSource(long reference) = 0;

// 取得时间戳。此时间戳包含采样位置和系统时间两个部分。

virtual ASIOError getSamplePosition(ASIOSamples *sPos, 

ASIOTimeStamp *tStamp) = 0;

// 取得声道信息。声道可分组,可被命名,两个同类型的声道可组成立体声。

// 非常灵活。

virtual ASIOError getChannelInfo(ASIOChannelInfo *info) = 0;

// 此接口为初始化过程中的重要一步,非常重要。且难于实现,容易出错。

// 它将为每个声道创建双缓冲。

virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, 

long numChannels, long bufferSize, ASIOCallbacks *callbacks) = 0;

// 销毁输入和输出缓冲区,与createBuffers相逆。

virtual ASIOError disposeBuffers() =0; 

// 启动控制面板程序,如果有的话。正是这个接口实现了在音频软件(比如Cubase

// 中点击一下按钮,神奇地弹出控制面板程序。

virtual ASIOError controlPanel() =0; 

// 可定制的特征函数。SDK中定义了一些标准的特质ID

// 通过future函数可知ASIO驱动的具体功能,比如是否支持outputReady等。

// 对于和ASIO驱动绑得紧的应用软件,可能知道一些定制的future ID

// 以据此实现特殊功能。

virtual ASIOError future(long selector,void *opt) = 0; 

// 新加入的接口。音频软件把数据准备好的时候,会调用此函数通知驱动处理

// SDK文档对ASIOOutputReady函数夸得天花乱坠,但我觉得它挺平常的,本该如此

virtual ASIOError outputReady() = 0; 

};

罗哩罗嗦地终于把IASIO介绍完了。我们的ASIOSample接口类IASIO继承声明如下:

class AsioSample : public IASIO, public CUnknown

{

...

}

4)内部机理

按照正常的逻辑,我现在应该来一一介绍AsioSample里面的实现。但我下意识地对此感到厌烦。我觉得这样不仅让读者感到枯燥,而且对我来说,也是一种折磨。所以我想再多写一点理论的东西。

我来说一下作为一个ASIO驱动,它是怎么完成驱动任务的,它的工作流程,内部机理是怎样的OK

做一点预先的准备工作。介绍一下我们需要什么:

1,需要硬件——硬件的抽象也就是说,我们手里的音频数据应该交给谁处理

2,需要数据缓冲区。而且是输入输出应当各有一个,最好是双缓冲,最好是以声道为单位安排缓冲。缓冲区用来存储音数据。

如何准备硬件呢?IASIO准备了接口init()

如何准备数据Buffer呢?IASIO准备了接口createBuffers()

当这两样东西都准备好之后,读者可能会抱着双臂想,是不是还缺点什么呢?哈哈,对了,还缺了源头的东西:

3音乐数据。关于这个,我不想多说什么了。你应该已经能够很灵活地调用各种音频处理相关的APIMMEDSound接口,其他编解码库函数等),以处理各种格式的音频文件最终你必须把需要播放的音频文件都转换成PCM格式。如果你有wav格式的音乐文件,则非常地简单,因为你只需要调用CreateFile函数将此文件打开就可以直接使用了。我现在假设你已经打开了一个这样的wav文件,句柄为hWavFile(你应该解析wav文件头的wav文件格式,把句柄的position设置到data所在的地方)。

到这里我们应该已经可以开始播放音乐了。为了播放音乐,start接口将被调用。下面我用图例法来解释start被调用后,音乐播放的过程,以便更显清晰。

1. 开始

处理缓冲区a

最新状态

内核反馈

处理缓冲区b

结束

不知读者对上面的副图看明没有,我略微解释一下。第一副图和第六幅图,分别对应了StartStop函数被调用后,ASIO驱动对内核驱动所进行的动作。当收到开始通知后,内核会为接下来的内核流数据处理做一些必要的准备,比如:可能需要创建数据缓冲区,如果和设备的数据I/O过程停止了,则需要重启这个过程,等。当收到结束通知后,内核也会做一些善后的工作,比如:如果ASIO是内核驱动的最后一个客户,那么ASIO结束后,就再也没有人在使用内核驱动了,应该结束掉内核驱动和设备之间的I/O过程以节省系统资源。

从第二幅图开始,到第五幅图,是音频数据处理的四个步骤。这个处理涉及到了三个模块之间的同步,即:音频软件是音频数据的制造者,ASIO驱动是数据的中转站,内核驱动是数据的消费者。三个模块间的同步就非常重要。

第二幅图中,ASIO驱动把双缓冲(即缓冲a和缓冲b)中已经准备好了数据的缓冲a,通知给内核驱动,令内核驱动处理;由于缓冲b尚无有效数据,同时通知音频软件往缓冲b中填充有效数据。此后ASIO驱动进入休眠或者说等待状态:音频数据处理(即数据拷贝等一些操作)的时间非常快,而音频数据的播放则相对很慢,所以ASIO驱动就有大把花不掉的时间需要在休眠中度过。

第三幅图是在ASIO驱动休眠过程中其状态发生的改变。一旦缓冲a被通知到内核驱动,其内容即属无效(请大家这样理解,实际上,内核驱动可能是和ASIO驱动共享缓冲区的;所以虽然理解成“内容无效”,但并不能往里面随便地填入无效数据,应保持其内容不变);而音频软件异步地将数据填入缓冲b中,令缓冲b变为有效缓冲。

第四幅图,当内核驱动处理好缓冲a中内容后,通知ASIO驱动,让它立刻传入新的数据内容供其消耗。

第五幅图中ASIO驱动得到内核通知,立刻重复第二幅图中的动作,只不过这次传入内核的是缓冲b的内容。

图二到图五的这个过程,周而复始地进行着,直到图六中的结束事件发生才结束。

关于上图中的“休眠”,这里有更多的话要说。肯定有人对这两个字心怀疑惑吧,但如果我告诉你,所有ASIO驱动的Buffer处理操作,都是在一个线程中进行的,你可能就会释然许多这正是图一中的说明文字:ASIO驱动创建专门的数据处理线程。

看下面的线程函数代码:

static DWORD_stdcall ASIOThread (void *param)

{

 do

 {

    WriteDataToDevice ();// 向设备提交数据

    Sleep (latencyTime); // 休眠,同步。

      // 酌情设置latencyTim值,应小于等于数据处理所需时间

 } while (!done);     // 一直循环,直到stop函数被调用。

 return 0;

}

代码中调用的Sleep函数,只是同步的一种方法,并且是很不好的方法。上面已经说明过来,所以需要同步,是因为数据传输与处理的时间非常短,而音频数据播放的时间则相对较长,所以需要等待前面的数据块被播放结束后,才能处理接下来的新数据块。

Sleep绝不是好的同步手段,这里仅仅用来示范,表明作者意思!应该用事件(Event)或者其它更有效的同步手段。但要注意,同步不是盲目进行的,使用一切同步手段的前提是:所谓同步,一定是两个人的事情,不管你采用什么样的同步手段,必须和被同步的对象(这里即内核驱动)商量好了才行

5)ASIO延迟

延迟是一定存在的,除非操作系统能够快到收到一个字节,立刻播放一个字节的速度。既然有缓冲区,就有先后、等待。等待的时间就是延迟。

ASIO延迟与它采用的双缓冲策略紧密相关。而双缓冲策略,恰让我们能更容易地理解这里所讲述的ASIO延迟(我在第一版中用了一种很晦涩的解释方式,这里把它废弃了;使用双缓冲原理来解释,非常易懂):

假设ASIO使用的双缓冲为缓冲a和缓冲b。当ASIO驱动准备把a缓冲数据传给设备播放的时候,ASIO驱动先通知音频软件填充数据到b缓冲。b缓冲被填充后等待被ASIO驱动处理;其等待时间即被称为ASIO驱动的延迟时间。而b缓冲的等待时间,恰好是a缓冲中的数据被全部处理好(即播放完)所需耗费的时间,a缓冲中的数据越多,所需被处理的时间越长,反之越短。由于缓冲a和缓冲b的长度是一样的,所以我们就笼统地讲,ASIO驱动的延迟与缓冲长度成正比。

这样,我们可以得出结论:最好的情况下(即不考虑硬件设备本身的延迟)ASIO驱动的延迟是一个跟数据Buffer的大小成正比的时间值。公式如下:

延迟 Buffer长度采样率

3)打开设备

在准备写这个小节的时候,我已经预备让自己承受一点压力了。原因是我至今未拥有一块真正地支持ASIO的声卡。我根本不知道怎么跟一块支持ASIO的声卡进行底层交互,也不知道是有标准的API可调用(注:此文写于07年,当时确实没有自己的声卡,对内核音频驱动的开发也不熟悉,现在已不是这样了,且一笑)。真是该死,读者是不是开始觉得我从开始到现在,一直在唬人,行哄骗的伎俩?绝不是这样的!马上就证明给你看:在非ASIO声卡上也能实现ASIO驱动!

我的一条理由是:有多少人能拥有一块支持ASIO驱动的声卡(注:现在应该便宜多了)用经济的手段,实现昂贵的享受,才是我们本文的目的。

我的另一条理由是:你大概用过ASIO4All或者ASIO2K吧?OK!它们都能够在WDM声卡上实现ASIO接口,并且用的方法和我将在这一节里讲的一模一样。

WDM声卡上实现ASIO?哈哈,是不是有人开始糊涂了?我需要赶紧来解释一下了,是这样的WDM驱动其实也暴露了一系列的内核接口,由于某些原因,它们没有被文档化,但却真实存在在,只要我们能获得这些内核就可,就可以在用户层调用它们以实现避过KMixer的目的,完成ASIO驱动。下图比较粗糙,但意思已经明了

5 ASIO驱动实现原理图

WDM框架的音频驱动,又叫做内核流驱动。微软为了让自己的操作系统成为家庭娱乐平台,在内核流上动了不少脑筋。从内核流1.0(发布了两个内核流classKernel stremingPort class),到内核流2.0AVStream),再到VistaWin7平台下的革新。内核流操作系统架构中,其实占有着很重要的地位。

内核流驱动的接口,是经过详细和严格定义的。用户层如何与它们进行交互呢?长期以来都是一份未公开的技术存在着由微软发布,但又不被微软所鼓励。微软提供了全部源代码,读者却又被告知它们是未文档化的。所谓未文档化也就是说,微软可能会在将来的某个时候改变其接口或内部实现,并且不必通知你。但很长时间里,它们仍是相当稳定的,可以为我们所用。这个未文档技术叫做:DirectKS——直接内核流操作。

你可以从微软的官网上下载到相关的源代码。结构不是很复杂,但非常有用。可以根据它来写威力更为强大的应用程序,以控制音频设备,达到比调用MMIODirectSound低很多的延迟效果。它的可扩展性也很好,如果你够专业的话,能写出非常象样的软件来。我注意到,千千静听等一些软件已经确实使用了这门技术,并把通过DirectKS获得的音频接口称作Kernel Streaming接口。可在千千静听的音频设备选项中查看到,如下图所示:

千千静听中通过DirectKS实现的内核流接口

我不准备在这篇文档里讲解DirectKS的技术细节,这需要写一篇新的文档。研读的任务暂时交由读者自行去完成

但如果你是想走一条捷径,快速上手的话,我到有一点好的建议:在你的ASIO工程目录中DirectKS工程中除kssample.cppResource.h外的所有.h.cpp文件全部拷贝过去;VC打开ASIO工程,在solution属性页中添加一个叫做DirectKSfilter目录,把你刚才拷贝过来的DirectK文件都加入到这个filter中;然后再Setupapi.lib加入ASIO工程的link项里,asiosample.cpp头部加#include ".//DirectKS//kssample.h"

现在试着Rebuild一下你的ASIO工程,运气好的话,它就通过了。

我在自己的实现中,新写了三个函数:CreateFilterCreateCapturePinCreateRenderPin。在init中调用了CreateFilter函数,也就是打开设备start中,调用CreateCapturePinCreateRenderPin之一,也就是打开了输入输出端口。

兹将相关代码罗列于下,仅供读者参考:

// 全局变量的声明

CKsAudRenFilter *g_pRenFilter = NULL;

CKsAudCapFilter *g_pCapFilter = NULL;

CKsAudRenPin* g_pPinRen = NULL;

CKsAudCapPin* g_pPinCap = NULL;

BOOL CreateFilter()

{

HRESULT hr = S_OK;

try

{

// enumerate audio renderers

CKsEnumFilters* pEnumerator = new CKsEnumFilters(&hr);

ThrowOnNull(pEnumerator, "Failed to allocate CKsEnumFilters");

ThrowOnFail(hr, "Failed to create CKsEnumFilters");

GUID aguidEnumCatsRen[] = { STATIC_KSCATEGORY_AUDIO, STATIC_KSCATEGORY_RENDER };

GUID aguidEnumCatsCap[] = { STATIC_KSCATEGORY_AUDIO, STATIC_KSCATEGORY_CAPTURE };

hr = pEnumerator->EnumFilters(

eAudRen, // create audio render filters ...

aguidEnumCatsRen, // ... of these categories

2, // There are 2 categories

TRUE, // While you're at it, enumerate the pins

FALSE, // ... but don't bother with nodes

TRUE // Instantiate the filters

);

ThrowOnFail(hr, "CKsEnumFilters::EnumFilters failed");

// just use the first audio render filter in the list

pEnumerator->m_listFilters.GetHead((CKsFilter**)&g_pRenFilter);

ThrowOnNull(g_pRenFilter, "No filters available for rendering");

hr = pEnumerator->EnumFilters(

eAudCap, // create audio render filters ...

aguidEnumCatsCap, // ... of these categories

2, // There are 2 categories

TRUE, // While you're at it, enumerate the pins

FALSE, // ... but don't bother with nodes

TRUE // Instantiate the filters

);

ThrowOnFail(hr, "CKsEnumFilters::EnumFilters failed");

// just use the first audio render filter in the list

pEnumerator->m_listFilters.GetHead((CKsFilter**)&g_pCapFilter);

ThrowOnNull(g_pCapFilter, "No filters available for capture");

}

catch (LPSTR strErr)

{

printf ("Error: %s. hr = 0x%08x/n/n", strErr, hr);

}

if(hr != S_OK)

return FALSE;

else

return TRUE;

}

CKsAudRenPin* CreateRenderPin(WAVEFORMATEX* pwfx = NULL)

{

HRESULT hr = S_OK;

PBYTE pBuffer = NULL;

CKsAudRenPin* pPin = NULL;

try

{

if(NULL == pwfx) return NULL;

if(NULL == g_pRenFilter) return NULL;

pPin = g_pRenFilter->CreateRenderPin(pwfx, FALSE);

}

catch (...)

{

printf ("hr = 0x%08x/n/n", hr);

}

return pPin;

}

CKsAudCapPin* CreateCapturePin(WAVEFORMATEX* pwfx = NULL)

{

HRESULT hr = S_OK;

PBYTE pBuffer = NULL;

CKsAudCapPin* pPin = NULL;

try

{

if(NULL == pwfx) return NULL;

if(NULL == g_pCapFilter) return NULL;

pPin = g_pCapFilter->CreateCapturePin(pwfx, FALSE);

}

catch (...)

{

printf ("hr = 0x%08x/n/n", hr);

}

return pPin;

}

  最后要忘记结束的时候要对filterpin所占用的资源进行释放。pin的释放,调用PIN::ClosePin()方法,filter则只要调用delete删除即可(据微软源码)。另外记住,在start方法里面,要对pin调用SetStaus(KSSTATE_RUN);在stop方法里,对pin调用SetStaus(KSSTATE_STOP)

4)播放音乐——发出声音

在本文档中,上面的三个函数是相当重要的。有了上面的三个函数后,播放音乐已经不是什么困难的事情了。剩下的事情是需要音频数据源源不断地往设备中送去就可以了。在此之前,我们回顾一下上面给出的函数ASIOThread,里面有一个核心调用:WriteDataToDevice,它实现了将音频软件送过来的音频数据送入音频设备的任务。我有必要一下这个函数的一种典型工作流程

WriteDataToDevice()

{

bufferSwitchTimeInfo();  // 通知音频软件准备新数据

g_pPinRen->WriteData();  // 数据写入内核

}

bufferSwitchTimeInfo函数不是ASIO驱动的内部函数,而是有音频软件提供的接口函数。当它被调用的时候,音频软件即收到通知,立刻准备新的音频数据传给ASIO驱动。当数据准备好后,ASIO驱动的outready函数会被调用,这样ASIO驱动也能知道有新数据内容可以使用了。ASIO驱动正是通过这种方法,实现了音频数据的不断更新。

基本上来说,你已经听到声音了

到这里,文章就告一段落了。文介绍了ASIO驱动的一种简单实现,利用DirectKS技术对普通的WDM声卡提供ASIO支持,最后还让你成功听到了音乐(由于同步的原因,未必很流畅)。我会在不久的将来,写一篇详细的关于DirectKS技术的文档——如果有很多人需要的话~


小结:本文初作于20076月间,写作时,我也才刚刚接触ASIO半个多月,所以写这篇文档,不过是一种学习的总结罢了。文档完成后,在网上流传了两年多左右,基本上是中文关于ASIO技术讲述的最靠谱的文档。但毕竟当时初学乍碰,好多内容写得很稚;但一直没时间细心地修改。直到最近,我和几位朋友一起成立了一家软件公司,从事内核方面开发,也做一些硬件方案。谈起这篇文档,为文责故,才终于决定修改。再读一遍的时候,果然发现好多不合适的地方,忙哧哧修改。时间已经是2010年的春节了。

作者:张佩,江苏扬中人,现在《麦盒数码》担任项目主管

可以通过如下途径联系到我:

QQ7849739 

Msnchangpei1982@hotmail.com

Mailchangpei1982@gmail.com

ASIO 2.3 SDK Contents --------------------- readme.txt - this file changes.txt - contains change information between SDK releases ASIO SDK 2.3.pdf - ASIO SDK 2.3 specification Steinberg ASIO Licensing Agreement.pdf - Licencing Agreement common: asio.h - ASIO C definition iasiodrv.h - interface definition for the ASIO driver class asio.cpp - asio host interface (not used on Mac) asiodrvr.h asiodrvr.cpp - ASIO driver class base definition combase.h combase.cpp - COM base definitions (PC only) dllentry.cpp - DLL functions (PC only) register.cpp - driver self registration functionality wxdebug.h debugmessage.cpp - some debugging help host: asiodrivers.h asiodrivers.cpp - ASIO driver managment (enumeration and instantiation) ASIOConvertSamples.h ASIOConvertSamples.cpp - sample data format conversion class ginclude.h - platform specific definitions host/pc: asiolist.h asiolist.cpp - instantiates an ASIO driver via the COM model host/sample: hostsample.cpp - a simple console app which shows ASIO hosting hostsample.dsp - MSVC++ 5.0 project hostsample.vcproj - Visual Studio 2005 project (32 and 64 bit targets) driver/asiosample: asiosmpl.h asiosmpl.cpp - ASIO 2.0 sample driver wintimer.cpp - bufferSwitch() wakeup thread (Windows) asiosample.def - Windows DLL module export definition mactimer.cpp - bufferSwitch() wakeup thread (Macintosh) macnanosecs.cpp - Macintosh system reference time makesamp.cpp - Macintosh driver object instantiation driver/asiosample/asiosample: asiosample.dsp - MSVC++ 5.0 project asiosample.vcproj - Visual Studio 2005 project (32 and 64 bit targets)
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值