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是针对于像麦克这种输入设备流的。
 


StreamContext类图
 
      其中OutputStreamContext派生了六个类,M代表单音道,S代表的是立体音,8/16是8/16比特采样了。 SPDIF(SONY/PHILIPS DIGITAL INTERFACE)是一种最新的音频传输格式,它通过光纤进行数字音频信号传输以取代传统的模拟信号传输方式,因此可以取得更高质量的音质效果。
 
     StreamContext是一个管理音频数据流的对象,像智能手机中可能存在用media player播放音乐,同时又开着FM,突然又来电。从上篇文章中我们知道,要想调用wave驱动的播放功能,每个应用都有一份StreamContext对象,上面提到的状况,就会有三个StreamContext对象被创建。 在硬件只要一个的条件下,那么这三个StreamContext是如果协同工作的呢?而DeviceContext正是管理StreamContext对象的。
 
如下是DeviceContext类图:
 

DeviceContext类图
 
DeviceContext派生出InputDeviceContext和OutputDeviceContext,他们分别管理InputStreamContext和OutputStreamContext。在DeviceContext内部维护了一个双向链表来管理StreamContext。
 
HardwareContext是具体操作硬件相关的类,其内部包含InputDeviceContext和OutputDeviceContext对象,下面这种图,就是三个类的关系图,一看就知道他们的对应关系了。
 


DeivceContext和StreamContext关系图
 
    对于HardwareContext是具体操作硬件的东西,不具有代码性,只要仔细看看代码就行了。现在我们主要分析下DeviceContext和StreamContext的关系。
 
DeviceContext的作用是管理StreamContext,可以分为几套函数,见Devctxt.h, Devctxt.cpp
 
音量增益管理:下面这个函数主要是设置设备的整个音量增益,设置了设备音量增益后,对流音量的增益起了限制做用的。
 
音量函数如下
 
 


view plaincopy to clipboardprint?
DWORD GetGain();  
DWORD SetGain(DWORD dwGain);  
DWORD GetDefaultStreamGain();  
DWORD SetDefaultStreamGain(DWORD dwGain);  
DWORD GetSecondaryGainLimit(DWORD GainClass);  
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 plaincopy to clipboardprint?
01.case WODM_SETVOLUME:  
02.{  
03.    StreamContext *pStreamContext;  
04.    pStreamContext = (StreamContext *) dwUser;  
05.  
06.    LONG dwGain = dwParam1;  
07.    if (pStreamContext)  
08.    {  
09.        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 plaincopy to clipboardprint?
01.DWORD SetGain(DWORD dwGain)  
02.    {  
03.        m_dwGain = dwGain;  
04.        RecalcAllGains();  
05.        return MMSYSERR_NOERROR;  
06.    }  
 

用m_dwGain保存设备音量,然后调用RecalcAllGains来重新计算所有StreamContext的音量增益。
 
在Devctxt.cpp中的RecalcAllGains的实现如下
 

 

view plaincopy to clipboardprint?
01.void DeviceContext::RecalcAllGains()  
02.{  
03.    PLIST_ENTRY pListEntry;  
04.    StreamContext *pStreamContext;  
05.  
06.    for (pListEntry = m_StreamList.Flink;  
07.        pListEntry != &m_StreamList;  
08.        pListEntry = pListEntry->Flink)  
09.    {  
10.        pStreamContext = CONTAINING_RECORD(pListEntry,StreamContext,m_Link);  
11.        pStreamContext->GainChange();  
12.    }  
13.    return;  
14.}  
 

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

 

view plaincopy to clipboardprint?
01.void GainChange()  
02.   {  
03.       m_fxpGain = MapGain(m_dwGain);  
04.}  
05.DWORD StreamContext::MapGain(DWORD Gain)  
06.{  
07.    DWORD TotalGain = Gain & 0xFFFF;  
08.    DWORD SecondaryGain = m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass) & 0xFFFF;  
09.    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 plaincopy to clipboardprint?
01.PBYTE OutputStreamContextS16::Render2(PBYTE pBuffer, PBYTE pBufferEnd, PBYTE pBufferLast)  
02.{  
03.    LONG CurrT = m_CurrT;  
04.    LONG DeltaT = m_DeltaT;  
05.    LONG CurrSamp0 = m_CurrSamp[0];  
06.    LONG PrevSamp0 = m_PrevSamp[0];  
07.    PBYTE pCurrData = m_lpCurrData;  
08.    PBYTE pCurrDataEnd = m_lpCurrDataEnd;  
09.    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 plaincopy to clipboardprint?
StreamContext *CreateStream(LPWAVEOPENDESC lpWOD);  
DWORD OpenStream(LPWAVEOPENDESC lpWOD, DWORD dwFlags, StreamContext **ppStreamContext);  
HRESULT Open(DeviceContext *pDeviceContext, LPWAVEOPENDESC lpWOD, DWORD dwFlags);  
void NewStream(StreamContext *pStreamContext);  
void DeleteStream(StreamContext *pStreamContext);  
void StreamReadyToRender(StreamContext *pStreamContext);  
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 plaincopy to clipboardprint?
01.case WODM_OPEN:  
02.{  
03.    StreamContext *pStreamContext;  
04.    pStreamContext = (StreamContext *) dwUser;  
05.    dwRet = pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)pStreamContext);  
06.    break;  
07.}  
 

OpenStream的其流程图如下
 


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


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

 

view plaincopy to clipboardprint?
01.case WODM_WRITE:  
02.{  
03.    StreamContext *pStreamContext;  
04.    pStreamContext = (StreamContext *) dwUser;  
05.    dwRet = pStreamContext->QueueBuffer((LPWAVEHDR)dwParam1);  
06.    break;  
07.}  
 

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


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


结尾时,想起大学教育的一个问题,居然不学语文了。见别人写的文章文采飞扬,而我的确是很僵硬,风趣和文采都不足。
 
还是老话,本人才疏学浅,有错误之处,请更正.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值