Android N的铃声问题(2)

本文详细解析了Android N系统的铃声读取流程,从来电时启动铃声的处理到铃声的具体播放过程,包括如何获取铃声资源以及设置铃声路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       在上一篇文章中介绍了Android N的铃声保存问题,这一篇文章介绍的为Android N的铃声读取流程,以手机响铃过程作介绍,当手机判断来电时会调用packages\services\Telecomm\src\com\android\server\telecom\Ringer.java的startRinging方法:

    public void startRinging(Call foregroundCall) {
        if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
            return;
        }

        if (foregroundCall == null) {
            /// M: ALPS02801645 @{
            // Consider case: 1A+1W, airplane mode on => receive disconnect of active call first.
            // => audio change to ringing. When we come here, W has been removed in another thread.
            // Original code:
            // Log.wtf(this, "startRinging called with null foreground call.");
            Log.d(this, "startRinging called with null foreground call.");
            /// @}
            return;
        }

        if (mInCallController.doesConnectedDialerSupportRinging()) {
            Log.event(foregroundCall, Log.Events.SKIP_RINGING);
            return;
        }

        stopCallWaiting();

        if (!shouldRingForContact(foregroundCall.getContactUri())) {
            return;
        }

        AudioManager audioManager =
                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
            mRingingCall = foregroundCall;
            Log.event(foregroundCall, Log.Events.START_RINGER);
            // Because we wait until a contact info query to complete before processing a
            // call (for the purposes of direct-to-voicemail), the information about custom
            // ringtones should be available by the time this code executes. We can safely
            // request the custom ringtone from the call and expect it to be current.
            mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
        } else {
            Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
        }

        if (shouldVibrate(mContext) && !mIsVibrating) {
            mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
                    VIBRATION_ATTRIBUTES);
            mIsVibrating = true;
        }
    }
而真正响铃的代码为mRingtonePlayer.play(mRingtoneFactory, foregroundCall);接着进入

packages\services\Telecomm\src\com\android\server\telecom\AsyncRingtonePlayer.java的play方法

public void play(RingtoneFactory factory, Call incomingCall) {
        Log.d(this, "Posting play.");
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = factory;
        args.arg2 = incomingCall;
        postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
    }
由值EVENT_PLAY传递过去最终进入的方法为:

    private void handlePlay(SomeArgs args) {
        RingtoneFactory factory = (RingtoneFactory) args.arg1;
        Call incomingCall = (Call) args.arg2;
        args.recycle();
        // don't bother with any of this if there is an EVENT_STOP waiting.
        if (mHandler.hasMessages(EVENT_STOP)) {
            return;
        }

        // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play
        // anything.
        if(Uri.EMPTY.equals(incomingCall.getRingtone())) {
            mRingtone = null;
            return;
        }

        ThreadUtil.checkNotOnMainThread();
        Log.i(this, "Play ringtone.");

        if (mRingtone == null) {
            mRingtone = factory.getRingtone(incomingCall);
            if (mRingtone == null) {
                Uri ringtoneUri = incomingCall.getRingtone();
                String ringtoneUriString = (ringtoneUri == null) ? "null" :
                        ringtoneUri.toSafeString();
                Log.event(null, Log.Events.ERROR_LOG, "Failed to get ringtone from factory. " +
                        "Skipping ringing. Uri was: " + ringtoneUriString);
                return;
            }
        }

        handleRepeat();
    }

获取铃声为mRingtone = factory.getRingtone(incomingCall);此行代码进入

packages\services\Telecomm\src\com\android\server\telecom\RingtoneFactory.java的

public Ringtone getRingtone(Call incomingCall) {
        // Use the default ringtone of the work profile if the contact is a work profile contact.
        Context userContext = isWorkContact(incomingCall) ?
                getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
                getContextForUserHandle(mCallsManager.getCurrentUserHandle());
        Uri ringtoneUri = incomingCall.getRingtone();
        Ringtone ringtone = null;

        if(ringtoneUri != null && userContext != null) {
            // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
            ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri);
        }
        if(ringtone == null) {
            // Contact didn't specify ringtone or custom Ringtone creation failed. Get default
            // ringtone for user or profile.
            ringtone = RingtoneManager.getRingtone(
                    hasDefaultRingtoneForUser(userContext) ? userContext : mContext,
                    Settings.System.DEFAULT_RINGTONE_URI);
        }
        if (ringtone != null) {
            ringtone.setStreamType(AudioManager.STREAM_RING);
        }
        return ringtone;
    }
一般Uri ringtoneUri = incomingCall.getRingtone();中获取的Uri ringtoneUri 为null,所以进入

ringtone = RingtoneManager.getRingtone(
                    hasDefaultRingtoneForUser(userContext) ? userContext : mContext,
                    Settings.System.DEFAULT_RINGTONE_URI);
而在frameworks\base\media\java\android\media\RingtoneManager.java中

    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
        Log.d(TAG, "getRingtone() ringtoneUri = " + ringtoneUri
                   + ", streamType = " + streamType);
        try {
            final Ringtone r = new Ringtone(context, true);
            if (streamType >= 0) {
                r.setStreamType(streamType);
            }
            r.setUri(ringtoneUri);
            return r;
        } catch (Exception ex) {
            Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
        }

        return null;
    }
真正设置铃声的为r.setUri(ringtoneUri);进入到frameworks\base\media\java\android\media\Ringtone.java的setUri方法:

    public void setUri(Uri uri) {
        destroyLocalPlayer();

        mUri = uri;
        if (mUri == null) {
            return;
        }

        // TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing

        // try opening uri locally before delegating to remote player
        mLocalPlayer = new MediaPlayer();
        try {
            mLocalPlayer.setDataSource(mContext, mUri);
            mLocalPlayer.setAudioAttributes(mAudioAttributes);
            synchronized (mPlaybackSettingsLock) {
                applyPlaybackProperties_sync();
            }
            mLocalPlayer.prepare();

        } catch (SecurityException | IOException e) {
            destroyLocalPlayer();
            if (!mAllowRemote) {
                Log.w(TAG, "Remote playback not allowed: " + e);
            }
        }

        if (LOGD) {
            if (mLocalPlayer != null) {
                Log.d(TAG, "Successfully created local player");
            } else {
                Log.d(TAG, "Problem opening; delegating to remote player");
            }
        }
    }
mLocalPlayer.setDataSource(mContext, mUri);此为设置铃声的响铃路径,进入frameworks\base\media\java\android\media\MediaPlayer.java的setDataSource方法:

 

    public void setDataSource(@NonNull Context context, @NonNull Uri uri,
            @Nullable Map<String, String> headers) throws IOException, IllegalArgumentException,
                    SecurityException, IllegalStateException {
        final ContentResolver resolver = context.getContentResolver();
        final String scheme = uri.getScheme();
        if (scheme == null) {
            setDataSource(uri.toString());
            return;
        } else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
            setDataSource(uri.getPath());
            return;
        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
                && Settings.AUTHORITY.equals(uri.getAuthority())) {
            // Try cached ringtone first since the actual provider may not be
            // encryption aware, or it may be stored on CE media storage
            final int type = RingtoneManager.getDefaultType(uri);
            final Uri cacheUri = RingtoneManager.getCacheForType(type);
            final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
            if (attemptDataSource(resolver, cacheUri)) {
                return;
            } else if (attemptDataSource(resolver, actualUri)) {
                return;
            } else {
                setDataSource(uri.toString(), headers);
            }
        } else {
            // Try requested Uri locally first, or fallback to media server
            if (attemptDataSource(resolver, uri)) {
                return;
            } else {
                IOmaSettingHelper helper = MPlugin.createInstance(IOmaSettingHelper.class.getName(),
                        context);
                if (helper != null) {
                    headers = helper.setSettingHeader(context, uri, headers);
                } else {
                    Log.w(TAG, "IOmaSettingHelper plugin returns null, uses default headers");
                }
                setDataSource(uri.toString(), headers);
            }
        }
    }

最后设置铃声的为

        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
                && Settings.AUTHORITY.equals(uri.getAuthority())) {
            // Try cached ringtone first since the actual provider may not be
            // encryption aware, or it may be stored on CE media storage
            final int type = RingtoneManager.getDefaultType(uri);
            final Uri cacheUri = RingtoneManager.getCacheForType(type);
            final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
            if (attemptDataSource(resolver, cacheUri)) {
                return;
            } else if (attemptDataSource(resolver, actualUri)) {
                return;
            } else {
                setDataSource(uri.toString(), headers);
            }

只有attemptDataSource(resolver, cacheUri)不成功,返回的为false,才会进入attemptDataSource(resolver, actualUri),

private boolean attemptDataSource(ContentResolver resolver, Uri uri) {
        try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
            setDataSource(afd);
            return true;
        } catch (NullPointerException | SecurityException | IOException ex) {
            Log.w(TAG, "Couldn't open " + uri + ": " + ex);
            return false;
        }
    }
而final Uri cacheUri = RingtoneManager.getCacheForType(type);获取的为保存在\data\system_de\0\ringtones\目录下面手机铃声ringtone_cache文件里面的铃声,只有此设置失败了,才会设置attemptDataSource(resolver, actualUri)此路径的铃声,而

final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);的铃声为此铃声的实际铃声路径,所以当设置SD卡音乐为铃声时当此音乐不存在还是会想起设置的音乐铃声,此为android N新的铃声机制。




<think>嗯,用户问的是在VOIP通话过程中,铃声响起时会用到哪些类和函数。我需要先理解VOIP通话的基本流程。VOIP通话通常涉及SIP协议,比如注册、邀请、会话建立等步骤。铃声响起应该是在被叫方收到INVITE请求后,发送180 Ringing响应的时候。 接下来,我需要确定用户可能使用的开发框架。比如,如果是用Android的SIP API,可能会涉及到SipManager、SipProfile、SipAudioCall等类。在iOS上可能用CallKit或者第三方库如Linphone、PJSIP。不同的平台和库会有不同的类和函数。 用户可能是在开发VOIP应用时遇到了问题,或者需要了解铃声触发的机制。需要详细列举各个平台相关的类和方法,比如Android中的SipAudioCall.Listener的onRinging方法,或者CallKit中的CXCallController和CXProviderDelegate中的相关回调。 还要考虑底层音频处理,比如使用AudioManager来管理音频焦点,MediaPlayer或AudioTrack来播放铃声。此外,可能涉及系统权限,如使用振动或通知权限。 用户可能没有明确说明是哪个平台,所以需要分情况讨论,涵盖Android和iOS的主要类。同时,可能需要提到SIP协议中的相关状态码,如180 Ringing,以及如何在代码中处理这些状态。 另外,用户可能想知道具体的函数调用流程,比如如何设置监听器,如何触发铃声播放,如何处理用户接听或拒绝的操作。需要详细说明每个步骤涉及的类和方法,并给出示例代码片段。 最后,确保回答覆盖了铃声响起的整个流程,从收到邀请到播放铃声,再到用户响应的处理,以及可能的错误处理和资源释放。同时,相关问题需要引导用户进一步了解VOIP开发的其他方面,如状态处理、音频管理、跨平台方案等。</think>在VOIP通话中铃声响起的过程涉及协议栈、音频控制、系统服务等多层交互。以下是典型实现中涉及的类和函数(以Android/iOS通用逻辑为例): --- ### 一、SIP协议层(基于RFC 3261) 1. **SipSession类** - `onCallEstablished()`:通话建立时触发铃声播放 - `onCallRinging()`:收到180 Ringing响应时触发 - `onCallProgress()`:媒体协商成功后准备音频流 2. **SipMessage处理** - `sendResponse(SIPResponse response)`:发送180 Ringing响应 - `handleIncomingRequest(SIPRequest request)`:解析INVITE请求 --- ### 二、Android平台核心类(基于SipManager) 1. **SipAudioCall类** ```java public void startAudio() // 启动音频通道 public void setSpeakerMode(int mode) // 设置扬声器模式 public void playDtmf(int digit) // 播放DTMF音(可选) ``` 2. **SipAudioCall.Listener回调** ```java onRinging(SipAudioCall call) { // 收到远端振铃信号 startRingtone(); } onRingingBack() { // 本地振铃开始 vibrateDevice(); } ``` 3. **AudioManager音频管理** ```java setMode(AudioManager.MODE_IN_CALL) // 切换到通话模式 setSpeakerphoneOn(true) // 开启扬声器 ``` --- ### 三、iOS平台核心类(基于CallKit) 1. **CXCallController** ```swift func requestTransaction(with action: CXAction) // 发起振铃请求 func reportNewIncomingCall(with uuid: UUID) // 报告来电 ``` 2. **CXProviderDelegate回调** ```swift func provider(_ provider: CXProvider, perform action: CXStartCallAction) { // 开始播放铃声 playRingtone() } ``` 3. **AVAudioSession配置** ```swift setCategory(.playAndRecord) // 设置音频类别 setActive(true) // 激活音频会话 ``` --- ### 四、底层音频处理 1. **音频播放类** - Android:`MediaPlayer.create(context, R.raw.ringtone)` - iOS:`AVAudioPlayer(contentsOf: ringtoneURL)` 2. **振动控制** - Android:`Vibrator.vibrate(VibrationEffect.createOneShot(...))` - iOS:`AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)` 3. **媒体协商** - `MediaStream.setAudioEnabled(true)` // 启用音频轨道 - `RtpSender.setParameters(RtpParameters)` // 配置编解码器 --- ### 五、典型调用流程 1. 收到SIP INVITE请求 → 触发`onCallRinging()` 2. 调用`AudioManager.setMode(MODE_IN_CALL)`切换音频模式 3. 通过`MediaPlayer`或`AVAudioPlayer`播放铃声资源 4. 启动振动服务(可选) 5. 用户接听时调用`acceptCall()`建立媒体连接
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值