Android 音效流程分析

原创 2015年11月18日 17:01:29

音效流程分析

音效事件起源

Android 平台所有view类型控件的touch,遥控器按键等事件在系统音效开启时,都可以触发按键音。

音效事件流程分析

从touch事件为例,整个流程如下图所示:



 如图中所示,在touch事件触发后,onTouchEvent会被调用,接着performClick就会处理Click事件,
    /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }
在performClick里面主要做了两件事,调用监听器的onClick回调和playSoundEffect,监听器的回调是在setOnClickListener中注册下来,下面主要分析playSoundEffect
    /**
     * Play a sound effect for this view.
     *
     * <p>The framework will play sound effects for some built in actions, such as
     * clicking, but you may wish to play these effects in your widget,
     * for instance, for internal navigation.
     *
     * <p>The sound effect will only be played if sound effects are enabled by the user, and
     * {@link #isSoundEffectsEnabled()} is true.
     *
     * @param soundConstant One of the constants defined in {@link SoundEffectConstants}
     */
    public void playSoundEffect(int soundConstant) {
        if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
            return;
        }
        mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
    }
利用成员变量mAttachInfo的mRootCallbacks回调调用另外一个音效处理函数playSoundEffect,这里找到mAttachInfo和mRootCallbacks的源头就很重要,其实每个View控件
在创建后,都会绑定到对应窗口Window,下面看一下函数dispatchAttachedToWindow
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //System.out.println("Attached! " + this);
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }
        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
        }
        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);
    }
函数开始处有mAttachInfo = info,就给mAttachInfo赋值了,继续向上索引,ViewRootImpl.performTraversals中调用host.dispatchAttachedToWindow(attachInfo, 0);
将ViewRootImpl.mAttachInfo设置下去,而此成员在ViewRootImpl构造函数中赋值。
    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();

        mDisplayAdjustments = display.getDisplayAdjustments();

        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this);
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        mAccessibilityManager = AccessibilityManager.getInstance(context);
        mAccessibilityInteractionConnectionManager =
            new AccessibilityInteractionConnectionManager();
        mAccessibilityManager.addAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
        mViewConfiguration = ViewConfiguration.get(context);
        mDensity = context.getResources().getDisplayMetrics().densityDpi;
        mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
        mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
        mChoreographer = Choreographer.getInstance();

        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mAttachInfo.mScreenOn = powerManager.isScreenOn();
        loadSystemProperties();
    }
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); 
再看AttachInfo构造函数,如下:
        /**
         * Creates a new set of attachment information with the specified
         * events handler and thread.
         *
         * @param handler the events handler the view must use
         */
        AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;
        }
mRootCallbacks 其实就是ViewRootImp的实例,这样View.playSoundEffect就进入了ViewRootImp.playSoundEffect.

音频系统接管音效控制

从ViewRootImp的函数playSoundEffect
    public void playSoundEffect(int effectId) {
        checkThread();

        if (mMediaDisabled) {
            return;
        }

        try {
            final AudioManager audioManager = getAudioManager();

            switch (effectId) {
                case SoundEffectConstants.CLICK:
                    audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
                    return;
                case SoundEffectConstants.NAVIGATION_DOWN:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
                    return;
                case SoundEffectConstants.NAVIGATION_LEFT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
                    return;
                case SoundEffectConstants.NAVIGATION_RIGHT:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
                    return;
                case SoundEffectConstants.NAVIGATION_UP:
                    audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
                    return;
                default:
                    throw new IllegalArgumentException("unknown effect id " + effectId +
                            " not defined in " + SoundEffectConstants.class.getCanonicalName());
            }
        } catch (IllegalStateException e) {
            // Exception thrown by getAudioManager() when mView is null
            Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);
            e.printStackTrace();
        }
    }
AudioManager接管音效控制权,接着AudioService的onPlaySoundEffect会被调用
        private void onPlaySoundEffect(int effectType, int volume) {
            synchronized (mSoundEffectsLock) {

                onLoadSoundEffects();

                if (mSoundPool == null) {
                    return;
                }
                float volFloat;
                // use default if volume is not specified by caller
                if (volume < 0) {
                    volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
                } else {
                    volFloat = (float) volume / 1000.0f;
                }

                if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
                    mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
                                        volFloat, volFloat, 0, 0, 1.0f);
                } else {
                    MediaPlayer mediaPlayer = new MediaPlayer();
                    try {
                        String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH +
                                    SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
                        mediaPlayer.setDataSource(filePath);
                        mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                        mediaPlayer.prepare();
                        mediaPlayer.setVolume(volFloat);
                        mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
                            public void onCompletion(MediaPlayer mp) {
                                cleanupPlayer(mp);
                            }
                        });
                        mediaPlayer.setOnErrorListener(new OnErrorListener() {
                            public boolean onError(MediaPlayer mp, int what, int extra) {
                                cleanupPlayer(mp);
                                return true;
                            }
                        });
                        mediaPlayer.start();
                    } catch (IOException ex) {
                        Log.w(TAG, "MediaPlayer IOException: "+ex);
                    } catch (IllegalArgumentException ex) {
                        Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
                    } catch (IllegalStateException ex) {
                        Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
                    }
                }
            }
        }

sSoundEffectVolumeDb 是音效使用的音量大小值,mSoundPool.play完成音效的播放功能。


音频系统音效全局控制
    /**
     * Plays a sound effect (Key clicks, lid open/close...)
     * @param effectType The type of sound effect. One of
     *            {@link #FX_KEY_CLICK},
     *            {@link #FX_FOCUS_NAVIGATION_UP},
     *            {@link #FX_FOCUS_NAVIGATION_DOWN},
     *            {@link #FX_FOCUS_NAVIGATION_LEFT},
     *            {@link #FX_FOCUS_NAVIGATION_RIGHT},
     *            {@link #FX_KEYPRESS_STANDARD},
     *            {@link #FX_KEYPRESS_SPACEBAR},
     *            {@link #FX_KEYPRESS_DELETE},
     *            {@link #FX_KEYPRESS_RETURN},
     *            {@link #FX_KEYPRESS_INVALID},
     * NOTE: This version uses the UI settings to determine
     * whether sounds are heard or not.
     */
    public void  playSoundEffect(int effectType) {
        if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
            return;
        }

        if (!querySoundEffectsEnabled()) {
            return;
        }
        //Ctv Patch Begin
        effectTypeForHandler = effectType;
        userHandler.removeCallbacks(queueRun);
        userHandler.postDelayed(queueRun, 20);

        /*IAudioService service = getService();
        try {
            service.playSoundEffect(effectType);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in playSoundEffect"+e);
        }*/
    }
querySoundEffectsEnabled来查询系统是否设置开机全局按键音,而系统开启关闭按键也是通过Settings.System.SOUND_EFFECTS_ENABLED的设置来完成控制。

例如:Settings.System.putInt(mContext.getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0/1) 关闭/开启 









android 音效处理(1)

在Android 2.3中,对音频播放提供了更强大的音效支持,其实现位于android.media.audiofx包中。目前Android支持的音效包括:重低音(BassBoost)、环 绕音(Vir...

android 音效处理 (2)

android 音效混响处理,系统提供了2个类分别是 EnvironmentalReverb 和 PresetReverb。 PresetReverb 使用比较简单,内置了一些固定的混响效果,但实际...

android音频处理

原文出处:http://mindtherobot.com/blog/555/android-audio-problems-hidden-limitations-and-opensl-es/ 我最...

android音效的加载方式

1、SoundPool 1. 游戏音效SoundPool 游 戏中会根据不同的动作 , 产生各种音效 , 这些音效的特点是短暂(叫声,爆炸声可能持续不到一秒) , 重复(一个文件不断重复播放)...

Android音频混响特效的设置

混响即通过声音在不同路径传播下造成的反射叠加产生的声音特效,在Android中,Google给出了两个实现:EnvironmentalReverb和PresetReverb,其中推荐在游戏场景中应用E...

Android中声音处理流程

在android\frameworks\base\media\java\android\media\AudioManager.java中 /** * @hide */ p...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Android开发中,那些让您觉得相见恨晚的方法、类或接口

Android开发中,那些让你觉得相见恨晚的方法、类或接口 本篇文章内容提取自知乎Android开发中,有哪些让你觉得相见恨晚的方法、类或接口?,其实有一部是JAVA的,但是在android开发中也...
  • smxueer
  • smxueer
  • 2016年09月01日 18:37
  • 2259

android 按键声音功能实现介绍

1.Android的audio流的类型有以下12种: /* The audio stream for phone calls */       public static fina...

android SoundPool 音效播放

音效播放:资源文件: res/raw/filename声音池类:SoundPool sp = new SoundPool(同时最大播放个数,AudioManager.STREAM_MUSIC,0);将...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 音效流程分析
举报原因:
原因补充:

(最多只允许输入30个字)