Wave Driver介绍-7(驱动中对音量的控制操作-非硬件音量控制)

下面一步步的描述驱动中对音量控制的调整过程:

第一步:wave driver接口中对音量的处理

         应用程序调用waveOutSetVolume去配置stream音量或者设备音量,导出接口WAV_IOControl将会接收到WODM_SETVOLUME消息,并调用相应的代码进行处理。

这部分代码如下:

case WODM_SETVOLUME:

{

    UINT NumDevs = g_pHWContext->GetNumOutputDevices();

    LONG dwGain = dwParam1;

 

     NKDbgPrintfW(L"WODM_SETVOLUME. dwGain 0x%8x, NumDevs %d, pStreamContext %s, sizeof(dwGain) %d/r/n", dwGain, NumDevs, pStreamContext?L"One":L"ALL", sizeof(dwGain));

    if (pStreamContext)

    {

[1]        dwRet = pStreamContext->SetGain(dwGain);

    }

    else

    {

        DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);

[2]      dwRet = pDeviceContext->SetGain(dwGain);

    }

 

    break;

}

         简单说明如下:

[1]    在上面的代码中会去判断是否传入了Stream句柄,对于调用waveOutSetVolume时候传入句柄的情况,将会走到这个分支。

[2]    对于调用waveOutSetVolume的时候传入设备ID的情况,将会走到这个分支,也即调整的是设备音量。

         我们这里以第一个分支的调用Stack为例来进行介绍。

第二步:类StreamContext中对音量的处理

         驱动中依次调用SetGainà GainChangeà MapGain,代码如下:

DWORD StreamContext::SetGain(DWORD dwGain)

{

[0] m_dwGain = dwGain;

    GainChange();

    return MMSYSERR_NOERROR;

}

void StreamContext::GainChange()

{

    for (int i = 0; i < 2; i++)

    {

[1]     m_fxpGain[i] = MapGain(m_dwGain, i);

    }

}

// Channel 0 is the left channel, which is the low 16-bits of volume data

DWORD StreamContext::MapGain(DWORD StreamGain, DWORD Channel)

{

     //NKDbgPrintfW(L"MapGain:: StreamGain %d, Channel %d/n", StreamGain, Channel);

 

     // Get correct stream gain based on channel

     // 左右声道是一样的,所以这里如果判定声道为的话,就右移bit

     if (Channel == 1)

     {

[2]      StreamGain >>= 16;

     }

 

[3]  StreamGain &= 0xFFFF;

 

     // Get Device gain

     DWORD DeviceGain;

 

[4]  if (m_SecondaryGainClass >= SECONDARYDEVICEGAINCLASSMAX)

     {

         DeviceGain = 0xFFFF;

     }

     else

     {

         // Apply device gain

[5]      DeviceGain = m_pDeviceContext->GetGain();

 

         if (Channel == 1)

         {

[6]           DeviceGain >>= 16;

         }

 

         DeviceGain &= 0xFFFF;

     }

 

     // Get Secondary gain

     DWORD SecondaryGain;

 

[7]  SecondaryGain = m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass);

 

     SecondaryGain &= 0xFFFF; // For now, only use lowest 16 bits for both channels

 

[8]  DWORD fGainMultiplier;

 

     // Special handling- if any gain is totally 0, mute the output

[9]  if ((StreamGain == 0) || (DeviceGain == 0) || (SecondaryGain == 0))

     {

         fGainMultiplier = 0;

     }

     else

     {

         // Now calculate attenuation of each in dB using appropriate ranges

 

         // Stream volume is normalized to the range from 0 to -100 dB

         // Device and secondary gain are normalized from 0 to -35 dB

         // These can be modified in hwctxt.h

 

         DWORD dBAttenStream, dBAttenDevice, dBAttenSecondary, dBAttenTotal;

 

         // 这里之所以用xffff与具体的音量相减是因为GainMap是倒序,而且计算的使用了x10000

[10]     dBAttenStream    = ((0xFFFF - StreamGain)    * STREAM_ATTEN_MAX);

         dBAttenDevice    = ((0xFFFF - DeviceGain)    * DEVICE_ATTEN_MAX);

         dBAttenSecondary = ((0xFFFF - SecondaryGain) * CLASS_ATTEN_MAX);

 

         // Add together

         dBAttenTotal = dBAttenStream + dBAttenDevice + dBAttenSecondary;

 

         // Multiply result by 2 for .5 dB steps in the table

[10]      dBAttenTotal *= 2;

 

         // 这里其实就是为了后面通过移位对dBAttenTotal进行取整

         // Round up to account for rounding errors in lower 16 bits

[11]     dBAttenTotal += 0x8000;

 

         // Now shift back to the lowest 16 bits to get an index into the table

         dBAttenTotal >>= 16;

 

         // dBAttenTotal should range from 0 to something like 340 (if all terms were close to 0)

 

         // Special case 0 as totally muted. The table starts at -.5dB, rather than 0dB, since

         // 0dB would take more than the 16-bits we allowed per entry.

 

         if (dBAttenTotal == 0)

         {

[12]          fGainMultiplier = 0x10000;

         }

         else if (dBAttenTotal > 200)

         {

[13]          fGainMultiplier = 0;

         }

         else

         {

[14]          fGainMultiplier = (DWORD)GainMap[dBAttenTotal-1];

         }

     }

     //NKDbgPrintfW(L"MapGain::fGainMultiplier %d, StreamGain %d, SecondaryGain %d, DeviceGain %d/n", fGainMultiplier, StreamGain, SecondaryGain, DeviceGain);

     return fGainMultiplier;

}

[0]    将应用层传入的音量保存到类StreamContext成员m_dwGain中,每一个Stream都会拥有一个类StreamContext类的实例。因此,当用户运行很多个播放器(每个播放器都会有一个Stream)的时候,可以独立的来控制每一个播放器的音量。

[1]    Stream中可以分左右声道来分别存放音量到m_fxpGain[]数组中,这也就是所谓的左右声道独立控制音量。

         应用程序中可以通过waveOutGetDevCaps获取到设备是否支持左右声道独立控制音量。

[2]    单声道情况下的音量保存和双声道稍有不同,双声道情况下音量用一个DWORD的高低16位分别表示,其中高16位表示左声道,而低16位表示右声道。而单声道情况下,仅仅使用高16位来表示音量。附带说一句,对于双声道情况下的右声道,0xffff表示满音量播放,也即使用当前的设备音量100%进行播放,对于0x8000表示使用50%进行播放。

[3]    stream音量的有效位数为16

[4]    微软在音量控制的时候,引入了Class的概念。前面曾经提到,音量控制通过设备音量/Stream音量和Class音量综合起来进行控制。系统中可以维护多个音量Class,每一个音量Class对应一个音量值,通过应用层制定是否哪一个Class来对系统音量进行进一步的控制。

[5]    获取设备音量。

[6]    设备音量与流音量的组织方式一致,同样分为左右声道各16位。

[7]    这里从数组中获取[4]所提到的class音量。

[8]    这个变量用来记录综合了设备音量/Stream音量和Class音量的结果,后面会使用这个值到gainmap中查表找到对应具体音量增益的值。

[9]    如果三个音量中任何一个为零,就表示启动mute功能。

[10]  其实这个地方没有太明白,我的理解是将计算后的音量加倍,最终导致的结果就是查询gainmap的时候粗略一点,不是那么的细腻。Eg,加入说本来是结果为10,也即使用gainmap[10-1],直接翻倍的结果就是使用gainmap[20-2],这样控制音量的step更大一些。

[11]  这里是为了后面右移16位取整做准备。至于右移16位的原因,前面多次提到,因为音量值范围为0~0xffff,如果为0x10000就表示直接使用设备音量,不进行衰减,不使用Class音量和Stream音量。

[12]  对于dBAttenTotal的情况,也就是直接设备音量,不进行衰减,所以赋值为0x10000,当然了查表gainmap的至少要衰减0.5dB

[13]  这里就相当于静音,与前面[9]的差别在于,这里是音量太小而静音,而不是没有音量。

[14]  查表找到音量。

         后面,当开始对播放的采样数据进行初步处理的时候,将会利用到这里的fGainMultiplier,如下:

//将数据从m_lpCurrData~m_lpCurrDataEnd中取出并添加增益后填充到pBuffer起始的位置,共后续播放使用

// m_dwByteCount: 填充到DMA buffer的数据个数,以字节为单位

// m_lpCurrData: 更新之后的待操作的user buffer的偏移位置

// m_PrevSamp: 前一个sample的值,这个是未调整增益之前的值

// m_CurrSamp: 当前一个sample的值,尚未调整增益的原始值

// 函数的返回值是用户buffer中已经取过sample的下一个sample的位置

PBYTE OutputStreamContext2M8::Render2(PBYTE pBuffer, PBYTE pBufferEnd, PBYTE pBufferLast, TRANSFER_STATUS *pTransferStatus)

{

     // 这里的移位用来保证sample有效位数不超过采样位数

[1]        OutSamp0 = (OutSamp0 * fxpGain[0]) >> VOLSHIFT;

 

        OutSamp1 = (OutSamp1 * fxpGain[1]) >> VOLSHIFT;

 

        if (pBuffer < pBufferLast)

        {

            OutSamp0 += ((HWSAMPLE *)pBuffer)[0];

            OutSamp1 += ((HWSAMPLE *)pBuffer)[1];

        }

 

[2]        ((HWSAMPLE *)pBuffer)[0] = (HWSAMPLE)OutSamp0;

        ((HWSAMPLE *)pBuffer)[1] = (HWSAMPLE)OutSamp1;

}

[1]    OutSamp0是具体的采样数据,而fxpGain[0]为增益,VOLSHIFT是(32-实际的采样位宽),这里的右移操作是保证采样点中的数据越界。举个例子,如果是4位采样的话,肯定要把DWORD右移(32-4)位,以保证处理后的采样数据为4位。

[2]    将处理后的采样数据保存到播放的buffer中,实际上也就是DMA使用的buffer(对于DMA方式播放而言)。

         另外,对于class gaindevice gain这里没有做说明,因为其代码比较简单,可以参照驱动中相关代码来进行理解。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值