Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析

Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析

      上篇文章中,我们模拟了WAV API。现在进入我们正在要解析的Wave 驱动的架构。我们了解一个驱动的时候,先不去看具体跟硬件操作相关的东西,而是从流程入手,把整个流程搞清楚了,调试起来就非常的容易了。我们着重看 hwctxt.cpp,hwctxt.H,devctxt.cpp,devctxt.H,strmctxt.cpp,strmctxt.H这几个源文件。 其中hwctxt是类HardwareContext代码文件,devctxt是DeviceContext代码文件,strmctxt是 StreamContext代码文件。这几个类的其他一些功能,还在其他一些文件中实现,如output.Cpp,midistrm.Cpp等。

      现在我们来看下StreamContext的类图,StreamContext是管理音频流的对象,包括播放、暂停、停止、设置音量、获取播放位置等。从 下面的StreamContext的类图中,我们可以看到它派生了WaveStreamContext和MidiStream。然后 WaveStreamContext又派生了Input和Output类型的Stream。不用说也可以知道InputStreamContext是针对 于像麦克这种输入设备流的。

wps_clip_image-597[4]

StreamContext类图

      其中OutputStreamContext派生了六个类,M代表单音道,S代表的是立体音,8/16是8/16比特采样了。 SPDIF(SONY/PHILIPS DIGITAL INTERFACE)是一种最新的音频传输格式,它通过光纤进行数字音频信号传输以取代传统的模拟信号传输方式,因此可以取得更高质量的音质效果。

     StreamContext是一个管理音频数据流的对象,像智能手机中可能存在用media player播放音乐,同时又开着FM,突然又来电。从上篇文章中我们知道,要想调用wave驱动的播放功能,每个应用都有一份 StreamContext对象,上面提到的状况,就会有三个StreamContext对象被创建。 在硬件只要一个的条件下,那么这三个StreamContext是如果协同工作的呢?而DeviceContext正是管理StreamContext对 象的。

如下是DeviceContext类图:

wps_clip_image-1047[4]
DeviceContext类图

DeviceContext派生出InputDeviceContext和OutputDeviceContext,他们分别管理 InputStreamContext和OutputStreamContext。在DeviceContext内部维护了一个双向链表来管理 StreamContext。

HardwareContext是具体操作硬件相关的类,其内部包含InputDeviceContext和OutputDeviceContext对象,下面这种图,就是三个类的关系图,一看就知道他们的对应关系了。

wps_clip_image-1339[4]

DeivceContext和StreamContext关系图

    对于HardwareContext是具体操作硬件的东西,不具有代码性,只要仔细看看代码就行了。现在我们主要分析下DeviceContext和StreamContext的关系。

DeviceContext的作用是管理StreamContext,可以分为几套函数,见Devctxt.h, Devctxt.cpp

音量增益管理:下面这个函数主要是设置设备的整个音量增益,设置了设备音量增益后,对流音量的增益起了限制做用的。

音量函数如下

 

view plain copy to clipboard print ?
  1. DWORD  GetGain();  
  2. DWORD  SetGain( DWORD  dwGain);  
  3. DWORD  GetDefaultStreamGain();  
  4. DWORD  SetDefaultStreamGain( DWORD  dwGain);  
  5. DWORD  GetSecondaryGainLimit( DWORD  GainClass);  
  6. DWORD  SetSecondaryGainLimit( DWORD  GainClass,  DWORD  Limit);  

先来讲下设备音量增益(Device Gain)和流音量增益(Stream Gain)的关系。我们从微软Media Player中,很容易就看到了设备音量和流音量的关系。设备音量时通过音量键来控制系统的音量,从而改变整个输出设备的音量的,但是在Media Player中,还是有一个单独的音量控制按钮,它能调节Media Player的音量(不要问我在哪里,自己找),但是调试它是受限制于系统音量,是如何限制,请看下面讲解。

我们现在看下设置系统音量和设置流音量的整个流程,来了解整个音量控制的过程。用户设置时,会调用waveOutSetVolume

MMRESULT waveOutSetVolume(

  HWAVEOUT hwo,

  DWORD dwVolume

);

当HWAVEOUT传入为空时,设置的就是设备音量,当HWAVEOUT是通过调用waveOutOpen返回的句柄是,设置的就是流音量。

好,我们进入到驱动中区看看,waveOutSetVolume会调用到来看wavemain.Cpp中HandleWaveMessage的WODM_SETVOLUME分支,我在代码中去掉了不重要的部分,可以看得更清晰些。

view plain copy to clipboard print ?
  1. case  WODM_SETVOLUME:  
  2. {  
  3.     StreamContext *pStreamContext;  
  4.     pStreamContext = (StreamContext *) dwUser;  
  5.   
  6.     LONG  dwGain = dwParam1;  
  7.     if  (pStreamContext)  
  8.     {  
  9.         dwRet = pStreamContext->SetGain(dwGain);  
  10.     }  
  11.     else   
  12.     {  
  13.         DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);  
  14.         dwRet = pDeviceContext->SetGain(dwGain);  
  15.     }  
  16. }  

dwUser 指向的是StreamContext对象(在前文中已经讲过),如果pStreamContext为空,那么就调用DeviceContext的 SetGain函数,否则调用StreamContext的SetGain函数。调用StreamContext的Gain只对当前的 StreamContext的音量起作用,不影响其他的Stream音量。但是对DeviceContext设置音量增益是对DeviceContext 管理的所有StreamContext起了控制作用,但是具体是如何影响的,还是根据代码来分析:

在Devctxt.h中的SetGain函数代码如下

view plain copy to clipboard print ?
  1. DWORD  SetGain( DWORD  dwGain)  
  2.     {  
  3.         m_dwGain = dwGain;  
  4.         RecalcAllGains();  
  5.         return  MMSYSERR_NOERROR;  
  6.     }  

用m_dwGain保存设备音量,然后调用RecalcAllGains来重新计算所有StreamContext的音量增益。

在Devctxt.cpp中的RecalcAllGains的实现如下

view plain copy to clipboard print ?
  1. void  DeviceContext::RecalcAllGains()  
  2. {  
  3.     PLIST_ENTRY pListEntry;  
  4.     StreamContext *pStreamContext;  
  5.   
  6.     for  (pListEntry = m_StreamList.Flink;  
  7.         pListEntry != &m_StreamList;  
  8.         pListEntry = pListEntry->Flink)  
  9.     {  
  10.         pStreamContext = CONTAINING_RECORD(pListEntry,StreamContext,m_Link);  
  11.         pStreamContext->GainChange();  
  12.     }  
  13.     return ;  
  14. }  

它便利所有的StreamContext,并调用pStreamContext->GainChange()来改变StreamContext对象的音量。接着看StreamContext类中的GainChange的实现

view plain copy to clipboard print ?
  1. void  GainChange()  
  2.    {  
  3.        m_fxpGain = MapGain(m_dwGain);  
  4. }  
  5. DWORD  StreamContext::MapGain( DWORD  Gain)  
  6. {  
  7.     DWORD  TotalGain = Gain & 0xFFFF;  
  8.     DWORD  SecondaryGain = m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass) & 0xFFFF;  
  9.     if  (m_SecondaryGainClass < SECONDARYDEVICEGAINCLASSMAX)  
  10.     {  
  11.         // Apply device gain   
  12.         DWORD  DeviceGain = m_pDeviceContext->GetGain() & 0xFFFF;  
  13.         TotalGain *= DeviceGain;  
  14.         TotalGain += 0xFFFF;  // Round up   
  15.         TotalGain >>= 16;     // Shift to lowest 16 bits   
  16.     }  
  17.   
  18.     // Apply secondary gain   
  19.     TotalGain *= SecondaryGain;  
  20.     TotalGain += 0xFFFF;  // Round up   
  21.     TotalGain >>= 16;     // Shift to lowest 16 bits   
  22.   
  23.     // Special case 0 as totally muted   
  24.     if  (TotalGain==0)  
  25.     {  
  26.         return  0;  
  27.     }  
  28.   
  29.     // Convert to index into table   
  30.     DWORD  Index = 63 - (TotalGain>>10);  
  31.     return  GainMap[Index];  
  32. }  

音量在系统中用一个DWORD值来表示,其高低两个字节分别来表示左右声道,一般情况下左声道和右声道的音量大小是一样的,所以只取其低两个字节,DWORD TotalGain = Gain & 0xFFFF;

TotalGain是DeviceGain和m_dwGain的乘机,然后再左移16位得到的。其实就是 TotalGain=DeviceGain*m_dwGain/最高音量,如果把DeviceGain/最高音量,用百分比来算的话,就很更容易理解了, 那么最后的公式就变成TotalGain=DeviceGain*系统音量百分比。那么这里就解释了系统音量是如何限制流音量的疑问。

我们设置好音量增益后,最终会再哪里体现呢:首先看一下Output.cpp文件,WaveStreamContext::Render之后的数据 就是直接发送到外部声音芯片的数据,他根据参数以及标志位选择OutputStreamContextXXX::Render2,XXX表示双声道S单声 道M,bit位是8位还是16位。以双声道OutputStreamContextS16::Render2为例,BSP里面的代码如下:

view plain copy to clipboard print ?
  1. PBYTE  OutputStreamContextS16::Render2( PBYTE  pBuffer,  PBYTE  pBufferEnd,  PBYTE  pBufferLast)  
  2. {  
  3.     LONG  CurrT = m_CurrT;  
  4.     LONG  DeltaT = m_DeltaT;  
  5.     LONG  CurrSamp0 = m_CurrSamp[0];  
  6.     LONG  PrevSamp0 = m_PrevSamp[0];  
  7.     PBYTE  pCurrData = m_lpCurrData;  
  8.     PBYTE  pCurrDataEnd = m_lpCurrDataEnd;  
  9.     LONG  fxpGain = m_fxpGain;  
  10.     LONG  OutSamp0;  
  11.   
  12.     __try   
  13.     {  
  14.         while  (pBuffer < pBufferEnd)  
  15.         {  
  16.             while  (CurrT >= 0x100)  
  17.             {  
  18.                 if  (pCurrData>=pCurrDataEnd)  
  19.                 {  
  20.                     goto  Exit;  
  21.                 }  
  22.                 CurrT -= 0x100;  
  23.                 PrevSamp0 = CurrSamp0;  
  24.                 PPCM_SAMPLE pSampleSrc = (PPCM_SAMPLE)pCurrData;  
  25.                 CurrSamp0 =  (LONG )pSampleSrc->s16.sample_left;  
  26.                 CurrSamp0 += (LONG )pSampleSrc->s16.sample_right;  
  27.                 CurrSamp0 = CurrSamp0>>1;  
  28.                 pCurrData+=4;  
  29.             }  
  30.             OutSamp0 = PrevSamp0 + (((CurrSamp0 - PrevSamp0) * CurrT) >> 8);  
  31.             // 设置增益   
  32.             OutSamp0 = (OutSamp0 * fxpGain) >> VOLSHIFT;  
  33.             CurrT += DeltaT;  
  34.       
  35.             if  (pBuffer < pBufferLast)  
  36.             {  
  37.                 OutSamp0 += *(HWSAMPLE *)pBuffer;  
  38.             }  
  39.             *(HWSAMPLE *)pBuffer = (HWSAMPLE)OutSamp0;  
  40.             pBuffer += sizeof (HWSAMPLE);  
  41.   
  42.         }  
  43.     }//end the __try block   
  44.     __except (EXCEPTION_EXECUTE_HANDLER)  
  45.     {  
  46.         RETAILMSG(1, (TEXT("InputStreamContext::Render2!/r/n" )));  
  47.         m_lpCurrData = m_lpCurrDataEnd = NULL;    
  48.         return  NULL;  
  49.     }  
  50. Exit:  
  51.     m_dwByteCount += (pCurrData - m_lpCurrData);  
  52.     m_lpCurrData = pCurrData;  
  53.     m_CurrT = CurrT;  
  54.     m_PrevSamp[0] = PrevSamp0;  
  55.     m_CurrSamp[0] = CurrSamp0;  
  56.     return  pBuffer;  
  57.     }  

从上面看到是与采样数据相乘,然后在左移16位。跟上面提到的系统音量影响流音量是一样的。

上面讲了,DeviceContext的音量增益管理,现在来看下它的流管理。

StreamContext流管理:主要来管理StreamContext的创建、删除、渲染、传输等功能。

主要有如下几个函数

view plain copy to clipboard print ?
  1. StreamContext *CreateStream(LPWAVEOPENDESC lpWOD);  
  2. DWORD  OpenStream(LPWAVEOPENDESC lpWOD,  DWORD  dwFlags, StreamContext **ppStreamContext);  
  3. HRESULT  Open(DeviceContext *pDeviceContext, LPWAVEOPENDESC lpWOD,  DWORD  dwFlags);  
  4. void  NewStream(StreamContext *pStreamContext);  
  5. void  DeleteStream(StreamContext *pStreamContext);  
  6. void  StreamReadyToRender(StreamContext *pStreamContext);  
  7. PBYTE  TransferBuffer( PBYTE  pBuffer,  PBYTE  pBufferEnd,  DWORD  *pNumStreams,  BOOL  bMuteFlag);  

在DeviceContext中有个m_StreamList的双向链表(LIST_ENTRY), m_StreamList用来指向链表的头。在StreamConext中也存在一个m_Link(LIST_ENTRY)。StreamContext 是调用DeviceContext的OpenStream来创建的,然后把StreamContext对象加入到DeviceContext的 m_StreamList中。我们从代码中去直接分析:

上层调用waveoutOpen,在wavedev2中会调用WODM_OPEN这个分支。在WODM_OPEN中的代码如下:

view plain copy to clipboard print ?
  1. case  WODM_OPEN:  
  2. {  
  3.     StreamContext *pStreamContext;  
  4.     pStreamContext = (StreamContext *) dwUser;  
  5.     dwRet = pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)pStreamContext);  
  6.     break ;  
  7. }  

OpenStream的其流程图如下

wps_clip_image-7555[4]

StreamContext 初始化流程

CreateStream是根据WAVEFORMATEX这个结构体,来判断具体要创建StreamContext的哪个派生类,下面是CreateStream的流程图,不可不提,还是流程图清晰。

wps_clip_image-7702[4]

OutputDeviceContext:: CreateStream流程图

上面讲了上层通过WODM_OPEN创建一个StreamContext的过程,那么音频流被打开之后,接下来就是给StreamContext传 入音频数据开始播放音乐。Wavedev2提供了WODM_WRITE来向音频设置写入数据。我们先看下WODM_WRITE分支的代码

view plain copy to clipboard print ?
  1. case  WODM_WRITE:  
  2. {  
  3.     StreamContext *pStreamContext;  
  4.     pStreamContext = (StreamContext *) dwUser;  
  5.     dwRet = pStreamContext->QueueBuffer((LPWAVEHDR)dwParam1);  
  6.     break ;  
  7. }  

这里调用了StreamContext中的QueueBuffer,QueueBuffer的作用就是把WAVEHDR中的数据加入到StreamContext的队列中,等待播放。下面是QueueBuffer的流程图

wps_clip_image-8173[4]

QueueBuffer流程图

在QueueBuffer中调用DeviceContext中的StreamReadyToReander通知可以开始渲染了,流程图中的箭头方向 是StreamReadyToReander调用流程,最终调用SetEvent(hOutputIntEvent),来通知线程数据已经准备好,得到通 知后,就开始播放了。该线程在HardwareContext中的OutputInterruptThread函数中

OutputInterruptThread流程如下

wps_clip_image-8440[5]

结尾时,想起大学教育的一个问题,居然不学语文了。见别人写的文章文采飞扬,而我的确是很僵硬,风趣和文采都不足。

还是老话,本人才疏学浅,有错误之处,请更正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/243179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unshelve Instance 操作详解 - 每天5分钟玩转 OpenStack(39)

上一节我们 shelve instance 到 Glance&#xff0c;本节讨论如何通过 unshelve 操作恢复该 instance。 因为 Glance 中保存了 instance 的 image&#xff0c;unshelve 的过程其实就是通过该 image launch 一个新的 instance&#xff0c;nova-scheduler 也会调度合适的计算节点来…

位拆分与运算

位拆分与运算 题目描述 现在输入了一个压缩的16位数据&#xff0c;实际上包含了四个数据[3:0][7:4][11:8][15:12] 按照sel选择输出四个数据的相加结果&#xff0c;并输出valid_out信号&#xff08;在不输出时候拉低&#xff09; 0&#xff1a;输出[3:0][7:4] 1&#xff1a;输出…

得意而忘乎形:谈葛水平的水墨画

不知道葛水平早年学过画没有&#xff0c;只知道她搞过戏曲&#xff0c;好像也有过舞台表演的经验。现在主要写小说&#xff0c;成绩斐然&#xff0c;创作之余画水墨。贾平凹看了她的画后说&#xff1a;“每个人都有绘画潜质&#xff0c;只是大与小和开发与不开发。”这话有理。…

Eclipse安装Perl插件

http://www.cnblogs.com/emanlee/archive/2012/08/11/2633701.html Eclipse安装Perl EPIC插件,在Eclipse中开发Perl项目 前提&#xff1a; 已经安装了 Eclipse&#xff0c;Java&#xff0c;Perl(例如 ActivePerl)。 步骤&#xff1a; 启动Eclipse&#xff0c;Eclipse中&#x…

风鬣霜蹄马王出

今年夏天&#xff0c;在内蒙古莱德马业繁殖基地的母马放养草场,我看到一群特殊的马点缀在绿色的草原之中。它们的颜色令我大开眼界&#xff1a;黑、白、枣红、黄骠、铁青……远远望去&#xff0c;五颜六色&#xff0c;神态各异。莱德马业的负责人朱方清告诉我&#xff0c;这里除…

多功能数据处理器

多功能数据处理器 题目描述 根据指示信号select&#xff0c;对输入信号a,b实现不同的运算。输入信号a,b为8bit有符号数&#xff0c;当select信号为0&#xff0c;输出a&#xff1b;当select信号为1,输出b&#xff1b;当select信号为2&#xff0c;输出ab;当select信号为3&#x…

Live Migrate 操作 - 每天5分钟玩转 OpenStack(42)

Migrate 操作会先将 instance 停掉&#xff0c;也就是所谓的“冷迁移”。而 Live Migrate 是“热迁移”&#xff0c;也叫“在线迁移”&#xff0c;instance不会停机。 Live Migrate 分两种&#xff1a; 源和目标节点没有共享存储&#xff0c;instance 在迁移的时候需要将其镜像…

计算节点宕机了怎么办?- 每天5分钟玩转 OpenStack(43)

Rebuild 可以恢复损坏的 instance。 那如果是宿主机坏了怎么办呢&#xff1f; 比如硬件故障或者断电造成整台计算节点无法工作&#xff0c;该节点上运行的 instance 如何恢复呢&#xff1f; 用 Shelve 或者 Migrate 可不可以&#xff1f; 很不幸&#xff0c;这两个操作都要求 i…

eclipse 全屏插件

eclipse-fullscreen 插件下载链接&#xff1a; http://code.google.com/p/eclipse-fullscreen/&#xff0c; 安装方式&#xff1a;将下载下来的压缩文件解压&#xff0c;将里边的jar包放到eclipse目录下的plugin文件夹中&#xff0c;重启eclipse即可。 在Fedora & Windows …

使用子模块实现三输入数的大小比较

使用子模块实现三输入数的大小比较 题目描述 在数字芯片设计中&#xff0c;通常把完成特定功能且相对独立的代码编写成子模块。在需要的时候再在主模块中例化使用&#xff0c;以提高代码的可复用性和设计的层次性&#xff0c;方便后续的修改。 请编写一个子模块&#xff0c;将…

寻求神谕的词语:谈海日寒诗集《空山集》

诗人杨炼曾用两个“他者”概括了当代中国诗歌所面对的两个检验体系&#xff1a;“背后是中文古典诗歌杰作”&#xff0c;“面前是古今世界文学精品”1。他提出&#xff1a;“全球化语境中&#xff0c;我们能否找到——创造一种更深也更新的标准来判断作品&#xff1f;去建立那个…

批量给Linux服务器推送文件、执行指令的工具推荐 - wgcloud-bach-agent

wgcloud-bach-agent是wgcloud官方开发的一个工具&#xff0c;具有批量给Linux主机上传文件&#xff0c;执行指令的功能 当我们有一个文件&#xff0c;需要上传到很多主机&#xff0c;或需要在很多主机执行同一条指令的时候&#xff0c;这个工具就非常实用了&#xff0c;可以极…

Neutron Router 工作原理 - 每天5分钟玩转 OpenStack(142)

上一节我们创建了 router 连通了 vlan100 和 vlan101&#xff0c; 今天分析router是如何工作的。 首先查看控制节点的网络结构发生了什么变化&#xff1a; br-int 上多了两个 port: 1. qr-d295b258-45&#xff0c;从命名上可以推断该 interface 对应 router_100_101 的 interf…

访问 Neutron 外部网络 - 每天5分钟玩转 OpenStack(143)

前面我们学习了位于不同 Neutron subnet 的 instance 可以通过 router 通信&#xff0c;今天开始讨论 instance 如何访问外部网络。 这里的外部网络是指的租户网络以外的网络。租户网络是由 Neutron 创建和维护的网络。 外部网络不由 Neutron 创建。如果是私有云&#xff0c;外…

使用函数实现数据大小端转换

使用函数实现数据大小端转换 题目描述 在数字芯片设计中&#xff0c;经常把实现特定功能的模块编写成函数&#xff0c;在需要的时候再在主模块中调用&#xff0c;以提高代码的复用性和提高设计的层次&#xff0c;分别后续的修改。 请用函数实现一个4bit数据大小端转换的功能。…

创建 OVS 外部网络 ext_net - 每天5分钟玩转 OpenStack(144)

上一节完成连接外网的配置准备工作&#xff0c;今天就来创建 OVS 外部网络 ext_net。 进入 Admin -> Networks 菜单&#xff0c;点击 “Create Network” 按钮。 显示创建页面。 Provider Network Type 选择 “Flat”。 Network 填写 “external”&#xff0c;与 ml2_conf.…

Linux学习之zImage内核镜像解压过程详解

zImage内核镜像解压过程详解 收藏 zImage内核镜像解压过程详解 作者&#xff1a; 刘洪涛&#xff0c;华清远见嵌入式培训中心 讲师。 本文以linux-2.6.14内核在S3C2410平台上运行为例&#xff0c;讲解内核的解压过程。 内核编译完成后会生成zImage内核镜像文件。关于…

4位数值比较器电路

4位数值比较器电路 题目描述&#xff1a;使用门级描述方式&#xff0c;实现4位数值比较器 某4位数值比较器的功能如下表 timescale 1ns/1nsmodule comparator_4(input [3:0] A ,input [3:0] B ,output wire Y2 , //A>Boutput wire Y1 …

将 ext_net 连接到 router - 每天5分钟玩转 OpenStack(145)

上一节完我们创建了外部网络 ext_net&#xff0c;接下来需要将其连接到 Neutron 的虚拟路由器&#xff0c;这样 instance 才能访问外网。 点击菜单 Project -> Network -> Routers 进入 router 列表。 点击 router_100_101 的 “Set Gateway” 按钮。 在 “External Netw…

Xilinx_ISE和ModelSim的联合使用方法 / 从Xilinx ISE 14.7启动ModelSim时遇到的问题

解决方法&#xff1a; 前提是安装了 xilinx ise14.7 和modelsim se 10.1a 1〉从Windows的Start Menu开始&#xff0c;Xilinx ISE Design Suite 14.7 —〉EDK —〉Tools —〉Compile Simulation Libraries 按照提示编译好library&#xff0c;编译的library输出目录是&#xff…