在上一篇文章中,我们已经分析了:a2dp初始化流程 这篇文章主要分析a2dp的连接流程,其中还是涉及到一些底层的profile以及protocol,SDP、AVDTP以及L2CAP等。
当蓝牙设备与主机配对完成之后,作为一个BREDR设备,会走SDP的流程进行服务搜索,当服务搜索完成之后,上层应用得到了该设备的相关的服务,将启动相关的profile 的连接流程,如果对方是一个音箱设备,那么就会触发a2dp的连接流程。我们就从开始调用a2dp connect的地方进行分析:
D/BluetoothA2dp( 3698): connect(C9:50:76:F2:3C:B6)
上面的log 的打印是在BluetoothA2dp.java 中,
public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); if (mService != null && isEnabled() && isValidDevice(device)) { try { return mService.connect(device);//进行连接 } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } if (mService == null) Log.w(TAG, "Proxy not attached to service"); return false; }
注:文章中涉及到 Android里面的通信机制,binder之类的机制,限于篇幅,暂时不讲,重点讲 代码的执行流程。
这里的mService 就是A2dpService,其实现在package/app/bluetooth 下面,我们继续看:
public boolean connect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { //在上传uuid的时候就已经设置了PRIORITY_ON return false; } ParcelUuid[] featureUuids = device.getUuids(); if ((BluetoothUuid.containsAnyUuid(featureUuids, A2DP_SOURCE_UUID)) && !(BluetoothUuid.containsAllUuids(featureUuids ,A2DP_SOURCE_SINK_UUIDS))) { //判断是否支持sink Log.e(TAG,"Remote does not have A2dp Sink UUID"); return false; } int connectionState = mStateMachine.getConnectionState(device); if (connectionState == BluetoothProfile.STATE_CONNECTED || connectionState == BluetoothProfile.STATE_CONNECTING) { return false; } mStateMachine.sendMessage(A2dpStateMachine.CONNECT, device);//发送消息进行连接 return true; }
下面接着看 消息的处理,这个消息是发往A2dpStateMachine的,最开始是进入到Disconnected的状态机,看看其处理:
switch(message.what) { case CONNECT: BluetoothDevice device = (BluetoothDevice) message.obj; broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); if (!connectA2dpNative(getByteAddress(device)) ) { //调用native 的connect broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); break; }
这里涉及到JNI的通信,其机制很简单,限于篇幅也不讲解了。其实现在com_android_com_android_bluetooh.cpp中:
static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) { jbyte *addr; bt_bdaddr_t * btAddr; bt_status_t status; ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface); if (!sBluetoothA2dpInterface) return JNI_FALSE; addr = env->GetByteArrayElements(address, NULL); btAddr = (bt_bdaddr_t *) addr; if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) { //调用bluedroid里面的a2dp的connect接口 ALOGE("Failed HF connection, status: %d", status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; }
到这里之后,连接的流程就直接进入到协议栈了,这之后就完全是C语言的代码,a2dp的connect 接口实现在btif_av.c里面 ,我们继续看,
static bt_status_t src_connect_sink(bt_bdaddr_t *bd_addr) { CHECK_BTAV_INIT(); return btif_queue_connect(UUID_SERVCLASS_AUDIO_SOURCE, bd_addr, connect_int); }
我们继续看 btif_queue_connect 做了什么?从他的注释中,我们看出他是先把建立连接的信息加入到queue中,并且触发queue中的下一次的连接操作。
/******************************************************************************* ** ** Function btif_queue_connect ** ** Description Add a new connection to the queue and trigger the next ** scheduled connection. ** ** Returns BT_STATUS_SUCCESS if successful ** *******************************************************************************/ bt_status_t btif_queue_connect(uint16_t uuid, const bt_bdaddr_t *bda, btif_connect_cb_t connect_cb) { connect_node_t node; memset(&node, 0, sizeof(connect_node_t)); memcpy(&node.bda, bda, sizeof(bt_bdaddr_t)); node.uuid = uuid; node.connect_cb = connect_cb; return btif_transfer_context(queue_int_handle_evt, BTIF_QUEUE_CONNECT_EVT, (char *)&node, sizeof(connect_node_t), NULL); }
把建立连接的事情 transfer到 btif task中,我们继续看:
static void queue_int_handle_evt(UINT16 event, char *p_param) { switch(event) { case BTIF_QUEUE_CONNECT_EVT: queue_int_add((connect_node_t *)p_param);//这里果然是加入到队列中了 break; case BTIF_QUEUE_ADVANCE_EVT: queue_int_advance(); break; } if (stack_manager_get_interface()->get_stack_is_running()) btif_queue_connect_next();//触发下一个连接 }
我们看看btif_queue_connect_next 的实现:
// This function dispatches the next pending connect request. It is called from // stack_manager when the stack comes up. bt_status_t btif_queue_connect_next(void) { if (!connect_queue || list_is_empty(connect_queue)) return BT_STATUS_FAIL; connect_node_t *p_head = list_front(connect_queue); // If the queue is currently busy, we return success anyway, // since the connection has been queued... if (p_head->busy) return BT_STATUS_SUCCESS; p_head->busy = true; return p_head->connect_cb(&p_head->bda, p_head->uuid);//执行队列中第一个连接任务 }
看到上面的代码,我们明白,a2dp的连接执行的函数就是connect_int(bd_addr,UUID_SERVCLASS_AUDIO_SOURCE) ,我们继续分析这个函数:
/******************************************************************************* ** ** Function connect ** ** Description Establishes the AV signalling channel with the remote headset ** ** Returns bt_status_t ** *******************************************************************************/ static bt_status_t connect_int(bt_bdaddr_t *bd_addr, uint16_t uuid) { btif_av_connect_req_t connect_req; connect_req.target_bda = bd_addr; connect_req.uuid = uuid; btif_av_cb.uuid = uuid;//保存uuid:UUID_SERVCLASS_AUDIO_SOURCE btif_sm_dispatch(btif_av_cb.sm_handle, BTIF_AV_CONNECT_REQ_EVT, (char*)&connect_req);//放置到状态表中让其自动执行 return BT_STATUS_SUCCESS; }
当前的状态是idle,
/***************************************************************************** ** ** Function btif_av_state_idle_handler ** ** Description State managing disconnected AV link ** ** Returns TRUE if event was processed, FALSE otherwise ** *******************************************************************************/ static BOOLEAN btif_av_state_idle_handler(btif_sm_event_t event, void *p_data) { switch (event) { ... case BTIF_AV_CONNECT_REQ_EVT: { if (event == BTIF_AV_CONNECT_REQ_EVT) { memcpy(&btif_av_cb.peer_bda, ((btif_av_connect_req_t*)p_data)->target_bda, sizeof(bt_bdaddr_t)); BTA_AvOpen(btif_av_cb.peer_bda.address, btif_av_cb.bta_handle, TRUE, BTA_SEC_AUTHENTICATE, ((btif_av_connect_req_t*)p_data)->uuid);//BTA_AvOpen } else if (event == BTA_AV_PENDING_EVT) { bdcpy(btif_av_cb.peer_bda.address, ((tBTA_AV*)p_data)->pend.bd_addr); BTA_AvOpen(btif_av_cb.peer_bda.address, btif_av_cb.bta_handle, TRUE, BTA_SEC_AUTHENTICATE, UUID_SERVCLASS_AUDIO_SOURCE); } btif_sm_change_state(btif_av_cb.sm_handle, BTIF_AV_STATE_OPENING);//上报状态 } break;
状态改变,最终会上报到JNI层,这里就不分析了。下面我们重点分析BTA_AvOpen 流程,这个BTA_AvOpen 函数名字是不是有点眼熟?在a2dp初始化的流程中,出现的两个函数是BTA_AvEnable和BTA_AvRegister,这里来分析其兄弟函数BTA_AvOpen,
/******************************************************************************* ** ** Function BTA_AvOpen ** ** Description Opens an advanced audio/video connection to a peer device. ** When connection is open callback function is called ** with a BTA_AV_OPEN_EVT. ** ** Returns void ** *******************************************************************************/ void BTA_AvOpen(BD_ADDR bd_addr, tBTA_AV_HNDL handle, BOOLEAN use_rc, tBTA_SEC sec_mask, UINT16 uuid) { tBTA_AV_API_OPEN *p_buf; if ((p_buf = (tBTA_AV_API_OPEN *) GKI_getbuf(sizeof(tBTA_AV_API_OPEN))) != NULL) { p_buf->hdr.event = BTA_AV_API_OPEN_EVT; p_buf->hdr.layer_specific = handle; bdcpy(p_buf->bd_addr, bd_addr); p_buf->use_rc = use_rc; p_buf->sec_mask = sec_mask; p_buf->switch_res = BTA_AV_RS_NONE; p_buf->uuid = uuid; bta_sys_sendmsg(p_buf); } }
发送BTA_AV_API_OPEN_EVT 时间到btu task.继续分析:
根据我们之前的分析,这个event是在bta_av_ssm_execute 里面处理的:
/******************************************************************************* ** ** Function bta_av_ssm_execute ** ** Description Stream state machine event handling function for AV ** ** ** Returns void ** *******************************************************************************/ void bta_av_ssm_execute(tBTA_AV_SCB *p_scb, UINT16 event, tBTA_AV_DATA *p_data) { tBTA_AV_SST_TBL state_table; UINT8 action; int i, xx; ... /* look up the state table for the current state */ state_table = bta_av_sst_tbl[p_scb->state]; event -= BTA_AV_FIRST_SSM_EVT; /* set next state */ p_scb->state = state_table[event][BTA_AV_SNEXT_STATE]; /* execute action functions */ for(i=0; i< BTA_AV_SACTIONS; i++) { if ((action = state_table[event][i]) != BTA_AV_SIGNORE) { (*p_scb->p_act_tbl[action])(p_scb, p_data); } else break; } }
发现其处理思路都是差不多,先去查阅状态转换表,然后去执行,当前的stream state machine 的状态是bta_av_sst_init
/* AP_OPEN_EVT */ {BTA_AV_DO_DISC, BTA_AV_SIGNORE, BTA_AV_OPENING_SST },
发现其执行的动作是BTA_AV_DO_DISC,下一个状态是BTA_AV_OPENING_SST,执行的函数是bta_av_do_disc_a2d,发现现在做的操作是先搜索,我们看看其具体的实现:
/******************************************************************************* ** ** Function bta_av_do_disc_a2d ** ** Description Do service discovery for A2DP. ** ** Returns void ** *******************************************************************************/ void bta_av_do_disc_a2d (tBTA_AV_SCB *p_scb, tBTA_AV_DATA *p_data) { BOOLEAN ok_continue = FALSE; tA2D_SDP_DB_PARAMS db_params; UINT16 attr_list[] = {ATTR_ID_SERVICE_CLASS_ID_LIST, ATTR_ID_PROTOCOL_DESC_LIST, ATTR_ID_BT_PROFILE_DESC_LIST}; UINT16 sdp_uuid = 0; /* UUID for which SDP has to be done */ memcpy (&(p_scb->open_api), &(p_data->api_open), sizeof(tBTA_AV_API_OPEN)); switch(p_data->api_open.switch_res) { case BTA_AV_RS_NONE: if (bta_av_switch_if_needed(p_scb) || !bta_av_link_role_ok(p_scb, A2D_SET_MULTL_BIT)) { /* waiting for role switch result. save the api to control block */ memcpy(&p_scb->q_info.open, &p_data->api_open, sizeof(tBTA_AV_API_OPEN)); p_scb->wait |= BTA_AV_WAIT_ROLE_SW_RES_OPEN; p_scb->q_tag = BTA_AV_Q_TAG_OPEN; } else { ok_continue = TRUE; } break; ... } ... /* store peer addr other parameters */ bta_av_save_addr(p_scb, p_data->api_open.bd_addr); p_scb->sec_mask = p_data->api_open.sec_mask; p_scb->use_rc = p_data->api_open.use_rc; bta_sys_app_open(BTA_ID_AV, p_scb->app_id, p_scb->peer_addr);//power manager相关,略过 /* allocate discovery database */ if (p_scb->p_disc_db == NULL) { p_scb->p_disc_db = (tSDP_DISCOVERY_DB *) GKI_getbuf(BTA_AV_DISC_BUF_SIZE); } /* only one A2D find service is active at a time */ bta_av_cb.handle = p_scb->hndl; if(p_scb->p_disc_db) { /* set up parameters */ db_params.db_len = BTA_AV_DISC_BUF_SIZE; db_params.num_attr = 3; db_params.p_db = p_scb->p_disc_db; db_params.p_attrs = attr_list; p_scb->uuid_int = p_data->api_open.uuid; if (p_scb->uuid_int == UUID_SERVCLASS_AUDIO_SINK) sdp_uuid = UUID_SERVCLASS_AUDIO_SOURCE; else if (p_scb->uuid_int == UUID_SERVCLASS_AUDIO_SOURCE) sdp_uuid = UUID_SERVCLASS_AUDIO_SINK; APPL_TRACE_DEBUG("uuid_int 0x%x, Doing SDP For 0x%x", p_scb->uuid_int, sdp_uuid); if(A2D_FindService(sdp_uuid, p_scb->peer_addr, &db_params, bta_av_a2d_sdp_cback) == A2D_SUCCESS)//具体搜索sink service { return; } } ... }
上面的流程很简单,就是进行 sink service的搜索, 其实搜索的att list如下:
UINT16 attr_list[] = {ATTR_ID_SERVICE_CLASS_ID_LIST,
ATTR_ID_PROTOCOL_DESC_LIST,
ATTR_ID_BT_PROFILE_DESC_LIST};
我们继续看搜索服务的实现流程:
/****************************************************************************** ** ** Function A2D_FindService ** ** Description This function is called by a client application to ** perform service discovery and retrieve SRC or SNK SDP ** record information from a server. Information is ** returned for the first service record found on the ** server that matches the service UUID. The callback ** function will be executed when service discovery is ** complete. There can only be one outstanding call to ** A2D_FindService() at a time; the application must wait ** for the callback before it makes another call to ** the function. ** ** Input Parameters: ** service_uuid: Indicates SRC or SNK. ** ** bd_addr: BD address of the peer device. ** ** p_db: Pointer to the information to initialize ** the discovery database. ** ** p_cback: Pointer to the A2D_FindService() ** callback function. ** ** Output Parameters: ** None. ** ** Returns A2D_SUCCESS if function execution succeeded, ** A2D_INVALID_PARAMS if bad parameters are given. ** A2D_BUSY if discovery is already in progress. ** A2D_FAIL if function execution failed. ** ******************************************************************************/ tA2D_STATUS A2D_FindService(UINT16 service_uuid, BD_ADDR bd_addr, tA2D_SDP_DB_PARAMS *p_db, tA2D_FIND_CBACK *p_cback) { tSDP_UUID uuid_list; BOOLEAN result = TRUE; UINT16 a2d_attr_list[] = {ATTR_ID_SERVICE_CLASS_ID_LIST, /* update A2D_NUM_ATTR, if changed */ ATTR_ID_BT_PROFILE_DESC_LIST, ATTR_ID_SUPPORTED_FEATURES, ATTR_ID_SERVICE_NAME, ATTR_ID_PROTOCOL_DESC_LIST, ATTR_ID_PROVIDER_NAME}; /* set up discovery database */ uuid_list.len = LEN_UUID_16; uuid_list.uu.uuid16 = service_uuid; if(p_db->p_attrs == NULL || p_db->num_attr == 0)//已经设置好了 { p_db->p_attrs = a2d_attr_list; p_db->num_attr = A2D_NUM_ATTR; } result = SDP_InitDiscoveryDb(p_db->p_db, p_db->db_len, 1, &uuid_list, p_db->num_attr, p_db->p_attrs);//初始化数据库 if (result == TRUE) { /* store service_uuid and discovery db pointer */ a2d_cb.find.p_db = p_db->p_db; a2d_cb.find.service_uuid = service_uuid; a2d_cb.find.p_cback = p_cback; /* perform service search */ result = SDP_ServiceSearchAttributeRequest(bd_addr, p_db->p_db, a2d_sdp_cback);//执行service search if(FALSE == result) { a2d_cb.find.service_uuid = 0; } } return (result ? A2D_SUCCESS : A2D_FAIL); }
关于SDP_ServiceSearchAttributeRequest 这个函数在sdp 服务搜索流程 中已经详细讲过,这里不再详细讲解,搜索完成之后执行回调函数a2d_sdp_cback,我们看其实现:
/****************************************************************************** ** ** Function a2d_sdp_cback ** ** Description This is the SDP callback function used by A2D_FindService. ** This function will be executed by SDP when the service ** search is completed. If the search is successful, it ** finds the first record in the database that matches the ** UUID of the search. Then retrieves various parameters ** from the record. When it is finished it calls the ** application callback function. ** ** Returns Nothing. ** ******************************************************************************/ static void a2d_sdp_cback(UINT16 status) { tSDP_DISC_REC *p_rec = NULL; tSDP_DISC_ATTR *p_attr; BOOLEAN found = FALSE; tA2D_Service a2d_svc; tSDP_PROTOCOL_ELEM elem; if (status == SDP_SUCCESS) { /* loop through all records we found */ do { /* get next record; if none found, we're done */ if ((p_rec = SDP_FindServiceInDb(a2d_cb.find.p_db, a2d_cb.find.service_uuid, p_rec)) == NULL) { break; } memset(&a2d_svc, 0, sizeof(tA2D_Service)); /* get service name */ if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_SERVICE_NAME)) != NULL) { a2d_svc.p_service_name = (