Android中声音处理流程

在android\frameworks\base\media\java\android\media\AudioManager.java中

 /**
     * @hide
     */
    public void handleKeyDown(KeyEvent event, int stream) {
        int keyCode = event.getKeyCode();
	    Log.d("zmq","AudioManager handleKeyDown keyCode = "+keyCode);
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                /*
                 * Adjust the volume in on key down since it is more
                 * responsive to the user.   在按键按下去的时候处理,给用户响应更快。
                 */
                int flags = FLAG_SHOW_UI | FLAG_VIBRATE;         //1|16得到的值为17
		Log.d("zmq","AudioManager handleKeyDown flags = "+flags);
		Log.d("zmq","AudioManager handleKeyDown mUseMasterVolume = "+mUseMasterVolume);
                if (mUseMasterVolume) {            //使用主音量,在config中定义,为true
                    adjustMasterVolume(
                            keyCode == KeyEvent.KEYCODE_VOLUME_UP
                                    ? ADJUST_RAISE
                                    : ADJUST_LOWER,
                            flags);
                } else {
                    adjustSuggestedStreamVolume(
                            keyCode == KeyEvent.KEYCODE_VOLUME_UP
                                    ? ADJUST_RAISE
                                    : ADJUST_LOWER,
                            stream,
                            flags);
                }
                break;
            case KeyEvent.KEYCODE_VOLUME_MUTE:
                if (event.getRepeatCount() == 0) {
                    if (mUseMasterVolume) {
                        setMasterMute(!isMasterMute());
                    } else {
                        // TODO: Actually handle MUTE.
                    }
                }
                break;
        }
    }
    /**
     * Show a toast containing the current volume.
     *
     * @see #adjustStreamVolume(int, int, int)
     * @see #adjustVolume(int, int)
     * @see #setStreamVolume(int, int, int)
     * @see #setRingerMode(int)
     */
    public static final int FLAG_SHOW_UI = 1 << 0;      //其值为1
     /**
     * Whether to vibrate if going into the vibrate ringer mode.
     */
    public static final int FLAG_VIBRATE = 1 << 4;     //其值为16
    private final boolean mUseMasterVolume;    //在AudioManager的构造函数中进行初始化
    /**
     * @hide
     */
    public AudioManager(Context context) {
        mContext = context;
        mUseMasterVolume = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_useMasterVolume);          //在config.xml中定义初始值
        mUseVolumeKeySounds = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_useVolumeKeySounds);
    }
变量config_useMasterVolume在android\device\mstar\mango\overlay\frameworks\base\core\res\res\values\config.xml中定义

<!-- Flag indicating that the media framework should allow changing
         master volume stream and nothing else . -->
    <bool name="config_useMasterVolume">true</bool>
/**
     * Adjusts the master volume for the device's audio amplifier.
     * @param steps The number of volume steps to adjust. A positive
     *            value will raise the volume.
     * @param flags One or more flags.
     * @hide
     */
    public void adjustMasterVolume(int steps, int flags) {
        Log.d("zmq","AudioManager adjustMasterVolume ");
    
        IAudioService service = getService();
        try {
            service.adjustMasterVolume(steps, flags, mContext.getOpPackageName());
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in adjustMasterVolume", e);
        }
    }
调用到android\frameworks\base\media\java\android\media\AudioService.java中,真正有实现功能的地方

/** @see AudioManager#adjustMasterVolume(int, int) */
    public void adjustMasterVolume(int steps, int flags, String callingPackage) {
        Log.d("zmq","AudioService adjustMasterVolume");
        if (mUseFixedVolume) {
            return;
        }
        ensureValidSteps(steps);          //确保steps的绝对值不超过4
	Log.d("zmq","AudioService adjustMasterVolume AudioSystem.getMasterVolume() = "
                +AudioSystem.getMasterVolume());
        int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);   //需要*100,从AudioSystem中得到的是0.0~1.0
	Log.d("zmq","AudioService adjustMasterVolume volume1 = "+volume);
        int delta = 0;                       //需要增加的音量坡度
        int numSteps = Math.abs(steps);
	Log.d("zmq","AudioService adjustMasterVolume numSteps = "+numSteps);
        int direction = steps > 0 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
	Log.d("zmq", "AudioService adjustMasterVolume direction = "+direction);
        for (int i = 0; i < numSteps; ++i) {
            delta = findVolumeDelta(direction, volume);
			Log.d("zmq","AudioService adjustMasterVolume delta = "+delta);
            volume += delta;
			Log.d("zmq", "AudioService adjustMasterVolume volume2 = "+volume);
        }
        //Log.d(TAG, "adjustMasterVolume volume: " + volume + " steps: " + steps);
        setMasterVolume(volume, flags, callingPackage);
    }
 private final boolean mUseFixedVolume;
 mUseFixedVolume = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_useFixedVolume);
变量在config_useFixedVolume在android\frameworks\base\core\res\res\values\config.xml中

 <!-- Flag indicating that the media framework should not allow changes or mute on any
         stream or master volumes. -->
    <bool name="config_useFixedVolume">false</bool>
    private void ensureValidSteps(int steps) {
        if (Math.abs(steps) > MAX_BATCH_VOLUME_ADJUST_STEPS) {
            throw new IllegalArgumentException("Bad volume adjust steps " + steps);
        }
    }
// Maximum volume adjust steps allowed in a single batch call.
    private static final int MAX_BATCH_VOLUME_ADJUST_STEPS = 4;
调用到android\frameworks\base\media\java\android\media\AudioSystem.java中,会调用本地层,先不做追踪。
public static native float getMasterVolume();
// Internally master volume is a float in the 0.0 - 1.0 range,
    // but to support integer based AudioManager API we translate it to 0 - 100
    private static final int MAX_MASTER_VOLUME = 100;
private int findVolumeDelta(int direction, int volume) {
		Log.d("zmq"," AudioService findVolumeDelta");
        int delta = 0;
        if (direction == AudioManager.ADJUST_RAISE) {
            if (volume == MAX_MASTER_VOLUME) {
                return 0;
            }
            // This is the default value if we make it to the end
            delta = mMasterVolumeRamp[1];    //为1
	    Log.d("zmq", "AudioService findVolumeDelta delta = "+delta);
            // If we're raising the volume move down the ramp array until we
            // find the volume we're above and use that groups delta.
            for (int i = mMasterVolumeRamp.length - 1; i > 1; i -= 2) {    // 没有执行
	    Log.d("zmq","AudioService findVolumeDelta for ");
                if (volume >= mMasterVolumeRamp[i - 1]) {
                    delta = mMasterVolumeRamp[i];
                    break;
                }
            }
        } else if (direction == AudioManager.ADJUST_LOWER){
            if (volume == 0) {
                return 0;
            }
            int length = mMasterVolumeRamp.length;
	    Log.d("zmq","AudioService findVolumeDelta length = "+length);
            // This is the default value if we make it to the end
            delta = -mMasterVolumeRamp[length - 1];
			Log.d("zmq","AudioService findVolumeDelta delta = "+delta);
            // If we're lowering the volume move up the ramp array until we
            // find the volume we're below and use the group below it's delta
            for (int i = 2; i < length; i += 2) {     //没有执行
		Log.d("zmq","AudioService findVolumeDelta for ");
                if (volume <= mMasterVolumeRamp[i]) {
                    delta = -mMasterVolumeRamp[i - 1];
                    break;
                }
            }
        }
        return delta;
    }
private final int[] mMasterVolumeRamp;
 mMasterVolumeRamp = context.getResources().getIntArray(
                com.android.internal.R.array.config_masterVolumeRamp);
变量config_masterVolumeRamp在android\frameworks\base\core\res\res\values\config.xml中

<!-- Array of integer pairs controlling the rate at which the master volume changes
         in response to volume up and down key events.
         The first integer of each pair is compared against the current master volume
         (in range 0 to 100).
         The last pair with first integer <= the current volume is chosen,
         and the second integer of the pair indicates the amount to increase the master volume
         when volume up is pressed. -->
    <integer-array name="config_masterVolumeRamp">
        <item>0</item>  <item>5</item>  <!-- default: always increase volume by 5% -->
    </integer-array>
一般可以在android\device\mstar\mango\overlay\frameworks\base\core\res\res\values\config.xml中覆盖
<integer-array name="config_masterVolumeRamp">
        <item>0</item>  <item>1</item>  <!-- default: always increase volume by 5% -->
    </integer-array>
public void setMasterVolume(int volume, int flags, String callingPackage) {
		Log.d("zmq","AudioService setMasterVolume");
        if (mUseFixedVolume) {
            return;
        }
        if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, Binder.getCallingUid(),
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            return;
        }
        if (volume < 0) {
            volume = 0;
        } else if (volume > MAX_MASTER_VOLUME) {
            volume = MAX_MASTER_VOLUME;
        }
        doSetMasterVolume((float)volume / MAX_MASTER_VOLUME, flags);
    }
doSetMasterVolume经过了原厂的修改

private void doSetMasterVolume(float volume, int flags) {
        boolean bSetA2dpVolume = false;
        if ((isPipOn() == false) && isBluetoothA2dpOn()) {
            bSetA2dpVolume = true;
        } else {
            bSetA2dpVolume = false;
        }
		
		 if (isMasterMute() ) {
            if(bSetA2dpVolume){
                AudioSystem.setA2dpMute(false);
            } else {
            //disappear mute
                AudioSystem.setMasterMute(false);
                // Post a persist master volume msg
                sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, false ? 1
                    : 0, 0, null, PERSIST_DELAY);
            }
            sendMasterMuteUpdate(false, flags);
        }
        int oldVolume = getMasterVolume();
		AudioSystem.setMasterVolume(volume);
        int newVolume = getMasterVolume();
        if (newVolume != oldVolume) {
            // Post a persist master volume msg
            if(bSetA2dpVolume){
                sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, SENDMSG_REPLACE,
                    Math.round(volume * (float)1000.0), 1, null, PERSIST_DELAY);
            } else {
                sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, SENDMSG_REPLACE,
                    Math.round(volume * (float)1000.0), 0, null, PERSIST_DELAY);
            }
        }
		// Send the volume update regardless whether there was a change.
        sendMasterVolumeUpdate(flags, oldVolume, newVolume);
     
    }

最终调用AudioSystem.java中的setMasterVolume。
下面为显示音量UI部分
// UI update and Broadcast Intent
    private void sendMasterVolumeUpdate(int flags, int oldVolume, int newVolume) {
        mVolumePanel.postMasterVolumeChanged(flags);
        Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION);
        intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume);
        intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume);
        sendBroadcastToAll(intent);
    }

在android\frameworks\base\core\java\android\view\VolumePanel.java中

public void postMasterVolumeChanged(int flags) {
        postVolumeChanged(STREAM_MASTER, flags);
    }
 public void postVolumeChanged(int streamType, int flags) {
        if (hasMessages(MSG_VOLUME_CHANGED)) return;
        synchronized (this) {
            if (mStreamControls == null) {
                createSliders();
            }
        }
        removeMessages(MSG_FREE_RESOURCES);
        obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
    }
 private void createSliders() {
        LayoutInflater inflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
        Resources res = mContext.getResources();
        for (int i = 0; i < STREAMS.length; i++) {     //STREAMS.length=8
            StreamResources streamRes = STREAMS[i];
            int streamType = streamRes.streamType;
            if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
                streamRes = StreamResources.RingerStream;
            }
            StreamControl sc = new StreamControl();
            sc.streamType = streamType;
            sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
            sc.group.setTag(sc);
            sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon);
            sc.icon.setTag(sc);
            sc.icon.setContentDescription(res.getString(streamRes.descRes));
            sc.iconRes = streamRes.iconRes;
            sc.iconMuteRes = streamRes.iconMuteRes;
            sc.icon.setImageResource(sc.iconRes);
            sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
            int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
                    streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
            sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
			sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
            sc.seekbarView.setOnSeekBarChangeListener(this);
            sc.seekbarView.setTag(sc);
           
            sc.seekBarValue = (TextView) sc.group.findViewById(R.id.seekbarvalue);
			sc.seekBarValue.setText(String.valueOf(getStreamVolume(sc.streamType)));
            
            mStreamControls.put(streamType, sc);
        }
    }
 public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_VOLUME_CHANGED: {
                onVolumeChanged(msg.arg1, msg.arg2);
                break;
            }
            case MSG_MUTE_CHANGED: {
                onMuteChanged(msg.arg1, msg.arg2);
                break;
            }
            case MSG_FREE_RESOURCES: {
                onFreeResources();
                break;
            }
            case MSG_STOP_SOUNDS: {
                onStopSounds();
                break;
            }
            case MSG_PLAY_SOUND: {
                onPlaySound(msg.arg1, msg.arg2);
                break;
            }
            case MSG_VIBRATE: {
                onVibrate();
                break;
            }
            case MSG_TIMEOUT: {
                if (mDialog.isShowing()) {
                    // MStar Android Patch Begin
                    // Don't dismiss mute panel.
                    if ((mActiveStreamType != -1) && (!isMuted(mActiveStreamType))) {
                        mDialog.dismiss();
                        mActiveStreamType = -1;
                    }
                    // MStar Android Patch End
                }
                synchronized (sConfirmSafeVolumeLock) {
                    if (sConfirmSafeVolumeDialog != null) {
                        sConfirmSafeVolumeDialog.dismiss();
                    }
                }
                break;
            }
            case MSG_RINGER_MODE_CHANGED: {
                if (mDialog.isShowing()) {
                    updateStates();
                }
                break;
            }
            case MSG_REMOTE_VOLUME_CHANGED: {
                onRemoteVolumeChanged(msg.arg1, msg.arg2);
                break;
            }
            case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
                onRemoteVolumeUpdateIfShown();
                break;
            case MSG_SLIDER_VISIBILITY_CHANGED:
                onSliderVisibilityChanged(msg.arg1, msg.arg2);
                break;
            case MSG_DISPLAY_SAFE_VOLUME_WARNING:
                onDisplaySafeVolumeWarning(msg.arg1);
                break;
        }
    }
/**
     * Override this if you have other work to do when the volume changes (for
     * example, vibrating, playing a sound, etc.). Make sure to call through to
     * the superclass implementation.
     */
    protected void onVolumeChanged(int streamType, int flags) {
        if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
        if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
            synchronized (this) {
                if (mActiveStreamType != streamType) {
                    reorderSliders(streamType);
                }
                onShowVolumeChanged(streamType, flags);
            }
        }
        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
            removeMessages(MSG_PLAY_SOUND);
            sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
        }
        if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
            removeMessages(MSG_PLAY_SOUND);
            removeMessages(MSG_VIBRATE);
            onStopSounds();
        }
        removeMessages(MSG_FREE_RESOURCES);
        sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
        resetTimeout();
    }
 private void reorderSliders(int activeStreamType) {
        mSliderGroup.removeAllViews();
        StreamControl active = mStreamControls.get(activeStreamType);
        if (active == null) {
            Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
            mActiveStreamType = -1;
        } else {
            mSliderGroup.addView(active.group);
            mActiveStreamType = activeStreamType;
            active.group.setVisibility(View.VISIBLE);
            updateSlider(active);
        }
        addOtherVolumes();
    }
/** Update the mute and progress state of a slider */
    private void updateSlider(StreamControl sc) {
        sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
        final boolean muted = isMuted(sc.streamType);
        // Force reloading the image resource
        sc.icon.setImageDrawable(null);
        sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
        if (((sc.streamType == AudioManager.STREAM_RING) ||
                (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
                mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
            sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
        }
        if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
            // never disable touch interactions for remote playback, the muting is not tied to
            // the state of the phone.
            sc.seekbarView.setEnabled(true);
        } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
                        (sConfirmSafeVolumeDialog != null)) {
            sc.seekbarView.setEnabled(false);
        } else {
            sc.seekbarView.setEnabled(true);
        }
    }
private void addOtherVolumes() {
        if (!mShowCombinedVolumes) return;
        for (int i = 0; i < STREAMS.length; i++) {
            // Skip the phone specific ones and the active one
            final int streamType = STREAMS[i].streamType;
            if (!STREAMS[i].show || streamType == mActiveStreamType) {
                continue;
            }
            StreamControl sc = mStreamControls.get(streamType);
            mSliderGroup.addView(sc.group);
            updateSlider(sc);
        }
    }
 protected void onShowVolumeChanged(int streamType, int flags) {
        int index = getStreamVolume(streamType);
        mRingIsSilent = false;
        if (LOGD) {
            Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
                    + ", flags: " + flags + "), index: " + index);
        }
        // get max volume for progress bar
        int max = getStreamMaxVolume(streamType);
        switch (streamType) {
            case AudioManager.STREAM_RING: {
//                setRingerIcon();
                Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
                        mContext, RingtoneManager.TYPE_RINGTONE);
                if (ringuri == null) {
                    mRingIsSilent = true;
                }
                break;
            }
            case AudioManager.STREAM_MUSIC: {
                // Special case for when Bluetooth is active for music
                if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) &
                        (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
                        AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
                        AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
                    setMusicIcon(R.drawable.ic_audio_bt, R.drawable.ic_audio_bt_mute);
                } else {
                    setMusicIcon(R.drawable.ic_audio_vol, R.drawable.ic_audio_vol_mute);
                }
                break;
            }
            case AudioManager.STREAM_VOICE_CALL: {
                /*
                 * For in-call voice call volume, there is no inaudible volume.
                 * Rescale the UI control so the progress bar doesn't go all
                 * the way to zero and don't show the mute icon.
                 */
                index++;
                max++;
                break;
            }
            case AudioManager.STREAM_ALARM: {
                break;
            }
            case AudioManager.STREAM_NOTIFICATION: {
                Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri(
                        mContext, RingtoneManager.TYPE_NOTIFICATION);
                if (ringuri == null) {
                    mRingIsSilent = true;
                }
                break;
            }
            case AudioManager.STREAM_BLUETOOTH_SCO: {
                /*
                 * For in-call voice call volume, there is no inaudible volume.
                 * Rescale the UI control so the progress bar doesn't go all
                 * the way to zero and don't show the mute icon.
                 */
                index++;
                max++;
                break;
            }
            case AudioService.STREAM_REMOTE_MUSIC: {
                if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
                break;
            }
        }
        StreamControl sc = mStreamControls.get(streamType);
        if (sc != null) {
            if (sc.seekbarView.getMax() != max) {
                sc.seekbarView.setMax(max);
            }
            sc.seekbarView.setProgress(index);
            if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
                    (streamType != mAudioManager.getMasterStreamType() &&
                     streamType != AudioService.STREAM_REMOTE_MUSIC &&
                     isMuted(streamType)) ||
                     sConfirmSafeVolumeDialog != null) {
                sc.seekbarView.setEnabled(false);
            } else {
                sc.seekbarView.setEnabled(true);
            }
        }
        if (!mDialog.isShowing()) {
            int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
            // when the stream is for remote playback, use -1 to reset the stream type evaluation
            mAudioManager.forceVolumeControlStream(stream);
            mDialog.setContentView(mView);
            // Showing dialog - use collapsed state
            if (mShowCombinedVolumes) {
                collapse();
            }
            mDialog.show();
        }
        // Do a little vibrate if applicable (only when going into vibrate mode)
        if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
                ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
                mAudioService.isStreamAffectedByRingerMode(streamType) &&
                mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
            sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
        }
    }
 protected void onPlaySound(int streamType, int flags) {
        if (hasMessages(MSG_STOP_SOUNDS)) {
            removeMessages(MSG_STOP_SOUNDS);
            // Force stop right now
            onStopSounds();
        }
        synchronized (this) {
            ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
            if (toneGen != null) {
                toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
                sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION);
            }
        }
    }
 protected void onStopSounds() {
        synchronized (this) {
            int numStreamTypes = AudioSystem.getNumStreamTypes();
            for (int i = numStreamTypes - 1; i >= 0; i--) {
                ToneGenerator toneGen = mToneGenerators[i];
                if (toneGen != null) {
                    toneGen.stopTone();
                }
            }
        }
    }
 protected void onFreeResources() {
        synchronized (this) {
            for (int i = mToneGenerators.length - 1; i >= 0; i--) {
                if (mToneGenerators[i] != null) {
                    mToneGenerators[i].release();
                }
                mToneGenerators[i] = null;
            }
        }
    }
framework层调用,不涉及JNI层。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值