Chinaunix首页 | 论坛 | 博客
  • 博客访问: 430599
  • 博文数量: 56
  • 博客积分: 2262
  • 博客等级: 大尉
  • 技术积分: 711
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-08 20:04
文章分类

全部博文(56)

文章存档

2013年(1)

2012年(9)

2011年(10)

2010年(7)

2009年(7)

2008年(22)

我的朋友

分类:

2008-07-22 22:38:02

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键值,然后通过调用该方法就能实现对按键声音的设置。
ScreenMute的设置也可以用该方法,至此,对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::Render2XXX表示双声道S单声道Mbit位是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;
            }
            else if (OutSamp0
            {
                OutSamp0=AUDIO_SAMPLE_MIN;
            }
            if (OutSamp1>AUDIO_SAMPLE_MAX)
            {
                OutSamp1=AUDIO_SAMPLE_MAX;
            }
            else if (OutSamp1
            {
                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_fxpGainRm_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,我发现他启动几个周期之后,才有数据的传送,但是具体启动了几个周期之后才开始传送数据的是不确定的,所有才会有左右声道交换的情况!恰好2410LRCLK又是不能控制的!
         如果有人解决了也遇到了同样的情况,欢迎与我讨论。
 
阅读(3720) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~