天天看點

com.android.musicFx設定音效流程 -- 從app到AudioFlinger

      本文主要内容如标題所示,主要描述下針對com.android.musicFx這個應用打開音效設定時的音效的函數調用流程。先簡單說com.android.musicFx(後面簡寫成MusicFx)的相關知識,MusicFx第一次出現是在android2.3版本,預設入口在Music播放界面menu菜單,菜單裡有一個音效選項拉起MusicFx應用,進入應用後界面比較簡單,一個spinner 和幾個seekbar來選擇類型(這裡的類型指的是鄉村,爵士,搖滾等)和設定具體數值,我們分析的起點就從改變seekbar滑動的值開始。

      在開始之前,還要提及幾個原則性概念,在2.3版本中同時還增加了對音頻混響的支援,代碼主要展現在android.media.audiofx這個包中,其中AudioEffect是android audio framework(android 音頻架構)提供的音頻效果控制的基類,我們隻能使用它的派生類。下面列出它的派生類:    BassBoost重低音,Equalizer均衡器,Virtualizer虛拟器,  PresetReverb預置混響,EnvirenmentReverb環境音混響,Visaulizer可視化。       當建立AudioEffect時,如果音頻效果應用到一個具體的AudioTrack和MediaPlayer的執行個體,應用程式必須指定該執行個體的音頻session ID,如果要應用Global音頻輸出混響的效果必須制定Session。       以下開始具體的代碼流程, 以MusicFx為例(第三方應用未必是下面的界面,但執行的思路應該是一樣的),音效設定界面是ActivityMusic.java,在這個界面有一些seekbar的監聽函數,任意取一段代碼如下,    

if (mVirtualizerSupported) {
          seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {                  
              @Override
              public void onProgressChanged(final SeekBar seekBar, final int progress,
                      final boolean fromUser) {
                  // set parameter and state
                  ControlPanelEffect.setParameterInt(mContext, mCallingPackageName,
                          mAudioSession, ControlPanelEffect.Key.virt_strength, progress);
              }

              // If slider pos was 0 when starting re-enable effect
              @Override
              public void onStartTrackingTouch(final SeekBar seekBar) {
                  if (seekBar.getProgress() == 0) {
                      ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName,
                              mAudioSession, ControlPanelEffect.Key.virt_enabled, true);
                  }
              }

              // If slider pos = 0 when stopping disable effect
              @Override
              public void onStopTrackingTouch(final SeekBar seekBar) {
                  if (seekBar.getProgress() == 0) {
                      // disable
                      ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName,
                              mAudioSession, ControlPanelEffect.Key.virt_enabled, false);
                  }
              }
          });
   }
           

     從代碼上看先走onStartTrackingTouch的setParameterBoolean,實際是也是這樣的,去ControlPanelEffect這個類看下它做了什麼,

final Virtualizer virtualizerEffect = getVirtualizerEffect(audioSession);
                        if (virtualizerEffect != null) {
                            virtualizerEffect.setEnabled(prefs.getBoolean(
                                    Key.virt_enabled.toString(), VIRTUALIZER_ENABLED_DEFAULT));
                            final int vIStrength = prefs.getInt(Key.virt_strength.toString(),
                                    VIRTUALIZER_STRENGTH_DEFAULT);
                            setParameterInt(context, packageName,
                                    audioSession, Key.virt_strength, vIStrength);
                        }
           

       這段代碼說明或證明以下事實, 前面提到過如果是一個具體音效執行個體必須有audioSession這個參數, 參數來自于調用音效的具體應用程式,以音樂Music為例,跳轉音效設定的代碼如下,

Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
    i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mService.getAudioSessionId());
    startActivityForResult(i, EFFECTS_PANEL);         
           

      在set之前先執行getVirtualizerEffect,其中會通過getParameter先擷取一下值,然後再調用setParameterInt。       上面的代碼是以Virtualizer為例,Virtualizer是一Audioeffect的子類,前面提到過我們也隻能使用Audioeffect的派生類,是以一定還可以找到BassBoost、Equalizer類似結構的代碼。              現在看下setParameterInt函數,

// Virtualizer
      case virt_strength: {
          final Virtualizer virtualizerEffect = getVirtualizerEffect(audioSession);
          if (virtualizerEffect != null) {
              virtualizerEffect.setStrength((short) value); // 關注這裡
              value = virtualizerEffect.getRoundedStrength();
          }
          break;}
         
    public void setStrength(short strength)
    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
        checkStatus(setParameter(PARAM_STRENGTH, strength));  //終于看到setParameter了
    }  
           

     getParameter和setParameter代碼調用流程類似,看完setParameter函數兩個應該都可以明白了,是以按思維習慣,看set過程時先忽略getParameter的細節。     

public int setParameter(byte[] param, byte[] value)
            throws IllegalStateException {
        checkState("setParameter()");
        return native_setParameter(param.length, param, value.length, value); // native 要用到JNI了
    }
           

      根據android JNI的名稱轉換原則, AudioEffect在android.media包下,是以下面的代碼在audio_media_AudioEffect.cpp中,在那裡會找下面對應關系,要注意下面代碼段中的注釋。      

audio_media_AUdioEffect.cpp  
    {"native_setParameter",  "(I[BI[B)I",  (void *)android_media_AudioEffect_native_setParameter},
    去android_media_AudioEffect_native_setParameter裡看看有什麼,
   
    static jint android_media_AudioEffect_native_setParameter(JNIEnv *env,
        jobject thiz, int psize, jbyteArray pJavaParam, int vsize,
        jbyteArray pJavaValue) {
     //省略很多暫時無關代碼
       AudioEffect* lpAudioEffect = (AudioEffect *) env->GetIntField(thiz,
            fields.fidNativeAudioEffect);
    
    lStatus = lpAudioEffect->setParameter(p); //看到這裡要去Audioeffect裡去看看了, 注意這回是 Audioeffect.cpp
    if (lStatus == NO_ERROR) {
        lStatus = p->status;
    }
   
    status_t AudioEffect::setParameter(effect_param_t *param)
{
          //省略很多暫時無關代碼
    return mIEffect->command(EFFECT_CMD_SET_PARAM, sizeof (effect_param_t) + psize, param, &size, ¶m->status);
}
           

      看到這裡肯定很想知道mIEffect是什麼,就在Audioeffect.cpp這個檔案裡找,可以找到  mIEffect的由iEffect指派,而iEffect初始化代碼如下,

iEffect = audioFlinger->createEffect(getpid(), (effect_descriptor_t *)&mDescriptor,
            mIEffectClient, priority, io, mSessionId, &mStatus, &mId, &enabled);  
           

      到這裡我們就來到AudioFlinger,對于建立音效的後面流程,還要涉及到EffectHandle,EffectModule一些類,更具體過程還有待分析,上面描述應該可以說清楚app層的值是怎麼傳到audioFlinger的了,是以本文的分析暫時算結束了,有不對的地方歡迎留言讨論。