作者:茫天靖剑


WINCE6.0 驱动音量调节的实现


WINCE音量调节的实现

       在WINCE平台下,用户可以通过设置控制面板的音量调节按钮,实现系统音量的调节。同时,如果使用一些媒体播放器,如mplayer,则可以对输入的音频流实行单独的调节,而不影响系统其他的音量。我们可以把整个架构理解为WINDOWS平台,用户通过设置右下角的声音按钮,实现整个系统的音量设置,而在播放如千千静听,暴风影音时,可以单独的设置软件的音量,而不影响整个系统的音量。WINCE的音量调节模型正是如此。
在手持机上,会用到专业的音频芯片,比较典型的有UDA1341,WM8976,WM9713等。这些芯片都留有I2C接口,供处理器设置芯片的寄存器,从而达到调节音量等的功能。在底层驱动中,通过留相应接口,实现在调节控制面板里面的音量时,最终调节音频芯片的寄存器,实现音量调节。
这是最常用的一种方法。但是有些OEM厂商为了节约成本,在硬件上大做文章,选择的音频芯片根本没有音量调节的功能,甚至连I2C接口都没有,仅有一个I2S接口用于音频解码。那么这时候,WINCE是否还能像前面的那样,实现音量调节?答案是肯定的。
在Wavemain.cpp中,音量调节程序如下:

case WODM_GETVOLUME://获取喇叭音量
         {
             PULONG pdwGain = (PULONG)dwParam1;

             if (pStreamContext)
             {
                 *pdwGain = pStreamContext->GetGain();
             }
             else
             {
                 // Handle device gain in hardware
                 //*pdwGain = g_pHWContext->GetOutputGain();//没有硬件音量调节支持
                 // Handle device gain in software
                 DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);//软音量获取
                 *pdwGain = pDeviceContext->GetGain();
             }
             dwRet = MMSYSERR_NOERROR;
             break;
         }

     case WODM_SETVOLUME://音量设置.
         {
             LONG dwGain = dwParam1;
             if (pStreamContext)//如果存在音频流,则这里调节音频流音量
             {
                     //RETAILMSG(1,(_T("Stream volume set...\n")));// lqm test.
                 dwRet = pStreamContext->SetGain(dwGain);// dwGain=dwParam1,由应用层如mplayer传入.
                                 //RETAILMSG(1,(_T("Stream volume set dwRet = %d\n"),dwRet));// lqm test.
             }
             else//如果不存在音频流,通过控制面板调节音量最终由这里控制音量。
             {
                     //RETAILMSG(1,(_T("hardware volume set...\n")));// lqm test.
                 // Handle device gain in hardware
                 //dwRet = g_pHWContext->SetOutputGain(dwGain);//如果音频芯片有音量调节功能,使用硬调节。
                 // Handle device gain in software
                 DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
                 dwRet = pDeviceContext->SetGain(dwGain);//由于音频芯片没有音量调节功能,使用软算法。
             }
             break;
         }


第一个case: WODM_GETVOLUME用于获得当前音量的大小。注意在else里面,有GetOutputGain()和GetGain()两个函数。前面的函数用于获取硬件音量,即音频芯片的音量,后面的函数用于获取软件音量,即音频流音量。如果音频芯片具备音量调节功能,使用第一个函数,否则使用第二个函数。
第二个case: WODM_SETVOLUME用于设置音量。同样,在else里面,有SetOutputGain和SetGain两个函数。前面的函数用于设置硬件增益,即音频芯片寄存器。如果音频芯片不支持音量调节功能,则需要使用后面的函数。该函数用于音量软设置。
在第二个case中有一个if判断语句,标识位为pStreamContext。也就是说,如果存在音频流,即正在播放音频文件时,如果调用该case语句,则调用if里面程序进行音量设置,否则调用else下面的语句设置。如果采用软设置方法,两者最终都通过GainChange函数来完成,该函数代码如下:

virtual void GainChange()
     {
         m_fxpGain = MapGain(m_dwGain);//m_fxpGain保存音频流的音量大小
     }


这里m_dwGain = dwGain = dwParam1,在HandleWaveMessage函数中,可以看到dwParam1 = pParams->dwParam1;也就是说m_dwGain是应用传下来的值,如通过mplayer,控制面板的音量设置按钮等。
我们再进入MapGain函数:
DWORD StreamContext::MapGain(DWORD Gain)//Gain由应用层如媒体播放器传入,条件是有音频流的情况下。

{
     DWORD TotalGain = Gain & 0xFFFF;
     DWORD SecondaryGain = m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass) & 0xFFFF;

         //RETAILMSG(1,(_T("MapGain Volume set Gain = 0x%x\n"),Gain));//lqm test.

     if (m_SecondaryGainClass < SECONDARYDEVICEGAINCLASSMAX)
     {
         // Apply device gain
         DWORD DeviceGain = m_pDeviceContext->GetGain() & 0xFFFF;
                 RETAILMSG(1,(_T("Gain=0x%x,DeviceGain=0x%x\n"),Gain,DeviceGain));//lqm test.
         TotalGain *= DeviceGain;
         TotalGain += 0xFFFF;  // Round up
         TotalGain >>= 16;     // Shift to lowest 16 bits
     }

     // Apply secondary gain
     TotalGain *= SecondaryGain;
     TotalGain += 0xFFFF;  // Round up
     TotalGain >>= 16;     // Shift to lowest 16 bits

         RETAILMSG(1,(_T("MapGain Volume set TotalGain = 0x%x\n"),TotalGain));//lqm test.

     // Special case 0 as totally muted
     if (TotalGain==0)
     {
         return 0;
     }

     // Convert to index into table
     DWORD Index = 63 - (TotalGain>>10);
         RETAILMSG(1,(_T("MapGain Index = 0x%x\n"),Index));//lqm test.
         //Index = 50;//lqm added for test.10-05-06
     return GainMap[Index];
 }


传入参数Gain即上面提到的m_dwGain,该参数为32位寄存器,高16位和低16位分别存放左右声道。由于一般情况下左右声道的音量都是一样的,故程序中只取了低16位。
TotalGain是DeviceGain和m_dwGain的乘积,然后右移16位得到的。其实就是TotalGain=DeviceGain*m_dwGain/最高音量,如果把DeviceGain/最高音量,用百分比来算的话,就很更容易理解了,那么最后的公式就变成TotalGain=DeviceGain*系统音量百分比。那么这里就解释了系统音量是如何限制流音量的疑问。
最终通过一个索引求出对应音量的权值,再通过音量表设置音量。可以看到Index = 63 - (TotalGain>>10);Index在0到63的范围内。返回的GainMap表如下:

const DWORD GainMap[] =
 {
 0x10000, // 0: 0.000000 dB
 0xec77, // 1: -1.587302 dB
 0xda6d, // 2: -3.174603 dB
 0xc9c2, // 3: -4.761905 dB
 0xba5d, // 4: -6.349206 dB
 0xac25, // 5: -7.936508 dB
 0x9f03, // 6: -9.523810 dB
 0x92e1, // 7: -11.111111 dB
 0x87ac, // 8: -12.698413 dB
 0x7d52, // 9: -14.285714 dB
 0x73c2, // 10: -15.873016 dB
 0x6aed, // 11: -17.460317 dB
 0x62c5, // 12: -19.047619 dB
 0x5b3b, // 13: -20.634921 dB
 0x5445, // 14: -22.222222 dB
 0x4dd7, // 15: -23.809524 dB
 0x47e7, // 16: -25.396825 dB
 0x426b, // 17: -26.984127 dB
 0x3d59, // 18: -28.571429 dB
 0x38ab, // 19: -30.158730 dB
 0x3458, // 20: -31.746032 dB
 0x305a, // 21: -33.333333 dB
 0x2ca9, // 22: -34.920635 dB
 0x2941, // 23: -36.507937 dB
 0x261b, // 24: -38.095238 dB
 0x2333, // 25: -39.682540 dB
 0x2083, // 26: -41.269841 dB
 0x1e08, // 27: -42.857143 dB
 0x1bbe, // 28: -44.444444 dB
 0x19a0, // 29: -46.031746 dB
 0x17ab, // 30: -47.619048 dB
 0x15dd, // 31: -49.206349 dB
 0x1432, // 32: -50.793651 dB
 0x12a7, // 33: -52.380952 dB
 0x113b, // 34: -53.968254 dB
 0x0fea, // 35: -55.555556 dB
 0x0eb3, // 36: -57.142857 dB
 0x0d94, // 37: -58.730159 dB
 0x0c8b, // 38: -60.317460 dB
 0x0b96, // 39: -61.904762 dB
 0x0ab4, // 40: -63.492063 dB
 0x09e3, // 41: -65.079365 dB
 0x0921, // 42: -66.666667 dB
 0x086f, // 43: -68.253968 dB
 0x07ca, // 44: -69.841270 dB
 0x0732, // 45: -71.428571 dB
 0x06a6, // 46: -73.015873 dB
 0x0624, // 47: -74.603175 dB
 0x05ac, // 48: -76.190476 dB
 0x053d, // 49: -77.777778 dB
 0x04d7, // 50: -79.365079 dB
 0x0478, // 51: -80.952381 dB
 0x0421, // 52: -82.539683 dB
 0x03d0, // 53: -84.126984 dB
 0x0386, // 54: -85.714286 dB
 0x0341, // 55: -87.301587 dB
 0x0301, // 56: -88.888889 dB
 0x02c6, // 57: -90.476190 dB
 0x0290, // 58: -92.063492 dB
 0x025e, // 59: -93.650794 dB
 0x0230, // 60: -95.238095 dB
 0x0205, // 61: -96.825397 dB
 0x01de, // 62: -98.412698 dB
 0x01b9, // 63: -100.000000 dB
 };


这就是软音量设置最终调用的音量配置表。具体它是如何通过这个表来实现软音量设置的,还需要认真研究,难道通过I2S送到音频芯片的数字信号里面已经带有音量数据?不过到这里,软的音量调节功能已经实现了,详细的原理有空再仔细研究。