Android音频相关(九)SystemUI接到通知播放声音的流程RingtonePlayer

最近项目上遇到一个问题,来电无提示音,分析下来因为SystemUI创建的Track太多,导致Track的内存不足从而无法播放。但是就在想铃声播放为什么是SystemUI创建的AudioTrack太多呢?下面我们梳理一下这个流程,方便我们解决相应的问题。

问题的原因:When a call comes, the phone module asynchronously sends a message to the system UI and requests the system UI to play the ringtone. At this time, the system UI applies for Audio Track. Audio finds that the system UI has applied for 14 tracks and does not support the application for new ones, so the ringtone playing fails. 

同时这个也是的Ringtone.java的mRemotePlayer播放器远程播放器。

注意:RingtonePlayer里面是有两个播放方法的1. Client里面的play方法(播放铃声的远程播放器)2.NotificationPlayer的playAsync(播放通知音)

一、RingtonePlayer的初始化

二、通知音播放

三、铃声的远程播放

一、RingtonePlayer的初始化

首先我们需要知道在SystemUI启动的时候会Start()一个媒体播放的类RingtonePlayer。

RingtonePlayer.java (frameworks\base\packages\systemui\src\com\android\systemui\media) 

public class RingtonePlayer extends SystemUI {
    private static final String TAG = "RingtonePlayer";
    private static final boolean LOGD = false;

    // TODO: support Uri switching under same IBinder

    private IAudioService mAudioService;

    private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
    private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();

    @Override
    public void start() {
        mAsyncPlayer.setUsesWakeLock(mContext);
        //获取Audio service
        mAudioService = IAudioService.Stub.asInterface(
                ServiceManager.getService(Context.AUDIO_SERVICE));
        try {
            //将一个媒体播放回调实例mCallback传递给AudioService
            mAudioService.setRingtonePlayer(mCallback);
        } catch (RemoteException e) {
            Log.e(TAG, "Problem registering RingtonePlayer: " + e);
        }
    }

    /**
     * Represents an active remote {@link Ringtone} client.
     */
    private class Client implements IBinder.DeathRecipient {
        private final IBinder mToken;
        private final Ringtone mRingtone;

        public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
            mToken = token;

            mRingtone = new Ringtone(getContextForUser(user), false);
            mRingtone.setAudioAttributes(aa);
            mRingtone.setUri(uri);
        }

        @Override
        public void binderDied() {
            if (LOGD) Log.d(TAG, "binderDied() token=" + mToken);
            synchronized (mClients) {
                mClients.remove(mToken);
            }
            mRingtone.stop();
        }
    }

    private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
        @Override
        public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
                throws RemoteException {
            if (LOGD) {
                Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                        + Binder.getCallingUid() + ")");
            }
            Client client;
            synchronized (mClients) {
                client = mClients.get(token);
                if (client == null) {
                    final UserHandle user = Binder.getCallingUserHandle();
                    client = new Client(token, uri, user, aa);
                    token.linkToDeath(client, 0);
                    mClients.put(token, client);
                }
            }
            client.mRingtone.setLooping(looping);
            client.mRingtone.setVolume(volume);
            client.mRingtone.play();
        }

        @Override
        public void stop(IBinder token) {
            if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
            Client client;
            synchronized (mClients) {
                client = mClients.remove(token);
            }
            if (client != null) {
                client.mToken.unlinkToDeath(client, 0);
                client.mRingtone.stop();
            }
        }

        @Override
        public boolean isPlaying(IBinder token) {
            if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
            Client client;
            synchronized (mClients) {
                client = mClients.get(token);
            }
            if (client != null) {
                return client.mRingtone.isPlaying();
            } else {
                return false;
            }
        }

        @Override
        public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
            Client client;
            synchronized (mClients) {
                client = mClients.get(token);
            }
            if (client != null) {
                client.mRingtone.setVolume(volume);
                client.mRingtone.setLooping(looping);
            }
            // else no client for token when setting playback properties but will be set at play()
        }

        @Override
        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
            if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Async playback only available from system UID.");
            }
            if (UserHandle.ALL.equals(user)) {
                user = UserHandle.SYSTEM;
            }
            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
        }

        @Override
        public void stopAsync() {
            if (LOGD) Log.d(TAG, "stopAsync()");
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Async playback only available from system UID.");
            }
            mAsyncPlayer.stop();
        }

        @Override
        public String getTitle(Uri uri) {
            final UserHandle user = Binder.getCallingUserHandle();
            return Ringtone.getTitle(getContextForUser(user), uri,
                    false /*followSettingsUri*/, false /*allowRemote*/);
        }

        @Override
        public ParcelFileDescriptor openRingtone(Uri uri) {
            final UserHandle user = Binder.getCallingUserHandle();
            final ContentResolver resolver = getContextForUser(user).getContentResolver();

            // Only open the requested Uri if it's a well-known ringtone or
            // other sound from the platform media store, otherwise this opens
            // up arbitrary access to any file on external storage.
            if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
                try (Cursor c = resolver.query(uri, new String[] {
                        MediaStore.Audio.AudioColumns.IS_RINGTONE,
                        MediaStore.Audio.AudioColumns.IS_ALARM,
                        MediaStore.Audio.AudioColumns.IS_NOTIFICATION
                }, null, null, null)) {
                    if (c.moveToFirst()) {
                        if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) {
                            try {
                                return resolver.openFileDescriptor(uri, "r");
                            } catch (IOException e) {
                                throw new SecurityException(e);
                            }
                        }
                    }
                }
            }
            throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
        }
    };

    private Context getContextForUser(UserHandle user) {
        try {
            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
        } catch (NameNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Clients:");
        synchronized (mClients) {
            for (Client client : mClients.values()) {
                pw.print("  mToken=");
                pw.print(client.mToken);
                pw.print(" mUri=");
                pw.println(client.mRingtone.getUri());
            }
        }
    }
}

2.在start()方法中 mAudioService.setRingtonePlayer(mCallback);调用下面的方法

 AudioService.java (frameworks\base\services\core\java\com\android\server\audio)  

    @Override
    public void setRingtonePlayer(IRingtonePlayer player) {
        mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
        mRingtonePlayer = player;
    }

    @Override
    public IRingtonePlayer getRingtonePlayer() {
        return mRingtonePlayer;
    }

这也就是为什么在new Ringtone对象的时候,调用AudioManager的getRingtonePlayer方法能拿到RingtonePlayer的原因了。

//Ringtone.java (frameworks\base\media\java\android\media)   
/** {@hide} */
    public Ringtone(Context context, boolean allowRemote) {
        mContext = context;
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        mAllowRemote = allowRemote;
        mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
        mRemoteToken = allowRemote ? new Binder() : null;
    }

二、通知音播放

1.通知的播放会用到下面的类 首先通过AudioService里面的getRingtonePlayer()获取到相应的RingtonePlayer的对象,然后调用到RingtonePlayer里面的播放方法。

NotificationManagerService.java (frameworks\base\services\core\java\com\android\server\notification)  


void buzzBeepBlinkLocked(NotificationRecord record) {
....
 
          if (hasValidSound) {
           mSoundNotificationKey = key;
             if (mInCall) {
                           
                playInCallNotification();
                beep = true;
                } else {
                //没有来电的情况下
                beep = playSound(record, soundUri);
                        }
                    }
....
}
 
private boolean playSound(final NotificationRecord record, Uri soundUri) {
        boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
        // do not play notifications if there is a user of exclusive audio focus
        // or the device is in vibrate mode
        boolean muted = Settings.System.getInt(getContext().getContentResolver(),
                SettingsSmt.System.VOLUME_PANEL_MUTE_ENABLE, 0) == 1 ? true : false;
        int audioStreamType = AudioAttributes.toLegacyStreamType(record.getAudioAttributes());
        if (DBG) Log.v(TAG, "system muted: " + muted+" audioStreamType:"+audioStreamType);
        if ((((!muted || audioStreamType == AudioManager.STREAM_ALARM)
                && (mAudioManager.getStreamVolume(audioStreamType) != 0))
                || audioStreamType == AudioManager.STREAM_VOICE_CALL)
                && !mAudioManager.isAudioFocusExclusive()
                && mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) {
            final long identity = Binder.clearCallingIdentity();
            try {
                //最终调用到上面AudioService里面的getRingtonePlayer()
                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
                if (player != null) {
                    if (DBG) Slog.v(TAG, "Playing sound " + soundUri
                            + " with attributes " + record.getAudioAttributes());
                    //调用到RingtonePlayer里面的播放方法。
                    player.playAsync(soundUri, record.sbn.getUser(), looping,
                            record.getAudioAttributes());
                    return true;
                }
            } catch (RemoteException e) {
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
        return false;
    }

2.最后会调用RingtonePlayer中的播放方法

@Override
        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
            if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Async playback only available from system UID.");
            }
            if (UserHandle.ALL.equals(user)) {
                user = UserHandle.OWNER;
            } else if (user.getIdentifier() == UserHandle.USER_DOPPELGANGER) {
                user = UserHandle.OWNER;
            }
            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
}

这里面调用的是NotificationPlayer.java里面的play方法来播放的通知音

NotificationPlayer的play()会通过enqueueLocked()创建CmdThread线程。在CmdThread线程中startSound(),在startSound()中创建CreationAndCompletionThread线程


我们可以发现通知音的播放最后也是mediaplayer来播放的。同时注意在上面的方法中会申请音频焦点。

当然上面流程将的是非incall下面播放通知铃声的。

最后在啰嗦一句,在incall过程中,如果有通知来的以后,也会听到短暂的通知音的。这个有时间再补上。


三、铃声的远程播放

当然可以回顾一下之前的知识

Android音频相关(八)来电铃声播放流程

1.在Ringtone中的mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping)会调用到RingtonePlayer里面的mCallback里面的play方法。

Ringtone.java (frameworks\base\media\java\android\media)

/**
     * Plays the ringtone.
     */
    public void play() {
        if (mLocalPlayer != null) {
            // do not play ringtones if stream volume is 0
            // (typically because ringer mode is silent).
            if (mAudioManager.getStreamVolume(
                    AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {
                startLocalPlayer();
            }
        } else if (mAllowRemote && (mRemotePlayer != null)) {
            /// M: Avoid NullPointerException cause by mUri is null.
            final Uri canonicalUri = (mUri == null ? null : mUri.getCanonicalUri());
            final boolean looping;
            final float volume;
            synchronized (mPlaybackSettingsLock) {
                looping = mIsLooping;
                volume = mVolume;
            }
            try {
                //调用远程播放
                mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);
            } catch (RemoteException e) {
                if (!playFallbackRingtone()) {
                    Log.w(TAG, "Problem playing ringtone: " + e);
                }
            }
        } else {
            if (!playFallbackRingtone()) {
                Log.w(TAG, "Neither local nor remote playback available");
            }
        }
    }

2.play的方法是实现回调接口中实现的

private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
        @Override
        public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
                throws RemoteException {
            if (LOGD) {
                Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                        + Binder.getCallingUid() + ")");
            }
            Client client;
            synchronized (mClients) {
                client = mClients.get(token);
                if (client == null) {
                    final UserHandle user = Binder.getCallingUserHandle();
                    client = new Client(token, uri, user, aa);
                    token.linkToDeath(client, 0);
                    mClients.put(token, client);
                }
            }
            client.mRingtone.setLooping(looping);
            client.mRingtone.setVolume(volume);
            client.mRingtone.play();
        }

3.Cilent的构造方法

        public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
            mToken = token;

            mRingtone = new Ringtone(getContextForUser(user), false);
            mRingtone.setAudioAttributes(aa);
            mRingtone.setUri(uri);
        }

这里可以看到client内部构造Ringtone时,关闭了远程调用,通过传入自己的uri成功调用了本地播放器,所以这里的client.mRingtone.play()最终通过startLocalPlayer启动播放器

在说一下这个token是干嘛的,每次播放的时候创建的token会到通过mClients的HashMap保存,以便在binderDied和回调stop的时候释放对应的资源,这里的token是在构造ringtone的时候创建的。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值