MP3播放
音量设置
最近一段时间在修改音量需求变化的时候,让我对在WINCE下对各种音量的设置有了一定的理解,现在将我的理解写到blog上,让各位指教。
首先我们来看下,这些声音设置都在注册表:HKEY_CURRENT_USER\ControlPanel\Volume下,里面的几个键值都是控制声音的。先解释如下:
Volume: 系统的主音量,范围是0x0 ~ 0xFFFFFFFF.
Screen: 屏幕敲击声. 当数值为0(或65536)无声,1为柔和,65538为洪亮
Key: 键盘敲击声,数值的意义和Screen相同.
Mute: 控制其它静音的选项. 置0x04位为1时允许事件声音,0x02允许应用程序声音,0x01允许警告声.需要注意的是,如果不允许应用程序声音,则警告声位也将被忽略.
知道各个键值的意义后,我从最主要的系统的主音量Volume说起。
一、对系统主音量Volume的操作
首先我们来看一段最简单的改变音量的代码:
DWORD dwVolume = 0xAAAAAAAA;
waveOutSetVolume(0,dwVolume);
waveOutSetVolume()的第一个参数是设备ID,因为需要更改的是整个系统音量,所以在这里直接取0值即可;第二个参数是需要设置的音量数值,范围是从 0x0 ~ 0xFFFFFFFF。
通过waveOutSetVolume()这个API,我们可以很容易的更改系统设备的音量,但这个时候,如果你去查看注册表的Volume的键值是没有变化的,因为它只修改了设备的音量,变化还没有这么快到达注册表。但你可以到“控制面板”中的“音量与声音”打开一下,注册表的值也随之改变。(反之,通过对注册表的单独操作对具体音量是起不到作用的)
所以,我对音量的操作,首先对注册表中的Volume进行操作,在用waveOutSetVolume()这个API具体改变音量,这样可以达到一致。具体操作代码如下:
DWORD dwVolume = 0;
CReg* pVolumeReg = NULL;
pVolumeReg = new CReg( HKEY_CURRENT_USER, TEXT("ControlPanel\\Volume") );
dwVolume = pVolumeReg->ValueDW(TEXT("Volume"));
。。。。。。。。。。。。。。对音量的具体设置
if( waveOutSetVolume(NULL, dwVolume) != MMSYSERR_NOERROR ) 音量设置是否成功
{
DBGMSG(ZONE_1, (TEXT("waveOutSetVolume failed, [MainLayere.cpp, SetVolume]")));
}
pVolumeReg->SetDW(TEXT("Volume"),dwVolume); 设置注册表
delete pVolumeReg;
pVolumeReg = NULL;
上面用到的CReg是一个对注册表操作的类,是我们小组用的一个基类(Walzer注:在WINCE500目录下搜索CReg可以找到, 其实微软的人也很懒)。这样我们对主音量设置就很完善了。
二、对硬件按键声音(KEY键值)的设置
尽管waveOutSetVolume()这个API对主音量设置很好用,但这个函数的功能却也是非常有限的,也就是说,它只能更改系统的主音量;如果想修改硬件按键声音或屏幕敲击声,则就无能为力.
有些比较细心的朋友可能会从"控制面板"的"声音"入手,发现每次在控制面板调节声音,相应的"ControlPanel\Volume"下的键值数值都会变更.但如果是直接修其下的改注册表,却是无论如何都达不到相应的功能的----因为没有通知系统,注册表已经被修改.
如果需要告知系统,注册表已经修改,并请系统依照修改的数值来更改音量,则需要调用微软一个未公开的API:AudioUpdateFromRegistry().
这个API在文档中是无法搜索到,如果需要调用这个函数,可以有两种方法.
一是直接包含"pwinuser.h"文件,然后直接调用.
二是调用coredll.dll库,引出该函数并使用.
第一种方法比较不稳定,因为有一些人的sdk中没有这个pwinuser.h文件,所以程序找不到。我还是推荐用第二种方法,直接调用该API(就象我们组长说的暴力调用API,这个方法真的还是不错的。)
其具体代码如下:
typedef void (WINAPI *DLL_AUDIOUPDATEFROMREGISTRY)();定义一个新类型指针,指向WINAPI
DLL_AUDIOUPDATEFROMREGISTRY Dll_AudioUpdateFromRegistry = NULL;
HINSTANCE hCoreDll = LoadLibrary(TEXT("coredll.dll"));
if (hCoreDll)
{
Dll_AudioUpdateFromRegistry = (DLL_AUDIOUPDATEFROMREGISTRY)GetProcAddress(hCll, _T("AudioUpdateFromRegistry")); 调用该API
if (Dll_AudioUpdateFromRegistry)
{
(Dll_AudioUpdateFromRegistry)();
}
else
{
return FALSE;
}
FreeLibrary(hCoreDll);
}
else
{
return FALSE;
}
return TRUE;
这样通过修改注册表Key键值,然后通过调用该方法就能实现对按键声音的设置。
对Screen和Mute的设置也可以用该方法,至此,对WINCE各种音量的设置就基本掌握了。三、MP3音量设置
Volume : -1000…….0 Balance : -1000……..1000
IBasicAudio *m_pBA;
BOOL CItsMp3Play::SetVolume(LONG lVolume, LONG lBalance) //mp3音量设置,非系统音量
{
if(m_pBA == NULL)
{
return FALSE;
}
if(lVolumeMAX_VOLUME&&lBalanceMAX_BALANCE)
{
return FALSE;
}
m_pBA->put_Volume(lVolume);
m_pBA->put_Balance(lBalance);
return TRUE;
}
四.解决wince420不能设置左右声道音量的问题
在wince420中不能使用WaveOutSetVolume分别设置左右声道音量,这是因为在wince底层的wav解码里面只使用了参数的低16位,并没有将高低位分开来设置左右声道增益。因此我跟踪调试进入底层,找出设置系统音量的函数!
还有一点是需要说明的,在很多应用中使用的声音芯片都是低成本的,一般不支持左右声道音量调节功能,因此我们只有在wav播放中设置左右声道数据的增益,从而达到左右声道音量分别控制的效果。
默认系统是一个双声道系统。
首先看一下PLATFORM\SMDK2410\DRIVERS\WAVEDEV\Output.cpp文件:
WaveStreamContext::Render之后的数据就是直接发送到外部声音芯片的数据,他根据参数以及标志位选择OutputStreamContextXXX::Render2,XXX表示双声道S单声道M,bit位是8位还是16位。
以双声道OutputStreamContextS16::Render2为例,我的BSP里面的代码如下:
PBYTE OutputStreamContextS16::Render2(PBYTE pBuffer, PBYTE pBufferEnd, PBYTE pBufferLast)
{
LONG CurrT = m_CurrT;
LONG DeltaT = m_DeltaT;
LONG CurrSamp0 = m_CurrSamp[0];
LONG CurrSamp1 = m_CurrSamp[1];
LONG PrevSamp0 = m_PrevSamp[0];
LONG PrevSamp1 = m_PrevSamp[1];
PBYTE pCurrData = m_lpCurrData;
PBYTE pCurrDataEnd = m_lpCurrDataEnd;
LONG fxpGain = m_fxpGain;//修改过后,表示两个声道,原文件中只有一个
LONG fxpGain1 = m_fxpGain1;
LONG OutSamp0;
LONG OutSamp1;
while (pBuffer < pBufferEnd)
{
while (CurrT >= 0x10000)
{
if (pCurrData>=pCurrDataEnd)
{
goto Exit;
}
CurrT -= 0x10000;
PrevSamp0 = CurrSamp0;
PrevSamp1 = CurrSamp1;
PPCM_SAMPLE pSampleSrc = (PPCM_SAMPLE)pCurrData;
CurrSamp0 = (LONG)pSampleSrc->s16.sample_left;
CurrSamp1 = (LONG)pSampleSrc->s16.sample_right;
pCurrData+=4;
}
//在这个地方将左右声道数据增益分别设置,这样输出的数据音量增益就会不同,当然我们耳朵听的声音大小就不一样了
OutSamp0 = PrevSamp0 + (((CurrSamp0 - PrevSamp0) * CurrT) >> 16);
OutSamp0 = (OutSamp0 * fxpGain) >> VOLSHIFT;
OutSamp1 = PrevSamp1 + (((CurrSamp1 - PrevSamp1) * CurrT) >> 16);
OutSamp1 = (OutSamp1 * fxpGain1) >> VOLSHIFT;
CurrT += DeltaT;
if (pBuffer < pBufferLast)
{
OutSamp0 += ((HWSAMPLE *)pBuffer)[0];
OutSamp1 += ((HWSAMPLE *)pBuffer)[1];
#if USE_MIX_SATURATE
// Handle saturation
if (OutSamp0>AUDIO_SAMPLE_MAX)
{
OutSamp0=AUDIO_SAMPLE_MAX;
}
{
OutSamp0=AUDIO_SAMPLE_MIN;
}
if (OutSamp1>AUDIO_SAMPLE_MAX)
{
OutSamp1=AUDIO_SAMPLE_MAX;
}
{
OutSamp1=AUDIO_SAMPLE_MIN;
}
#endif
}
((HWSAMPLE *)pBuffer)[0] = (HWSAMPLE)OutSamp0;
((HWSAMPLE *)pBuffer)[1] = (HWSAMPLE)OutSamp1;
pBuffer += 2*sizeof(HWSAMPLE);
}
Exit:
m_dwByteCount += (pCurrData - m_lpCurrData);
m_lpCurrData = pCurrData;
m_CurrT = CurrT;
m_PrevSamp[0] = PrevSamp0;
m_PrevSamp[1] = PrevSamp1;
m_CurrSamp[0] = CurrSamp0;
m_CurrSamp[1] = CurrSamp1;
return pBuffer;
}
知道了左右声道增益设置方法,哪么我们就去找m_fxpGain这个参数,这是原文件中的参数,当然我们可以给他添加一个后缀,比如m_fxpGainR、m_fxpGainL。这样在阅读的时候也要方便一些,我这里就直接使用我试验时修改的代码。我发现m_fxGain参数是在StreamContext这个类里面定义的!在PLATFORM\SMDK2410\DRIVERS\WAVEDEV\Strmctxt.h文件中可以找到,在这个类里面我们可以看到这样两个函数:
DWORD SetGain(DWORD dwGain)
{
m_dwGain = dwGain&0xFFFFFFFF;
GainChange();
return MMSYSERR_NOERROR;
}
这个是设置音量调用的函数。在原文件中m_dwGain = dwGain&0xFFFF;这样一来就把WaveOutSetVolume传进来的32位音量参数与成16位有效了,哪么我们传进来的高16位的音量就没有作用了,我们要做的就是把高16位的功能启动起来!
virtual void GainChange()
{
DWORD m_dwGainL = (m_dwGain >> 16) & 0xffff;
DWORD m_dwGainR = m_dwGain & 0xffff;
m_fxpGain = MapGain(m_dwGainR);
m_fxpGain1 = MapGain(m_dwGainL);
}
在这个函数中调用MapGain将我们传送进来的数据转换成音量增益,这里我们可以不管他,因为音量增益是一个指数型增长的数据,所以在这里需要转换,这样在设置音量的时候我们才能感觉到他是直线型增长的!
在这个函数中就已经可以看出,已经将两个声道的音量分离开了,也就是在前面提到的Render中所使用的音量增益。
这个参数是在PLATFORM\SMDK2410\DRIVERS\WAVEDEV\WaveMain.cpp里面的WAV_IOControl传进来的,
case WODM_SETVOLUME:
{ UINT NumDevs = g_pHWContext->GetNumOutputDevices();
LONG dwGain = dwParam1;
if (pStreamContext)
{
dwRet = pStreamContext->SetGain(dwGain);
}
else
{
DeviceContext *pDeviceContext= _pHWContext->GetOutputDeviceContext(uDeviceId);
dwRet = pDeviceContext->SetGain(dwGain);
}
break;
}
在这里面有两种情况,一个是设置整个系统的音量,一个是设置流的音量,这样我们前面所说的联系起来,就能很容易的了解到系统音量设置的整个流程了!
设置之后当然是没有改变注册表中音量的值的。
在实现左右声道音量设置的时候我发现了一个奇怪的现象,那就我们虽然能设置左右声道音量增益,但是没有办法稳定的实现在一个声道播放声音,例如:我们播放一个音频,声音设置为单声道,就是说将32位参数中的高16位或者低16设为0,连续播放多次这个音频,理论上来说是一个喇叭有声音,一个喇叭没有声音!
在测试中我发现,两个声道的音量交换,就好像是我们开始设置的是0xFFFF0000,播放几次之后,就变成了0xFFFF了,左右声道的音量交换了。
我用示波器看了一下数据和左右声道选择线的波形,在s3c2410中,是使用的IIS总线,左右声道选择线是LRCLK,我发现他启动几个周期之后,才有数据的传送,但是具体启动了几个周期之后才开始传送数据的是不确定的,所有才会有左右声道交换的情况!恰好2410的LRCLK又是不能控制的!
如果有人解决了也遇到了同样的情况,欢迎与我讨论。