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

2.0版本在原文档基础上,增加实现了一款ASIO音频软件,讲述其实现代码和内部逻辑。原文在本博客中搜索,本文只包含新增部分。

注释(2011/2/22):我新出版的《竹林蹊径——深入浅出Windows驱动开发》一书中,有一章专门阐述了ASIO驱动开发,包括完整的用户层ASIO接口驱动和内核ASIO驱动实现,它不需要实际硬件,即能完整实现ASIO功能并工作,这种形式的ASIO被我定义为V-ASIO。由于书刚出版,我还不能把文档免费发布,但今年晚些时候,我会争取发出来。请大家关注。下图是V-ASIO原理图:

图1 V-ASIO原理图

附录. 简易ASIO音频播录软件

图2 ASIO软件界面

上面章节讲了IASIO接口类中的接口函数。和读SDK文档中的介绍文字一样,看过之后可能会觉得有一种看得见、但摸不着的感觉。这个一个小节,我用一个音频软件的简例,力争让大家有机会能摸得着这些接口。

简单介绍一下这个例子程序。这是一个用Win32控制台编写的简易ASIO音频软件,能够实现ASIO的播录功能。它首先根据指定的ASIO驱动名称查找并获取ASIO驱动类的一个实例指针。然后初始化这个实例,并在用户控制下进行播放或录音操作。

代码大体改编自SDK中的sample,但根据我收到的读者反馈,SDK中的sample有几个很不好的地方。第一是写得太标准了(一笑),以至于好多读者都不耐心去分析那一个子函数套一个子函数的结构;第二是竟然用把数字打印到屏幕上的方式来模拟音频输出,要知道大部分读者是没有耐心的,可能还急眼啦,他们需要的,是能够直接听到声音!

我在重写的过程中,针对第一点,尽量把代码写得简单,在主函数中完成了大部分的初始化工作;针对第二点(读者会微笑的),我使得它既能播放,又能录音。

这个例子程序可运用于任何ASIO驱动,本人在XP系统上用AudioPhile、麦盒(Mi-Box I)测试过,也试过ASIO4ALL,完美通过。如果读者的系统中已经安装有某个ASIO驱动,也可试试。希望这个例子不是美人图,而是美人胚,让你看到且摸到。

5.1 主函数

主函数包含了最主要的内容,全部ASIO驱动初始化都在主函数中完成。我们需要获取IASIO接口类的一个实例,初始化实例,然后获取ASIO驱动的有用信息。我发现代码并不多,所以就全部粘贴过来,代码中详细的注释足以让读者读懂它们。

int _tmain(int argc, _TCHAR* argv[])

{

ASIOError result;

char strErr[128];

ASIOCallbacks asioCallbacks;

__try

{

//

// 获取ASIO驱动接口类的实例;这是第一步工作,如果成功才能进行下面的初始化工作

//

gpASIO = loadDriver(argv[1]);

if(gpASIO == NULL){

printf(“找不到ASIO设备/n”);

__leave;

}

//

// 初始化

//

result = gpASIO->init(NULL);

if(ASIOTrue != result)

{

gpASIO->getErrorMessage(strErr); // 获取错误信息

printf(“调用init方法失败:%d %s”, result, strErr);

__leave;

}

// 获取ASIO驱动名称

gpASIO->getDriverName(gASIODrvInfo.driverInfo.name);

gASIODrvInfo.driverInfo.driverVersion = gpASIO->getDriverVersion();

printf (“/n名称: %s/n版本: %d/n”,

gASIODrvInfo.driverInfo.name,

gASIODrvInfo.driverInfo.driverVersion

);

// 获取缓冲信息

result = gpASIO->getBufferSize(&gASIODrvInfo.minSize,

&gASIODrvInfo.maxSize,

&gASIODrvInfo.preferredSize,

&gASIODrvInfo.granularity

);

if(result != ASE_OK)

{

gpASIO->getErrorMessage(strErr); // 获取错误信息

printf(“调用getBufferSize方法失败:%d %s”, result, strErr);

__leave;

}

// 获取当前采样频率

result = gpASIO->getSampleRate(&gASIODrvInfo.sampleRate);

if(result != ASE_OK)

{

gpASIO->getErrorMessage(strErr); // 获取错误信息

printf(“调用getSampleRate方法失败:%d %s”, result, strErr);

__leave;

}

// 判断获取的当前采样频率值是否在有效区间

if (gASIODrvInfo.sampleRate <= 0.0 || gASIODrvInfo.sampleRate > 96000.0)

{

result = gpASIO->setSampleRate(44100.0);

if(ASE_OK != result)

{

printf("不支持默认采样频率:%d", result);__leave;

}

result = gpASIO->getSampleRate(&gASIODrvInfo.sampleRate);

if(result != ASE_OK)

{

gpASIO->getErrorMessage(strErr); // 获取错误信息printf("调用getSampleRate方法失败:%d %s", result, strErr);__leave;

}

}

// 获取输入/输出声道数

result = gpASIO->getChannels(

&gASIODrvInfo.inputChannels,

&gASIODrvInfo.outputChannels

);

if(result != ASE_OK)

{

gpASIO->getErrorMessage(strErr); // 获取错误信息

printf(“调用getChannels方法失败:%d %s”, result, strErr);

__leave;

}

// 针对每个声道,初始化ASIO缓冲

//

ASIOBufferInfo *info = gASIODrvInfo.bufferInfos;

// input

if (gASIODrvInfo.inputChannels > kMaxInputChannels)

gASIODrvInfo.inputBuffers = kMaxInputChannels;

else

gASIODrvInfo.inputBuffers = gASIODrvInfo.inputChannels;

for(int i = 0; i < gASIODrvInfo.inputBuffers; i++, info++)

{

info->isInput = ASIOTrue;

info->channelNum = i;

info->buffers[0] = info->buffers[1] = 0;

}

// outputs

if (gASIODrvInfo.outputChannels > kMaxOutputChannels)

gASIODrvInfo.outputBuffers = kMaxOutputChannels;

else

gASIODrvInfo.outputBuffers = gASIODrvInfo.outputChannels;

for(int i = 0; i < gASIODrvInfo.outputBuffers; i++, info++)

{

info->isInput = ASIOFalse;

info->channelNum = i;

info->buffers[0] = info->buffers[1] = 0;

}

// 初始化回调函数数组。这些函数必须由音频程序自己提供,被ASIO驱动调用。

// bufferSwitch是实现音乐播放的关键,此处传入音乐文件数据,就能播放出相应的音乐了。

// sampleRateChanged在设备采样率改变时被调用,以提醒播放软件更改显示信息。

// bufferSwitch和bufferSwitchTimeInfo两个回调函数,ASIO驱动在需要更新音频数据的时候调用它们。

// 后者是前者的升级版,即ASIO驱动在调用的时候会传入一个时间信息结构指针,便于音频软件做同步、定位等操作。

// asioMessages回调可以让ASIO驱动更好地了解音频软件的“能力”,比如它所能支持的ASIO版本,是1.0还是2.0等。

asioCallbacks.bufferSwitch = &bufferSwitch;

asioCallbacks.sampleRateDidChange = &sampleRateChanged;

asioCallbacks.asioMessage = &asioMessages;

asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo;

// 创建缓冲区,注册回调函数

result = gpASIO->createBuffers(gASIODrvInfo.bufferInfos,

gASIODrvInfo.inputBuffers + gASIODrvInfo.outputBuffers,

gASIODrvInfo.preferredSize,

&asioCallbacks

);

if(result != ASE_OK)

{

gpASIO->getErrorMessage(strErr);

printf(“调用createBuffers方法失败:%d %s”, result, strErr);

__leave;

}

// 调用createBuffers后,可获取设备的输入输出延迟。

// 延迟是一定存在的,除非操作系统能够快到收到一个字节,立刻播放一个字节的速度。

// 既然有缓冲区,就有先后、等待。等待的时间就是延迟。

// 输入延迟:一个音频采样从被设备获取到被传入音频软件,所经历的时间

// 输出延迟:一个采样数据从音频软件输出去,直到它被声卡播放出来,期间所经历的时间

gpASIO->getLatencies(&gASIODrvInfo.inputLatency, &gASIODrvInfo.outputLatency);

// 获取声道的详细信息

printf(“声道信息…/n/n输入声道:%d个/t输出声道:%d个”, gASIODrvInfo.inputBuffers, gASIODrvInfo.outputBuffers);

for (int i = 0; i < gASIODrvInfo.inputBuffers + gASIODrvInfo.outputBuffers; i++)

{

gASIODrvInfo.channelInfos[i].channel =

gASIODrvInfo.bufferInfos[i].channelNum;

gASIODrvInfo.channelInfos[i].isInput =

gASIODrvInfo.bufferInfos[i].isInput;

result = gpASIO->getChannelInfo(&gASIODrvInfo.channelInfos[i]);

if (result == ASE_OK)

printf("%d %s声道%d:%s group: %d type: %d/n", i, gASIODrvInfo.channelInfos[i].isInput ? "输入":"输出",gASIODrvInfo.channelInfos[i].channel,gASIODrvInfo.channelInfos[i].name,gASIODrvInfo.channelInfos[i].channelGroup,gASIODrvInfo.channelInfos[i].type);

}

printf(“/n音频信息…/n/n”);

printf( “采样率:%d/n”, gASIODrvInfo.sampleRate);

printf( “输入延迟:%d, 输出延迟:%d/n”,

gASIODrvInfo.inputLatency, gASIODrvInfo.outputLatency);

printf( “最大缓冲:%d, 最小缓冲:%d, 当前缓冲:%d, 缓冲粒度:%d/n”,

gASIODrvInfo.maxSize, gASIODrvInfo.minSize,

gASIODrvInfo.preferredSize, gASIODrvInfo.granularity);

// 测试ASIO驱动是否支持outputReady接口。

// 如果ASIO驱动不支持,按照SDK中所做说明,返回值当为ASE_NotPresent;

// 这种情况下,我们程序下面就不应当调用outputReady与ASIO驱动进行数据同步。

if(ASE_OK == gpASIO->outputReady())

gASIODrvInfo.postOutput = TRUE;

else

gASIODrvInfo.postOutput = FALSE;

InitializeCommandVaribles();

// 提高线程优先级

SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

// 等待并处理用户命令,直到退出。

while (!gASIODrvInfo.bMainQuit)

{

CommandProcess();

}

}

__finally

{

if(gpASIO)

{

unloadASIO(gpASIO);

delete gpASIO;

}

if(data_buff)

delete data_buff;

}

return 0;

}

最后我拎出几个要点讲一下:

  1. 音频软件由于数据吞吐量大,并且ASIO又要求低延迟和高性能保证,所以需要提高数据处理线程的优先级。这样处理后的效果是相当明显的;读者可尝试把提高和不提高优先级两种情况进行比较:把ASIO驱动的延迟降到最低,然后修改代码分别在提高优先级和不提高优先级的情况下运行软件。经过我多次测试后发现,不提高优先级的时候爆音情况普遍严重。

  2. ASIO驱动初始化函数的调用需要注意先后顺序。比如一般只在createBuffers被调用后才能调用getLatencies获取延迟值。读者应阅读SDK中的说明。

  3. 四个ASIO回调函数非常重要。音频软件向ASIO驱动注册这些函数,自己是不会调用的,而是由ASIO驱动在适当的时候调用。初始化结束后,剩余的工作大部分都是由这几个回调在操作着。另外一个要点是,回调函数的调用是不被音频软件控制的,它们的运行环境不可知,可能在主线程中,也可能在辅助线程中。所以要注意线程同步。

5.2 数据同步

数据同步的重任落在bufferSwitch或bufferSwitchTimeInfo两个回调函数的肩膀上。bufferSwitchTimeInfo函数比bufferSwitch多接受一个时间戳参数。软件可以根据时间戳,更新界面上显示的进度。

数据同步是以声道为单位进行的:先处理声道0数据,再处理声道1数据…但音频文件一般都是双声道的,所以可以变通一下将声道0和声道1组成立体声同时处理,这样更方便。可参看代码中的实现。

数据同步需要判断采样类型,采样类型其实就是采样深度,有16位、24位、32位,以及大序(Big Edian),小序(Little Edian)等。如声道0的采样类型为32位,当音频文件格式为16位采样深度时,需将文件中的每个采样转换为32位后再拷贝到声道0的缓冲中。转换算法算不上复杂,但如果弄错了,就会听到噪音。

播放的时候,将音频文件或内存中的数据拷贝到ASIO驱动的声道缓冲中;录音的时候,将声道缓冲中的数据保存到文件或内存中。这样,就实现了音频播录。

5.3 其他注意点

  1. 流程控制。ASIO数据流总是输入/输出操作并行的,所以不管是启动播放操作,还是启动录音操作,都将把两个方向的数据流同时开启。而如果要彻底关闭ASIO数据流,需要同时关闭播放与录音。

  2. 音频格式。播放音频文件就会涉及到文件读写,正确解析WAV音频格式很重要。重要的信息包括:采样深度、采样频率、声道数等。

  3. 用户命令。软件接受用户输入的简单命令,输入h或?命令,会打印出帮助信息。软件运行方法:asioTool [ASIOName]。如果参数ASIOName为空,软件将把系统中找到的第一个ASIO驱动作为当前驱动。

用户命令包括:P-播放;R-录音;T-停止;Q-退出;S-设置;h/?-帮助

到这里,文章就告一段落了。本文介绍了ASIO驱动的一种简单实现,并利用DirectKS技术对普通的WDM声卡提供了ASIO支持,最后还让你成功听到了音乐。我又在附件中为读者展示了一款微型ASIO音频软件,讲解了其代码实现和内部逻辑。通过这些讲解,ASIO从里到外的知识都能串起来了。
————————————————
原文链接:https://blog.csdn.net/blog_index/article/details/5411694

ASIO音频驱动开发指南 2.0相关推荐

  1. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. 【正点原子Linux连载】第六十七章 Linux USB驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  3. 【正点原子Linux连载】第四十三章 Linux设备树 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  4. 【正点原子Linux连载】第二十三章 DDR3实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  5. 【正点原子Linux连载】第四十一章 嵌入式Linux LED驱动开发实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  6. 【正点原子Linux连载】第四十四章 设备树下的LED驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  7. 【正点原子Linux连载】第三十七章 Linux内核移植 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. 【正点原子Linux连载】第四十五章 pinctrl和gpio子系统实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. 【正点原子Linux连载】第三十八章 根文件系统构建 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  10. 【正点原子Linux连载】第七十一章 Linux 4G通信实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

最新文章

  1. Swift解读专题四——字符串与字符
  2. java序列化算法透析_Java序列化机制与原理的深入分析
  3. 使用Lubuntu开发Android应用
  4. 【CSS实现Loading遮罩】点击按钮,弹出一个DIV层窗口
  5. 英特尓祭出开挖数据价值的“六脉神剑”!
  6. Gephi教程【1】安装
  7. cad批量打印快捷键_CAD布局批量打印必备工具之一
  8. 在火狐(Firefox)浏览器中安装IE Tab插件
  9. 云开发数据库update函数控制台显示更新成功,但数据库中的数据并没有更新(已解决)
  10. PTC:能源互联网“双子星”,引领风电企业研发数字化变革
  11. 永久域名注册流程知识
  12. 安全架构-HTTP协议幂等性
  13. 关于版权声明的写法(转)
  14. 谷歌验证码reCAPTCHA的运用
  15. Python在cmd下pip快速下载安装包的国内安装镜像
  16. 说说org.json.JSONObject功能和源码(二)
  17. 计算机无法安装蓝牙设备,笔记本蓝牙无法添加设备解决方法
  18. 移动端vue实现部门结构功能_基于Vue制作组织架构树组件
  19. 蚁群算法解决TSP问题(2#JAVA代码+详细注释+对比动态规划【JAVA】)
  20. Python Appium移动端app自动化测试框架

热门文章

  1. 学习笔记14--车联网辅助定位技术
  2. 【转】Excel表格的35招必学秘技
  3. java redis令牌桶_接口限流令牌桶算法Redis分布式限流
  4. 使用MV制作最简单的游戏:我要做游戏(1)
  5. 网页分享接口代码格式
  6. 让数据分析更easy的选择—贪心科技AI商业数据分析课程深度测评
  7. 苹果cmsV10二开视频+图片+小说网站源码
  8. Android自定义view之围棋动画,kotlin实现接口
  9. 凤凰系统运行linux,凤凰系统率先升级内核到Linux4.9,支持更多新硬件
  10. 数字孪生技术方案下的智慧城市建设治理体系优势