http://blog.csdn.net/daydayupfromnowon/article/details/6009333
下面一步步的描述驱动中对音量控制的调整过程:
第一步: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 gain和device gain这里没有做说明,因为其代码比较简单,可以参照驱动中相关代码来进行理解。