基于Android Q的蓝牙通话无声问题

分享一下这几天改蓝牙通话无声的bug,文章有点长,希望各位看官看完能帮助到大家—今天分析的主题是蓝牙通话没有声音之运行流程分析

一. 结果说在前面

​ 蓝牙通话分别有七个阶段,基本上每个阶段都会走到底层,把数据回调到上层,这么一个流程叫一个阶段;回调的数据代表一个状态,而我这个问题就在于底层回调数据到上层时,携带的数据不满足上层的要求 导致无法进入下一个阶段而产生的蓝牙通话无声问题;

​ 蓝牙通话的七个连接阶段使用的模式是:状态机模式,没弄明白状态机模式之前有点不好入手;我这里分享一下状态机模式的详解文章:状态机模式介绍

二.涉及到的文件/内容

  1. vendor/mediatek/proprietary/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java

  2. vendor/mediatek/proprietary/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java

  3. vendor/mediatek/proprietary/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java

  4. frameworks/base/services/core/java/com/android/server/audio/BtHelper.java

    蓝牙服务类别:

  5. HeadsetPro-file(HSP)代表耳机功能,提供手机与耳机之间通信所需的基本功能。

  6. HandProfile(HFP)则代表免提功能,HFP在HSP的基础上增加了某些扩展功能。

  7. Advanced Audio Distribution Profile(A2DP),指的是 蓝牙音频传输模型协定。

  8. 蓝牙物理链路SCO(Synchronous Connection Oriented)主要用来传输对时间要求很高的数据通信,通常用于通话

三.蓝牙连接状态分析

1.连接状态分析

​ 蓝牙连接步骤分为七个状态,分别是:

  1. Disconnected

    该状态为初始化阶段,在蓝牙打开阶段会执行

  2. Connecting

    该状态在连接蓝牙时执行

  3. Connected

    该状态在蓝牙连接完成后执行

  4. AudioConnecting

    该状态在接听电话后执行,处于一个连接中的一个状态,很快就能连接上

  5. AudioOn

    该状态在接听电话后,AudioConnecting阶段完成后执行,这个时候通常就能用蓝牙耳机通话了

  6. AudioDiconnecting

    该状态在挂断电话后执行,此时又回到了Connected状态,因为蓝牙还是开启的

  7. Disconnecting

    该状态在蓝牙断开中执行,执行完成后回到Disconnected该状态

以上就是蓝牙连接到蓝牙断开的七个连接状态,每个状态都继承了状态机,接下来看看每个连接状态的具体方法,具体实现稍后分析

2.状态机方法

​ 每个连接状态都有一个状态模式,都分别继承于HeadsetStateBaseConnectedBase,它们共同的父父类就是State,先来看一段代码,七个连接状态

2.1 HeadsetStateMachine.java
 // State machine states
    private final Disconnected mDisconnected = new Disconnected();
    private final Connecting mConnecting = new Connecting();
    private final Disconnecting mDisconnecting = new Disconnecting();
    private final Connected mConnected = new Connected();
    private final AudioConnecting mAudioConnecting = new AudioConnecting();
    private final AudioOn mAudioOn = new AudioOn();
    private final AudioDisconnecting mAudioDisconnecting = new AudioDisconnecting();
... 
            // Initialize state machine
        addState(mDisconnected);
        addState(mConnecting);
        addState(mDisconnecting);
        addState(mConnected);
        addState(mAudioOn);
        addState(mAudioConnecting);
        addState(mAudioDisconnecting);
        setInitialState(mDisconnected);

分别把七个连接阶段加入到状态机中,初始化mDisconnected为默认状态,每个状态机都有四个方法,具体看代码

    class 七个连接状态 extends ConnectedBase 或者 HeadsetStateBase {
        @Override
        int getAudioStateInt() {
            ...
        }

        @Override
        public void enter() {
            ...
        }

        // ... 
            
        @Override
        public boolean processMessage(Message message) {
            ...
        }

        @Override
        public void exit() {
            ...
        }
    }

这三个方法是每个连接阶段必有的,getAudioStateInt用于一个常量代表当前返回的状态,enter进入该方法,processMessage事件逻辑处理,exit退出当前状态,这四个方法是顺序执行。

3.连接状态具体实现

之前说道基本上每个连接状态都会走到底层拿数据回调上层,这里我们来具体看看,主要分析AudioConnecting阶段,大致分析其他阶段

3.1 Disconnected
 class Disconnected extends HeadsetStateBase {
      ...
       @Override
        public boolean processMessage(Message message) {
           ...
                    if (!mNativeInterface.connectHfp(device)) { // 第一步
                        stateLogE("CONNECT failed for connectHfp(" + device + ")");
                        // No state transition is involved, fire broadcast immediately
                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                                BluetoothProfile.STATE_DISCONNECTED);
                        break;
                    }
                    transitionTo(mConnecting); // 第二步
             ...
            return HANDLED;
        }   
      ...
 }

很关键的两步,第一步连接之前提到过的HFP服务,调用底层,连接成功后发送广播;第二步状态切换,进入下一步连接状态;这里的返回值一定要是true才行,才能进入exit,HANDLED默认就是true;底层回调:HeadsetNativeInterface.java # private native boolean connectHfpNative(byte[] address);

3.2 Connecting
class Connecting extends HeadsetStateBase {
      ...
      
        @Override
        public void processConnectionEvent(Message message, int state) {
            switch (state) {
            ...
                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED://走了一系列流程后进入该case
                    stateLogD("SLC connected");
                    transitionTo(mConnected);//调用该方法进入下一个阶段
                    break;
             ...
            }
        } 
      ...
 }

这里的processConnectionEvent方法的state值是从HeadsetNativeInterface#onConnectionStateChanged开始调用底层,携带state数据,再回调上层来,接着再根据state来走case是否进入mConnected阶段

每个阶段差不多都是这样的流程,这里就不一一去分析了,直接看AudioConnecting阶段

3.3 AudioConnecting

这个阶段就是我遇到问题的一个阶段,由于state值导致无法进入下一阶段,上代码

     //各阶段常量
    static final int CONNECT = 1;
    static final int DISCONNECT = 2;
    static final int CONNECT_AUDIO = 3;//注意
    static final int DISCONNECT_AUDIO = 4;

class AudioConnecting extends ConnectedBase {
        @Override
        int getAudioStateInt() {
            return BluetoothHeadset.STATE_AUDIO_CONNECTING; //记住这段代码,它的值为11
        }

        @Override
        public void enter() {
            super.enter();
            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);//发送消息给状态机的SmHandler-设置超时,延时发送和当前状态信息
            broadcastStateTransitions();//发送广播给BtHleper.java,该方法稍后分析
        }

        @Override
        public boolean processMessage(Message message) {
            /// M: add log. @{
            stateLogD("processMessage:" + message.what);//
            /// @}
            switch (message.what) {//该阶段的msg.what == 3,也就对应case CONNECT_AUDIO
                case CONNECT:
                case DISCONNECT:
                case CONNECT_AUDIO:
                case DISCONNECT_AUDIO:
                    deferMessage(message);//把msg放进消息队列
                    break;
                case CONNECT_TIMEOUT: {//超时处理,我调试时没走该段代码
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    if (!mDevice.equals(device)) {
                        stateLogW("CONNECT_TIMEOUT for unknown device " + device);
                        break;
                    }
                    stateLogW("CONNECT_TIMEOUT");
                    transitionTo(mConnected);
                    break;
                }
                default: //当没有匹配的case时
                    return super.processMessage(message);
            }
            return HANDLED;
        }

        @Override
        public void processAudioEvent(int state) {
            switch (state) {
                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
                    stateLogW("processAudioEvent: audio connection failed");
                    transitionTo(mConnected);
                    break;
                case HeadsetHalConstants.AUDIO_STATE_CONNECTING://目前匹配的该条case;常量值:1
                    // ignore, already in audio connecting state
                    break;
                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
                    // ignore, there is no BluetoothHeadset.STATE_AUDIO_DISCONNECTING
                    break;
                case HeadsetHalConstants.AUDIO_STATE_CONNECTED://如果case走这一步就一切正常,能进行状态切换;常量值:2
                    stateLogI("processAudioEvent: audio connected");
                    transitionTo(mAudioOn);
                    break;
                default:
                    stateLogE("processAudioEvent: bad state: " + state);
                    break;
            }
        }

        @Override
        public void exit() {
            removeMessages(CONNECT_TIMEOUT);
            super.exit();
        }
    }

进入该阶段时,调用processMessage方法,会调用N次,第一次为msg.what == 3是正常的,看log日志后发现还有101的值传过来,该方法里匹配不到则走了default,导致循环调用,最后走到了processAudioEvent,state为1,产生的异常,那么这个101的值代表什么呢?又从哪里进来的?带着这个疑问继续跟踪代码

static final int STACK_EVENT = 101; //看该常量的意思,应该是发送粘性事件

发现它在HeadsetService.java中被调用了,跟着走,上代码

 /**
     * Handle messages from native (JNI) to Java. This needs to be synchronized to avoid posting
     * messages to state machine before start() is done
     *
     * @param stackEvent event from native stack
     */
    void messageFromNative(HeadsetStackEvent stackEvent) {
        Objects.requireNonNull(stackEvent.device,
                "Device should never be null, event: " + stackEvent);
        synchronized (mStateMachines) {
            HeadsetStateMachine stateMachine = mStateMachines.get(stackEvent.device);
            if (stackEvent.type == HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
                switch (stackEvent.valueInt) {
                    case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
                    case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: {
                        // Create new state machine if none is found
                        if (stateMachine == null) {
                            stateMachine = HeadsetObjectsFactory.getInstance()
                                    .makeStateMachine(stackEvent.device,
                                            mStateMachinesThread.getLooper(), this, mAdapterService,
                                            mNativeInterface, mSystemInterface);
                            mStateMachines.put(stackEvent.device, stateMachine);
                        }
                        break;
                    }
                }
            }
            if (stateMachine == null) {
                throw new IllegalStateException(
                        "State machine not found for stack event: " + stackEvent);
            }
            //101值在这里进行调用了,发送到AudioConnecting阶段
            stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent);
        }
    }

注意看最开始的官方注释,这是一套JNI代码,说明这里肯定是走了底层;我又有两个疑惑了

  1. 走AudioConnecting阶段时,正常的话是不是不会走messageFromNative方法
  2. 如果正常的情况也要走该段代码,这个101值是必传给AudioConnecting的,那么是不是传过去后这个101值处理有问题

带着这两个问题,又继续出发,看看messageFromNative是在哪里被调用的,发现在HeadsetNativeInterface.java#sendMessageToService中去调用了,而且不止一处被调用,很多JNI相关的方法都执行了该方法,具体来看看一段截图
基于Android Q的蓝牙通话无声问题_第1张图片以上这些方法都是底层回调数据到上层的方法,这下刚刚的疑惑就得到解决了,那么这个101状态值肯定是必须要走的,期间把event作为传入传递过去了,其中event就包含了state和type信息,记住此时的state为1,type为EVENT_TYPE_AUDIO_STATE_CHANGED,至此源头就找到了,接着回到AudioConnecting连接状态,看如下片段代码

     @Override
        public boolean processMessage(Message message) {
            /// M: add log. @{
            stateLogD("processMessage:" + message.what);// 此时这里的what已经是101了
            /// @}
            switch (message.what) {
                case CONNECT:
                case DISCONNECT:
                case CONNECT_AUDIO:
                case DISCONNECT_AUDIO:
                    deferMessage(message);
                    break;
                case CONNECT_TIMEOUT: {
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    if (!mDevice.equals(device)) {
                        stateLogW("CONNECT_TIMEOUT for unknown device " + device);
                        break;
                    }
                    stateLogW("CONNECT_TIMEOUT");
                    transitionTo(mConnected);
                    break;
                }
                default: //因为没有101常量值匹配,走default
                    return super.processMessage(message);
            }
            return HANDLED;
        }

再看看super.processMessage(msg);上代码

 /**
         * Handle common messages in connected states. However, state specific messages must be
         * handled individually.
         *
         * @param message Incoming message to handle
         * @return True if handled successfully, False otherwise
         */
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
            ... 
                case STACK_EVENT: //此时走了该case,因为子类把message也传过来了
                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
                    stateLogD("STACK_EVENT: " + event);
                    if (!mDevice.equals(event.device)) {
                        stateLogE("Event device does not match currentDevice[" + mDevice
                                + "], event: " + event);
                        break;
                    }
                    switch (event.type) { //拿到底层给的event数据
                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
                            processConnectionEvent(message, event.valueInt);
                            break;
                        case HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED://正好type值匹配的该条case
                            processAudioEvent(event.valueInt); //该方法是AudioConnecting连接阶段的一个回调方法,而event.valueInt == 1,也就是state == 1
                            break;
                          ...
                    }
                    break;
                default:
                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
                    return NOT_HANDLED;
            }
            return HANDLED;
        }

这时回到了AudioConnecting连接状态的processAudioEvent方法,上面已经提到过了,再来看一遍

@Override
        public void processAudioEvent(int state) {//此时为1
            switch (state) {
                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
                    stateLogW("processAudioEvent: audio connection failed");
                    transitionTo(mConnected);
                    break;
                case HeadsetHalConstants.AUDIO_STATE_CONNECTING://该常量值:1
                    // ignore, already in audio connecting state
                    break;
                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
                    // ignore, there is no BluetoothHeadset.STATE_AUDIO_DISCONNECTING
                    break;
                case HeadsetHalConstants.AUDIO_STATE_CONNECTED://该常量值:2
                    stateLogI("processAudioEvent: audio connected");
                    transitionTo(mAudioOn);
                    break;
                default:
                    stateLogE("processAudioEvent: bad state: " + state);
                    break;
            }
        }

而刚好state为1,走的 case HeadsetHalConstants.AUDIO_STATE_CONNECTING,导致直接break;什么事情都没做,如果此时state为2,则进入case HeadsetHalConstants.AUDIO_STATE_CONNECTED,再调用transitionTo(mAudioOn);进行状态切换,就一切正常了,那么基于之前的一些疑问,在这里就已经解决了

分析完上层,接着看底层是怎么把state值回调到上层的。

3.4 底层状态分析

com_android_bluetooth_hfp.cpp

void AudioStateCallback(bluetooth::headset::bthf_audio_state_t state,
                          RawAddress* bd_addr) override {
    ALOGI("%s, %d for %s", __func__, state, bd_addr->ToString().c_str());

    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || !mCallbacksObj) return;

    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
    if (!addr.get()) return;
    // (jint)state 该值将回调到上层
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
                                 (jint)state, addr.get());
  }

接着跟踪state值怎么拿到的

btif_hf.cc

bt_status_t HeadsetInterface::ConnectAudio(RawAddress* bd_addr) {
  CHECK_BTHF_INIT();
  int idx = btif_hf_idx_by_bdaddr(bd_addr);
  if ((idx < 0) || (idx >= BTA_AG_MAX_NUM_CLIENTS)) {
    BTIF_TRACE_ERROR("%s: Invalid index %d", __func__, idx);
    return BT_STATUS_FAIL;
  }
  /* Check if SLC is connected */
  if (!IsSlcConnected(bd_addr)) {
    LOG(ERROR) << ": SLC not connected for " << *bd_addr;
    return BT_STATUS_NOT_READY;
  }
   // 注意看
  do_in_jni_thread(base::Bind(&Callbacks::AudioStateCallback,
                              // Manual pointer management for now
                              base::Unretained(bt_hf_callbacks),
                              BTHF_AUDIO_STATE_CONNECTING,//该值就是state,而恰好这个常量就是1,上层也是卡在connecting无法执行
                              &btif_hf_cb[idx].connected_bda));
  BTA_AgAudioOpen(btif_hf_cb[idx].handle);
  return BT_STATUS_SUCCESS;
}

BTHF_AUDIO_STATE_CONNECTING该值就是state =1 ,说明还没执行完就在这卡住了,所以上层也会卡在connecting。如果继续执行的话 --看代码

static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {

 ...
     case BTA_AG_WBS_EVT: //先走这条case,用于编解码音频
      BTIF_TRACE_DEBUG(
          "BTA_AG_WBS_EVT Set codec status %d codec %d 1=CVSD 2=MSBC",
          p_data->val.hdr.status, p_data->val.num);
    //如果p_data->val.num = 1则执行CVSD编码,为2的话走MSBC编码
      if (p_data->val.num == BTA_AG_CODEC_CVSD) {
        bt_hf_callbacks->WbsCallback(BTHF_WBS_NO,
                                     &btif_hf_cb[idx].connected_bda);
      } else if (p_data->val.num == BTA_AG_CODEC_MSBC) {
        bt_hf_callbacks->WbsCallback(BTHF_WBS_YES,
                                     &btif_hf_cb[idx].connected_bda);
      } else {
        bt_hf_callbacks->WbsCallback(BTHF_WBS_NONE,
                                     &btif_hf_cb[idx].connected_bda);
      }
      break;
    ...
      case BTA_AG_AUDIO_OPEN_EVT://然后走这条case,把state=2回调给上层
      bt_hf_callbacks->AudioStateCallback(BTHF_AUDIO_STATE_CONNECTED,
                                          &btif_hf_cb[idx].connected_bda);
      break;
 ...
}

btif_hf_upstreams_evt方法会被多次执行,具体怎么执行的暂不清楚,上面很关键的两条case,选择音频编解码和state=2回调到上层,但是我遇到的问题就是只走了编解码,state=2的case没有执行

至此 整个流程差不多就要结束了,回到上层来,既然状态机的各个阶段都正常的话,是不是该把广播发出去,通知各个方法。接着看到broadcastStateTransitions()方法,它在每个状态机阶段都会执行一次,发送不同阶段的广播以便于知道执行到哪个阶段了

void broadcastStateTransitions() {
            if (mPrevState == null) {
                return;
            }
            // TODO: Add STATE_AUDIO_DISCONNECTING constant to get rid of the 2nd part of this logic
            if (getAudioStateInt() != mPrevState.getAudioStateInt() || (
                    mPrevState instanceof AudioDisconnecting && this instanceof AudioOn)) {
                stateLogD("audio state changed: " + mDevice + ": " + mPrevState + " -> " + this);
                broadcastAudioState(mDevice, mPrevState.getAudioStateInt(), getAudioStateInt());//发送当前的device和上一个状态和当前状态,这个状态指的是每个状态机的 getAudioStateInt 方法返回的值 ,AudioConnecting状态机的返回值=11,AudioOn状态机的返回值=12
       
            }
            if (getConnectionStateInt() != mPrevState.getConnectionStateInt()) {
                stateLogD(
                        "connection state changed: " + mDevice + ": " + mPrevState + " -> " + this);
                broadcastConnectionState(mDevice, mPrevState.getConnectionStateInt(),
                        getConnectionStateInt());
            }
        }
  //发送广播
   void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
            stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
            StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
                    mAdapterService.obfuscateAddress(device),
                    getConnectionStateFromAudioState(toState),
                    TextUtils.equals(mAudioParams.get(HEADSET_WBS), HEADSET_AUDIO_FEATURE_ON)
                            ? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
                            : BluetoothHfpProtoEnums.SCO_CODEC_CVSD);
            mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
            Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
          //写入当前state状态
            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
            mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
                    HeadsetService.BLUETOOTH_PERM);
        }

该广播会在BtHelper.java#receiveBtEvent中接收

    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
    /*package*/ synchronized void receiveBtEvent(Intent intent) {
        final String action = intent.getAction();
        if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
            ...
        } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
            boolean broadcast = false;
            int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
            int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);//拿到state值
            // broadcast intent if the connection was initated by AudioService
        ...
            switch (btState) {// 正常的话 state状态值为12
                case BluetoothHeadset.STATE_AUDIO_CONNECTED://该值=12
                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
                    }
                    //开启SCO服务,再执行startBluetoothScoOn
                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
                    break;
              ...
                case BluetoothHeadset.STATE_AUDIO_CONNECTING://该值=11
                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
                    }
                    break;
                default:
                    // do not broadcast CONNECTING or invalid state
                    broadcast = false;
                    break;
            }
            //当state=12时,broadcast=true,代码没贴进来--作用是建立SCO服务
            if (broadcast) {
                broadcastScoConnectionState(scoAudioState);
               ...
                Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
                newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
                sendStickyBroadcastToAll(newIntent);
            }
        }
    }

至此,全部流程结束

四.蓝牙通话无声问题解决

[根本原因]

state卡在了BTHF_AUDIO_STATE_CONNECTING,执行了音频编解码后没继续往后执行,根据log提示,SCO link无法响应,无法连接,导致SCO服务挂掉 进而产生的一些列异常;

[解决办法]

既然是SCO link无法响应,那就是Modem端无法给出正确的response;发现是蓝牙端本身的一个bug,需要合入MTK提供的俩个official patch

合入过后观察底层和上层的state值和状态机,发现一切正常,但是…还是通话无声…问题又来了…原因是要 enable merge interface ,那就来开启该宏

[音频无声解决办法]

1.ProjectConfig.mk # MTK_MERGE_INTERFACE_SUPPORT=yes

2.kernel-4.9/arch/arm64/configs/dl35_debug_defconfig # CONFIG_MTK_MERGE_INTERFACE_SUPPORT=y

3.kernel-4.9/arch/arm64/configs/dl35_defconfig # CONFIG_MTK_MERGE_INTERFACE_SUPPORT=y

至此 问题解决

你可能感兴趣的:(Android工作记录,android,蓝牙,aosp,java)