android音频系统(7):通话过程中的音频输出设备切换

前言:由于通话比较特殊,Android对于通话过程中音频输出设备的切换做了特殊处理,它在上层也是通过切换音频播放状态来完成切换操作的,android用CallAudioState来封装通话过程中的音频播放状态;

1.CallAudioState解析
CallAudioState的属性

CallAudioState.java
public static final int ROUTE_EARPIECE      = 0x00000001;//听筒
public static final int ROUTE_BLUETOOTH     = 0x00000002;//蓝牙
public static final int ROUTE_WIRED_HEADSET = 0x00000004;//有线耳机
public static final int ROUTE_SPEAKER       = 0x00000008;//扬声器
public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET;//听筒或者耳机
public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
        ROUTE_SPEAKER;//全部状态

通话过程中的音频播放状态共有4种,分别是听筒,蓝牙,有线耳机,扬声器;

CallAudioState中的重要方法有:

CallAudioState.java
//是否静音
public boolean isMuted() {
    return isMuted;
}

//返回音频状态
public int getRoute() {
    return route;
}

 

2.InCallService
切换通话过程中的音频播放状态需要用到InCallService,InCallService是一个代理;
InCallService是一个抽象类,需要写一个子类来继承它,看下InCallService中几个比较重要的方法;

void onCallAudioStateChanged(CallAudioState audioState);//当音频播放状态发生了切换时,就会调用该函数,该方法经常用到
void onBringToForeground(boolean showDialpad);
void onCallAdded(Call call);
void onCallRemoved(Call call);
void onCanAddCallChanged(boolean canAddCall);

上面只是罗列了比较常用的方法,更多的方法剖析请去InCallService里面查看;


3.下面就来看下几种状态之间的切换
假设现在的通话模式是扬声器,想切换到听筒或者耳机,可以调用:mInCallService.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
mInCallService就是InCallService;

CallAudioState.ROUTE_WIRED_OR_EARPIECE表示:如果此时是扬声器模式,当调用CallAudioState.ROUTE_WIRED_OR_EARPIECE参数时,系统会将音频输出设备切换成听筒或者是耳机,至于最后到底是听筒还是耳机播放,这个取决于它们的优先级,如果手机当中插入了耳机,耳机的优先级会比听筒要高;

假如此时现在的通话模式是耳机,想切换到扬声器,可以调用:mInCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER);

 

4.我们来分析下mInCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER)的流程:

InCallService.java
public final void setAudioRoute(int route) {
    if (mPhone != null) {
        mPhone.setAudioRoute(route);
    }
}

会看到一个mPhone,这个Phone是这么来的呢?

InCallService.java
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        if (mPhone == null && msg.what != MSG_SET_IN_CALL_ADAPTER) {
            return;
        }

        switch (msg.what) {
            case MSG_SET_IN_CALL_ADAPTER:
                String callingPackage = getApplicationContext().getOpPackageName();
                mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,
                        getApplicationContext().getApplicationInfo().targetSdkVersion);
                mPhone.addListener(mPhoneListener);
                //调用onPhoneCreated()
                onPhoneCreated(mPhone);
                break;
                
            ............
        }
    }
}

mPhone是在处理MSG_SET_IN_CALL_ADAPTER消息时被创建的,那MSG_SET_IN_CALL_ADAPTER消息又是谁传递的呢?

InCallService.java
private final class InCallServiceBinder extends IInCallService.Stub {
    @Override
    public void setInCallAdapter(IInCallAdapter inCallAdapter) {
        mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget();
    }

    @Override
    public void addCall(ParcelableCall call) {
        mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
    }

    @Override
    public void updateCall(ParcelableCall call) {
        mHandler.obtainMessage(MSG_UPDATE_CALL, call).sendToTarget();
    }
    ............
}

由于我们在后面会用到inCallAdapter,所以分析下inCallAdapter的由来;
InCallServiceBinder很明显就是个服务代理,也就是绑定服务时传递进来的,绑定服务需要ServiceConnection,那这个ServiceConnection到底是谁呢?
答案在packages/services/Telecomm/src/com/android/server/telecom/InCallController.java中,它在绑定InCallService时,调用到了setInCallAdapter()方法,代码如下:

InCallController.java
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
    intent.setComponent(mInCallServiceInfo.getComponentName());
    if (call != null && !call.isIncoming() && !call.isExternalCall()){
        intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
                call.getIntentExtras());
        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                call.getTargetPhoneAccount());
    }

    Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
    mIsConnected = true;
    if (!mContext.bindServiceAsUser(intent, mServiceConnection,
                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE |
                Context.BIND_ABOVE_CLIENT,
                UserHandle.CURRENT)) {
        Log.w(this, "Failed to connect.");
        mIsConnected = false;
    }
    ............
}

private final ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.startSession("ICSBC.oSC");
        synchronized (mLock) {
            try {
                Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
                mIsBound = true;
                if (mIsConnected) {
                    // Only proceed if we are supposed to be connected.
                    onConnected(service);
                }
            } finally {
                Log.endSession();
            }
        }
    }

    ............
};

rotected void onConnected(IBinder service) {
    boolean shouldRemainConnected =
            InCallController.this.onConnected(mInCallServiceInfo, service);
    if (!shouldRemainConnected) {
        disconnect();
    }
}

private boolean onConnected(InCallServiceInfo info, IBinder service) {
    Trace.beginSection("onConnected: " + info.getComponentName());
    Log.i(this, "onConnected to %s", info.getComponentName());

    IInCallService inCallService = IInCallService.Stub.asInterface(service);
    mInCallServices.put(info, inCallService);

    try {
        inCallService.setInCallAdapter(
                new InCallAdapter(
                        mCallsManager,
                        mCallIdMapper,
                        mLock,
                        info.getComponentName().getPackageName()));
    } catch (RemoteException e) {
        Log.e(this, e, "Failed to set the in-call adapter.");
        Trace.endSection();
        return false;
    }
    ............
}

终于找到这个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

renshuguo123723

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值