From f1b34e8dd9b1fdccaf026fb61fff3e60bf7a0dd7 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Sun, 19 Mar 2017 22:32:10 +0100 Subject: [PATCH 01/12] pbap: add Phone Book Access Client (PBAP) over General Object Exchange (GOEP) create RFCOMM connection send OBEX Connect and receive response retrieve phone book repeat GET request when response incomplete start extracting goep_client.h support CONNECT message support GET message add obex_iterator use obex_iterator goep_client adds connection_id if available avoid accessing internal goep state from pbap start extracting pbap_client.h events: add meta events and data packet types for GOEP and PBAP use events and data packet type for goep use events and data packet type for pbap remove private fields from GOEP_SUBEVENT_CONNECTION_OPENED implement SetPhoneBook return cid in connect operations, return errors if busy, return obex errors limit OBEX packet size by bearer MTU split into obex.h, goep_client.h, goep_client.c, pbap_client.h, pbab_client.c fix state after pull phonebook add console UI for testing clean up code provide packet_handler in create connection for pbap_client and goep_client annotate headers for goep_client and pbap_client --- example/Makefile.inc | 4 + example/pbap_client_demo.c | 246 +++++++++++++++++++++ example/sm_pairing_peripheral.c | 2 +- src/btstack_defines.h | 67 +++++- src/btstack_event.h | 181 +++++++++++++++- src/classic/goep_client.c | 328 ++++++++++++++++++++++++++++ src/classic/goep_client.h | 177 +++++++++++++++ src/classic/obex.h | 82 +++++++ src/classic/obex_iterator.c | 181 ++++++++++++++++ src/classic/obex_iterator.h | 76 +++++++ src/classic/pbap_client.c | 371 ++++++++++++++++++++++++++++++++ src/classic/pbap_client.h | 90 ++++++++ tool/btstack_event_generator.py | 27 ++- 13 files changed, 1821 insertions(+), 11 deletions(-) create mode 100644 example/pbap_client_demo.c create mode 100644 src/classic/goep_client.c create mode 100644 src/classic/goep_client.h create mode 100644 src/classic/obex.h create mode 100644 src/classic/obex_iterator.c create mode 100644 src/classic/obex_iterator.h create mode 100644 src/classic/pbap_client.c create mode 100644 src/classic/pbap_client.h diff --git a/example/Makefile.inc b/example/Makefile.inc index a64823b0d..497d25640 100644 --- a/example/Makefile.inc +++ b/example/Makefile.inc @@ -112,6 +112,7 @@ EXAMPLES = \ hsp_hs_demo \ sm_pairing_peripheral \ sm_pairing_central \ + pbap_client \ EXAMPLES_USING_LE = \ ancs_client_demo \ @@ -158,6 +159,9 @@ sm_pairing_peripheral.h: sm_pairing_peripheral.gatt sdp_rfcomm_query: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${PAN_OBJ} ${SDP_CLIENT} sdp_rfcomm_query.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ +pbap_client: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} obex_iterator.c goep_client.c pbap_client.c pbap_client_demo.c + ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + sdp_general_query: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} sdp_general_query.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ diff --git a/example/pbap_client_demo.c b/example/pbap_client_demo.c new file mode 100644 index 000000000..7d31573df --- /dev/null +++ b/example/pbap_client_demo.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#include "btstack_config.h" + +#include +#include +#include +#include + +#include "btstack_run_loop.h" +#include "l2cap.h" +#include "rfcomm.h" +#include "btstack_event.h" +#include "classic/goep_client.h" +#include "classic/pbap_client.h" + +#ifdef HAVE_POSIX_STDIN +#include +#include "stdin_support.h" +#endif + +#ifndef HAVE_POSIX_STDIN +static int step = 0; +#endif + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); + +static bd_addr_t remote_addr; +// MBP2016 "F4-0F-24-3B-1B-E1" +// Nexus 7 "30-85-A9-54-2E-78" +static const char * remote_addr_string = "30-85-A9-54-2E-78"; + +static btstack_packet_callback_registration_t hci_event_callback_registration; +static uint16_t pbap_cid; + +#ifdef HAVE_POSIX_STDIN + +// Testig User Interface +static void show_usage(void){ + bd_addr_t iut_address; + gap_local_bd_addr(iut_address); + + printf("\n--- Bluetooth PBAP Client (HF) Test Console %s ---\n", bd_addr_to_str(iut_address)); + printf("\n"); + printf("a - establish PBAP connection to %s\n", bd_addr_to_str(remote_addr)); + printf("b - set phonebook '/telecom/pb'\n"); + printf("c - set phonebook '/SIM1/telecom/pb'\n"); + printf("d - pull phonebook\n"); + printf("e - disconnnect\n"); + printf("---\n"); + printf("Ctrl-c - exit\n"); + printf("---\n"); +} + +static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ + UNUSED(ds); + UNUSED(callback_type); + + char cmd = btstack_stdin_read(); + + switch (cmd){ + case 'a': + printf("[+] Connecting to %s...\n", bd_addr_to_str(remote_addr)); + pbap_connect(&packet_handler, remote_addr, &pbap_cid); + break; + case 'b': + printf("[+] Set Phonebook 'telecom/pb'\n"); + pbap_set_phonebook(pbap_cid, "telecom/pb"); + break; + case 'c': + printf("[+] Set Phonebook 'SIM1/telecom/pb'\n"); + pbap_set_phonebook(pbap_cid, "SIM1/telecom/pb"); + break; + case 'd': + pbap_pull_phonebook(pbap_cid); + break; + case 'e': + pbap_disconnect(pbap_cid); + break; + default: + show_usage(); + break; + } +} + +// packet handler for interactive console +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + int i; + switch (packet_type){ + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case BTSTACK_EVENT_STATE: + // BTstack activated, get started + if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING){ + show_usage(); + } + break; + case HCI_EVENT_PBAP_META: + switch (hci_event_pbap_meta_get_subevent_code(packet)){ + case PBAP_SUBEVENT_CONNECTION_OPENED: + printf("[+] Connected\n"); + break; + case PBAP_SUBEVENT_CONNECTION_CLOSED: + printf("[+] Connection closed\n"); + break; + case PBAP_SUBEVENT_OPERATION_COMPLETED: + printf("[+] Operation complete\n"); + break; + default: + break; + } + break; + default: + break; + } + break; + case PBAP_DATA_PACKET: + for (i=0;i +#include +#include +#include + +#include "btstack_debug.h" +#include "hci_dump.h" +#include "bluetooth_sdp.h" +#include "btstack_event.h" +#include "classic/goep_client.h" +#include "classic/obex.h" +#include "classic/obex_iterator.h" +#include "classic/rfcomm.h" +#include "classic/sdp_client_rfcomm.h" + +//------------------------------------------------------------------------------------------------------------ +// goep_client.c +// + +typedef enum { + GOEP_INIT, + GOEP_W4_SDP, + GOEP_W4_CONNECTION, + GOEP_CONNECTED, +} goep_state_t; + +typedef struct { + uint16_t cid; + goep_state_t state; + bd_addr_t bd_addr; + hci_con_handle_t con_handle; + uint8_t incoming; + uint8_t bearer_l2cap; + uint16_t bearer_port; // l2cap: psm, rfcomm: channel nr + uint16_t bearer_cid; + uint16_t bearer_mtu; + + uint8_t obex_opcode; + uint32_t obex_connection_id; + int obex_connection_id_set; + + btstack_packet_handler_t client_handler; +} goep_client_t; + +static goep_client_t _goep_client; +static goep_client_t * goep_client = &_goep_client; + +static inline void goep_client_emit_connected_event(goep_client_t * context, uint8_t status){ + uint8_t event[22]; + int pos = 0; + event[pos++] = HCI_EVENT_GOEP_META; + pos++; // skip len + event[pos++] = GOEP_SUBEVENT_CONNECTION_OPENED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[pos++] = status; + memcpy(&event[pos], context->bd_addr, 6); + pos += 6; + little_endian_store_16(event,pos,context->con_handle); + pos += 2; + event[pos++] = context->incoming; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("goep_client_emit_connected_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static inline void goep_client_emit_connection_closed_event(goep_client_t * context){ + uint8_t event[5]; + int pos = 0; + event[pos++] = HCI_EVENT_GOEP_META; + pos++; // skip len + event[pos++] = GOEP_SUBEVENT_CONNECTION_CLOSED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("goep_client_emit_connection_closed_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static inline void goep_client_emit_can_send_now_event(goep_client_t * context){ + uint8_t event[5]; + int pos = 0; + event[pos++] = HCI_EVENT_GOEP_META; + pos++; // skip len + event[pos++] = GOEP_SUBEVENT_CAN_SEND_NOW; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("goep_client_emit_can_send_now_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static void goep_client_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + uint8_t status; + switch (packet_type){ + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case RFCOMM_EVENT_CHANNEL_OPENED: + status = rfcomm_event_channel_opened_get_status(packet); + if (status) { + log_info("goep_client: RFCOMM channel open failed, status %u", rfcomm_event_channel_opened_get_status(packet)); + goep_client->state = GOEP_INIT; + } else { + goep_client->bearer_mtu = rfcomm_event_channel_opened_get_max_frame_size(packet); + log_info("goep_client: RFCOMM channel open succeeded. cid %u, max frame size %u", goep_client->bearer_cid, goep_client->bearer_mtu); + goep_client->state = GOEP_CONNECTED; + } + goep_client_emit_connected_event(goep_client, status); + return; + case RFCOMM_EVENT_CAN_SEND_NOW: + goep_client_emit_can_send_now_event(goep_client); + break; + case RFCOMM_CHANNEL_CLOSED: + goep_client->state = GOEP_INIT; + goep_client_emit_connection_closed_event(goep_client); + break; + default: + break; + } + break; + case RFCOMM_DATA_PACKET: + goep_client->client_handler(GOEP_DATA_PACKET, goep_client->cid, packet, size); + break; + default: + break; + } +} + +static void goep_client_handle_query_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(packet_type); + UNUSED(channel); + UNUSED(size); + + switch (packet[0]){ + case SDP_EVENT_QUERY_RFCOMM_SERVICE: + goep_client->bearer_port = sdp_event_query_rfcomm_service_get_rfcomm_channel(packet); + break; + case SDP_EVENT_QUERY_COMPLETE: + if (goep_client->bearer_port == 0){ + log_info("Remote GOEP RFCOMM Server Channel not found"); + goep_client->state = GOEP_INIT; + goep_client_emit_connected_event(goep_client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + break; + } + log_info("Remote GOEP RFCOMM Server Channel: %u", goep_client->bearer_port); + rfcomm_create_channel(&goep_client_packet_handler, goep_client->bd_addr, goep_client->bearer_port, &goep_client->bearer_cid); + break; + } +} + +static void goep_client_packet_append(const uint8_t * data, uint16_t len){ + uint8_t * buffer = rfcomm_get_outgoing_buffer(); + uint16_t pos = big_endian_read_16(buffer, 1); + memcpy(&buffer[pos], data, len); + pos += len; + big_endian_store_16(buffer, 1, pos); +} + +static void goep_client_packet_init(uint16_t goep_cid, uint8_t opcode){ + UNUSED(goep_cid); + rfcomm_reserve_packet_buffer(); + uint8_t * buffer = rfcomm_get_outgoing_buffer(); + buffer[0] = opcode; + big_endian_store_16(buffer, 1, 3); + // store opcode for parsing of response + goep_client->obex_opcode = opcode; +} + +static void goep_client_packet_add_connection_id(uint16_t goep_cid){ + UNUSED(goep_cid); + // add connection_id header if set, must be first header if used + if (goep_client->obex_connection_id != OBEX_CONNECTION_ID_INVALID){ + uint8_t header[5]; + header[0] = OBEX_HEADER_CONNECTION_ID; + big_endian_store_32(header, 1, goep_client->obex_connection_id); + goep_client_packet_append(&header[0], sizeof(header)); + } +} + +void goep_client_init(void){ + memset(goep_client, 0, sizeof(goep_client_t)); + goep_client->state = GOEP_INIT; + goep_client->cid = 1; + goep_client->obex_connection_id = OBEX_CONNECTION_ID_INVALID; +} + +uint8_t goep_client_create_connection(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t uuid, uint16_t * out_cid){ + if (goep_client->state != GOEP_INIT) return BTSTACK_MEMORY_ALLOC_FAILED; + goep_client->client_handler = handler; + goep_client->state = GOEP_W4_SDP; + memcpy(goep_client->bd_addr, addr, 6); + sdp_client_query_rfcomm_channel_and_name_for_uuid(&goep_client_handle_query_rfcomm_event, goep_client->bd_addr, uuid); + *out_cid = goep_client->cid; + return 0; +} + +uint8_t goep_client_disconnect(uint16_t goep_cid){ + UNUSED(goep_cid); + rfcomm_disconnect(goep_client->bearer_cid); + return 0; +} + +void goep_client_set_connection_id(uint16_t goep_cid, uint32_t connection_id){ + UNUSED(goep_cid); + goep_client->obex_connection_id = connection_id; +} + +uint8_t goep_client_get_request_opcode(uint16_t goep_cid){ + UNUSED(goep_cid); + return goep_client->obex_opcode; +} + +void goep_client_request_can_send_now(uint16_t goep_cid){ + UNUSED(goep_cid); + rfcomm_request_can_send_now_event(goep_client->bearer_cid); +} + +void goep_client_create_connect_request(uint16_t goep_cid, uint8_t obex_version_number, uint8_t flags, uint16_t maximum_obex_packet_length){ + UNUSED(goep_cid); + goep_client_packet_init(goep_cid, OBEX_OPCODE_CONNECT); + uint8_t fields[4]; + fields[0] = obex_version_number; + fields[1] = flags; + // workaround: limit OBEX packet len to RFCOMM MTU to avoid handling of fragemented packets + maximum_obex_packet_length = btstack_min(maximum_obex_packet_length, goep_client->bearer_mtu); + big_endian_store_16(fields, 2, maximum_obex_packet_length); + goep_client_packet_append(&fields[0], sizeof(fields)); +} + +void goep_client_create_get_request(uint16_t goep_cid){ + UNUSED(goep_cid); + goep_client_packet_init(goep_cid, OBEX_OPCODE_GET | OBEX_OPCODE_FINAL_BIT_MASK); + goep_client_packet_add_connection_id(goep_cid); +} + +void goep_client_create_set_path_request(uint16_t goep_cid, uint8_t flags){ + UNUSED(goep_cid); + goep_client_packet_init(goep_cid, OBEX_OPCODE_SETPATH); + uint8_t fields[2]; + fields[0] = flags; + fields[1] = 0; // reserved + goep_client_packet_append(&fields[0], sizeof(fields)); + goep_client_packet_add_connection_id(goep_cid); +} + +void goep_client_add_header_target(uint16_t goep_cid, uint16_t length, const uint8_t * target){ + UNUSED(goep_cid); + uint8_t header[3]; + header[0] = OBEX_HEADER_TARGET; + big_endian_store_16(header, 1, 1 + 2 + length); + goep_client_packet_append(&header[0], sizeof(header)); + goep_client_packet_append(target, length); +} + +void goep_client_add_header_name(uint16_t goep_cid, const char * name){ + UNUSED(goep_cid); + int len_incl_zero = strlen(name) + 1; + uint8_t * buffer = rfcomm_get_outgoing_buffer(); + uint16_t pos = big_endian_read_16(buffer, 1); + buffer[pos++] = OBEX_HEADER_NAME; + big_endian_store_16(buffer, pos, 1 + 2 + len_incl_zero*2); + pos += 2; + int i; + // @note name[len] == 0 + for (i = 0 ; i < len_incl_zero ; i++){ + buffer[pos++] = 0; + buffer[pos++] = *name++; + } + big_endian_store_16(buffer, 1, pos); + } + +void goep_client_add_header_type(uint16_t goep_cid, const char * type){ + UNUSED(goep_cid); + uint8_t header[3]; + header[0] = OBEX_HEADER_TYPE; + int len_incl_zero = strlen(type) + 1; + big_endian_store_16(header, 1, 1 + 2 + len_incl_zero); + goep_client_packet_append(&header[0], sizeof(header)); + goep_client_packet_append((const uint8_t*)type, len_incl_zero); +} + +int goep_client_execute(uint16_t goep_cid){ + UNUSED(goep_cid); + uint8_t * buffer = rfcomm_get_outgoing_buffer(); + uint16_t pos = big_endian_read_16(buffer, 1); + return rfcomm_send_prepared(goep_client->bearer_cid, pos); +} diff --git a/src/classic/goep_client.h b/src/classic/goep_client.h new file mode 100644 index 000000000..c087f9712 --- /dev/null +++ b/src/classic/goep_client.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#ifndef __GOEP_CLIENT_H + +#if defined __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#include "btstack_defines.h" + +//------------------------------------------------------------------------------------------------------------ +// goep_client.h +// +// Communicate with remote OBEX server - General Object Exchange +// + +/* API_START */ + +/** + * Setup GOEP Client + */ +void goep_client_init(void); + +/* + * @brief Create GOEP connection to a GEOP server with specified UUID on a remote deivce. + * @param handler + * @param addr + * @param uuid + * @param out_cid to use for further commands + * @result status +*/ +uint8_t goep_client_create_connection(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t uuid, uint16_t * out_cid); + +/** + * @brief Disconnects GOEP connection with given identifier. + * @param gope_cid + * @return status + */ +uint8_t goep_client_disconnect(uint16_t goep_cid); + +/** + * @brief Request emission of GOEP_SUBEVENT_CAN_SEND_NOW as soon as possible + * @note GOEP_SUBEVENT_CAN_SEND_NOW might be emitted during call to this function + * so packet handler should be ready to handle it + * @param goep_cid + */ +void goep_client_request_can_send_now(uint16_t goep_cid); + +/** + * @brief Get Opcode from last created request, needed for parsing of OBEX response packet + * @param gope_cid + * @return opcode + */ +uint8_t goep_client_get_request_opcode(uint16_t goep_cid); + +/** + * @brief Set Connection ID used for newly created requests + * @param gope_cid + */ +void goep_client_set_connection_id(uint16_t goep_cid, uint32_t connection_id); + +/** + * @brief Start Connect request + * @param gope_cid + * @param obex_version_number + * @param flags + * @param maximum_obex_packet_length + */ +void goep_client_create_connect_request(uint16_t goep_cid, uint8_t obex_version_number, uint8_t flags, uint16_t maximum_obex_packet_length); + +/** + * @brief Start Get request + * @param gope_cid + */ +void goep_client_create_get_request(uint16_t goep_cid); + +/** + * @brief Start Set Path request + * @param gope_cid + */ +void goep_client_create_set_path_request(uint16_t goep_cid, uint8_t flags); + +// not implemented yet +// void goep_client_create_put(uint16_t goep_cid); + +/** + * @brief Add name header to current request + * @param goep_cid + * @param name + */ +void goep_client_add_header_name(uint16_t goep_cid, const char * name); + +/** + * @brief Add target header to current request + * @param goep_cid + * @param target + */ +void goep_client_add_header_target(uint16_t goep_cid, uint16_t length, const uint8_t * target); + +/** + * @brief Add type header to current request + * @param goep_cid + * @param type + */ +void goep_client_add_header_type(uint16_t goep_cid, const char * type); + +/** + * @brief Add count header to current request + * @param goep_cid + * @param count + */ +void goep_client_add_header_count(uint16_t goep_cid, uint32_t count); + +/** + * @brief Add application parameters header to current request + * @param goep_cid + * @param lenght of application parameters + * @param daa + */ +void goep_client_add_header_application_parameters(uint16_t goep_cid, uint16_t length, uint8_t * data); + +// int goep_client_add_body_static(uint16_t goep_cid, uint32_t length, uint8_t * data); +// int goep_client_add_body_dynamic(uint16_t goep_cid, uint32_t length, void (*data_callback)(uint32_t offset, uint8_t * buffer, uint32_t len)); + +/** + * @brief Execute prepared request + * @param goep_cid + * @param daa + */ +int goep_client_execute(uint16_t goep_cid); + +/* API_END */ + +#if defined __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/classic/obex.h b/src/classic/obex.h new file mode 100644 index 000000000..22428bfc8 --- /dev/null +++ b/src/classic/obex.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#ifndef __OBEX_H + +#define OBEX_OPCODE_CONNECT 0x80 +#define OBEX_OPCODE_DISCONNECT 0x81 +#define OBEX_OPCODE_PUT 0x02 +#define OBEX_OPCODE_CLOSE 0x82 +#define OBEX_OPCODE_GET 0x03 +#define OBEX_OPCODE_SETPATH 0x85 +#define OBEX_OPCODE_SESSION 0x87 +#define OBEX_OPCODE_ABORT 0xFF + +#define OBEX_RESP_SUCCESS 0xA0 +#define OBEX_RESP_CONTINUE 0x90 +#define OBEX_RESP_CANCELED 0xC1 +#define OBEX_RESP_NOT_FOUND 0xC4 +#define OBEX_RESP_REFUSED 0xC6 + +#define OBEX_HEADER_BODY 0x48 +#define OBEX_HEADER_END_OF_BODY 0x49 +#define OBEX_HEADER_COUNT 0xC0 +#define OBEX_HEADER_NAME 0x01 +#define OBEX_HEADER_TYPE 0x42 +#define OBEX_HEADER_LENGTH 0xC3 +#define OBEX_HEADER_TIME_ISO_8601 0x44 +#define OBEX_HEADER_TIME_4_BYTE 0xC4 +#define OBEX_HEADER_DESCRIPTION 0x05 +#define OBEX_HEADER_TARGET 0x46 +#define OBEX_HEADER_HTTP 0x47 +#define OBEX_HEADER_WHO 0x4A +#define OBEX_HEADER_OBJECT_CLASS 0x4F +#define OBEX_HEADER_APPLICATION_PARAMETERS 0x4C +#define OBEX_HEADER_CONNECTION_ID 0xCb + +#define OBEX_OPCODE_FINAL_BIT_MASK 0x80 + +#define OBEX_VERSION 0x14 + +#define OBEX_PACKET_HEADER_SIZE 3 +#define OBEX_PACKET_OPCODE_OFFSET 0 +#define OBEX_PACKET_LENGTH_OFFSET 1 +#define OBEX_MAX_PACKETLEN_DEFAULT 0xffff + +#define OBEX_CONNECTION_ID_INVALID 0xFFFFFFFF + +#endif diff --git a/src/classic/obex_iterator.c b/src/classic/obex_iterator.c new file mode 100644 index 000000000..0851ea20f --- /dev/null +++ b/src/classic/obex_iterator.c @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#include "btstack_config.h" + +#include +#include + +#include "hci_cmd.h" +#include "btstack_debug.h" +#include "hci.h" +#include "bluetooth_sdp.h" +#include "classic/sdp_client_rfcomm.h" +#include "btstack_event.h" + +#include "classic/obex.h" +#include "classic/obex_iterator.h" + +static int obex_packet_header_offset_for_opcode(uint8_t opcode){ + switch (opcode){ + case OBEX_OPCODE_SETPATH: + return 5; + case OBEX_OPCODE_CONNECT: + return 7; + default: + return 3; + } +} + +static void obex_iterator_init(obex_iterator_t *context, int header_offset, const uint8_t * packet_data, uint16_t packet_len){ + memset(context, 0, sizeof(obex_iterator_t)); + context->data = packet_data + header_offset; + context->length = packet_len - header_offset; +} + +void obex_iterator_init_with_request_packet(obex_iterator_t *context, const uint8_t * packet_data, uint16_t packet_len){ + int header_offset = obex_packet_header_offset_for_opcode(packet_data[0]); + obex_iterator_init(context, header_offset, packet_data, packet_len); +} + +void obex_iterator_init_with_response_packet(obex_iterator_t *context, uint8_t request_opcode, const uint8_t * packet_data, uint16_t packet_len){ + int header_offset = request_opcode == OBEX_OPCODE_CONNECT ? 7 : 3; + obex_iterator_init(context, header_offset, packet_data, packet_len); +} + +int obex_iterator_has_more(const obex_iterator_t * context){ + return context->offset < context->length; +} + +void obex_iterator_next(obex_iterator_t * context){ + int len = 0; + const uint8_t * data = context->data + context->offset; + int encoding = data[0] >> 6; + switch (encoding){ + case 0: + case 1: + // 16-bit length info prefixed + len = 2 + big_endian_read_16(data, 1); + break; + case 2: + // 8-bit value + len = 1; + break; + case 3: + // 32-bit value + len = 4; + break; + // avoid compiler warning about unused cases (by unclever compilers) + default: + break; + } + context->offset += 1 + len; +} + +// OBEX packet header access functions + +// @note BODY/END-OF-BODY headers might be incomplete +uint8_t obex_iterator_get_hi(const obex_iterator_t * context){ + return context->data[context->offset]; +} +uint8_t obex_iterator_get_data_8(const obex_iterator_t * context){ + return context->data[context->offset+1]; +} +uint32_t obex_iterator_get_data_32(const obex_iterator_t * context){ + return big_endian_read_32(context->data, context->offset + 1); +} +uint32_t obex_iterator_get_data_len(const obex_iterator_t * context){ + const uint8_t * data = context->data + context->offset; + int encoding = data[0] >> 6; + switch (encoding){ + case 0: + case 1: + // 16-bit length info prefixed + return big_endian_read_16(data, 1) - 3; + case 2: + // 8-bit value + return 1; + case 3: + // 32-bit value + return 4; + // avoid compiler warning about unused cases (by unclever compilers) + default: + return 0; + } +} + +const uint8_t * obex_iterator_get_data(const obex_iterator_t * context){ + const uint8_t * data = context->data + context->offset; + int encoding = data[0] >> 6; + switch (encoding){ + case 0: + case 1: + // 16-bit length info prefixed + return &data[3]; + default: + // 8-bit value + // 32-bit value + return &data[1]; + } +} + +void obex_dump_packet(uint8_t request_opcode, uint8_t * packet, uint16_t size){ + // printf("RCV: '"); + // printf_hexdump(packet, size); + obex_iterator_t it; + printf("Opcode: 0x%02x\n", packet[0]); + for (obex_iterator_init_with_response_packet(&it, request_opcode, packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){ + uint8_t hi = obex_iterator_get_hi(&it); + printf("HI: %x - ", hi); + uint8_t encoding = hi >> 6; + uint16_t len; + switch (encoding){ + case 0: + case 1: + len = obex_iterator_get_data_len(&it); + printf_hexdump(obex_iterator_get_data(&it), len); + break; + case 2: + printf("%02x\n", obex_iterator_get_data_8(&it)); + break; + case 3: + printf("%08x\n", (int) obex_iterator_get_data_32(&it)); + break; + } + + } +} diff --git a/src/classic/obex_iterator.h b/src/classic/obex_iterator.h new file mode 100644 index 000000000..0d837180a --- /dev/null +++ b/src/classic/obex_iterator.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#ifndef __OBEX_ITERATOR_H + +#if defined __cplusplus +extern "C" { +#endif + +#include + +/* API_START */ + +typedef struct obex_iterator { + const uint8_t * data; + uint8_t offset; + uint8_t length; +} obex_iterator_t; + +// OBEX packet header iterator +void obex_iterator_init_with_request_packet(obex_iterator_t *context, const uint8_t * packet_data, uint16_t packet_len); +void obex_iterator_init_with_response_packet(obex_iterator_t *context, uint8_t request_opcode, const uint8_t * packet_data, uint16_t packet_len); +int obex_iterator_has_more(const obex_iterator_t * context); +void obex_iterator_next(obex_iterator_t * context); + +// OBEX packet header access functions +// @note BODY/END-OF-BODY headers might be incomplete +uint8_t obex_iterator_get_hi(const obex_iterator_t * context); +uint8_t obex_iterator_get_data_8(const obex_iterator_t * context); +uint32_t obex_iterator_get_data_32(const obex_iterator_t * context); +uint32_t obex_iterator_get_data_len(const obex_iterator_t * context); +const uint8_t * obex_iterator_get_data(const obex_iterator_t * context); + +/* API_END */ + +// debug +void obex_dump_packet(uint8_t request_opcode, uint8_t * packet, uint16_t size); + +#if defined __cplusplus +} +#endif +#endif diff --git a/src/classic/pbap_client.c b/src/classic/pbap_client.c new file mode 100644 index 000000000..64e18e8cc --- /dev/null +++ b/src/classic/pbap_client.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +// ***************************************************************************** +// +#if 0 + 0x0000 = uint32(65542), + // BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE + 0x0001 = { uuid16(11 2f) }, + // BLUETOOTH_PROTOCOL_L2CAP, BLUETOOTH_PROTOCOL_RFCOMM, BLUETOOTH_PROTOCOL_OBEX + 0x0004 = { { uuid16(01 00) }, { uuid16(00 03), uint8(19) }, { uuid16(00 08) } } + 0x0005 = { uuid16(10 02) }, + // BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS, v1.01 = 0x101 + 0x0009 = { { uuid16(11 30), uint16(257) } }, + 0x0100 = string(OBEX Phonebook Access Server + // BLUETOOTH_ATTRIBUTE_SUPPORTED_FEATURES -- should be 0x317 BLUETOOTH_ATTRIBUTE_PBAP_SUPPORTED_FEATURES? + 0x0311 = uint8(3), + // BLUETOOTH_ATTRIBUTE_SUPPORTED_REPOSITORIES + 0x0314 = uint8(1), +#endif +// +// ***************************************************************************** + +#include "btstack_config.h" + +#include +#include +#include +#include + +#include "hci_cmd.h" +#include "btstack_run_loop.h" +#include "btstack_debug.h" +#include "hci.h" +#include "btstack_memory.h" +#include "hci_dump.h" +#include "l2cap.h" +#include "bluetooth_sdp.h" +#include "classic/sdp_client_rfcomm.h" +#include "btstack_event.h" + +#include "classic/obex.h" +#include "classic/obex_iterator.h" +#include "classic/goep_client.h" +#include "classic/pbap_client.h" + +// 796135f0-f0c5-11d8-0966- 0800200c9a66 +uint8_t pbap_uuid[] = { 0x79, 0x61, 0x35, 0xf0, 0xf0, 0xc5, 0x11, 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66}; +const char * pbap_type = "x-bt/phonebook"; +const char * pbap_name = "pb.vcf"; + +typedef enum { + PBAP_INIT = 0, + PBAP_W4_GOEP_CONNECTION, + PBAP_W2_SEND_CONNECT_REQUEST, + PBAP_W4_CONNECT_RESPONSE, + PBAP_CONNECT_RESPONSE_RECEIVED, + PBAP_CONNECTED, + // + PBAP_W2_PULL_PHONE_BOOK, + PBAP_W4_PHONE_BOOK, + PBAP_W2_SET_PATH_ROOT, + PBAP_W4_SET_PATH_ROOT_COMPLETE, + PBAP_W2_SET_PATH_ELEMENT, + PBAP_W4_SET_PATH_ELEMENT_COMPLETE, +} pbap_state_t; + +typedef struct pbap_client { + pbap_state_t state; + uint16_t cid; + bd_addr_t bd_addr; + hci_con_handle_t con_handle; + uint8_t incoming; + uint16_t goep_cid; + btstack_packet_handler_t client_handler; + const char * current_folder; + uint16_t set_path_offset; +} pbap_client_t; + +static pbap_client_t _pbap_client; +static pbap_client_t * pbap_client = &_pbap_client; + +static inline void pbap_client_emit_connected_event(pbap_client_t * context, uint8_t status){ + uint8_t event[15]; + int pos = 0; + event[pos++] = HCI_EVENT_PBAP_META; + pos++; // skip len + event[pos++] = PBAP_SUBEVENT_CONNECTION_OPENED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[pos++] = status; + memcpy(&event[pos], context->bd_addr, 6); + pos += 6; + little_endian_store_16(event,pos,context->con_handle); + pos += 2; + event[pos++] = context->incoming; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("goep_client_emit_connected_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static inline void pbap_client_emit_connection_closed_event(pbap_client_t * context){ + uint8_t event[5]; + int pos = 0; + event[pos++] = HCI_EVENT_PBAP_META; + pos++; // skip len + event[pos++] = PBAP_SUBEVENT_CONNECTION_CLOSED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("pbap_client_emit_connection_closed_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static inline void pbap_client_emit_operation_complete_event(pbap_client_t * context, uint8_t status){ + uint8_t event[6]; + int pos = 0; + event[pos++] = HCI_EVENT_PBAP_META; + pos++; // skip len + event[pos++] = PBAP_SUBEVENT_OPERATION_COMPLETED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[pos++]= status; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("pbap_client_emit_can_send_now_event size %u", pos); + context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static void pbap_handle_can_send_now(void){ + uint8_t path_element[20]; + uint16_t path_element_start; + uint16_t path_element_len; + + switch (pbap_client->state){ + case PBAP_W2_SEND_CONNECT_REQUEST: + goep_client_create_connect_request(pbap_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT); + goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid); + // state + pbap_client->state = PBAP_W4_CONNECT_RESPONSE; + // send packet + goep_client_execute(pbap_client->goep_cid); + return; + case PBAP_W2_PULL_PHONE_BOOK: + goep_client_create_get_request(pbap_client->goep_cid); + goep_client_add_header_type(pbap_client->goep_cid, pbap_type); + goep_client_add_header_name(pbap_client->goep_cid, pbap_name); + // state + pbap_client->state = PBAP_W4_PHONE_BOOK; + // send packet + goep_client_execute(pbap_client->goep_cid); + break; + case PBAP_W2_SET_PATH_ROOT: + goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Don’t create directory + // On Android 4.2 Cyanogenmod, using "" as path fails + // goep_client_add_header_name(pbap_client->goep_cid, ""); // empty == / + // state + pbap_client->state = PBAP_W4_SET_PATH_ROOT_COMPLETE; + // send packet + goep_client_execute(pbap_client->goep_cid); + break; + case PBAP_W2_SET_PATH_ELEMENT: + // find '/' or '\0' + path_element_start = pbap_client->set_path_offset; + while (pbap_client->current_folder[pbap_client->set_path_offset] != '\0' && + pbap_client->current_folder[pbap_client->set_path_offset] != '/'){ + pbap_client->set_path_offset++; + } + // skip / + if (pbap_client->current_folder[pbap_client->set_path_offset] == '/'){ + pbap_client->set_path_offset++; + } + path_element_len = pbap_client->set_path_offset-path_element_start; + memcpy(path_element, &pbap_client->current_folder[path_element_start], path_element_len); + path_element[path_element_len] = 0; + + goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Don’t create directory + goep_client_add_header_name(pbap_client->goep_cid, (const char *) path_element); // next element + // state + pbap_client->state = PBAP_W4_SET_PATH_ELEMENT_COMPLETE; + // send packet + goep_client_execute(pbap_client->goep_cid); + break; + default: + break; + } +} + +static void pbap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + obex_iterator_t it; + uint8_t status; + switch (packet_type){ + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_GOEP_META: + switch (hci_event_goep_meta_get_subevent_code(packet)){ + case GOEP_SUBEVENT_CONNECTION_OPENED: + status = goep_subevent_connection_opened_get_status(packet); + pbap_client->con_handle = goep_subevent_connection_opened_get_con_handle(packet); + pbap_client->incoming = goep_subevent_connection_opened_get_incoming(packet); + goep_subevent_connection_opened_get_bd_addr(packet, pbap_client->bd_addr); + if (status){ + log_info("pbap: connection failed %u", status); + pbap_client->state = PBAP_INIT; + pbap_client_emit_connected_event(pbap_client, status); + } else { + log_info("pbap: connection established"); + pbap_client->goep_cid = goep_subevent_connection_opened_get_goep_cid(packet); + pbap_client->state = PBAP_W2_SEND_CONNECT_REQUEST; + goep_client_request_can_send_now(pbap_client->goep_cid); + } + break; + case GOEP_SUBEVENT_CONNECTION_CLOSED: + if (pbap_client->state != PBAP_CONNECTED){ + pbap_client_emit_operation_complete_event(pbap_client, OBEX_DISCONNECTED); + } + pbap_client->state = PBAP_INIT; + pbap_client_emit_connection_closed_event(pbap_client); + break; + case GOEP_SUBEVENT_CAN_SEND_NOW: + pbap_handle_can_send_now(); + break; + } + break; + default: + break; + } + break; + case GOEP_DATA_PACKET: + // TODO: handle chunked data +#if 0 + obex_dump_packet(goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); +#endif + switch (pbap_client->state){ + case PBAP_W4_CONNECT_RESPONSE: + for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){ + uint8_t hi = obex_iterator_get_hi(&it); + if (hi == OBEX_HEADER_CONNECTION_ID){ + goep_client_set_connection_id(pbap_client->goep_cid, obex_iterator_get_data_32(&it)); + } + } + if (packet[0] == OBEX_RESP_SUCCESS){ + pbap_client->state = PBAP_CONNECTED; + pbap_client_emit_connected_event(pbap_client, 0); + } else { + log_info("pbap: obex connect failed, result 0x%02x", packet[0]); + pbap_client->state = PBAP_INIT; + pbap_client_emit_connected_event(pbap_client, OBEX_CONNECT_FAILED); + } + break; + case PBAP_W4_SET_PATH_ROOT_COMPLETE: + case PBAP_W4_SET_PATH_ELEMENT_COMPLETE: + if (packet[0] == OBEX_RESP_SUCCESS){ + if (pbap_client->current_folder){ + pbap_client->state = PBAP_W2_SET_PATH_ELEMENT; + goep_client_request_can_send_now(pbap_client->goep_cid); + } else { + pbap_client_emit_operation_complete_event(pbap_client, 0); + } + } else if (packet[0] == OBEX_RESP_NOT_FOUND){ + pbap_client->state = PBAP_CONNECTED; + pbap_client_emit_operation_complete_event(pbap_client, OBEX_NOT_FOUND); + } else { + pbap_client->state = PBAP_CONNECTED; + pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR); + } + break; + case PBAP_W4_PHONE_BOOK: + for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){ + uint8_t hi = obex_iterator_get_hi(&it); + if (hi == OBEX_HEADER_BODY || hi == OBEX_HEADER_END_OF_BODY){ + uint16_t data_len = obex_iterator_get_data_len(&it); + const uint8_t * data = obex_iterator_get_data(&it); + pbap_client->client_handler(PBAP_DATA_PACKET, pbap_client->cid, (uint8_t *) data, data_len); + } + } + if (packet[0] == OBEX_RESP_CONTINUE){ + pbap_client->state = PBAP_W2_PULL_PHONE_BOOK; + goep_client_request_can_send_now(pbap_client->goep_cid); + } else if (packet[0] == OBEX_RESP_SUCCESS){ + pbap_client->state = PBAP_CONNECTED; + pbap_client_emit_operation_complete_event(pbap_client, 0); + } else { + pbap_client->state = PBAP_CONNECTED; + pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR); + } + break; + default: + break; + } + break; + default: + break; + } +} + +void pbap_client_init(void){ + memset(pbap_client, 0, sizeof(pbap_client_t)); + pbap_client->state = PBAP_INIT; + pbap_client->cid = 1; +} + +uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid){ + if (pbap_client->state != PBAP_INIT) return BTSTACK_MEMORY_ALLOC_FAILED; + pbap_client->state = PBAP_W4_GOEP_CONNECTION; + pbap_client->client_handler = handler; + uint8_t err = goep_client_create_connection(&pbap_packet_handler, addr, BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE, &pbap_client->goep_cid); + *out_cid = pbap_client->cid; + if (err) return err; + return 0; +} + +uint8_t pbap_disconnect(uint16_t pbap_cid){ + UNUSED(pbap_cid); + if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; + goep_client_disconnect(pbap_client->goep_cid); + return 0; +} + +uint8_t pbap_pull_phonebook(uint16_t pbap_cid){ + UNUSED(pbap_cid); + if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; + pbap_client->state = PBAP_W2_PULL_PHONE_BOOK; + goep_client_request_can_send_now(pbap_client->goep_cid); + return 0; +} + +uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path){ + UNUSED(pbap_cid); + if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY; + pbap_client->state = PBAP_W2_SET_PATH_ROOT; + pbap_client->current_folder = path; + pbap_client->set_path_offset = 0; + goep_client_request_can_send_now(pbap_client->goep_cid); + return 0; +} diff --git a/src/classic/pbap_client.h b/src/classic/pbap_client.h new file mode 100644 index 000000000..dc65c5dfd --- /dev/null +++ b/src/classic/pbap_client.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#ifndef __GOEP_CLIENT_H + +#if defined __cplusplus +extern "C" { +#endif + +#include "btstack_config.h" +#include + +/* API_START */ + +/** + * Setup PhoneBook Access Client + */ +void pbap_client_init(void); + +/** + * @brief Create PBAP connection to a Phone Book Server (PSE) server on a remote deivce. + * @param handler + * @param addr + * @param out_cid to use for further commands + * @result status +*/ +uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid); + +/** + * @brief Disconnects PBAP connection with given identifier. + * @param pbap_cid + * @return status + */ +uint8_t pbap_disconnect(uint16_t pbap_cid); + +/** + * @brief Set current folder on PSE + * @param pbap_cid + * @param path - note: path is not copied + * @return status + */ +uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path); + +/** + * @brief Pull phone book from PSE + * @param pbap_cid + * @return status + */ + uint8_t pbap_pull_phonebook(uint16_t pbap_cid); + +/* API_END */ + +#if defined __cplusplus +} +#endif +#endif diff --git a/tool/btstack_event_generator.py b/tool/btstack_event_generator.py index ab4972ad6..1955a3602 100755 --- a/tool/btstack_event_generator.py +++ b/tool/btstack_event_generator.py @@ -8,6 +8,30 @@ import os import btstack_parser as parser +meta_events = [ + 'ANCS', + 'AVDTP', + 'AVRCP', + 'GOEP', + 'HFP', + 'HSP', + 'PBAP', + 'LE' +] + +supported_event_groups = meta_events + [ + 'BTSTACK', + 'GAP', + 'HCI', + 'SDP', + 'SM', + 'L2CAP', + 'RFCOMM', + 'GATT', + 'BNEP', + 'ATT', +] + program_info = ''' BTstack Event Getter Generator for BTstack Copyright 2016, BlueKitchen GmbH @@ -232,14 +256,13 @@ def create_events(events): fout.write(copyright) fout.write(hfile_header_begin) - meta_events = ['HSP', 'HFP', 'ANCS', 'LE']; for meta_event in meta_events: fout.write(meta_event_template.format(meta_event=meta_event.lower())) for event_type, event_name, format, args in events: parts = event_name.split("_") event_group = parts[0] - if not event_group in [ 'BTSTACK', 'GAP', 'HCI', 'HSP', 'HFP', 'SDP', 'ANCS', 'SM', 'L2CAP', 'RFCOMM', 'GATT', 'BNEP', 'ATT', 'AVDTP', 'AVRCP']: + if not event_group in supported_event_groups: print("// %s " % event_name) continue # print(event_name) From 262fedb80ea021940b93f74ddef6df070dfb3e88 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Mon, 16 Jan 2017 10:48:28 +0100 Subject: [PATCH 02/12] hci: mark hci_write_local_name as Classic only, hci_le_generate_dhkey depend on HAVE_HCI_CONTROLLER_DHKEY_SUPPORT, reduce HCI Command Buffer size for LE only --- doc/notes/hci_commands_used_le.txt | 8 +++++--- src/hci.h | 16 ++++++++++++++++ src/hci_cmd.c | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/doc/notes/hci_commands_used_le.txt b/doc/notes/hci_commands_used_le.txt index 735630d14..5af869ee3 100644 --- a/doc/notes/hci_commands_used_le.txt +++ b/doc/notes/hci_commands_used_le.txt @@ -3,16 +3,16 @@ hci_le_add_device_to_white_list hci_le_connection_update hci_le_create_connection hci_le_create_connection_cancel -hci_le_encrypt +hci_le_encrypt 32 hci_le_long_term_key_negative_reply -hci_le_long_term_key_request_reply +hci_le_long_term_key_request_reply 18 hci_le_rand hci_le_read_buffer_size hci_le_read_white_list_size hci_le_remove_device_from_white_list hci_le_set_advertise_enable hci_le_set_advertising_data -hci_le_set_advertising_parameters +hci_le_set_advertising_parameters 16 hci_le_set_random_address hci_le_set_scan_enable hci_le_set_scan_parameters @@ -27,3 +27,5 @@ hci_read_remote_supported_features_command hci_reset hci_set_event_mask hci_write_le_host_supported + +max parameter length: 32 diff --git a/src/hci.h b/src/hci.h index 2d1b8f1f8..ef685c9f8 100644 --- a/src/hci.h +++ b/src/hci.h @@ -69,10 +69,26 @@ extern "C" { #endif // packet buffer sizes + +// Max HCI Commadn LE payload size: +// 64 from LE Generate DHKey command +// 32 from LE Encrypt command +#if defined(ENABLE_LE_SECURE_CONNECTIONS) && !defined(HAVE_HCI_CONTROLLER_DHKEY_SUPPORT) +#define HCI_CMD_PAYLOAD_SIZE_LE 64 +#else +#define HCI_CMD_PAYLOAD_SIZE_LE 32 +#endif + // HCI_ACL_PAYLOAD_SIZE is configurable and defined in config.h // addition byte in even to terminate remote name request with '\0' #define HCI_EVENT_BUFFER_SIZE (HCI_EVENT_HEADER_SIZE + HCI_EVENT_PAYLOAD_SIZE + 1) + +#ifdef ENABLE_CLASSIC #define HCI_CMD_BUFFER_SIZE (HCI_CMD_HEADER_SIZE + HCI_CMD_PAYLOAD_SIZE) +#else +#define HCI_CMD_BUFFER_SIZE (HCI_CMD_HEADER_SIZE + HCI_CMD_PAYLOAD_SIZE_LE) +#endif + #define HCI_ACL_BUFFER_SIZE (HCI_ACL_HEADER_SIZE + HCI_ACL_PAYLOAD_SIZE) // size of hci buffers, big enough for command, event, or acl packet without H4 packet type diff --git a/src/hci_cmd.c b/src/hci_cmd.c index fb7db4914..30b97be7f 100644 --- a/src/hci_cmd.c +++ b/src/hci_cmd.c @@ -46,6 +46,7 @@ #include "classic/sdp_util.h" #include "hci.h" #include "hci_cmd.h" +#include "btstack_debug.h" #include @@ -578,12 +579,14 @@ const hci_cmd_t hci_delete_stored_link_key = { OPCODE(OGF_CONTROLLER_BASEBAND, 0x12), "B1" }; +#ifdef ENABLE_CLASSIC /** * @param local_name (UTF-8, Null Terminated, max 248 octets) */ const hci_cmd_t hci_write_local_name = { OPCODE(OGF_CONTROLLER_BASEBAND, 0x13), "N" }; +#endif /** */ @@ -1052,6 +1055,7 @@ OPCODE(OGF_LE_CONTROLLER, 0x25), "" // LE Read Local P-256 Public Key Complete is generated on completion }; +#ifdef HAVE_HCI_CONTROLLER_DHKEY_SUPPORT /** * @param end_test_cmd */ @@ -1059,6 +1063,7 @@ const hci_cmd_t hci_le_generate_dhkey = { OPCODE(OGF_LE_CONTROLLER, 0x26), "QQ" // LE Generate DHKey Complete is generated on completion }; +#endif #endif From d200c102718000ef0f0e6b250bdbc383f4a40916 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Mon, 16 Jan 2017 10:58:54 +0100 Subject: [PATCH 03/12] l2cap: reduce minimal HCI_ACL_PAYLOAD_SIZE to 27 for LE only --- src/l2cap.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/l2cap.h b/src/l2cap.h index dc3ebc98a..f27b4d1ab 100644 --- a/src/l2cap.h +++ b/src/l2cap.h @@ -56,9 +56,16 @@ extern "C" { #endif // check L2CAP MTU -#if (L2CAP_MINIMAL_MTU + L2CAP_HEADER_SIZE) > HCI_ACL_PAYLOAD_SIZE +#ifdef ENABLE_CLASSIC +#if (L2CAP_HEADER_SIZE + L2CAP_MINIMAL_MTU) > HCI_ACL_PAYLOAD_SIZE #error "HCI_ACL_PAYLOAD_SIZE too small for minimal L2CAP MTU of 48 bytes" #endif +#endif +#ifdef ENABLE_BLE +#if (L2CAP_HEADER_SIZE + L2CAP_LE_DEFAULT_MTU) > HCI_ACL_PAYLOAD_SIZE +#error "HCI_ACL_PAYLOAD_SIZE too small for minimal L2CAP LE MTU of 23 bytes" +#endif +#endif #define L2CAP_LE_AUTOMATIC_CREDITS 0xffff From c5bccd71ca50e350218257e1d87f9bb1c3c64131 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Mon, 16 Jan 2017 11:13:03 +0100 Subject: [PATCH 04/12] hci: reduced incoming prebuffer to 0 for LE-only, and outgoing to 0 if HAVE_HOST_CONTROLLER_API defined --- src/hci.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/hci.h b/src/hci.h index ef685c9f8..36e2f3e69 100644 --- a/src/hci.h +++ b/src/hci.h @@ -108,14 +108,20 @@ extern "C" { #endif #endif -// additional pre- and post-packet buffer for packets to Bluetooth module -// - pre-buffer used for HCI Transport H4 variants -#define HCI_OUTGOING_PRE_BUFFER_SIZE 1 -#define HCI_OUTGOING_POST_BUFFER_SIZE 0 +// additional pre-buffer space for packets to Bluetooth module, for now, used for HCI Transport H4 DMA +#ifdef HAVE_HOST_CONTROLLER_API +#define HCI_OUTGOING_PRE_BUFFER_SIZE 0 +#else +#define HCI_OUTGOING_PRE_BUFFER_SIZE 1 +#endif // BNEP may uncompress the IP Header by 16 bytes #ifndef HCI_INCOMING_PRE_BUFFER_SIZE +#ifdef ENABLE_CLASSIC #define HCI_INCOMING_PRE_BUFFER_SIZE (16 - HCI_ACL_HEADER_SIZE - 4) +#else +#define HCI_INCOMING_PRE_BUFFER_SIZE 0 +#endif #endif // From 75c2bb6417b65e3f0f7b12183aafc12784b21f1b Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Sun, 12 Mar 2017 21:09:15 +0100 Subject: [PATCH 05/12] hci: fix compile --- src/hci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hci.h b/src/hci.h index 36e2f3e69..6dd748e6c 100644 --- a/src/hci.h +++ b/src/hci.h @@ -670,7 +670,7 @@ typedef struct { // single buffer for HCI packet assembly + additional prebuffer for H4 drivers uint8_t * hci_packet_buffer; - uint8_t hci_packet_buffer_data[HCI_OUTGOING_PRE_BUFFER_SIZE + HCI_PACKET_BUFFER_SIZE + HCI_OUTGOING_POST_BUFFER_SIZE]; + uint8_t hci_packet_buffer_data[HCI_OUTGOING_PRE_BUFFER_SIZE + HCI_PACKET_BUFFER_SIZE]; uint8_t hci_packet_buffer_reserved; uint16_t acl_fragmentation_pos; uint16_t acl_fragmentation_total_size; From 52aef63f9ca114d5d3275eb15e9e59c718da84ef Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Sun, 12 Mar 2017 21:21:16 +0100 Subject: [PATCH 06/12] events: add params for BTSTACK_EVENT_NR_CONNECTIONS_CHANGED --- src/btstack_defines.h | 5 ++++- src/btstack_event.h | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/btstack_defines.h b/src/btstack_defines.h index 541cdfcea..497391196 100644 --- a/src/btstack_defines.h +++ b/src/btstack_defines.h @@ -284,7 +284,10 @@ typedef uint8_t sm_key_t[16]; */ #define BTSTACK_EVENT_STATE 0x60 -// data: event(8), len(8), nr hci connections +/** + * @format 1 + * @param number_connections + */ #define BTSTACK_EVENT_NR_CONNECTIONS_CHANGED 0x61 /** diff --git a/src/btstack_event.h b/src/btstack_event.h index 765a55c1e..4fb8731ca 100644 --- a/src/btstack_event.h +++ b/src/btstack_event.h @@ -964,6 +964,16 @@ static inline uint8_t btstack_event_state_get_state(const uint8_t * event){ return event[2]; } +/** + * @brief Get field number_connections from event BTSTACK_EVENT_NR_CONNECTIONS_CHANGED + * @param event packet + * @return number_connections + * @note: btstack_type 1 + */ +static inline uint8_t btstack_event_nr_connections_changed_get_number_connections(const uint8_t * event){ + return event[2]; +} + /** * @brief Get field discoverable from event BTSTACK_EVENT_DISCOVERABLE_ENABLED From d2eba9d1c59b45de3f76ec3a6e647bc326eeadce Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Mon, 20 Mar 2017 10:57:13 +0100 Subject: [PATCH 07/12] hfp: ENABLE_HFP_WIDE_BAND_SPEECH required for HFP mSBC --- doc/manual/docs/how_to.md | 3 +- example/hfp_ag_demo.c | 6 ++- example/hfp_hf_demo.c | 4 ++ example/sco_demo_util.c | 56 +++++++++++++++++++--------- port/libusb/btstack_config.h | 3 +- port/posix-h4/btstack_config.h | 3 +- port/posix-h5/btstack_config.h | 3 +- port/windows-h4/btstack_config.h | 3 +- port/windows-winusb/btstack_config.h | 3 +- 9 files changed, 60 insertions(+), 24 deletions(-) diff --git a/doc/manual/docs/how_to.md b/doc/manual/docs/how_to.md index 455cdb02e..8b305013e 100644 --- a/doc/manual/docs/how_to.md +++ b/doc/manual/docs/how_to.md @@ -72,7 +72,8 @@ ENABLE_EHCILL | Enable eHCILL low power mode on TI CC256x/WL18xx ENABLE_LOG_DEBUG | Enable log_debug messages ENABLE_LOG_ERROR | Enable log_error messages ENABLE_LOG_INFO | Enable log_info messages -ENABLE_SCO_OVER_HCI | Enable SCO over HCI for chipsets (only CC256x/WL18xx and USB CSR controllers) +ENABLE_SCO_OVER_HCI | Enable SCO over HCI for chipsets (only TI CC256x/WL18xx, CSR + Broadcom H2/USB)) +ENABLE_HFP_WIDE_BAND_SPEECH | Enable support for mSBC codec used in HFP profile for Wide-Band Speech ENBALE_LE_PERIPHERAL | Enable support for LE Peripheral Role in HCI and Security Manager ENBALE_LE_CENTRAL | Enable support for LE Central Role in HCI and Security Manager ENABLE_LE_SECURE_CONNECTIONS | Enable LE Secure Connections using [mbed TLS library](https://tls.mbed.org) diff --git a/example/hfp_ag_demo.c b/example/hfp_ag_demo.c index 328448dda..e48eb23fa 100644 --- a/example/hfp_ag_demo.c +++ b/example/hfp_ag_demo.c @@ -68,8 +68,12 @@ const char hfp_ag_service_name[] = "BTstack HFP AG Test"; static bd_addr_t device_addr; static const char * device_addr_string = "00:15:83:5F:9D:46"; +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH static uint8_t codecs[] = {HFP_CODEC_CVSD, HFP_CODEC_MSBC}; -// static uint8_t codecs[] = {HFP_CODEC_CVSD}; +#else +static uint8_t codecs[] = {HFP_CODEC_CVSD}; +#endif + static uint8_t negotiated_codec = HFP_CODEC_CVSD; static hci_con_handle_t acl_handle = -1; diff --git a/example/hfp_hf_demo.c b/example/hfp_hf_demo.c index 98a957922..65b5d57ca 100644 --- a/example/hfp_hf_demo.c +++ b/example/hfp_hf_demo.c @@ -77,7 +77,11 @@ static void show_usage(void); #endif static hci_con_handle_t acl_handle = -1; static hci_con_handle_t sco_handle; +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH static uint8_t codecs[] = {HFP_CODEC_CVSD, HFP_CODEC_MSBC}; +#else +static uint8_t codecs[] = {HFP_CODEC_CVSD}; +#endif static uint16_t indicators[1] = {0x01}; static uint8_t negotiated_codec = HFP_CODEC_CVSD; static btstack_packet_callback_registration_t hci_event_callback_registration; diff --git a/example/sco_demo_util.c b/example/sco_demo_util.c index 18f81a437..5f699e329 100644 --- a/example/sco_demo_util.c +++ b/example/sco_demo_util.c @@ -127,11 +127,16 @@ static int count_sent = 0; static int count_received = 0; static int negotiated_codec = -1; +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH btstack_sbc_decoder_state_t decoder_state; +#endif + btstack_cvsd_plc_state_t cvsd_plc_state; +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH FILE * msbc_file_in; FILE * msbc_file_out; +#endif int num_samples_to_write; int num_audio_frames; @@ -153,17 +158,6 @@ static const int16_t sine_int16_at_16000hz[] = { -19260, -17557, -15786, -13952, -12062, -10126, -8149, -6140, -4107, -2057, }; -// 16 kHz samples for mSBC encoder in host endianess -static void sco_demo_sine_wave_int16_at_16000_hz_host_endian(int num_samples, int16_t * data){ - int i; - for (i=0; i < num_samples; i++){ - data[i] = sine_int16_at_16000hz[phase++]; - if (phase >= (sizeof(sine_int16_at_16000hz) / sizeof(int16_t))){ - phase = 0; - } - } -} - // 8 kHz samples for CVSD/SCO packets in little endian static void sco_demo_sine_wave_int16_at_8000_hz_little_endian(int num_samples, int16_t * data){ int i; @@ -178,6 +172,18 @@ static void sco_demo_sine_wave_int16_at_8000_hz_little_endian(int num_samples, i } } +// 16 kHz samples for mSBC encoder in host endianess +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH +static void sco_demo_sine_wave_int16_at_16000_hz_host_endian(int num_samples, int16_t * data){ + int i; + for (i=0; i < num_samples; i++){ + data[i] = sine_int16_at_16000hz[phase++]; + if (phase >= (sizeof(sine_int16_at_16000hz) / sizeof(int16_t))){ + phase = 0; + } + } +} + static void sco_demo_msbc_fill_sine_audio_frame(void){ if (!hfp_msbc_can_encode_audio_frame_now()) return; int num_samples = hfp_msbc_num_audio_samples_per_frame(); @@ -187,6 +193,7 @@ static void sco_demo_msbc_fill_sine_audio_frame(void){ num_audio_frames++; } #endif +#endif #ifdef USE_PORTAUDIO static int portaudio_callback( const void *inputBuffer, void *outputBuffer, @@ -350,6 +357,7 @@ static void portaudio_terminate(void){ #if (SCO_DEMO_MODE == SCO_DEMO_MODE_SINE) || (SCO_DEMO_MODE == SCO_DEMO_MODE_MICROPHONE) +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, int sample_rate, void * context){ UNUSED(context); UNUSED(sample_rate); @@ -363,7 +371,7 @@ static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, i #ifdef HAVE_PORTAUDIO // samples in callback in host endianess, ready for PortAudio playback btstack_ring_buffer_write(&pa_output_ring_buffer, (uint8_t *)data, num_samples*num_channels*2); -#endif +#endif /* HAVE_PORTAUDIO */ #ifdef SCO_WAV_FILENAME if (!num_samples_to_write) return; @@ -373,11 +381,14 @@ static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, i if (num_samples_to_write == 0){ wav_writer_close(); } -#endif - -#endif +#endif /* SCO_WAV_FILENAME */ +#endif /* Demo mode sine or microphone */ } +#endif /* ENABLE_HFP_WIDE_BAND_SPEECH */ + + +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH static void sco_demo_init_mSBC(void){ printf("SCO Demo: Init mSBC\n"); @@ -418,6 +429,7 @@ static void sco_demo_receive_mSBC(uint8_t * packet, uint16_t size){ } btstack_sbc_decoder_process_data(&decoder_state, (packet[1] >> 4) & 3, packet+3, size-3); } +#endif static void sco_demo_init_CVSD(void){ printf("SCO Demo: Init CVSD\n"); @@ -482,9 +494,12 @@ void sco_demo_close(void){ printf("SCO demo close\n"); printf("SCO demo statistics: "); +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH if (negotiated_codec == HFP_CODEC_MSBC){ printf("Used mSBC with PLC, number of processed frames: \n - %d good frames, \n - %d zero frames, \n - %d bad frames.\n", decoder_state.good_frames_nr, decoder_state.zero_frames_nr, decoder_state.bad_frames_nr); - } else { + } else +#endif + { printf("Used CVSD with PLC, number of proccesed frames: \n - %d good frames, \n - %d bad frames.\n", cvsd_plc_state.good_frames_nr, cvsd_plc_state.bad_frames_nr); } @@ -509,7 +524,9 @@ void sco_demo_set_codec(uint8_t codec){ #if (SCO_DEMO_MODE == SCO_DEMO_MODE_SINE) || (SCO_DEMO_MODE == SCO_DEMO_MODE_MICROPHONE) if (negotiated_codec == HFP_CODEC_MSBC){ +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH sco_demo_init_mSBC(); +#endif } else { sco_demo_init_CVSD(); } @@ -561,6 +578,7 @@ void sco_demo_send(hci_con_handle_t sco_handle){ hci_reserve_packet_buffer(); uint8_t * sco_packet = hci_get_outgoing_packet_buffer(); #if SCO_DEMO_MODE == SCO_DEMO_MODE_SINE +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH if (negotiated_codec == HFP_CODEC_MSBC){ // overwrite sco_payload_length = 24; @@ -576,7 +594,9 @@ void sco_demo_send(hci_con_handle_t sco_handle){ } sco_demo_msbc_fill_sine_audio_frame(); - } else { + } else +#endif + { const int audio_samples_per_packet = sco_payload_length / CVSD_BYTES_PER_FRAME; sco_demo_sine_wave_int16_at_8000_hz_little_endian(audio_samples_per_packet, (int16_t *) (sco_packet+3)); } @@ -750,9 +770,11 @@ void sco_demo_receive(uint8_t * packet, uint16_t size){ #if (SCO_DEMO_MODE == SCO_DEMO_MODE_SINE) || (SCO_DEMO_MODE == SCO_DEMO_MODE_MICROPHONE) switch (negotiated_codec){ +#ifdef ENABLE_HFP_WIDE_BAND_SPEECH case HFP_CODEC_MSBC: sco_demo_receive_mSBC(packet, size); break; +#endif case HFP_CODEC_CVSD: sco_demo_receive_CVSD(packet, size); break; diff --git a/port/libusb/btstack_config.h b/port/libusb/btstack_config.h index a2f38b515..1053c80b4 100644 --- a/port/libusb/btstack_config.h +++ b/port/libusb/btstack_config.h @@ -14,8 +14,9 @@ // BTstack features that can be enabled #define ENABLE_BLE #define ENABLE_CLASSIC -#define ENABLE_LE_PERIPHERAL +#define ENABLE_HFP_WIDE_BAND_SPEECH #define ENABLE_LE_CENTRAL +#define ENABLE_LE_PERIPHERAL #define ENABLE_LE_SECURE_CONNECTIONS #define ENABLE_LOG_ERROR #define ENABLE_LOG_INFO diff --git a/port/posix-h4/btstack_config.h b/port/posix-h4/btstack_config.h index 4d94625d7..f5ac4d047 100644 --- a/port/posix-h4/btstack_config.h +++ b/port/posix-h4/btstack_config.h @@ -14,8 +14,9 @@ // BTstack features that can be enabled #define ENABLE_BLE #define ENABLE_CLASSIC -#define ENABLE_LE_PERIPHERAL +#define ENABLE_HFP_WIDE_BAND_SPEECH #define ENABLE_LE_CENTRAL +#define ENABLE_LE_PERIPHERAL #define ENABLE_LE_SECURE_CONNECTIONS #define ENABLE_LOG_ERROR #define ENABLE_LOG_INFO diff --git a/port/posix-h5/btstack_config.h b/port/posix-h5/btstack_config.h index 6534ee604..1832c7d2e 100644 --- a/port/posix-h5/btstack_config.h +++ b/port/posix-h5/btstack_config.h @@ -14,8 +14,9 @@ // BTstack features that can be enabled #define ENABLE_BLE #define ENABLE_CLASSIC -#define ENABLE_LE_PERIPHERAL +#define ENABLE_HFP_WIDE_BAND_SPEECH #define ENABLE_LE_CENTRAL +#define ENABLE_LE_PERIPHERAL #define ENABLE_LE_SECURE_CONNECTIONS #define ENABLE_LOG_ERROR #define ENABLE_LOG_INFO diff --git a/port/windows-h4/btstack_config.h b/port/windows-h4/btstack_config.h index a2f38b515..1053c80b4 100644 --- a/port/windows-h4/btstack_config.h +++ b/port/windows-h4/btstack_config.h @@ -14,8 +14,9 @@ // BTstack features that can be enabled #define ENABLE_BLE #define ENABLE_CLASSIC -#define ENABLE_LE_PERIPHERAL +#define ENABLE_HFP_WIDE_BAND_SPEECH #define ENABLE_LE_CENTRAL +#define ENABLE_LE_PERIPHERAL #define ENABLE_LE_SECURE_CONNECTIONS #define ENABLE_LOG_ERROR #define ENABLE_LOG_INFO diff --git a/port/windows-winusb/btstack_config.h b/port/windows-winusb/btstack_config.h index 1f58f58de..ab2633568 100644 --- a/port/windows-winusb/btstack_config.h +++ b/port/windows-winusb/btstack_config.h @@ -14,8 +14,9 @@ // BTstack features that can be enabled #define ENABLE_BLE #define ENABLE_CLASSIC -#define ENABLE_LE_PERIPHERAL +#define ENABLE_HFP_WIDE_BAND_SPEECH #define ENABLE_LE_CENTRAL +#define ENABLE_LE_PERIPHERAL #define ENABLE_LE_SECURE_CONNECTIONS #define ENABLE_LOG_ERROR #define ENABLE_LOG_INFO From 85a0c7cd626c3090d0b1c69eac0ae3c1d18fc55e Mon Sep 17 00:00:00 2001 From: Milanka Ringwald Date: Mon, 6 Mar 2017 22:22:17 +0100 Subject: [PATCH 08/12] a2dp: avdtp source sends sbc frames at constant speed a2dp: introduce context, move common code to avdtp.c a2dp: wrap source functionality a2dp: enable packet handler a2dp: draft implemetation source open stream avrcp: add streaming connection opened/released events avdtp source: introduce timer for storing audio data at const fr., encode into sbc, and prepare for sending wav_util: fix return of wav_reader_read_int16 sbc: rename sbc to msbc test sbc: improve usage output sbc: fix makefile sbc: remove hardcoded bitrate avdtp: add encode/decode sine example without using ring buffer avdtp: move ring buffer to stream endpoint avdtp: add encode/decode test with ring buffer sbc: use btstack_sbc_encoder_num_audio_frames - 1 audio frame contains 1 audio sample for mono and 2 for stereo sbc: use btstack_sbc_encoder_num_audio_frames - 1 audio frame contains 1 audio sample for mono and 2 for stereo sbc: revert sample counting avdtp: source sending correct audio first time avdtp: shorter timer for audio generation gets correct stream avdtp: set state to AVDTP_STREAM_ENDPOINT_STREAMING_W2_SEND when more data is ready avdtp: introduce transport category as mandatory avdtp: introduce transport category as mandatory --- example/gap_le_advertisements.c | 1 - platform/posix/wav_util.c | 12 +- src/btstack_defines.h | 21 +- src/btstack_event.h | 38 + src/classic/avdtp.c | 768 ++++++++++++++++++ src/classic/avdtp.h | 63 +- src/classic/avdtp_acceptor.c | 69 +- src/classic/avdtp_acceptor.h | 4 +- src/classic/avdtp_initiator.c | 73 +- src/classic/avdtp_initiator.h | 4 +- src/classic/avdtp_sink.c | 710 +--------------- src/classic/avdtp_sink.h | 2 - src/classic/avdtp_source.c | 314 ++++++- src/classic/avdtp_source.h | 104 +++ src/classic/avdtp_util.c | 143 +++- src/classic/avdtp_util.h | 18 +- src/classic/btstack_sbc.h | 5 +- src/classic/btstack_sbc_bludroid.c | 10 +- src/classic/hfp_msbc.c | 2 +- test/avdtp/.gitignore | 5 +- test/avdtp/Makefile | 15 +- .../avdtp/{avdtp_test.c => avdtp_sink_test.c} | 16 +- test/avdtp/avdtp_source_test.c | 469 +++++++++++ .../sine_encode_decode_ring_buffer_test.c | 247 ++++++ test/avdtp/sine_encode_decode_test.c | 133 +++ test/hfp/cvsd_plc_test.c | 6 +- test/sbc/Makefile | 4 +- ...sbc_encoder_test.c => msbc_encoder_test.c} | 6 +- test/sbc/sbc_decoder_test.c | 2 +- 29 files changed, 2468 insertions(+), 796 deletions(-) create mode 100644 src/classic/avdtp.c rename test/avdtp/{avdtp_test.c => avdtp_sink_test.c} (98%) create mode 100644 test/avdtp/avdtp_source_test.c create mode 100644 test/avdtp/sine_encode_decode_ring_buffer_test.c create mode 100644 test/avdtp/sine_encode_decode_test.c rename test/sbc/{sbc_encoder_test.c => msbc_encoder_test.c} (94%) diff --git a/example/gap_le_advertisements.c b/example/gap_le_advertisements.c index 582561618..8959d47f4 100644 --- a/example/gap_le_advertisements.c +++ b/example/gap_le_advertisements.c @@ -68,7 +68,6 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe static void gap_le_advertisements_setup(void){ hci_event_callback_registration.callback = &packet_handler; hci_add_event_handler(&hci_event_callback_registration); - // Active scanning, 100% (scan interval = scan window) gap_set_scan_parameters(1,48,48); } diff --git a/platform/posix/wav_util.c b/platform/posix/wav_util.c index 9b6eeb3f9..2f7fd5f9f 100644 --- a/platform/posix/wav_util.c +++ b/platform/posix/wav_util.c @@ -224,7 +224,11 @@ int wav_reader_read_int8(int num_samples, int8_t * data){ data[i] = buf[0] + 128; } } - return bytes_read == num_samples*bytes_per_sample; + if (bytes_read == num_samples*bytes_per_sample) { + return 0; + } else { + return 1; + } } int wav_reader_read_int16(int num_samples, int16_t * data){ @@ -236,7 +240,11 @@ int wav_reader_read_int16(int num_samples, int16_t * data){ bytes_read +=__read(wav_reader_fd, &buf, 2); data[i] = little_endian_read_16(buf, 0); } - return bytes_read == num_samples*bytes_per_sample; + if (bytes_read == num_samples*bytes_per_sample) { + return 0; + } else { + return 1; + } } diff --git a/src/btstack_defines.h b/src/btstack_defines.h index 497391196..397aa0379 100644 --- a/src/btstack_defines.h +++ b/src/btstack_defines.h @@ -1265,8 +1265,9 @@ typedef uint8_t sm_key_t[16]; #define AVDTP_SUBEVENT_SIGNALING_CONNECTION_ESTABLISHED 0x04 /** - * @format 1 + * @format 1H * @param subevent_code + * @param con_handle */ #define AVDTP_SUBEVENT_SIGNALING_CONNECTION_RELEASED 0x05 @@ -1336,11 +1337,27 @@ typedef uint8_t sm_key_t[16]; */ #define AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CONFIGURATION 0x0A +/** + * @format 1H1 + * @param subevent_code + * @param con_handle + * @param status 0 == OK + */ +#define AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED 0x0B + +/** + * @format 1H + * @param subevent_code + * @param con_handle + */ +#define AVDTP_SUBEVENT_STREAMING_CONNECTION_RELEASED 0x0C + + /** AVRCP Subevent */ /** - * @format 1H12B1 + * @format 1H12B * @param subevent_code * @param con_handle * @param status 0 == OK diff --git a/src/btstack_event.h b/src/btstack_event.h index 4fb8731ca..e0f76ed93 100644 --- a/src/btstack_event.h +++ b/src/btstack_event.h @@ -3965,6 +3965,15 @@ static inline uint8_t avdtp_subevent_signaling_connection_established_get_status return event[11]; } +/** + * @brief Get field con_handle from event AVDTP_SUBEVENT_SIGNALING_CONNECTION_RELEASED + * @param event packet + * @return con_handle + * @note: btstack_type H + */ +static inline hci_con_handle_t avdtp_subevent_signaling_connection_released_get_con_handle(const uint8_t * event){ + return little_endian_read_16(event, 3); +} /** * @brief Get field handle from event AVDTP_SUBEVENT_SIGNALING_SEP_FOUND @@ -4295,6 +4304,35 @@ static inline const uint8_t * avdtp_subevent_signaling_media_codec_other_configu return &event[11]; } +/** + * @brief Get field con_handle from event AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED + * @param event packet + * @return con_handle + * @note: btstack_type H + */ +static inline hci_con_handle_t avdtp_subevent_streaming_connection_established_get_con_handle(const uint8_t * event){ + return little_endian_read_16(event, 3); +} +/** + * @brief Get field status from event AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED + * @param event packet + * @return status + * @note: btstack_type 1 + */ +static inline uint8_t avdtp_subevent_streaming_connection_established_get_status(const uint8_t * event){ + return event[5]; +} + +/** + * @brief Get field con_handle from event AVDTP_SUBEVENT_STREAMING_CONNECTION_RELEASED + * @param event packet + * @return con_handle + * @note: btstack_type H + */ +static inline hci_con_handle_t avdtp_subevent_streaming_connection_released_get_con_handle(const uint8_t * event){ + return little_endian_read_16(event, 3); +} + /** * @brief Get field con_handle from event AVRCP_SUBEVENT_CONNECTION_ESTABLISHED * @param event packet diff --git a/src/classic/avdtp.c b/src/classic/avdtp.c new file mode 100644 index 000000000..31cae26a4 --- /dev/null +++ b/src/classic/avdtp.c @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2016 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + + +#include +#include +#include +#include +#include + +#include "btstack.h" +#include "avdtp.h" +#include "avdtp_util.h" +#include "avdtp_acceptor.h" +#include "avdtp_initiator.h" + +static uint8_t audio_samples_storage[44100*4]; // 1s buffer +// static btstack_ring_buffer_t audio_ring_buffer; + +static uint8_t sbc_samples_storage[44100*4]; +// static btstack_ring_buffer_t sbc_ring_buffer; + +static void (*handle_media_data)(avdtp_stream_endpoint_t * stream_endpoint, uint8_t *packet, uint16_t size); + +void avdtp_register_media_transport_category(avdtp_stream_endpoint_t * stream_endpoint){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_MEDIA_TRANSPORT, 1); + stream_endpoint->sep.registered_service_categories = bitmap; + printf("registered services AVDTP_MEDIA_TRANSPORT(%d) %02x\n", AVDTP_MEDIA_TRANSPORT, stream_endpoint->sep.registered_service_categories); +} + +void avdtp_register_reporting_category(avdtp_stream_endpoint_t * stream_endpoint){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_REPORTING, 1); + stream_endpoint->sep.registered_service_categories = bitmap; +} + +void avdtp_register_delay_reporting_category(avdtp_stream_endpoint_t * stream_endpoint){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_DELAY_REPORTING, 1); + stream_endpoint->sep.registered_service_categories = bitmap; +} + +void avdtp_register_recovery_category(avdtp_stream_endpoint_t * stream_endpoint, uint8_t maximum_recovery_window_size, uint8_t maximum_number_media_packets){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_RECOVERY, 1); + stream_endpoint->sep.registered_service_categories = bitmap; + stream_endpoint->sep.capabilities.recovery.recovery_type = 0x01; // 0x01 = RFC2733 + stream_endpoint->sep.capabilities.recovery.maximum_recovery_window_size = maximum_recovery_window_size; + stream_endpoint->sep.capabilities.recovery.maximum_number_media_packets = maximum_number_media_packets; +} + +void avdtp_register_content_protection_category(avdtp_stream_endpoint_t * stream_endpoint, uint16_t cp_type, const uint8_t * cp_type_value, uint8_t cp_type_value_len){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_CONTENT_PROTECTION, 1); + stream_endpoint->sep.registered_service_categories = bitmap; + stream_endpoint->sep.capabilities.content_protection.cp_type = cp_type; + stream_endpoint->sep.capabilities.content_protection.cp_type_value = cp_type_value; + stream_endpoint->sep.capabilities.content_protection.cp_type_value_len = cp_type_value_len; +} + +void avdtp_register_header_compression_category(avdtp_stream_endpoint_t * stream_endpoint, uint8_t back_ch, uint8_t media, uint8_t recovery){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_HEADER_COMPRESSION, 1); + stream_endpoint->sep.registered_service_categories = bitmap; + stream_endpoint->sep.capabilities.header_compression.back_ch = back_ch; + stream_endpoint->sep.capabilities.header_compression.media = media; + stream_endpoint->sep.capabilities.header_compression.recovery = recovery; +} + +void avdtp_register_media_codec_category(avdtp_stream_endpoint_t * stream_endpoint, avdtp_media_type_t media_type, avdtp_media_codec_type_t media_codec_type, const uint8_t * media_codec_info, uint16_t media_codec_info_len){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_MEDIA_CODEC, 1); + stream_endpoint->sep.registered_service_categories = bitmap; + printf("registered services AVDTP_MEDIA_CODEC(%d) %02x\n", AVDTP_MEDIA_CODEC, stream_endpoint->sep.registered_service_categories); + stream_endpoint->sep.capabilities.media_codec.media_type = media_type; + stream_endpoint->sep.capabilities.media_codec.media_codec_type = media_codec_type; + stream_endpoint->sep.capabilities.media_codec.media_codec_information = media_codec_info; + stream_endpoint->sep.capabilities.media_codec.media_codec_information_len = media_codec_info_len; +} + +void avdtp_register_multiplexing_category(avdtp_stream_endpoint_t * stream_endpoint, uint8_t fragmentation){ + if (!stream_endpoint){ + log_error("avdtp_register_media_transport_category: stream endpoint with given seid is not registered"); + return; + } + uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_MULTIPLEXING, 1); + stream_endpoint->sep.registered_service_categories = bitmap; + stream_endpoint->sep.capabilities.multiplexing_mode.fragmentation = fragmentation; +} + + +/* START: tracking can send now requests pro l2cap cid */ +void avdtp_handle_can_send_now(avdtp_connection_t * connection, uint16_t l2cap_cid, avdtp_context_t * context){ + if (connection->wait_to_send_acceptor){ + connection->wait_to_send_acceptor = 0; + avdtp_acceptor_stream_config_subsm_run(connection, context); + } else if (connection->wait_to_send_initiator){ + connection->wait_to_send_initiator = 0; + avdtp_initiator_stream_config_subsm_run(connection, context); + } else if (connection->wait_to_send_self){ + connection->wait_to_send_self = 0; + if (connection->disconnect){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &context->stream_endpoints); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); + if (stream_endpoint->connection == connection){ + if (stream_endpoint->state >= AVDTP_STREAM_ENDPOINT_OPENED && stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_DISCONNECTED){ + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_DISCONNECTED; + avdtp_request_can_send_now_self(connection, connection->l2cap_signaling_cid); + l2cap_disconnect(stream_endpoint->l2cap_media_cid, 0); + return; + } + } + } + connection->disconnect = 0; + connection->state = AVDTP_SIGNALING_CONNECTION_W4_L2CAP_DISCONNECTED; + l2cap_disconnect(connection->l2cap_signaling_cid, 0); + return; + } + } + + // re-register + int more_to_send = connection->wait_to_send_acceptor || connection->wait_to_send_initiator || connection->wait_to_send_self; + if (more_to_send){ + l2cap_request_can_send_now_event(l2cap_cid); + } +} +/* END: tracking can send now requests pro l2cap cid */ + +avdtp_connection_t * avdtp_create_connection(bd_addr_t remote_addr, avdtp_context_t * context){ + avdtp_connection_t * connection = btstack_memory_avdtp_connection_get(); + memset(connection, 0, sizeof(avdtp_connection_t)); + connection->state = AVDTP_SIGNALING_CONNECTION_IDLE; + connection->initiator_transaction_label++; + memcpy(connection->remote_addr, remote_addr, 6); + btstack_linked_list_add(&context->connections, (btstack_linked_item_t *) connection); + return connection; +} + +avdtp_stream_endpoint_t * avdtp_create_stream_endpoint(avdtp_sep_type_t sep_type, avdtp_media_type_t media_type, avdtp_context_t * context){ + avdtp_stream_endpoint_t * stream_endpoint = btstack_memory_avdtp_stream_endpoint_get(); + memset(stream_endpoint, 0, sizeof(avdtp_stream_endpoint_t)); + context->stream_endpoints_id_counter++; + stream_endpoint->sep.seid = context->stream_endpoints_id_counter; + stream_endpoint->sep.media_type = media_type; + stream_endpoint->sep.type = sep_type; + + memset(audio_samples_storage, 0, sizeof(audio_samples_storage)); + btstack_ring_buffer_init(&stream_endpoint->audio_ring_buffer, audio_samples_storage, sizeof(audio_samples_storage)); + + memset(sbc_samples_storage, 0, sizeof(sbc_samples_storage)); + btstack_ring_buffer_init(&stream_endpoint->sbc_ring_buffer, sbc_samples_storage, sizeof(sbc_samples_storage)); + + + btstack_linked_list_add(&context->stream_endpoints, (btstack_linked_item_t *) stream_endpoint); + return stream_endpoint; +} + + +static void handle_l2cap_data_packet_for_signaling_connection(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, avdtp_context_t * context){ + int offset = avdtp_read_signaling_header(&connection->signaling_packet, packet, size); + switch (connection->signaling_packet.message_type){ + case AVDTP_CMD_MSG: + avdtp_acceptor_stream_config_subsm(connection, packet, size, offset, context); + break; + default: + avdtp_initiator_stream_config_subsm(connection, packet, size, offset, context); + break; + } +} + +static void stream_endpoint_state_machine(avdtp_connection_t * connection, avdtp_stream_endpoint_t * stream_endpoint, uint8_t packet_type, uint8_t event, uint8_t *packet, uint16_t size, avdtp_context_t * context){ + uint16_t local_cid; + switch (packet_type){ + case L2CAP_DATA_PACKET:{ + int offset = avdtp_read_signaling_header(&connection->signaling_packet, packet, size); + if (connection->signaling_packet.message_type == AVDTP_CMD_MSG){ + avdtp_acceptor_stream_config_subsm(connection, packet, size, offset, context); + } else { + avdtp_initiator_stream_config_subsm(connection, packet, size, offset, context); + } + break; + } + case HCI_EVENT_PACKET: + switch (event){ + case L2CAP_EVENT_CHANNEL_OPENED: + if (stream_endpoint->l2cap_media_cid == 0){ + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED) return; + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_OPENED; + stream_endpoint->connection = connection; + stream_endpoint->l2cap_media_cid = l2cap_event_channel_opened_get_local_cid(packet); + printf(" -> AVDTP_STREAM_ENDPOINT_OPENED, stream endpoint %p, connection %p\n", stream_endpoint, connection); + avdtp_streaming_emit_connection_established(context->avdtp_callback, stream_endpoint->l2cap_media_cid, 0); + break; + } + break; + case L2CAP_EVENT_CHANNEL_CLOSED: + local_cid = l2cap_event_channel_closed_get_local_cid(packet); + if (stream_endpoint->l2cap_media_cid == local_cid){ + stream_endpoint->l2cap_media_cid = 0; + printf(" -> L2CAP_EVENT_CHANNEL_CLOSED: AVDTP_STREAM_ENDPOINT_IDLE\n"); + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_IDLE; + stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_STREAM_CONFIG_IDLE; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_STREAM_CONFIG_IDLE; + stream_endpoint->remote_seps_num = 0; + memset(stream_endpoint->remote_seps, 0, sizeof(avdtp_sep_t)*MAX_NUM_SEPS); + stream_endpoint->remote_sep_index = 0; + break; + } + if (stream_endpoint->l2cap_recovery_cid == local_cid){ + log_info(" -> L2CAP_EVENT_CHANNEL_CLOSED recovery cid 0x%0x", local_cid); + stream_endpoint->l2cap_recovery_cid = 0; + break; + } + + if (stream_endpoint->l2cap_reporting_cid == local_cid){ + log_info("L2CAP_EVENT_CHANNEL_CLOSED reporting cid 0x%0x", local_cid); + stream_endpoint->l2cap_reporting_cid = 0; + break; + } + break; + default: + break; + } + break; + default: + break; + } +} + +void avdtp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, avdtp_context_t * context){ + bd_addr_t event_addr; + hci_con_handle_t con_handle; + uint16_t psm; + uint16_t local_cid; + avdtp_stream_endpoint_t * stream_endpoint = NULL; + avdtp_connection_t * connection = NULL; + btstack_linked_list_t * avdtp_connections = &context->connections; + btstack_linked_list_t * stream_endpoints = &context->stream_endpoints; + handle_media_data = context->handle_media_data; + // printf("avdtp_packet_handler packet type %02x, event %02x \n", packet_type, hci_event_packet_get_type(packet)); + switch (packet_type) { + case L2CAP_DATA_PACKET: + connection = avdtp_connection_for_l2cap_signaling_cid(channel, context); + if (connection){ + handle_l2cap_data_packet_for_signaling_connection(connection, packet, size, context); + break; + } + + stream_endpoint = avdtp_stream_endpoint_for_l2cap_cid(channel, context); + if (!stream_endpoint){ + if (!connection) break; + handle_l2cap_data_packet_for_signaling_connection(connection, packet, size, context); + break; + } + + if (channel == stream_endpoint->connection->l2cap_signaling_cid){ + stream_endpoint_state_machine(stream_endpoint->connection, stream_endpoint, L2CAP_DATA_PACKET, 0, packet, size, context); + break; + } + + if (channel == stream_endpoint->l2cap_media_cid){ + (*handle_media_data)(stream_endpoint, packet, size); + break; + } + + if (channel == stream_endpoint->l2cap_reporting_cid){ + // TODO + printf("L2CAP_DATA_PACKET for reporting: NOT IMPLEMENTED\n"); + } else if (channel == stream_endpoint->l2cap_recovery_cid){ + // TODO + printf("L2CAP_DATA_PACKET for recovery: NOT IMPLEMENTED\n"); + } else { + log_error("avdtp packet handler L2CAP_DATA_PACKET: local cid 0x%02x not found", channel); + } + break; + + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case L2CAP_EVENT_INCOMING_CONNECTION: + l2cap_event_incoming_connection_get_address(packet, event_addr); + local_cid = l2cap_event_incoming_connection_get_local_cid(packet); + + connection = avdtp_connection_for_bd_addr(event_addr, context); + if (!connection){ + connection = avdtp_create_connection(event_addr, context); + connection->state = AVDTP_SIGNALING_CONNECTION_W4_L2CAP_CONNECTED; + l2cap_accept_connection(local_cid); + break; + } + + stream_endpoint = avdtp_stream_endpoint_for_seid(connection->query_seid, context); + if (!stream_endpoint) { + printf("L2CAP_EVENT_INCOMING_CONNECTION no streamendpoint found for seid %d\n", connection->query_seid); + break; + } + + if (stream_endpoint->l2cap_media_cid == 0){ + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED) break; + l2cap_accept_connection(local_cid); + break; + } + break; + + case L2CAP_EVENT_CHANNEL_OPENED: + // inform about new l2cap connection + l2cap_event_channel_opened_get_address(packet, event_addr); + if (l2cap_event_channel_opened_get_status(packet)){ + log_error("L2CAP connection to connection %s failed. status code 0x%02x", + bd_addr_to_str(event_addr), l2cap_event_channel_opened_get_status(packet)); + break; + } + psm = l2cap_event_channel_opened_get_psm(packet); + if (psm != PSM_AVDTP){ + log_error("unexpected PSM - Not implemented yet, avdtp sink: L2CAP_EVENT_CHANNEL_OPENED"); + return; + } + + con_handle = l2cap_event_channel_opened_get_handle(packet); + local_cid = l2cap_event_channel_opened_get_local_cid(packet); + + // printf("L2CAP_EVENT_CHANNEL_OPENED: Channel successfully opened: %s, handle 0x%02x, psm 0x%02x, local cid 0x%02x, remote cid 0x%02x\n", + // bd_addr_to_str(event_addr), con_handle, psm, local_cid, l2cap_event_channel_opened_get_remote_cid(packet)); + + if (psm != PSM_AVDTP) break; + + connection = avdtp_connection_for_bd_addr(event_addr, context); + if (!connection) break; + + if (connection->l2cap_signaling_cid == 0) { + if (connection->state != AVDTP_SIGNALING_CONNECTION_W4_L2CAP_CONNECTED) break; + connection->l2cap_signaling_cid = local_cid; + connection->con_handle = con_handle; + connection->query_seid = 0; + connection->state = AVDTP_SIGNALING_CONNECTION_OPENED; + printf(" -> AVDTP_SIGNALING_CONNECTION_OPENED, connection %p\n", connection); + avdtp_signaling_emit_connection_established(context->avdtp_callback, con_handle, event_addr, 0); + break; + } + + stream_endpoint = avdtp_stream_endpoint_for_seid(connection->query_seid, context); + if (!stream_endpoint){ + printf("L2CAP_EVENT_CHANNEL_OPENED: stream_endpoint not found"); + return; + } + stream_endpoint_state_machine(connection, stream_endpoint, HCI_EVENT_PACKET, L2CAP_EVENT_CHANNEL_OPENED, packet, size, context); + break; + + case L2CAP_EVENT_CHANNEL_CLOSED: + // data: event (8), len(8), channel (16) + local_cid = l2cap_event_channel_closed_get_local_cid(packet); + connection = avdtp_connection_for_l2cap_signaling_cid(local_cid, context); + printf(" -> L2CAP_EVENT_CHANNEL_CLOSED signaling cid 0x%0x\n", local_cid); + + if (connection){ + printf(" -> AVDTP_STREAM_ENDPOINT_IDLE, connection closed\n"); + btstack_linked_list_remove(avdtp_connections, (btstack_linked_item_t*) connection); + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, stream_endpoints); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_stream_endpoint_t * _stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); + avdtp_initialize_stream_endpoint(_stream_endpoint); + } + break; + } + + stream_endpoint = avdtp_stream_endpoint_for_l2cap_cid(local_cid, context); + if (!stream_endpoint) return; + + stream_endpoint_state_machine(connection, stream_endpoint, HCI_EVENT_PACKET, L2CAP_EVENT_CHANNEL_CLOSED, packet, size, context); + break; + + case HCI_EVENT_DISCONNECTION_COMPLETE: + break; + + case L2CAP_EVENT_CAN_SEND_NOW: + connection = avdtp_connection_for_l2cap_signaling_cid(channel, context); + if (!connection) { + stream_endpoint = avdtp_stream_endpoint_for_l2cap_cid(channel, context); + if (!stream_endpoint->connection) break; + if (stream_endpoint->state == AVDTP_STREAM_ENDPOINT_STREAMING_W2_SEND){ + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_STREAMING; + //send sbc + uint8_t rtp_version = 2; + uint8_t padding = 0; + uint8_t extention = 0; + uint8_t csrc_count = 0; + uint8_t marker = 0; + uint8_t payload_type = 0; + uint16_t sequence_number = stream_endpoint->sequence_number; + uint32_t timestamp = btstack_run_loop_get_time_ms(); + uint32_t ssrc = 0; + + // rtp header (min size 12B) + int pos = 0; + int mtu = l2cap_get_remote_mtu_for_local_cid(stream_endpoint->l2cap_media_cid); + + uint8_t media_packet[mtu]; + media_packet[pos++] = (rtp_version << 7) && (padding << 6) && (padding << 5) && (extention << 4) && csrc_count; + media_packet[pos++] = (marker << 7) && payload_type; + little_endian_store_16(media_packet, pos, sequence_number); + pos += 2; + little_endian_store_32(media_packet, pos, timestamp); + pos += 4; + little_endian_store_32(media_packet, pos, ssrc); // only used for multicast + pos += 4; + + // media payload + // sbc_header (size 1B) + uint8_t sbc_header_index = pos; + pos++; + uint8_t fragmentation = 0; + uint8_t starting_packet = 0; // set to 1 for the first packet of a fragmented SBC frame + uint8_t last_packet = 0; // set to 1 for the last packet of a fragmented SBC frame + uint8_t num_frames = 0; + + uint32_t total_sbc_bytes_read = 0; + uint8_t sbc_frame_size = 0; + // payload + while (mtu - 13 - total_sbc_bytes_read >= 120 && btstack_ring_buffer_bytes_available(&stream_endpoint->sbc_ring_buffer)){ + uint32_t number_of_bytes_read = 0; + btstack_ring_buffer_read(&stream_endpoint->sbc_ring_buffer, &sbc_frame_size, 1, &number_of_bytes_read); + btstack_ring_buffer_read(&stream_endpoint->sbc_ring_buffer, media_packet + pos, sbc_frame_size, &number_of_bytes_read); + pos += sbc_frame_size; + total_sbc_bytes_read += sbc_frame_size; + num_frames++; + // printf("send sbc frame: timestamp %d, seq. nr %d\n", timestamp, stream_endpoint->sequence_number); + } + media_packet[sbc_header_index] = (fragmentation << 7) && (starting_packet << 6) && (last_packet << 5) && num_frames; + stream_endpoint->sequence_number++; + l2cap_send(stream_endpoint->l2cap_media_cid, media_packet, pos); + if (btstack_ring_buffer_bytes_available(&stream_endpoint->sbc_ring_buffer)){ + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_STREAMING_W2_SEND; + l2cap_request_can_send_now_event(stream_endpoint->l2cap_media_cid); + } + } + connection = stream_endpoint->connection; + } + avdtp_handle_can_send_now(connection, channel, context); + break; + default: + printf("unknown HCI event type %02x\n", hci_event_packet_get_type(packet)); + break; + } + break; + + default: + // other packet type + break; + } +} + +void avdtp_disconnect(uint16_t con_handle, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection) return; + if (connection->state == AVDTP_SIGNALING_CONNECTION_IDLE) return; + if (connection->state == AVDTP_SIGNALING_CONNECTION_W4_L2CAP_DISCONNECTED) return; + + connection->disconnect = 1; + avdtp_request_can_send_now_self(connection, connection->l2cap_signaling_cid); +} + +void avdtp_open_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_media_connect: no connection for handle 0x%02x found\n", con_handle); + return; + } + + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { + printf("avdtp_media_connect: wrong connection state %d\n", connection->state); + return; + } + + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(acp_seid, context); + if (!stream_endpoint) { + printf("avdtp_media_connect: no stream_endpoint with acp seid %d found\n", acp_seid); + return; + } + + if (stream_endpoint->state < AVDTP_STREAM_ENDPOINT_CONFIGURED) return; + if (stream_endpoint->remote_sep_index == 0xFF) return; + + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = stream_endpoint->sep.seid; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_OPEN_STREAM; + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_W2_REQUEST_OPEN_STREAM; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + +void avdtp_start_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_media_connect: no connection for handle 0x%02x found\n", con_handle); + return; + } + + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { + printf("avdtp_media_connect: wrong connection state %d\n", connection->state); + return; + } + + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(acp_seid, context); + if (!stream_endpoint) { + printf("avdtp_media_connect: no stream_endpoint with acp_seid %d found\n", acp_seid); + return; + } + if (stream_endpoint->remote_sep_index == 0xFF) return; + if (stream_endpoint->state < AVDTP_STREAM_ENDPOINT_OPENED) return; + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = stream_endpoint->sep.seid; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_STREAMING_START; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + +void avdtp_stop_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_stop_stream: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { + printf("avdtp_stop_stream: wrong connection state %d\n", connection->state); + return; + } + + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(acp_seid, context); + if (!stream_endpoint) { + printf("avdtp_stop_stream: no stream_endpoint with acp seid %d found\n", acp_seid); + return; + } + if (stream_endpoint->remote_sep_index == 0xFF) return; + switch (stream_endpoint->state){ + case AVDTP_STREAM_ENDPOINT_OPENED: + case AVDTP_STREAM_ENDPOINT_STREAMING: + printf(" AVDTP_INITIATOR_W2_STREAMING_STOP \n"); + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = stream_endpoint->sep.seid; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_STREAMING_STOP; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + break; + default: + break; + } +} + +void avdtp_abort_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_abort_stream: no connection for handle 0x%02x found\n", con_handle); + return; + } + + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { + printf("avdtp_abort_stream: wrong connection state %d\n", connection->state); + return; + } + + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(acp_seid, context); + if (!stream_endpoint) { + printf("avdtp_abort_stream: no stream_endpoint for seid %d found\n", acp_seid); + return; + } + if (stream_endpoint->remote_sep_index == 0xFF) return; + switch (stream_endpoint->state){ + case AVDTP_STREAM_ENDPOINT_CONFIGURED: + case AVDTP_STREAM_ENDPOINT_CLOSING: + case AVDTP_STREAM_ENDPOINT_OPENED: + case AVDTP_STREAM_ENDPOINT_STREAMING: + printf(" AVDTP_INITIATOR_W2_STREAMING_ABORT \n"); + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = stream_endpoint->sep.seid; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_STREAMING_ABORT; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + break; + default: + break; + } +} + +void avdtp_discover_stream_endpoints(uint16_t con_handle, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_discover_stream_endpoints: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; + + switch (connection->initiator_connection_state){ + case AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE: + connection->initiator_transaction_label++; + connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_DISCOVER_SEPS; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + break; + default: + printf("avdtp_discover_stream_endpoints: wrong state\n"); + break; + } +} + + +void avdtp_get_capabilities(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_get_capabilities: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; + if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; + connection->initiator_transaction_label++; + connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_GET_CAPABILITIES; + connection->acp_seid = acp_seid; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + + +void avdtp_get_all_capabilities(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_get_all_capabilities: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; + if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; + connection->initiator_transaction_label++; + connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_GET_ALL_CAPABILITIES; + connection->acp_seid = acp_seid; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + +void avdtp_get_configuration(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_get_configuration: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; + if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; + connection->initiator_transaction_label++; + connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_GET_CONFIGURATION; + connection->acp_seid = acp_seid; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + +void avdtp_set_configuration(uint16_t con_handle, uint8_t int_seid, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + log_error("avdtp_set_configuration: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; + if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; + + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(int_seid, context); + if (!stream_endpoint) { + log_error("avdtp_set_configuration: no initiator stream endpoint for seid %d\n", int_seid); + return; + } + // printf("avdtp_set_configuration int seid %d, acp seid %d\n", int_seid, acp_seid); + + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = int_seid; + connection->remote_capabilities_bitmap = configured_services_bitmap; + connection->remote_capabilities = configuration; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_SET_CONFIGURATION; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + +void avdtp_reconfigure(uint16_t con_handle, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_reconfigure: no connection for handle 0x%02x found\n", con_handle); + return; + } + //TODO: if opened only app capabilities, enable reconfigure for not opened + if (connection->state < AVDTP_SIGNALING_CONNECTION_OPENED) return; + if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(acp_seid, context); + if (!stream_endpoint) return; + if (stream_endpoint->remote_sep_index == 0xFF) return; + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = stream_endpoint->sep.seid; + connection->remote_capabilities_bitmap = configured_services_bitmap; + connection->remote_capabilities = configuration; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_RECONFIGURE_STREAM_WITH_SEID; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} + +void avdtp_suspend(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context){ + avdtp_connection_t * connection = avdtp_connection_for_con_handle(con_handle, context); + if (!connection){ + printf("avdtp_suspend: no connection for handle 0x%02x found\n", con_handle); + return; + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; + if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(acp_seid, context); + if (!stream_endpoint) return; + if (stream_endpoint->remote_sep_index == 0xFF) return; + connection->initiator_transaction_label++; + connection->acp_seid = acp_seid; + connection->int_seid = stream_endpoint->sep.seid; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_SUSPEND_STREAM_WITH_SEID; + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); +} diff --git a/src/classic/avdtp.h b/src/classic/avdtp.h index d6ce7eb08..80036fcaa 100644 --- a/src/classic/avdtp.h +++ b/src/classic/avdtp.h @@ -52,6 +52,8 @@ #include #include "hci.h" +#include "classic/btstack_sbc.h" +#include "btstack_ring_buffer.h" #if defined __cplusplus extern "C" { @@ -284,9 +286,14 @@ typedef enum { AVDTP_STREAM_ENDPOINT_IDLE, AVDTP_STREAM_ENDPOINT_CONFIGURATION_SUBSTATEMACHINE, AVDTP_STREAM_ENDPOINT_CONFIGURED, + + AVDTP_STREAM_ENDPOINT_W2_REQUEST_OPEN_STREAM, AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED, + AVDTP_STREAM_ENDPOINT_OPENED, AVDTP_STREAM_ENDPOINT_STREAMING, + AVDTP_STREAM_ENDPOINT_STREAMING_W2_SEND, + AVDTP_STREAM_ENDPOINT_CLOSING, AVDTP_STREAM_ENDPOINT_ABORTING, AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_DISCONNECTED @@ -297,7 +304,9 @@ typedef enum { AVDTP_INITIATOR_W2_SET_CONFIGURATION, AVDTP_INITIATOR_W2_SUSPEND_STREAM_WITH_SEID, AVDTP_INITIATOR_W2_RECONFIGURE_STREAM_WITH_SEID, - AVDTP_INITIATOR_W2_MEDIA_CONNECT, + + AVDTP_INITIATOR_W2_OPEN_STREAM, + AVDTP_INITIATOR_W2_STREAMING_START, AVDTP_INITIATOR_W2_STREAMING_STOP, AVDTP_INITIATOR_W2_STREAMING_ABORT, @@ -313,9 +322,6 @@ typedef enum { AVDTP_ACCEPTOR_W2_ANSWER_RECONFIGURE, AVDTP_ACCEPTOR_W2_ANSWER_GET_CONFIGURATION, AVDTP_ACCEPTOR_W2_ANSWER_OPEN_STREAM, - - AVDTP_ACCEPTOR_W4_L2CAP_FOR_MEDIA_CONNECTED, - AVDTP_ACCEPTOR_W2_ANSWER_START_STREAM, AVDTP_ACCEPTOR_W2_ANSWER_CLOSE_STREAM, AVDTP_ACCEPTOR_W2_ANSWER_ABORT_STREAM, @@ -395,6 +401,7 @@ typedef struct { avdtp_signaling_packet_t signaling_packet; uint8_t disconnect; + uint8_t initiator_transaction_label; uint8_t acceptor_transaction_label; uint8_t query_seid; @@ -441,8 +448,56 @@ typedef struct avdtp_stream_endpoint { // register request for media L2cap connection release uint8_t media_disconnect; uint8_t media_connect; + + btstack_timer_source_t fill_audio_ring_buffer_timer; + uint32_t time_audio_data_sent; // ms + uint32_t acc_num_missed_samples; + btstack_sbc_encoder_state_t sbc_encoder_state; + uint16_t sequence_number; + + btstack_ring_buffer_t audio_ring_buffer; + btstack_ring_buffer_t sbc_ring_buffer; } avdtp_stream_endpoint_t; +typedef struct { + btstack_linked_list_t connections; + btstack_linked_list_t stream_endpoints; + uint16_t stream_endpoints_id_counter; + btstack_packet_handler_t avdtp_callback; + void (*handle_media_data)(avdtp_stream_endpoint_t * stream_endpoint, uint8_t *packet, uint16_t size); + btstack_packet_handler_t packet_handler; +} avdtp_context_t; + +void avdtp_register_media_transport_category(avdtp_stream_endpoint_t * stream_endpoint); +void avdtp_register_reporting_category(avdtp_stream_endpoint_t * stream_endpoint); +void avdtp_register_delay_reporting_category(avdtp_stream_endpoint_t * stream_endpoint); +void avdtp_register_recovery_category(avdtp_stream_endpoint_t * stream_endpoint, uint8_t maximum_recovery_window_size, uint8_t maximum_number_media_packets); +void avdtp_register_content_protection_category(avdtp_stream_endpoint_t * stream_endpoint, uint16_t cp_type, const uint8_t * cp_type_value, uint8_t cp_type_value_len); +void avdtp_register_header_compression_category(avdtp_stream_endpoint_t * stream_endpoint, uint8_t back_ch, uint8_t media, uint8_t recovery); +void avdtp_register_media_codec_category(avdtp_stream_endpoint_t * stream_endpoint, avdtp_media_type_t media_type, avdtp_media_codec_type_t media_codec_type, const uint8_t * media_codec_info, uint16_t media_codec_info_len); +void avdtp_register_multiplexing_category(avdtp_stream_endpoint_t * stream_endpoint, uint8_t fragmentation); +void avdtp_handle_can_send_now(avdtp_connection_t * connection, uint16_t l2cap_cid, avdtp_context_t * context); + +void avdtp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, avdtp_context_t * context); +avdtp_connection_t * avdtp_create_connection(bd_addr_t remote_addr, avdtp_context_t * context); +avdtp_stream_endpoint_t * avdtp_create_stream_endpoint(avdtp_sep_type_t sep_type, avdtp_media_type_t media_type, avdtp_context_t * context); + +void avdtp_disconnect(uint16_t con_handle, avdtp_context_t * context); +void avdtp_open_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_start_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_stop_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_abort_stream(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_discover_stream_endpoints(uint16_t con_handle, avdtp_context_t * context); +void avdtp_get_capabilities(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_get_all_capabilities(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_get_configuration(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); +void avdtp_set_configuration(uint16_t con_handle, uint8_t int_seid, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration, avdtp_context_t * context); +void avdtp_reconfigure(uint16_t con_handle, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration, avdtp_context_t * context); +void avdtp_suspend(uint16_t con_handle, uint8_t acp_seid, avdtp_context_t * context); + +void avdtp_source_stream_data_start(uint16_t con_handle); +void avdtp_source_stream_data_stop(uint16_t con_handle); + #if defined __cplusplus } #endif diff --git a/src/classic/avdtp_acceptor.c b/src/classic/avdtp_acceptor.c index a840565c2..84fa66b08 100644 --- a/src/classic/avdtp_acceptor.c +++ b/src/classic/avdtp_acceptor.c @@ -81,15 +81,15 @@ static int avdtp_acceptor_validate_msg_length(avdtp_signal_identifier_t signal_i return msg_size >= minimal_msg_lenght; } -void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t * packet, uint16_t size, int offset){ +void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t * packet, uint16_t size, int offset, avdtp_context_t * context){ avdtp_stream_endpoint_t * stream_endpoint; connection->acceptor_transaction_label = connection->signaling_packet.transaction_label; - + if (!avdtp_acceptor_validate_msg_length(connection->signaling_packet.signal_identifier, size)) { connection->error_code = BAD_LENGTH; connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_REJECT_WITH_ERROR_CODE; connection->reject_signal_identifier = connection->signaling_packet.signal_identifier; - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); return; } @@ -98,7 +98,7 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; printf(" ACP: AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_ANSWER_DISCOVER_SEPS\n"); connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_ANSWER_DISCOVER_SEPS; - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); return; case AVDTP_SI_GET_CAPABILITIES: case AVDTP_SI_GET_ALL_CAPABILITIES: @@ -110,7 +110,7 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t case AVDTP_SI_OPEN: case AVDTP_SI_RECONFIGURE: connection->query_seid = packet[offset++] >> 2; - stream_endpoint = get_avdtp_stream_endpoint_with_seid(connection->query_seid); + stream_endpoint = avdtp_stream_endpoint_with_seid(connection->query_seid, context); if (!stream_endpoint){ printf(" ACP: cmd %d - RESPONSE REJECT\n", connection->signaling_packet.signal_identifier); connection->error_code = BAD_ACP_SEID; @@ -124,7 +124,7 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_REJECT_CATEGORY_WITH_ERROR_CODE; } connection->reject_signal_identifier = connection->signaling_packet.signal_identifier; - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); return; } break; @@ -147,12 +147,12 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t connection->reject_service_category = connection->query_seid; connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_REJECT_CATEGORY_WITH_ERROR_CODE; connection->reject_signal_identifier = connection->signaling_packet.signal_identifier; - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); return; } // deal with first susspended seid connection->query_seid = connection->suspended_seids[0]; - stream_endpoint = get_avdtp_stream_endpoint_with_seid(connection->query_seid); + stream_endpoint = avdtp_stream_endpoint_with_seid(connection->query_seid, context); if (!stream_endpoint){ printf(" ACP: stream_endpoint not found, CATEGORY RESPONSE REJECT BAD_ACP_SEID\n"); connection->error_code = BAD_ACP_SEID; @@ -160,7 +160,7 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_REJECT_CATEGORY_WITH_ERROR_CODE; connection->reject_signal_identifier = connection->signaling_packet.signal_identifier; connection->num_suspended_seids = 0; - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); return; } break; @@ -169,7 +169,7 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_GENERAL_REJECT_WITH_ERROR_CODE; connection->reject_signal_identifier = connection->signaling_packet.signal_identifier; printf("AVDTP_CMD_MSG signal %d not implemented, general reject\n", connection->signaling_packet.signal_identifier); - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); return; } @@ -250,14 +250,14 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t if (get_bit16(sep.configured_service_categories, AVDTP_MEDIA_CODEC)){ switch (sep.configuration.media_codec.media_codec_type){ case AVDTP_CODEC_SBC: - avdtp_signaling_emit_media_codec_sbc_configuration(avdtp_sink_callback, connection->con_handle, sep.configuration.media_codec); + avdtp_signaling_emit_media_codec_sbc_configuration(context->avdtp_callback, connection->con_handle, sep.configuration.media_codec); break; default: - avdtp_signaling_emit_media_codec_other_configuration(avdtp_sink_callback, connection->con_handle, sep.configuration.media_codec); + avdtp_signaling_emit_media_codec_other_configuration(context->avdtp_callback, connection->con_handle, sep.configuration.media_codec); break; } } - avdtp_signaling_emit_accept(avdtp_sink_callback, connection->con_handle, connection->signaling_packet.signal_identifier, 0); + avdtp_signaling_emit_accept(context->avdtp_callback, connection->con_handle, connection->signaling_packet.signal_identifier, 0); break; } case AVDTP_SI_RECONFIGURE:{ @@ -300,14 +300,14 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t if (get_bit16(sep.configured_service_categories, AVDTP_MEDIA_CODEC)){ switch (sep.capabilities.media_codec.media_codec_type){ case AVDTP_CODEC_SBC: - avdtp_signaling_emit_media_codec_sbc_reconfiguration(avdtp_sink_callback, connection->con_handle, sep.capabilities.media_codec); + avdtp_signaling_emit_media_codec_sbc_reconfiguration(context->avdtp_callback, connection->con_handle, sep.capabilities.media_codec); break; default: - avdtp_signaling_emit_media_codec_other_reconfiguration(avdtp_sink_callback, connection->con_handle, sep.capabilities.media_codec); + avdtp_signaling_emit_media_codec_other_reconfiguration(context->avdtp_callback, connection->con_handle, sep.capabilities.media_codec); break; } } - avdtp_signaling_emit_accept(avdtp_sink_callback, connection->con_handle, connection->signaling_packet.signal_identifier, 0); + avdtp_signaling_emit_accept(context->avdtp_callback, connection->con_handle, connection->signaling_packet.signal_identifier, 0); break; } @@ -326,10 +326,11 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t printf(" ACP: AVDTP_STREAM_ENDPOINT_W2_ANSWER_OPEN_STREAM\n"); stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_W2_ANSWER_OPEN_STREAM; stream_endpoint->state = AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED; + connection->query_seid = stream_endpoint->sep.seid; break; case AVDTP_SI_START: if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_OPENED){ - printf(" ACP: REJECT AVDTP_SI_OPEN, BAD_STATE\n"); + printf(" ACP: REJECT AVDTP_SI_START, BAD_STATE\n"); stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_W2_REJECT_CATEGORY_WITH_ERROR_CODE; connection->error_code = BAD_STATE; connection->reject_signal_identifier = connection->signaling_packet.signal_identifier; @@ -409,7 +410,7 @@ void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t if (!request_to_send){ printf(" ACP: NOT IMPLEMENTED\n"); } - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); } static int avdtp_acceptor_send_seps_response(uint16_t cid, uint8_t transaction_label, avdtp_stream_endpoint_t * endpoints){ @@ -459,15 +460,15 @@ static int avdtp_acceptor_send_response_reject_with_error_code(uint16_t cid, avd return l2cap_send(cid, command, sizeof(command)); } -void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ +void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection, avdtp_context_t * context){ int sent = 1; - + btstack_linked_list_t * stream_endpoints = &context->stream_endpoints; + switch (connection->acceptor_connection_state){ case AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_ANSWER_DISCOVER_SEPS: - printf(" -> AVDTP_SIGNALING_CONNECTION_OPENED\n"); connection->state = AVDTP_SIGNALING_CONNECTION_OPENED; connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_IDLE; - avdtp_acceptor_send_seps_response(connection->l2cap_signaling_cid, connection->acceptor_transaction_label, (avdtp_stream_endpoint_t *)&stream_endpoints); + avdtp_acceptor_send_seps_response(connection->l2cap_signaling_cid, connection->acceptor_transaction_label, (avdtp_stream_endpoint_t *) stream_endpoints); break; case AVDTP_SIGNALING_CONNECTION_ACCEPTOR_W2_REJECT_WITH_ERROR_CODE: connection->acceptor_connection_state = AVDTP_SIGNALING_CONNECTION_ACCEPTOR_IDLE; @@ -484,9 +485,12 @@ void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ sent = 0; break; } - if (sent) return; + if (sent){ + printf(" ACP: DONE\n"); + return; + } - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(connection->query_seid); + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(connection->query_seid, context); if (!stream_endpoint) return; uint8_t reject_service_category = connection->reject_service_category; @@ -512,6 +516,8 @@ void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ if (connection->signaling_packet.packet_type != AVDTP_SINGLE_PACKET && connection->signaling_packet.packet_type != AVDTP_END_PACKET){ stream_endpoint->acceptor_config_state = acceptor_config_state; printf(" ACP: fragmented\n"); + } else { + printf(" ACP:DONE\n"); } l2cap_send_prepared(cid, pos); break; @@ -523,6 +529,8 @@ void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ if (connection->signaling_packet.packet_type != AVDTP_SINGLE_PACKET && connection->signaling_packet.packet_type != AVDTP_END_PACKET){ stream_endpoint->acceptor_config_state = acceptor_config_state; printf(" ACP: fragmented\n"); + } else { + printf(" ACP:DONE\n"); } l2cap_send_prepared(cid, pos); break; @@ -531,6 +539,10 @@ void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ printf(" -> AVDTP_STREAM_ENDPOINT_CONFIGURED\n"); stream_endpoint->connection = connection; stream_endpoint->state = AVDTP_STREAM_ENDPOINT_CONFIGURED; + // TODO: use actual config + // TODO: consider reconfiguration + btstack_sbc_encoder_init(&stream_endpoint->sbc_encoder_state, SBC_MODE_STANDARD, 16, 8, 2, 44100, 53); + avdtp_acceptor_send_accept_response(cid, trid, AVDTP_SI_SET_CONFIGURATION); break; case AVDTP_ACCEPTOR_W2_ANSWER_RECONFIGURE: @@ -547,13 +559,12 @@ void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ if (connection->signaling_packet.packet_type != AVDTP_SINGLE_PACKET && connection->signaling_packet.packet_type != AVDTP_END_PACKET){ stream_endpoint->acceptor_config_state = acceptor_config_state; printf(" ACP: fragmented\n"); + } else { + printf(" ACP:DONE\n"); } l2cap_send_prepared(cid, pos); break; } - case AVDTP_ACCEPTOR_W4_L2CAP_FOR_MEDIA_CONNECTED: - stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_W4_L2CAP_FOR_MEDIA_CONNECTED; - break; case AVDTP_ACCEPTOR_W2_ANSWER_OPEN_STREAM: printf(" ACP: DONE\n"); avdtp_acceptor_send_accept_response(cid, trid, AVDTP_SI_OPEN); @@ -602,10 +613,10 @@ void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection){ printf(" ACP: NOT IMPLEMENTED\n"); sent = 0; } - avdtp_signaling_emit_accept(avdtp_sink_callback, connection->con_handle, connection->signaling_packet.signal_identifier, status); + avdtp_signaling_emit_accept(context->avdtp_callback, connection->con_handle, connection->signaling_packet.signal_identifier, status); // check fragmentation if (connection->signaling_packet.packet_type != AVDTP_SINGLE_PACKET && connection->signaling_packet.packet_type != AVDTP_END_PACKET){ - avdtp_sink_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_acceptor(connection, connection->l2cap_signaling_cid); } } diff --git a/src/classic/avdtp_acceptor.h b/src/classic/avdtp_acceptor.h index 80d703020..31ac18bb2 100644 --- a/src/classic/avdtp_acceptor.h +++ b/src/classic/avdtp_acceptor.h @@ -51,8 +51,8 @@ extern "C" { #endif -void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, int offset); -void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection); +void avdtp_acceptor_stream_config_subsm(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, int offset, avdtp_context_t * context); +void avdtp_acceptor_stream_config_subsm_run(avdtp_connection_t * connection, avdtp_context_t * context); #if defined __cplusplus } diff --git a/src/classic/avdtp_initiator.c b/src/classic/avdtp_initiator.c index 61b5da2e0..177eb5aaf 100644 --- a/src/classic/avdtp_initiator.c +++ b/src/classic/avdtp_initiator.c @@ -89,22 +89,23 @@ static void avdtp_signaling_emit_media_codec_configuration(btstack_packet_handle } } -void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, int offset){ +void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, int offset, avdtp_context_t * context){ int status = 0; avdtp_stream_endpoint_t * stream_endpoint = NULL; + uint8_t remote_sep_index; avdtp_sep_t sep; if (connection->initiator_connection_state == AVDTP_SIGNALING_CONNECTION_INITIATOR_W4_ANSWER) { connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE; } else { - stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(connection->acp_seid); + stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(connection->acp_seid, context); if (!stream_endpoint){ - stream_endpoint = get_avdtp_stream_endpoint_with_seid(connection->int_seid); + stream_endpoint = avdtp_stream_endpoint_with_seid(connection->int_seid, context); } if (!stream_endpoint) return; sep.seid = connection->acp_seid; - printf("avdtp_initiator_stream_config_subsm int seid %d, acp seid %d, ident %d \n", connection->int_seid, connection->acp_seid, connection->signaling_packet.signal_identifier); + printf(" INT: local seid %d, remote seid %d, ident %d \n", connection->int_seid, connection->acp_seid, connection->signaling_packet.signal_identifier); if (stream_endpoint->initiator_config_state != AVDTP_INITIATOR_W4_ANSWER) return; stream_endpoint->initiator_config_state = AVDTP_INITIATOR_STREAM_CONFIG_IDLE; } @@ -139,7 +140,7 @@ void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_ sep.in_use = (packet[i] >> 1) & 0x01; sep.media_type = (avdtp_media_type_t)(packet[i+1] >> 4); sep.type = (avdtp_sep_type_t)((packet[i+1] >> 3) & 0x01); - avdtp_signaling_emit_sep(avdtp_sink_callback, connection->con_handle, sep); + avdtp_signaling_emit_sep(context->avdtp_callback, connection->con_handle, sep); } break; } @@ -148,14 +149,14 @@ void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_ case AVDTP_SI_GET_ALL_CAPABILITIES: printf("AVDTP_SI_GET(_ALL)_CAPABILITIES\n"); sep.registered_service_categories = avdtp_unpack_service_capabilities(connection, &sep.capabilities, packet+offset, size-offset); - avdtp_signaling_emit_media_codec_capability(avdtp_sink_callback, connection->con_handle, sep); + avdtp_signaling_emit_media_codec_capability(context->avdtp_callback, connection->con_handle, sep); break; case AVDTP_SI_GET_CONFIGURATION: printf("AVDTP_SI_GET_CONFIGURATION\n"); sep.configured_service_categories = avdtp_unpack_service_capabilities(connection, &sep.configuration, packet+offset, size-offset); - avdtp_signaling_emit_media_codec_configuration(avdtp_sink_callback, connection->con_handle, sep); + avdtp_signaling_emit_media_codec_configuration(context->avdtp_callback, connection->con_handle, sep); break; case AVDTP_SI_RECONFIGURE: @@ -188,21 +189,39 @@ void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_ stream_endpoint->remote_seps_num++; } stream_endpoint->remote_seps[stream_endpoint->remote_sep_index] = sep; - printf(" INT: configured seid %d, to %p\n", stream_endpoint->remote_seps[stream_endpoint->remote_sep_index].seid, stream_endpoint); + printf(" INT: configured remote seid %d, to %p\n", stream_endpoint->remote_seps[stream_endpoint->remote_sep_index].seid, stream_endpoint); stream_endpoint->state = AVDTP_STREAM_ENDPOINT_CONFIGURED; + + // TODO: use actual config + // TODO: consider reconfiguration + btstack_sbc_encoder_init(&stream_endpoint->sbc_encoder_state, SBC_MODE_STANDARD, 16, 8, 2, 44100, 53); break; } case AVDTP_SI_OPEN: printf("AVDTP_SI_OPEN\n"); - stream_endpoint->state = AVDTP_STREAM_ENDPOINT_OPENED; - break; + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W2_REQUEST_OPEN_STREAM) { + log_error("AVDTP_SI_OPEN in wrong stream endpoint state"); + return; + } + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED; + connection->query_seid = stream_endpoint->sep.seid; + l2cap_create_channel(context->packet_handler, connection->remote_addr, PSM_AVDTP, 0xffff, NULL); + return; case AVDTP_SI_START: printf("AVDTP_SI_START\n"); + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_OPENED) { + log_error("AVDTP_SI_START in wrong stream endpoint state"); + return; + } stream_endpoint->state = AVDTP_STREAM_ENDPOINT_STREAMING; break; case AVDTP_SI_SUSPEND: printf("AVDTP_SI_SUSPEND\n"); + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_STREAMING) { + log_error("AVDTP_SI_SUSPEND in wrong stream endpoint state"); + return; + } stream_endpoint->state = AVDTP_STREAM_ENDPOINT_OPENED; break; case AVDTP_SI_CLOSE: @@ -221,23 +240,25 @@ void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_ break; case AVDTP_RESPONSE_REJECT_MSG: printf(" AVDTP_RESPONSE_REJECT_MSG signal %d\n", connection->signaling_packet.signal_identifier); - avdtp_signaling_emit_reject(avdtp_sink_callback, connection->con_handle, connection->signaling_packet.signal_identifier); + avdtp_signaling_emit_reject(context->avdtp_callback, connection->con_handle, connection->signaling_packet.signal_identifier); return; case AVDTP_GENERAL_REJECT_MSG: printf(" AVDTP_GENERAL_REJECT_MSG signal %d\n", connection->signaling_packet.signal_identifier); - avdtp_signaling_emit_general_reject(avdtp_sink_callback, connection->con_handle, connection->signaling_packet.signal_identifier); + avdtp_signaling_emit_general_reject(context->avdtp_callback, connection->con_handle, connection->signaling_packet.signal_identifier); return; default: break; } + connection->initiator_transaction_label++; connection->int_seid = 0; connection->acp_seid = 0; - avdtp_signaling_emit_accept(avdtp_sink_callback, connection->con_handle, connection->signaling_packet.signal_identifier, status); + avdtp_signaling_emit_accept(context->avdtp_callback, connection->con_handle, connection->signaling_packet.signal_identifier, status); } -void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection){ +void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection, avdtp_context_t * context){ int sent = 1; + switch (connection->initiator_connection_state){ case AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_DISCOVER_SEPS: printf(" INT: AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_DISCOVER_SEPS\n"); @@ -268,11 +289,11 @@ void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection){ sent = 1; avdtp_stream_endpoint_t * stream_endpoint = NULL; - printf(" run int seid %d, acp seid %d\n", connection->int_seid, connection->acp_seid); + // printf(" run int seid %d, acp seid %d\n", connection->int_seid, connection->acp_seid); - stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(connection->acp_seid); + stream_endpoint = avdtp_stream_endpoint_associated_with_acp_seid(connection->acp_seid, context); if (!stream_endpoint){ - stream_endpoint = get_avdtp_stream_endpoint_with_seid(connection->int_seid); + stream_endpoint = avdtp_stream_endpoint_with_seid(connection->int_seid, context); } if (!stream_endpoint) return; @@ -283,7 +304,7 @@ void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection){ case AVDTP_INITIATOR_W2_SET_CONFIGURATION: case AVDTP_INITIATOR_W2_RECONFIGURE_STREAM_WITH_SEID:{ printf(" INT: AVDTP_INITIATOR_W2_(RE)CONFIGURATION bitmap, int seid %d, acp seid %d\n", connection->int_seid, connection->acp_seid); - printf_hexdump( connection->remote_capabilities.media_codec.media_codec_information, connection->remote_capabilities.media_codec.media_codec_information_len); + // printf_hexdump( connection->remote_capabilities.media_codec.media_codec_information, connection->remote_capabilities.media_codec.media_codec_information_len); connection->signaling_packet.acp_seid = connection->acp_seid; connection->signaling_packet.int_seid = connection->int_seid; @@ -315,10 +336,15 @@ void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection){ l2cap_send_prepared(connection->l2cap_signaling_cid, pos); break; } - case AVDTP_INITIATOR_W2_MEDIA_CONNECT: - printf(" INT: AVDTP_INITIATOR_W4_L2CAP_FOR_MEDIA_CONNECTED\n"); - stream_endpoint->state = AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED; - avdtp_initiator_send_signaling_cmd_with_seid(connection->l2cap_signaling_cid, AVDTP_SI_OPEN, connection->initiator_transaction_label, connection->acp_seid); + case AVDTP_INITIATOR_W2_OPEN_STREAM: + switch (stream_endpoint->state){ + case AVDTP_STREAM_ENDPOINT_W2_REQUEST_OPEN_STREAM: + printf(" INT: AVDTP_STREAM_ENDPOINT_W2_REQUEST_OPEN_STREAM\n"); + avdtp_initiator_send_signaling_cmd_with_seid(connection->l2cap_signaling_cid, AVDTP_SI_OPEN, connection->initiator_transaction_label, connection->acp_seid); + break; + default: + break; + } break; case AVDTP_INITIATOR_W2_SUSPEND_STREAM_WITH_SEID: printf(" INT: AVDTP_INITIATOR_W4_SUSPEND_STREAM_WITH_SEID\n"); @@ -341,8 +367,9 @@ void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection){ break; } + // check fragmentation if (connection->signaling_packet.packet_type != AVDTP_SINGLE_PACKET && connection->signaling_packet.packet_type != AVDTP_END_PACKET){ - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); } } diff --git a/src/classic/avdtp_initiator.h b/src/classic/avdtp_initiator.h index 1ad211be7..bee3ca6b4 100644 --- a/src/classic/avdtp_initiator.h +++ b/src/classic/avdtp_initiator.h @@ -51,8 +51,8 @@ extern "C" { #endif -void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, int offset); -void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection); +void avdtp_initiator_stream_config_subsm(avdtp_connection_t * connection, uint8_t *packet, uint16_t size, int offset, avdtp_context_t * context); +void avdtp_initiator_stream_config_subsm_run(avdtp_connection_t * connection, avdtp_context_t * context); #if defined __cplusplus } diff --git a/src/classic/avdtp_sink.c b/src/classic/avdtp_sink.c index edb5e63dd..2982828d0 100644 --- a/src/classic/avdtp_sink.c +++ b/src/classic/avdtp_sink.c @@ -52,17 +52,11 @@ static const char * default_avdtp_sink_service_name = "BTstack AVDTP Sink Service"; static const char * default_avdtp_sink_service_provider_name = "BTstack AVDTP Sink Service Provider"; -// TODO list of devices -static btstack_linked_list_t avdtp_connections; -static uint16_t stream_endpoints_id_counter; - -btstack_linked_list_t stream_endpoints; -btstack_packet_handler_t avdtp_sink_callback; - +static avdtp_context_t avdtp_sink_context; static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); -static void (*handle_media_data)(avdtp_stream_endpoint_t * stream_endpoint, uint8_t *packet, uint16_t size); +//static void (*handle_media_data)(avdtp_stream_endpoint_t * stream_endpoint, uint8_t *packet, uint16_t size); void a2dp_sink_create_sdp_record(uint8_t * service, uint32_t service_record_handle, uint16_t supported_features, const char * service_name, const char * service_provider_name){ uint8_t* attribute; @@ -144,121 +138,44 @@ void a2dp_sink_create_sdp_record(uint8_t * service, uint32_t service_record_han } -static avdtp_connection_t * avdtp_sink_create_connection(bd_addr_t remote_addr){ - avdtp_connection_t * connection = btstack_memory_avdtp_connection_get(); - memset(connection, 0, sizeof(avdtp_connection_t)); - connection->state = AVDTP_SIGNALING_CONNECTION_IDLE; - connection->initiator_transaction_label++; - memcpy(connection->remote_addr, remote_addr, 6); - btstack_linked_list_add(&avdtp_connections, (btstack_linked_item_t *) connection); - return connection; -} - -avdtp_stream_endpoint_t * avdtp_sink_create_stream_endpoint(avdtp_sep_type_t sep_type, avdtp_media_type_t media_type){ - avdtp_stream_endpoint_t * stream_endpoint = btstack_memory_avdtp_stream_endpoint_get(); - memset(stream_endpoint, 0, sizeof(avdtp_stream_endpoint_t)); - stream_endpoints_id_counter++; - stream_endpoint->sep.seid = stream_endpoints_id_counter; - stream_endpoint->sep.media_type = media_type; - stream_endpoint->sep.type = sep_type; - btstack_linked_list_add(&stream_endpoints, (btstack_linked_item_t *) stream_endpoint); - return stream_endpoint; -} - void avdtp_sink_register_media_transport_category(uint8_t seid){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_MEDIA_TRANSPORT, 1); - stream_endpoint->sep.registered_service_categories = bitmap; - printf("registered services AVDTP_MEDIA_TRANSPORT(%d) %02x\n", AVDTP_MEDIA_TRANSPORT, stream_endpoint->sep.registered_service_categories); + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_media_transport_category(stream_endpoint); } void avdtp_sink_register_reporting_category(uint8_t seid){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_REPORTING, 1); - stream_endpoint->sep.registered_service_categories = bitmap; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_reporting_category(stream_endpoint); } void avdtp_sink_register_delay_reporting_category(uint8_t seid){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_DELAY_REPORTING, 1); - stream_endpoint->sep.registered_service_categories = bitmap; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_delay_reporting_category(stream_endpoint); } void avdtp_sink_register_recovery_category(uint8_t seid, uint8_t maximum_recovery_window_size, uint8_t maximum_number_media_packets){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_RECOVERY, 1); - stream_endpoint->sep.registered_service_categories = bitmap; - stream_endpoint->sep.capabilities.recovery.recovery_type = 0x01; // 0x01 = RFC2733 - stream_endpoint->sep.capabilities.recovery.maximum_recovery_window_size = maximum_recovery_window_size; - stream_endpoint->sep.capabilities.recovery.maximum_number_media_packets = maximum_number_media_packets; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_recovery_category(stream_endpoint, maximum_recovery_window_size, maximum_number_media_packets); } void avdtp_sink_register_content_protection_category(uint8_t seid, uint16_t cp_type, const uint8_t * cp_type_value, uint8_t cp_type_value_len){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_CONTENT_PROTECTION, 1); - stream_endpoint->sep.registered_service_categories = bitmap; - stream_endpoint->sep.capabilities.content_protection.cp_type = cp_type; - stream_endpoint->sep.capabilities.content_protection.cp_type_value = cp_type_value; - stream_endpoint->sep.capabilities.content_protection.cp_type_value_len = cp_type_value_len; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_content_protection_category(stream_endpoint, cp_type, cp_type_value, cp_type_value_len); } void avdtp_sink_register_header_compression_category(uint8_t seid, uint8_t back_ch, uint8_t media, uint8_t recovery){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_HEADER_COMPRESSION, 1); - stream_endpoint->sep.registered_service_categories = bitmap; - stream_endpoint->sep.capabilities.header_compression.back_ch = back_ch; - stream_endpoint->sep.capabilities.header_compression.media = media; - stream_endpoint->sep.capabilities.header_compression.recovery = recovery; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_header_compression_category(stream_endpoint, back_ch, media, recovery); } void avdtp_sink_register_media_codec_category(uint8_t seid, avdtp_media_type_t media_type, avdtp_media_codec_type_t media_codec_type, const uint8_t * media_codec_info, uint16_t media_codec_info_len){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_MEDIA_CODEC, 1); - stream_endpoint->sep.registered_service_categories = bitmap; - printf("registered services AVDTP_MEDIA_CODEC(%d) %02x\n", AVDTP_MEDIA_CODEC, stream_endpoint->sep.registered_service_categories); - stream_endpoint->sep.capabilities.media_codec.media_type = media_type; - stream_endpoint->sep.capabilities.media_codec.media_codec_type = media_codec_type; - stream_endpoint->sep.capabilities.media_codec.media_codec_information = media_codec_info; - stream_endpoint->sep.capabilities.media_codec.media_codec_information_len = media_codec_info_len; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_media_codec_category(stream_endpoint, media_type, media_codec_type, media_codec_info, media_codec_info_len); } void avdtp_sink_register_multiplexing_category(uint8_t seid, uint8_t fragmentation){ - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(seid); - if (!stream_endpoint){ - log_error("avdtp_sink_register_media_transport_category: stream endpoint with seid %d is not registered", seid); - return; - } - uint16_t bitmap = store_bit16(stream_endpoint->sep.registered_service_categories, AVDTP_MULTIPLEXING, 1); - stream_endpoint->sep.registered_service_categories = bitmap; - stream_endpoint->sep.capabilities.multiplexing_mode.fragmentation = fragmentation; + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_sink_context); + avdtp_register_multiplexing_category(stream_endpoint, fragmentation); } // // media, reporting. recovery @@ -267,359 +184,33 @@ void avdtp_sink_register_multiplexing_category(uint8_t seid, uint8_t fragmentati // } -static avdtp_connection_t * get_avdtp_connection_for_bd_addr(bd_addr_t addr){ - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avdtp_connections); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_connection_t * connection = (avdtp_connection_t *)btstack_linked_list_iterator_next(&it); - if (memcmp(addr, connection->remote_addr, 6) != 0) continue; - return connection; - } - return NULL; -} - -static avdtp_connection_t * get_avdtp_connection_for_con_handle(hci_con_handle_t con_handle){ - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avdtp_connections); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_connection_t * connection = (avdtp_connection_t *)btstack_linked_list_iterator_next(&it); - if (connection->con_handle != con_handle) continue; - return connection; - } - return NULL; -} - -static avdtp_connection_t * get_avdtp_connection_for_l2cap_signaling_cid(uint16_t l2cap_cid){ - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avdtp_connections); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_connection_t * connection = (avdtp_connection_t *)btstack_linked_list_iterator_next(&it); - if (connection->l2cap_signaling_cid != l2cap_cid) continue; - return connection; - } - return NULL; -} - -static avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_for_l2cap_cid(uint16_t l2cap_cid){ - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &stream_endpoints); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); - if (stream_endpoint->l2cap_media_cid == l2cap_cid){ - return stream_endpoint; - } - if (stream_endpoint->l2cap_reporting_cid == l2cap_cid){ - return stream_endpoint; - } - if (stream_endpoint->l2cap_recovery_cid == l2cap_cid){ - return stream_endpoint; - } - } - return NULL; -} - -/* START: tracking can send now requests pro l2cap cid */ -static void avdtp_sink_handle_can_send_now(avdtp_connection_t * connection, uint16_t l2cap_cid){ - if (connection->wait_to_send_acceptor){ - connection->wait_to_send_acceptor = 0; - avdtp_acceptor_stream_config_subsm_run(connection); - } else if (connection->wait_to_send_initiator){ - connection->wait_to_send_initiator = 0; - avdtp_initiator_stream_config_subsm_run(connection); - } else if (connection->wait_to_send_self){ - connection->wait_to_send_self = 0; - if (connection->disconnect){ - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &stream_endpoints); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); - if (stream_endpoint->connection == connection){ - if (stream_endpoint->state >= AVDTP_STREAM_ENDPOINT_OPENED && stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_DISCONNECTED){ - stream_endpoint->state = AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_DISCONNECTED; - avdtp_sink_request_can_send_now_self(connection, connection->l2cap_signaling_cid); - l2cap_disconnect(stream_endpoint->l2cap_media_cid, 0); - return; - } - } - } - connection->disconnect = 0; - connection->state = AVDTP_SIGNALING_CONNECTION_W4_L2CAP_DISCONNECTED; - l2cap_disconnect(connection->l2cap_signaling_cid, 0); - return; - } - } - - // re-register - int more_to_send = connection->wait_to_send_acceptor || connection->wait_to_send_initiator || connection->wait_to_send_self; - if (more_to_send){ - l2cap_request_can_send_now_event(l2cap_cid); - } -} - - /* END: tracking can send now requests pro l2cap cid */ // TODO remove -static void handle_l2cap_data_packet_for_signaling_connection(avdtp_connection_t * connection, uint8_t *packet, uint16_t size){ - int offset = avdtp_read_signaling_header(&connection->signaling_packet, packet, size); - switch (connection->signaling_packet.message_type){ - case AVDTP_CMD_MSG: - avdtp_acceptor_stream_config_subsm(connection, packet, size, offset); - break; - default: - avdtp_initiator_stream_config_subsm(connection, packet, size, offset); - break; - } -} - -static void stream_endpoint_state_machine(avdtp_connection_t * connection, avdtp_stream_endpoint_t * stream_endpoint, uint8_t packet_type, uint8_t event, uint8_t *packet, uint16_t size){ - uint16_t local_cid; - switch (packet_type){ - case L2CAP_DATA_PACKET:{ - int offset = avdtp_read_signaling_header(&connection->signaling_packet, packet, size); - if (connection->signaling_packet.message_type == AVDTP_CMD_MSG){ - avdtp_acceptor_stream_config_subsm(connection, packet, size, offset); - } else { - avdtp_initiator_stream_config_subsm(connection, packet, size, offset); - } - break; - } - case HCI_EVENT_PACKET: - switch (event){ - case L2CAP_EVENT_CHANNEL_OPENED: - if (stream_endpoint->l2cap_media_cid == 0){ - if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED) return; - stream_endpoint->state = AVDTP_STREAM_ENDPOINT_OPENED; - stream_endpoint->connection = connection; - stream_endpoint->l2cap_media_cid = l2cap_event_channel_opened_get_local_cid(packet);; - printf(" -> AVDTP_STREAM_ENDPOINT_OPENED, stream endpoint %p, connection %p\n", stream_endpoint, connection); - break; - } - break; - case L2CAP_EVENT_CHANNEL_CLOSED: - local_cid = l2cap_event_channel_closed_get_local_cid(packet); - if (stream_endpoint->l2cap_media_cid == local_cid){ - stream_endpoint->l2cap_media_cid = 0; - printf(" -> L2CAP_EVENT_CHANNEL_CLOSED: AVDTP_STREAM_ENDPOINT_IDLE\n"); - stream_endpoint->state = AVDTP_STREAM_ENDPOINT_IDLE; - stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_STREAM_CONFIG_IDLE; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_STREAM_CONFIG_IDLE; - stream_endpoint->remote_seps_num = 0; - memset(stream_endpoint->remote_seps, 0, sizeof(avdtp_sep_t)*MAX_NUM_SEPS); - stream_endpoint->remote_sep_index = 0; - break; - } - if (stream_endpoint->l2cap_recovery_cid == local_cid){ - log_info(" -> L2CAP_EVENT_CHANNEL_CLOSED recovery cid 0x%0x", local_cid); - stream_endpoint->l2cap_recovery_cid = 0; - break; - } - - if (stream_endpoint->l2cap_reporting_cid == local_cid){ - log_info("L2CAP_EVENT_CHANNEL_CLOSED reporting cid 0x%0x", local_cid); - stream_endpoint->l2cap_reporting_cid = 0; - break; - } - break; - default: - break; - } - break; - default: - break; - } -} - -static void avdtp_initialize_stream_endpoint(avdtp_stream_endpoint_t * stream_endpoint){ - stream_endpoint->connection = NULL; - stream_endpoint->state = AVDTP_STREAM_ENDPOINT_IDLE; - stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_STREAM_CONFIG_IDLE; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_STREAM_CONFIG_IDLE; - stream_endpoint->remote_sep_index = 0; - stream_endpoint->media_disconnect = 0; - stream_endpoint->remote_seps_num = 0; - stream_endpoint->sep.in_use = 0; - memset(stream_endpoint->remote_seps, 0, sizeof(stream_endpoint->remote_seps)); - stream_endpoint->remote_sep_index = 0; -} static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ - bd_addr_t event_addr; - hci_con_handle_t con_handle; - uint16_t psm; - uint16_t local_cid; - avdtp_stream_endpoint_t * stream_endpoint = NULL; - avdtp_connection_t * connection = NULL; - - switch (packet_type) { - case L2CAP_DATA_PACKET: - connection = get_avdtp_connection_for_l2cap_signaling_cid(channel); - if (connection){ - handle_l2cap_data_packet_for_signaling_connection(connection, packet, size); - break; - } - - stream_endpoint = get_avdtp_stream_endpoint_for_l2cap_cid(channel); - if (!stream_endpoint){ - if (!connection) break; - handle_l2cap_data_packet_for_signaling_connection(connection, packet, size); - break; - } - - if (channel == stream_endpoint->connection->l2cap_signaling_cid){ - stream_endpoint_state_machine(stream_endpoint->connection, stream_endpoint, L2CAP_DATA_PACKET, 0, packet, size); - break; - } - - if (channel == stream_endpoint->l2cap_media_cid){ - (*handle_media_data)(stream_endpoint, packet, size); - break; - } - - if (channel == stream_endpoint->l2cap_reporting_cid){ - // TODO - printf("L2CAP_DATA_PACKET for reporting: NOT IMPLEMENTED\n"); - } else if (channel == stream_endpoint->l2cap_recovery_cid){ - // TODO - printf("L2CAP_DATA_PACKET for recovery: NOT IMPLEMENTED\n"); - } else { - log_error("avdtp packet handler L2CAP_DATA_PACKET: local cid 0x%02x not found", channel); - } - break; - - case HCI_EVENT_PACKET: - switch (hci_event_packet_get_type(packet)) { - case L2CAP_EVENT_INCOMING_CONNECTION: - l2cap_event_incoming_connection_get_address(packet, event_addr); - local_cid = l2cap_event_incoming_connection_get_local_cid(packet); - - connection = get_avdtp_connection_for_bd_addr(event_addr); - if (!connection){ - connection = avdtp_sink_create_connection(event_addr); - connection->state = AVDTP_SIGNALING_CONNECTION_W4_L2CAP_CONNECTED; - l2cap_accept_connection(local_cid); - break; - } - - stream_endpoint = get_avdtp_stream_endpoint_for_seid(connection->query_seid); - if (!stream_endpoint) { - printf("L2CAP_EVENT_INCOMING_CONNECTION no streamendpoint found for seid %d\n", connection->query_seid); - break; - } - - if (stream_endpoint->l2cap_media_cid == 0){ - if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_W4_L2CAP_FOR_MEDIA_CONNECTED) break; - l2cap_accept_connection(local_cid); - break; - } - break; - - case L2CAP_EVENT_CHANNEL_OPENED: - // inform about new l2cap connection - l2cap_event_channel_opened_get_address(packet, event_addr); - - if (l2cap_event_channel_opened_get_status(packet)){ - log_error("L2CAP connection to connection %s failed. status code 0x%02x", - bd_addr_to_str(event_addr), l2cap_event_channel_opened_get_status(packet)); - break; - } - psm = l2cap_event_channel_opened_get_psm(packet); - if (psm != PSM_AVDTP){ - log_error("unexpected PSM - Not implemented yet, avdtp sink: L2CAP_EVENT_CHANNEL_OPENED"); - return; - } - - con_handle = l2cap_event_channel_opened_get_handle(packet); - local_cid = l2cap_event_channel_opened_get_local_cid(packet); - - // printf("L2CAP_EVENT_CHANNEL_OPENED: Channel successfully opened: %s, handle 0x%02x, psm 0x%02x, local cid 0x%02x, remote cid 0x%02x\n", - // bd_addr_to_str(event_addr), con_handle, psm, local_cid, l2cap_event_channel_opened_get_remote_cid(packet)); - - if (psm != PSM_AVDTP) break; - - connection = get_avdtp_connection_for_bd_addr(event_addr); - if (!connection) break; - - if (connection->l2cap_signaling_cid == 0) { - if (connection->state != AVDTP_SIGNALING_CONNECTION_W4_L2CAP_CONNECTED) break; - connection->l2cap_signaling_cid = local_cid; - connection->con_handle = con_handle; - connection->query_seid = 0; - connection->state = AVDTP_SIGNALING_CONNECTION_OPENED; - printf(" -> AVDTP_SIGNALING_CONNECTION_OPENED, connection %p\n", connection); - avdtp_signaling_emit_connection_established(avdtp_sink_callback, con_handle, event_addr, 0); - break; - } - - stream_endpoint = get_avdtp_stream_endpoint_for_seid(connection->query_seid); - if (!stream_endpoint){ - printf("L2CAP_EVENT_CHANNEL_OPENED: stream_endpoint not found"); - return; - } - stream_endpoint_state_machine(connection, stream_endpoint, HCI_EVENT_PACKET, L2CAP_EVENT_CHANNEL_OPENED, packet, size); - break; - - case L2CAP_EVENT_CHANNEL_CLOSED: - // data: event (8), len(8), channel (16) - local_cid = l2cap_event_channel_closed_get_local_cid(packet); - connection = get_avdtp_connection_for_l2cap_signaling_cid(local_cid); - printf(" -> L2CAP_EVENT_CHANNEL_CLOSED signaling cid 0x%0x\n", local_cid); - - if (connection){ - printf(" -> AVDTP_STREAM_ENDPOINT_IDLE, connection closed\n"); - btstack_linked_list_remove(&avdtp_connections, (btstack_linked_item_t*) connection); - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &stream_endpoints); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_stream_endpoint_t * _stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); - avdtp_initialize_stream_endpoint(_stream_endpoint); - } - break; - } - - stream_endpoint = get_avdtp_stream_endpoint_for_l2cap_cid(local_cid); - if (!stream_endpoint) return; - - stream_endpoint_state_machine(connection, stream_endpoint, HCI_EVENT_PACKET, L2CAP_EVENT_CHANNEL_CLOSED, packet, size); - break; - - case HCI_EVENT_DISCONNECTION_COMPLETE: - break; - - case L2CAP_EVENT_CAN_SEND_NOW: - connection = get_avdtp_connection_for_l2cap_signaling_cid(channel); - if (!connection) { - stream_endpoint = get_avdtp_stream_endpoint_for_l2cap_cid(channel); - if (!stream_endpoint->connection) break; - connection = stream_endpoint->connection; - } - avdtp_sink_handle_can_send_now(connection, channel); - break; - default: - printf("unknown HCI event type %02x\n", hci_event_packet_get_type(packet)); - break; - } - break; - - default: - // other packet type - break; - } + avdtp_packet_handler(packet_type, channel, packet, size, &avdtp_sink_context); } // TODO: find out which security level is needed, and replace LEVEL_0 in avdtp_sink_init void avdtp_sink_init(void){ - stream_endpoints = NULL; - avdtp_connections = NULL; - stream_endpoints_id_counter = 0; + avdtp_sink_context.stream_endpoints = NULL; + avdtp_sink_context.connections = NULL; + avdtp_sink_context.stream_endpoints_id_counter = 0; + // TODO: assign dummy handlers; + l2cap_register_service(&packet_handler, PSM_AVDTP, 0xffff, LEVEL_0); } +avdtp_stream_endpoint_t * avdtp_sink_create_stream_endpoint(avdtp_sep_type_t sep_type, avdtp_media_type_t media_type){ + return avdtp_create_stream_endpoint(sep_type, media_type, &avdtp_sink_context); +} + void avdtp_sink_register_media_handler(void (*callback)(avdtp_stream_endpoint_t * stream_endpoint, uint8_t *packet, uint16_t size)){ if (callback == NULL){ log_error("avdtp_sink_register_media_handler called with NULL callback"); return; } - handle_media_data = callback; + avdtp_sink_context.handle_media_data = callback; } void avdtp_sink_register_packet_handler(btstack_packet_handler_t callback){ @@ -627,13 +218,13 @@ void avdtp_sink_register_packet_handler(btstack_packet_handler_t callback){ log_error("avdtp_sink_register_packet_handler called with NULL callback"); return; } - avdtp_sink_callback = callback; + avdtp_sink_context.avdtp_callback = callback; } void avdtp_sink_connect(bd_addr_t bd_addr){ - avdtp_connection_t * connection = get_avdtp_connection_for_bd_addr(bd_addr); + avdtp_connection_t * connection = avdtp_connection_for_bd_addr(bd_addr, &avdtp_sink_context); if (!connection){ - connection = avdtp_sink_create_connection(bd_addr); + connection = avdtp_create_connection(bd_addr, &avdtp_sink_context); } if (connection->state != AVDTP_SIGNALING_CONNECTION_IDLE) return; connection->state = AVDTP_SIGNALING_CONNECTION_W4_L2CAP_CONNECTED; @@ -641,262 +232,49 @@ void avdtp_sink_connect(bd_addr_t bd_addr){ } void avdtp_sink_disconnect(uint16_t con_handle){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection) return; - if (connection->state == AVDTP_SIGNALING_CONNECTION_IDLE) return; - if (connection->state == AVDTP_SIGNALING_CONNECTION_W4_L2CAP_DISCONNECTED) return; - - connection->disconnect = 1; - avdtp_sink_request_can_send_now_self(connection, connection->l2cap_signaling_cid); + avdtp_disconnect(con_handle, &avdtp_sink_context); } void avdtp_sink_open_stream(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_media_connect: no connection for handle 0x%02x found\n", con_handle); - return; - } - - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { - printf("avdtp_sink_media_connect: wrong connection state %d\n", connection->state); - return; - } - - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(acp_seid); - if (!stream_endpoint) { - printf("avdtp_sink_media_connect: no stream_endpoint with acp seid %d found\n", acp_seid); - return; - } - - if (stream_endpoint->state < AVDTP_STREAM_ENDPOINT_CONFIGURED) return; - if (stream_endpoint->remote_sep_index == 0xFF) return; - - printf(" AVDTP_INITIATOR_W2_MEDIA_CONNECT \n"); - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = stream_endpoint->sep.seid; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_MEDIA_CONNECT; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_open_stream(con_handle, acp_seid, &avdtp_sink_context); } void avdtp_sink_start_stream(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_media_connect: no connection for handle 0x%02x found\n", con_handle); - return; - } - - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { - printf("avdtp_sink_media_connect: wrong connection state %d\n", connection->state); - return; - } - - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(acp_seid); - if (!stream_endpoint) { - printf("avdtp_sink_media_connect: no stream_endpoint with acp_seid %d found\n", acp_seid); - return; - } - if (stream_endpoint->remote_sep_index == 0xFF) return; - if (stream_endpoint->state < AVDTP_STREAM_ENDPOINT_OPENED) return; - printf(" AVDTP_INITIATOR_W2_STREAMING_START \n"); - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = stream_endpoint->sep.seid; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_STREAMING_START; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_start_stream(con_handle, acp_seid, &avdtp_sink_context); } void avdtp_sink_stop_stream(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_stop_stream: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { - printf("avdtp_sink_stop_stream: wrong connection state %d\n", connection->state); - return; - } - - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(acp_seid); - if (!stream_endpoint) { - printf("avdtp_sink_stop_stream: no stream_endpoint with acp seid %d found\n", acp_seid); - return; - } - if (stream_endpoint->remote_sep_index == 0xFF) return; - switch (stream_endpoint->state){ - case AVDTP_STREAM_ENDPOINT_OPENED: - case AVDTP_STREAM_ENDPOINT_STREAMING: - printf(" AVDTP_INITIATOR_W2_STREAMING_STOP \n"); - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = stream_endpoint->sep.seid; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_STREAMING_STOP; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); - break; - default: - break; - } + avdtp_stop_stream(con_handle, acp_seid, &avdtp_sink_context); } void avdtp_sink_abort_stream(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_abort_stream: no connection for handle 0x%02x found\n", con_handle); - return; - } - - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) { - printf("avdtp_sink_abort_stream: wrong connection state %d\n", connection->state); - return; - } - - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(acp_seid); - if (!stream_endpoint) { - printf("avdtp_sink_abort_stream: no stream_endpoint for seid %d found\n", acp_seid); - return; - } - if (stream_endpoint->remote_sep_index == 0xFF) return; - switch (stream_endpoint->state){ - case AVDTP_STREAM_ENDPOINT_CONFIGURED: - case AVDTP_STREAM_ENDPOINT_CLOSING: - case AVDTP_STREAM_ENDPOINT_OPENED: - case AVDTP_STREAM_ENDPOINT_STREAMING: - printf(" AVDTP_INITIATOR_W2_STREAMING_ABORT \n"); - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = stream_endpoint->sep.seid; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_STREAMING_ABORT; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); - break; - default: - break; - } + avdtp_abort_stream(con_handle, acp_seid, &avdtp_sink_context); } void avdtp_sink_discover_stream_endpoints(uint16_t con_handle){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_discover_stream_endpoints: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; - - switch (connection->initiator_connection_state){ - case AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE: - connection->initiator_transaction_label++; - connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_DISCOVER_SEPS; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); - break; - default: - printf("avdtp_sink_discover_stream_endpoints: wrong state\n"); - break; - } + avdtp_discover_stream_endpoints(con_handle, &avdtp_sink_context); } - void avdtp_sink_get_capabilities(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_get_capabilities: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; - if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; - connection->initiator_transaction_label++; - connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_GET_CAPABILITIES; - connection->acp_seid = acp_seid; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_get_capabilities(con_handle, acp_seid, &avdtp_sink_context); } - void avdtp_sink_get_all_capabilities(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_get_all_capabilities: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; - if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; - connection->initiator_transaction_label++; - connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_GET_ALL_CAPABILITIES; - connection->acp_seid = acp_seid; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_get_all_capabilities(con_handle, acp_seid, &avdtp_sink_context); } void avdtp_sink_get_configuration(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_get_configuration: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; - if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; - connection->initiator_transaction_label++; - connection->initiator_connection_state = AVDTP_SIGNALING_CONNECTION_INITIATOR_W2_GET_CONFIGURATION; - connection->acp_seid = acp_seid; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_get_configuration(con_handle, acp_seid, &avdtp_sink_context); } void avdtp_sink_set_configuration(uint16_t con_handle, uint8_t int_seid, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_set_configuration: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; - if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; - - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_for_seid(int_seid); - if (!stream_endpoint) { - printf("avdtp_sink_set_configuration: no initiator stream endpoint for seid %d\n", int_seid); - return; - } - printf("avdtp_sink_set_configuration int seid %d, acp seid %d\n", int_seid, acp_seid); - - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = int_seid; - connection->remote_capabilities_bitmap = configured_services_bitmap; - connection->remote_capabilities = configuration; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_SET_CONFIGURATION; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_set_configuration(con_handle, int_seid, acp_seid, configured_services_bitmap, configuration, &avdtp_sink_context); } void avdtp_sink_reconfigure(uint16_t con_handle, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_reconfigure: no connection for handle 0x%02x found\n", con_handle); - return; - } - //TODO: if opened only app capabilities, enable reconfigure for not opened - if (connection->state < AVDTP_SIGNALING_CONNECTION_OPENED) return; - if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(acp_seid); - if (!stream_endpoint) return; - if (stream_endpoint->remote_sep_index == 0xFF) return; - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = stream_endpoint->sep.seid; - connection->remote_capabilities_bitmap = configured_services_bitmap; - connection->remote_capabilities = configuration; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_RECONFIGURE_STREAM_WITH_SEID; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_reconfigure(con_handle, acp_seid, configured_services_bitmap, configuration, &avdtp_sink_context); } void avdtp_sink_suspend(uint16_t con_handle, uint8_t acp_seid){ - avdtp_connection_t * connection = get_avdtp_connection_for_con_handle(con_handle); - if (!connection){ - printf("avdtp_sink_suspend: no connection for handle 0x%02x found\n", con_handle); - return; - } - if (connection->state != AVDTP_SIGNALING_CONNECTION_OPENED) return; - if (connection->initiator_connection_state != AVDTP_SIGNALING_CONNECTION_INITIATOR_IDLE) return; - avdtp_stream_endpoint_t * stream_endpoint = get_avdtp_stream_endpoint_associated_with_acp_seid(acp_seid); - if (!stream_endpoint) return; - if (stream_endpoint->remote_sep_index == 0xFF) return; - connection->initiator_transaction_label++; - connection->acp_seid = acp_seid; - connection->int_seid = stream_endpoint->sep.seid; - stream_endpoint->initiator_config_state = AVDTP_INITIATOR_W2_SUSPEND_STREAM_WITH_SEID; - avdtp_sink_request_can_send_now_initiator(connection, connection->l2cap_signaling_cid); + avdtp_suspend(con_handle, acp_seid, &avdtp_sink_context); } \ No newline at end of file diff --git a/src/classic/avdtp_sink.h b/src/classic/avdtp_sink.h index 6048e7145..c7bd81b26 100644 --- a/src/classic/avdtp_sink.h +++ b/src/classic/avdtp_sink.h @@ -177,8 +177,6 @@ void avdtp_sink_stop_stream(uint16_t con_handle, uint8_t acp_seid); /* API_END */ -extern btstack_packet_handler_t avdtp_sink_callback; -extern btstack_linked_list_t stream_endpoints; #if defined __cplusplus } diff --git a/src/classic/avdtp_source.c b/src/classic/avdtp_source.c index 6242947bd..1ae08534e 100644 --- a/src/classic/avdtp_source.c +++ b/src/classic/avdtp_source.c @@ -1,3 +1,4 @@ + /* * Copyright (C) 2016 BlueKitchen GmbH * @@ -41,16 +42,24 @@ #include #include #include +#include #include "btstack.h" #include "avdtp.h" +#include "avdtp_util.h" #include "avdtp_source.h" +#include "btstack_ring_buffer.h" +#include "wav_util.h" -static const char * default_avdtp_sink_service_name = "BTstack AVDTP Sink Service"; -static const char * default_avdtp_sink_service_provider_name = "BTstack AVDTP Sink Service Provider"; +static const char * default_avdtp_source_service_name = "BTstack AVDTP Source Service"; +static const char * default_avdtp_source_service_provider_name = "BTstack AVDTP Source Service Provider"; -void a2dp_source_create_sdp_record(uint8_t * service, uint32_t service_record_handle, uint16_t supported_features, const char * service_name, const char * service_provider_name){ +static avdtp_context_t avdtp_source_context; + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); + +void a2dp_source_create_sdp_record(uint8_t * service, uint32_t service_record_handle, uint16_t supported_features, const char * service_name, const char * service_provider_name){ uint8_t* attribute; de_create_sequence(service); @@ -113,7 +122,7 @@ void a2dp_source_create_sdp_record(uint8_t * service, uint32_t service_record_h if (service_name){ de_add_data(service, DE_STRING, strlen(service_name), (uint8_t *) service_name); } else { - de_add_data(service, DE_STRING, strlen(default_avdtp_sink_service_name), (uint8_t *) default_avdtp_sink_service_name); + de_add_data(service, DE_STRING, strlen(default_avdtp_source_service_name), (uint8_t *) default_avdtp_source_service_name); } // 0x0100 "Provider Name" @@ -121,7 +130,7 @@ void a2dp_source_create_sdp_record(uint8_t * service, uint32_t service_record_h if (service_provider_name){ de_add_data(service, DE_STRING, strlen(service_provider_name), (uint8_t *) service_provider_name); } else { - de_add_data(service, DE_STRING, strlen(default_avdtp_sink_service_provider_name), (uint8_t *) default_avdtp_sink_service_provider_name); + de_add_data(service, DE_STRING, strlen(default_avdtp_source_service_provider_name), (uint8_t *) default_avdtp_source_service_provider_name); } // 0x0311 "Supported Features" @@ -130,5 +139,300 @@ void a2dp_source_create_sdp_record(uint8_t * service, uint32_t service_record_h } +void avdtp_source_register_media_transport_category(uint8_t seid){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_media_transport_category(stream_endpoint); +} + +void avdtp_source_register_reporting_category(uint8_t seid){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_reporting_category(stream_endpoint); +} + +void avdtp_source_register_delay_reporting_category(uint8_t seid){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_delay_reporting_category(stream_endpoint); +} + +void avdtp_source_register_recovery_category(uint8_t seid, uint8_t maximum_recovery_window_size, uint8_t maximum_number_media_packets){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_recovery_category(stream_endpoint, maximum_recovery_window_size, maximum_number_media_packets); +} + +void avdtp_source_register_content_protection_category(uint8_t seid, uint16_t cp_type, const uint8_t * cp_type_value, uint8_t cp_type_value_len){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_content_protection_category(stream_endpoint, cp_type, cp_type_value, cp_type_value_len); +} + +void avdtp_source_register_header_compression_category(uint8_t seid, uint8_t back_ch, uint8_t media, uint8_t recovery){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_header_compression_category(stream_endpoint, back_ch, media, recovery); +} + +void avdtp_source_register_media_codec_category(uint8_t seid, avdtp_media_type_t media_type, avdtp_media_codec_type_t media_codec_type, const uint8_t * media_codec_info, uint16_t media_codec_info_len){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_media_codec_category(stream_endpoint, media_type, media_codec_type, media_codec_info, media_codec_info_len); +} + +void avdtp_source_register_multiplexing_category(uint8_t seid, uint8_t fragmentation){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_seid(seid, &avdtp_source_context); + avdtp_register_multiplexing_category(stream_endpoint, fragmentation); +} + +avdtp_stream_endpoint_t * avdtp_source_create_stream_endpoint(avdtp_sep_type_t sep_type, avdtp_media_type_t media_type){ + return avdtp_create_stream_endpoint(sep_type, media_type, &avdtp_source_context); +} + +void avdtp_source_register_packet_handler(btstack_packet_handler_t callback){ + if (callback == NULL){ + log_error("avdtp_source_register_packet_handler called with NULL callback"); + return; + } + avdtp_source_context.avdtp_callback = callback; +} + +void avdtp_source_connect(bd_addr_t bd_addr){ + avdtp_connection_t * connection = avdtp_connection_for_bd_addr(bd_addr, &avdtp_source_context); + if (!connection){ + connection = avdtp_create_connection(bd_addr, &avdtp_source_context); + } + if (connection->state != AVDTP_SIGNALING_CONNECTION_IDLE) return; + connection->state = AVDTP_SIGNALING_CONNECTION_W4_L2CAP_CONNECTED; + l2cap_create_channel(packet_handler, connection->remote_addr, PSM_AVDTP, 0xffff, NULL); +} + +void avdtp_source_disconnect(uint16_t con_handle){ + avdtp_disconnect(con_handle, &avdtp_source_context); +} + +void avdtp_source_open_stream(uint16_t con_handle, uint8_t acp_seid){ + avdtp_open_stream(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_start_stream(uint16_t con_handle, uint8_t acp_seid){ + avdtp_start_stream(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_stop_stream(uint16_t con_handle, uint8_t acp_seid){ + avdtp_stop_stream(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_abort_stream(uint16_t con_handle, uint8_t acp_seid){ + avdtp_abort_stream(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_discover_stream_endpoints(uint16_t con_handle){ + avdtp_discover_stream_endpoints(con_handle, &avdtp_source_context); +} + +void avdtp_source_get_capabilities(uint16_t con_handle, uint8_t acp_seid){ + avdtp_get_capabilities(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_get_all_capabilities(uint16_t con_handle, uint8_t acp_seid){ + avdtp_get_all_capabilities(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_get_configuration(uint16_t con_handle, uint8_t acp_seid){ + avdtp_get_configuration(con_handle, acp_seid, &avdtp_source_context); +} + +void avdtp_source_set_configuration(uint16_t con_handle, uint8_t int_seid, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration){ + avdtp_set_configuration(con_handle, int_seid, acp_seid, configured_services_bitmap, configuration, &avdtp_source_context); +} + +void avdtp_source_reconfigure(uint16_t con_handle, uint8_t acp_seid, uint16_t configured_services_bitmap, avdtp_capabilities_t configuration){ + avdtp_reconfigure(con_handle, acp_seid, configured_services_bitmap, configuration, &avdtp_source_context); +} + +void avdtp_source_suspend(uint16_t con_handle, uint8_t acp_seid){ + avdtp_suspend(con_handle, acp_seid, &avdtp_source_context); +} + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + avdtp_packet_handler(packet_type, channel, packet, size, &avdtp_source_context); +} +/* streaming part */ +#define NUM_CHANNELS 2 +#define SAMPLE_RATE 44100 +#define BYTES_PER_AUDIO_SAMPLE (2*NUM_CHANNELS) +#define LATENCY 300 // ms + +#ifndef M_PI +#define M_PI 3.14159265 +#endif +#define TABLE_SIZE_441HZ 100 + +typedef struct { + int16_t source[TABLE_SIZE_441HZ]; + int left_phase; + int right_phase; +} paTestData; + +static uint32_t fill_audio_ring_buffer_timeout = 25; //ms +static paTestData sin_data; +static int total_num_samples = 0; +static char * wav_filename = "test_output_sine.wav"; + +static btstack_sbc_decoder_state_t state; +static btstack_sbc_mode_t mode = SBC_MODE_STANDARD; + + + +static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, int sample_rate, void * context){ + UNUSED(sample_rate); + UNUSED(context); + printf("store %d samples\n",num_samples*num_channels); + wav_writer_write_int16(num_samples*num_channels, data); + // frame_count++; + total_num_samples+=num_samples*num_channels; +} + +static void fill_audio_ring_buffer(void *userData, int num_samples_to_write, avdtp_stream_endpoint_t * stream_endpoint){ + paTestData *data = (paTestData*)userData; + int count = 0; + while (btstack_ring_buffer_bytes_free(&stream_endpoint->audio_ring_buffer) >= BYTES_PER_AUDIO_SAMPLE && count < num_samples_to_write){ + uint8_t write_data[BYTES_PER_AUDIO_SAMPLE]; + *(int16_t*)&write_data[0] = data->source[data->left_phase]; + *(int16_t*)&write_data[2] = data->source[data->right_phase]; + + btstack_ring_buffer_write(&stream_endpoint->audio_ring_buffer, write_data, BYTES_PER_AUDIO_SAMPLE); + count++; + + data->left_phase += 1; + if (data->left_phase >= TABLE_SIZE_441HZ){ + data->left_phase -= TABLE_SIZE_441HZ; + } + data->right_phase += 1; + if (data->right_phase >= TABLE_SIZE_441HZ){ + data->right_phase -= TABLE_SIZE_441HZ; + } + } +} + +static void fill_sbc_ring_buffer(uint8_t * sbc_frame, int sbc_frame_size, avdtp_stream_endpoint_t * stream_endpoint){ + if (btstack_ring_buffer_bytes_free(&stream_endpoint->sbc_ring_buffer) >= sbc_frame_size ){ + // printf(" fill_sbc_ring_buffer\n"); + uint8_t size_buffer = sbc_frame_size; + btstack_ring_buffer_write(&stream_endpoint->sbc_ring_buffer, &size_buffer, 1); + btstack_ring_buffer_write(&stream_endpoint->sbc_ring_buffer, sbc_frame, sbc_frame_size); + } else { + printf("No space in sbc buffer\n"); + } +} + + +static void avdtp_source_stream_endpoint_run(avdtp_stream_endpoint_t * stream_endpoint){ + // performe sbc encoding + int total_num_bytes_read = 0; + int num_audio_samples_to_read = btstack_sbc_encoder_num_audio_frames(); + int audio_bytes_to_read = num_audio_samples_to_read * BYTES_PER_AUDIO_SAMPLE; + + // printf("run: audio_bytes_to_read: %d\n", audio_bytes_to_read); + // printf(" audio buf, bytes available: %d\n", btstack_ring_buffer_bytes_available(&stream_endpoint->audio_ring_buffer)); + // printf(" sbc buf, bytes free: %d\n", btstack_ring_buffer_bytes_free(&stream_endpoint->sbc_ring_buffer)); + + while (btstack_ring_buffer_bytes_available(&stream_endpoint->audio_ring_buffer) >= audio_bytes_to_read + && btstack_ring_buffer_bytes_free(&stream_endpoint->sbc_ring_buffer) >= 120){ // TODO use real value + + uint32_t number_of_bytes_read = 0; + uint8_t pcm_frame[256*BYTES_PER_AUDIO_SAMPLE]; + btstack_ring_buffer_read(&stream_endpoint->audio_ring_buffer, pcm_frame, audio_bytes_to_read, &number_of_bytes_read); + // printf(" num audio bytes read %d\n", number_of_bytes_read); + btstack_sbc_encoder_process_data((int16_t *) pcm_frame); + + uint16_t sbc_frame_bytes = 119; //btstack_sbc_encoder_sbc_buffer_length(); + + total_num_bytes_read += number_of_bytes_read; + fill_sbc_ring_buffer(btstack_sbc_encoder_sbc_buffer(), sbc_frame_bytes, stream_endpoint); + } + // schedule sending + if (total_num_bytes_read != 0){ + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_STREAMING_W2_SEND; + avdtp_request_can_send_now_self(stream_endpoint->connection, stream_endpoint->l2cap_media_cid); + } +} + +static void test_fill_audio_ring_buffer_timeout_handler(btstack_timer_source_t * timer){ + avdtp_stream_endpoint_t * stream_endpoint = btstack_run_loop_get_timer_context(timer); + btstack_run_loop_set_timer(&stream_endpoint->fill_audio_ring_buffer_timer, fill_audio_ring_buffer_timeout); // 2 seconds timeout + btstack_run_loop_add_timer(&stream_endpoint->fill_audio_ring_buffer_timer); + uint32_t now = btstack_run_loop_get_time_ms(); + + uint32_t update_period_ms = fill_audio_ring_buffer_timeout; + if (stream_endpoint->time_audio_data_sent > 0){ + update_period_ms = now - stream_endpoint->time_audio_data_sent; + } + uint32_t num_samples = (update_period_ms * 44100) / 1000; + stream_endpoint->acc_num_missed_samples += (update_period_ms * 44100) % 1000; + + if (stream_endpoint->acc_num_missed_samples >= 1000){ + num_samples++; + stream_endpoint->acc_num_missed_samples -= 1000; + } + + fill_audio_ring_buffer(&sin_data, num_samples, stream_endpoint); + stream_endpoint->time_audio_data_sent = now; + + avdtp_source_stream_endpoint_run(stream_endpoint); + // +} + +static void test_fill_audio_ring_buffer_timer_start(avdtp_stream_endpoint_t * stream_endpoint){ + btstack_run_loop_remove_timer(&stream_endpoint->fill_audio_ring_buffer_timer); + btstack_run_loop_set_timer_handler(&stream_endpoint->fill_audio_ring_buffer_timer, test_fill_audio_ring_buffer_timeout_handler); + btstack_run_loop_set_timer_context(&stream_endpoint->fill_audio_ring_buffer_timer, stream_endpoint); + btstack_run_loop_set_timer(&stream_endpoint->fill_audio_ring_buffer_timer, fill_audio_ring_buffer_timeout); // 50 ms timeout + btstack_run_loop_add_timer(&stream_endpoint->fill_audio_ring_buffer_timer); +} + +static void test_fill_audio_ring_buffer_timer_stop(avdtp_stream_endpoint_t * stream_endpoint){ + btstack_run_loop_remove_timer(&stream_endpoint->fill_audio_ring_buffer_timer); +} + +void avdtp_source_stream_data_start(uint16_t con_handle){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_l2cap_cid(con_handle, &avdtp_source_context); + if (!stream_endpoint) { + printf("no stream_endpoint found for 0x%02x", con_handle); + return; + } + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_STREAMING){ + printf("stream_endpoint in wrong state %d\n", stream_endpoint->state); + return; + } + + test_fill_audio_ring_buffer_timer_start(stream_endpoint); +} + +void avdtp_source_stream_data_stop(uint16_t con_handle){ + avdtp_stream_endpoint_t * stream_endpoint = avdtp_stream_endpoint_for_l2cap_cid(con_handle, &avdtp_source_context); + if (!stream_endpoint) { + log_error("no stream_endpoint found"); + return; + } + if (stream_endpoint->state != AVDTP_STREAM_ENDPOINT_STREAMING) return; + // TODO: initialize randomly sequence number + stream_endpoint->sequence_number = 0; + test_fill_audio_ring_buffer_timer_stop(stream_endpoint); + wav_writer_close(); +} + +void avdtp_source_init(void){ + avdtp_source_context.stream_endpoints = NULL; + avdtp_source_context.connections = NULL; + avdtp_source_context.stream_endpoints_id_counter = 0; + avdtp_source_context.packet_handler = packet_handler; + + /* initialise sinusoidal wavetable */ + int i; + for (i=0; iconnection = NULL; + stream_endpoint->state = AVDTP_STREAM_ENDPOINT_IDLE; + stream_endpoint->acceptor_config_state = AVDTP_ACCEPTOR_STREAM_CONFIG_IDLE; + stream_endpoint->initiator_config_state = AVDTP_INITIATOR_STREAM_CONFIG_IDLE; + stream_endpoint->remote_sep_index = 0; + stream_endpoint->media_disconnect = 0; + stream_endpoint->remote_seps_num = 0; + stream_endpoint->sep.in_use = 0; + memset(stream_endpoint->remote_seps, 0, sizeof(stream_endpoint->remote_seps)); + stream_endpoint->remote_sep_index = 0; +} + +avdtp_stream_endpoint_t * avdtp_stream_endpoint_for_seid(uint16_t seid, avdtp_context_t * context){ btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &stream_endpoints); + btstack_linked_list_iterator_init(&it, &context->stream_endpoints); while (btstack_linked_list_iterator_has_next(&it)){ avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); if (stream_endpoint->sep.seid == seid){ @@ -62,9 +75,72 @@ avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_with_seid(uint8_t seid){ return NULL; } -avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_associated_with_acp_seid(uint16_t acp_seid){ +avdtp_connection_t * avdtp_connection_for_bd_addr(bd_addr_t addr, avdtp_context_t * context){ btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &stream_endpoints); + btstack_linked_list_iterator_init(&it, &context->connections); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_connection_t * connection = (avdtp_connection_t *)btstack_linked_list_iterator_next(&it); + if (memcmp(addr, connection->remote_addr, 6) != 0) continue; + return connection; + } + return NULL; +} + +avdtp_connection_t * avdtp_connection_for_con_handle(hci_con_handle_t con_handle, avdtp_context_t * context){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &context->connections); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_connection_t * connection = (avdtp_connection_t *)btstack_linked_list_iterator_next(&it); + if (connection->con_handle != con_handle) continue; + return connection; + } + return NULL; +} + +avdtp_connection_t * avdtp_connection_for_l2cap_signaling_cid(uint16_t l2cap_cid, avdtp_context_t * context){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &context->connections); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_connection_t * connection = (avdtp_connection_t *)btstack_linked_list_iterator_next(&it); + if (connection->l2cap_signaling_cid != l2cap_cid) continue; + return connection; + } + return NULL; +} + +avdtp_stream_endpoint_t * avdtp_stream_endpoint_for_l2cap_cid(uint16_t l2cap_cid, avdtp_context_t * context){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &context->stream_endpoints); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); + if (stream_endpoint->l2cap_media_cid == l2cap_cid){ + return stream_endpoint; + } + if (stream_endpoint->l2cap_reporting_cid == l2cap_cid){ + return stream_endpoint; + } + if (stream_endpoint->l2cap_recovery_cid == l2cap_cid){ + return stream_endpoint; + } + } + return NULL; +} + +avdtp_stream_endpoint_t * avdtp_stream_endpoint_with_seid(uint8_t seid, avdtp_context_t * context){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &context->stream_endpoints); + while (btstack_linked_list_iterator_has_next(&it)){ + avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); + if (stream_endpoint->sep.seid == seid){ + return stream_endpoint; + } + } + return NULL; +} + +avdtp_stream_endpoint_t * avdtp_stream_endpoint_associated_with_acp_seid(uint16_t acp_seid, avdtp_context_t * context){ + btstack_linked_list_iterator_t it; + btstack_linked_list_iterator_init(&it, &context->stream_endpoints); while (btstack_linked_list_iterator_has_next(&it)){ avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); if (stream_endpoint->remote_sep_index >= 0 && stream_endpoint->remote_sep_index < MAX_NUM_SEPS){ @@ -333,9 +409,11 @@ void avdtp_prepare_capabilities(avdtp_signaling_packet_t * signaling_packet, uin if (signaling_packet->offset) return; uint8_t pack_all_capabilities = 1; signaling_packet->message_type = AVDTP_RESPONSE_ACCEPT_MSG; - signaling_packet->size = 0; int i; - signaling_packet->command[signaling_packet->size++] = signaling_packet->acp_seid << 2; + + signaling_packet->size = 0; + memset(signaling_packet->command, 0 , sizeof(signaling_packet->command)); + switch (identifier) { case AVDTP_SI_GET_CAPABILITIES: @@ -345,10 +423,12 @@ void avdtp_prepare_capabilities(avdtp_signaling_packet_t * signaling_packet, uin pack_all_capabilities = 1; break; case AVDTP_SI_SET_CONFIGURATION: + signaling_packet->command[signaling_packet->size++] = signaling_packet->acp_seid << 2; signaling_packet->command[signaling_packet->size++] = signaling_packet->int_seid << 2; signaling_packet->message_type = AVDTP_CMD_MSG; break; case AVDTP_SI_RECONFIGURE: + signaling_packet->command[signaling_packet->size++] = signaling_packet->acp_seid << 2; signaling_packet->message_type = AVDTP_CMD_MSG; break; default: @@ -356,17 +436,27 @@ void avdtp_prepare_capabilities(avdtp_signaling_packet_t * signaling_packet, uin break; } + // printf("registered_service_categories: 0x%02x\n", registered_service_categories); + + // printf("command before packing:\n"); + // printf_hexdump(signaling_packet->command, signaling_packet->size); for (i = 1; i < 9; i++){ - if (get_bit16(registered_service_categories, i)){ + int registered_category = get_bit16(registered_service_categories, i); + if (!registered_category && (identifier == AVDTP_SI_SET_CONFIGURATION || identifier == identifier == AVDTP_SI_RECONFIGURE){ + // TODO: introduce bitmap of mandatory categories + if (i == 1){ + registered_category = 1; + } + } + if (registered_category){ // service category + // printf("pack service category: %d\n", i); signaling_packet->command[signaling_packet->size++] = i; signaling_packet->size += avdtp_pack_service_capabilities(signaling_packet->command+signaling_packet->size, sizeof(signaling_packet->command)-signaling_packet->size, capabilities, (avdtp_service_category_t)i, pack_all_capabilities); } } - // signaling_packet->command[signaling_packet->size++] = 0x04; - // signaling_packet->command[signaling_packet->size++] = 0x02; - // signaling_packet->command[signaling_packet->size++] = 0x02; - // signaling_packet->command[signaling_packet->size++] = 0x00; + // printf("command after packing:\n"); + // printf_hexdump(signaling_packet->command, signaling_packet->size); signaling_packet->signal_identifier = identifier; signaling_packet->transaction_label = transaction_label; @@ -433,6 +523,19 @@ void avdtp_signaling_emit_connection_established(btstack_packet_handler_t callba (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); } +void avdtp_streaming_emit_connection_established(btstack_packet_handler_t callback, uint16_t con_handle, uint8_t status){ + if (!callback) return; + uint8_t event[6]; + int pos = 0; + event[pos++] = HCI_EVENT_AVDTP_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED; + little_endian_store_16(event, pos, con_handle); + pos += 2; + event[pos++] = status; + (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + void avdtp_signaling_emit_sep(btstack_packet_handler_t callback, uint16_t con_handle, avdtp_sep_t sep){ if (!callback) return; uint8_t event[9]; @@ -655,31 +758,19 @@ void avdtp_signaling_emit_media_codec_other_reconfiguration(btstack_packet_handl } -void avdtp_sink_request_can_send_now_acceptor(avdtp_connection_t * connection, uint16_t l2cap_cid){ +void avdtp_request_can_send_now_acceptor(avdtp_connection_t * connection, uint16_t l2cap_cid){ connection->wait_to_send_acceptor = 1; l2cap_request_can_send_now_event(l2cap_cid); } -void avdtp_sink_request_can_send_now_initiator(avdtp_connection_t * connection, uint16_t l2cap_cid){ +void avdtp_request_can_send_now_initiator(avdtp_connection_t * connection, uint16_t l2cap_cid){ connection->wait_to_send_initiator = 1; l2cap_request_can_send_now_event(l2cap_cid); } -void avdtp_sink_request_can_send_now_self(avdtp_connection_t * connection, uint16_t l2cap_cid){ +void avdtp_request_can_send_now_self(avdtp_connection_t * connection, uint16_t l2cap_cid){ connection->wait_to_send_self = 1; l2cap_request_can_send_now_event(l2cap_cid); } -avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_for_seid(uint16_t seid){ - btstack_linked_list_iterator_t it; - btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &stream_endpoints); - while (btstack_linked_list_iterator_has_next(&it)){ - avdtp_stream_endpoint_t * stream_endpoint = (avdtp_stream_endpoint_t *)btstack_linked_list_iterator_next(&it); - if (stream_endpoint->sep.seid == seid){ - return stream_endpoint; - } - } - return NULL; -} - uint8_t avdtp_get_index_of_remote_stream_endpoint_with_seid(avdtp_stream_endpoint_t * stream_endpoint, uint16_t seid){ if (stream_endpoint->remote_seps[stream_endpoint->remote_sep_index].seid == seid){ return stream_endpoint->remote_sep_index; diff --git a/src/classic/avdtp_util.h b/src/classic/avdtp_util.h index 55bf723d7..fbfc8c7f3 100644 --- a/src/classic/avdtp_util.h +++ b/src/classic/avdtp_util.h @@ -52,8 +52,13 @@ extern "C" { #endif -avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_with_seid(uint8_t seid); -avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_associated_with_acp_seid(uint16_t acp_seid); +avdtp_connection_t * avdtp_connection_for_bd_addr(bd_addr_t addr, avdtp_context_t * context); +avdtp_connection_t * avdtp_connection_for_con_handle(hci_con_handle_t con_handle, avdtp_context_t * context); +avdtp_connection_t * avdtp_connection_for_l2cap_signaling_cid(uint16_t l2cap_cid, avdtp_context_t * context); +avdtp_stream_endpoint_t * avdtp_stream_endpoint_for_l2cap_cid(uint16_t l2cap_cid, avdtp_context_t * context); +avdtp_stream_endpoint_t * avdtp_stream_endpoint_with_seid(uint8_t seid, avdtp_context_t * context); +avdtp_stream_endpoint_t * avdtp_stream_endpoint_associated_with_acp_seid(uint16_t acp_seid, avdtp_context_t * context); +avdtp_stream_endpoint_t * avdtp_stream_endpoint_for_seid(uint16_t seid, avdtp_context_t * context); uint8_t avdtp_header(uint8_t tr_label, avdtp_packet_type_t packet_type, avdtp_message_type_t msg_type); int avdtp_read_signaling_header(avdtp_signaling_packet_t * signaling_header, uint8_t * packet, uint16_t size); @@ -68,6 +73,7 @@ void avdtp_prepare_capabilities(avdtp_signaling_packet_t * signaling_packet, uin int avdtp_signaling_create_fragment(uint16_t cid, avdtp_signaling_packet_t * signaling_packet, uint8_t * out_buffer); void avdtp_signaling_emit_connection_established(btstack_packet_handler_t callback, uint16_t con_handle, bd_addr_t addr, uint8_t status); +void avdtp_streaming_emit_connection_established(btstack_packet_handler_t callback, uint16_t con_handle, uint8_t status); void avdtp_signaling_emit_sep(btstack_packet_handler_t callback, uint16_t con_handle, avdtp_sep_t sep); void avdtp_signaling_emit_accept(btstack_packet_handler_t callback, uint16_t con_handle, avdtp_signal_identifier_t identifier, uint8_t status); void avdtp_signaling_emit_general_reject(btstack_packet_handler_t callback, uint16_t con_handle, avdtp_signal_identifier_t identifier); @@ -81,13 +87,13 @@ void avdtp_signaling_emit_media_codec_other_configuration(btstack_packet_handler void avdtp_signaling_emit_media_codec_sbc_reconfiguration(btstack_packet_handler_t callback, uint16_t con_handle, adtvp_media_codec_capabilities_t media_codec); void avdtp_signaling_emit_media_codec_other_reconfiguration(btstack_packet_handler_t callback, uint16_t con_handle, adtvp_media_codec_capabilities_t media_codec); -void avdtp_sink_request_can_send_now_acceptor(avdtp_connection_t * connection, uint16_t l2cap_cid); -void avdtp_sink_request_can_send_now_initiator(avdtp_connection_t * connection, uint16_t l2cap_cid); -void avdtp_sink_request_can_send_now_self(avdtp_connection_t * connection, uint16_t l2cap_cid); +void avdtp_request_can_send_now_acceptor(avdtp_connection_t * connection, uint16_t l2cap_cid); +void avdtp_request_can_send_now_initiator(avdtp_connection_t * connection, uint16_t l2cap_cid); +void avdtp_request_can_send_now_self(avdtp_connection_t * connection, uint16_t l2cap_cid); uint8_t avdtp_get_index_of_remote_stream_endpoint_with_seid(avdtp_stream_endpoint_t * stream_endpoint, uint16_t acp_seid); -avdtp_stream_endpoint_t * get_avdtp_stream_endpoint_for_seid(uint16_t seid); +void avdtp_initialize_stream_endpoint(avdtp_stream_endpoint_t * stream_endpoint); #if defined __cplusplus } diff --git a/src/classic/btstack_sbc.h b/src/classic/btstack_sbc.h index 9d3dec482..ed61c1ed5 100644 --- a/src/classic/btstack_sbc.h +++ b/src/classic/btstack_sbc.h @@ -144,9 +144,10 @@ uint8_t * btstack_sbc_encoder_sbc_buffer(void); uint16_t btstack_sbc_encoder_sbc_buffer_length(void); /** - * @brief Return number of audio samples in one PCM frame + * @brief Return number of audio frames required for one SBC packet + * @note each audio frame contains 2 sample values in stereo modes */ -int btstack_sbc_encoder_num_audio_samples(void); +int btstack_sbc_encoder_num_audio_frames(void); /* API_END */ diff --git a/src/classic/btstack_sbc_bludroid.c b/src/classic/btstack_sbc_bludroid.c index da12533de..c5d44e3ce 100644 --- a/src/classic/btstack_sbc_bludroid.c +++ b/src/classic/btstack_sbc_bludroid.c @@ -503,8 +503,6 @@ void btstack_sbc_decoder_process_data(btstack_sbc_decoder_state_t * state, int p void btstack_sbc_encoder_init(btstack_sbc_encoder_state_t * state, btstack_sbc_mode_t mode, int blocks, int subbands, int allmethod, int sample_rate, int bitpool){ - UNUSED(bitpool); - if (sbc_encoder_state_singleton && sbc_encoder_state_singleton != state ){ log_error("SBC encoder: different sbc decoder state is allready registered"); } @@ -522,8 +520,10 @@ void btstack_sbc_encoder_init(btstack_sbc_encoder_state_t * state, btstack_sbc_m bd_encoder_state.context.s16NumOfBlocks = blocks; bd_encoder_state.context.s16NumOfSubBands = subbands; bd_encoder_state.context.s16AllocationMethod = allmethod; - bd_encoder_state.context.s16BitPool = 31; + bd_encoder_state.context.s16BitPool = bitpool; bd_encoder_state.context.mSBCEnabled = 0; + bd_encoder_state.context.s16ChannelMode = SBC_STEREO; + bd_encoder_state.context.s16NumOfChannels = 2; switch(sample_rate){ case 16000: bd_encoder_state.context.s16SamplingFreq = SBC_sf16000; break; @@ -564,9 +564,9 @@ void btstack_sbc_encoder_process_data(int16_t * input_buffer){ SBC_Encoder(context); } -int btstack_sbc_encoder_num_audio_samples(void){ +int btstack_sbc_encoder_num_audio_frames(void){ SBC_ENC_PARAMS * context = &((bludroid_encoder_state_t *)sbc_encoder_state_singleton->encoder_state)->context; - return context->s16NumOfSubBands * context->s16NumOfBlocks * context->s16NumOfChannels; + return context->s16NumOfSubBands * context->s16NumOfBlocks; } uint8_t * btstack_sbc_encoder_sbc_buffer(void){ diff --git a/src/classic/hfp_msbc.c b/src/classic/hfp_msbc.c index 1c1816449..0709eef1d 100644 --- a/src/classic/hfp_msbc.c +++ b/src/classic/hfp_msbc.c @@ -108,7 +108,7 @@ int hfp_msbc_num_bytes_in_stream(void){ } int hfp_msbc_num_audio_samples_per_frame(void){ - return btstack_sbc_encoder_num_audio_samples(); + return btstack_sbc_encoder_num_audio_frames(); } diff --git a/test/avdtp/.gitignore b/test/avdtp/.gitignore index 75e10b4a4..bf6f763fb 100644 --- a/test/avdtp/.gitignore +++ b/test/avdtp/.gitignore @@ -1,5 +1,8 @@ -avdtp_test +avdtp_source_test +avdtp_sink_test portaudio_test +sine_encode_decode_test +sine_encode_decode_ring_buffer_test *.sbc *.wav diff --git a/test/avdtp/Makefile b/test/avdtp/Makefile index 99ff3882e..6c716ecfc 100644 --- a/test/avdtp/Makefile +++ b/test/avdtp/Makefile @@ -64,13 +64,14 @@ SBC_ENCODER += \ AVDTP_SINK += \ avdtp_util.c \ + avdtp.c \ avdtp_initiator.c \ avdtp_acceptor.c \ avdtp_source.c \ avdtp_sink.c \ btstack_ring_buffer.c \ -AVDTP_TESTS = avdtp_test portaudio_test +AVDTP_TESTS = avdtp_source_test avdtp_sink_test portaudio_test sine_encode_decode_ring_buffer_test sine_encode_decode_test CORE_OBJ = $(CORE:.c=.o) COMMON_OBJ = $(COMMON:.c=.o) @@ -80,12 +81,22 @@ AVDTP_SINK_OBJ = $(AVDTP_SINK:.c=.o) all: ${AVDTP_TESTS} -avdtp_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} avdtp_test.o +avdtp_sink_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} avdtp_sink_test.o + ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + +avdtp_source_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} avdtp_source_test.o ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ portaudio_test: btstack_util.o hci_dump.o wav_util.o btstack_ring_buffer.o portaudio_test.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + +sine_encode_decode_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} sine_encode_decode_test.c + ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + +sine_encode_decode_ring_buffer_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} sine_encode_decode_ring_buffer_test.c + ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + test: all clean: diff --git a/test/avdtp/avdtp_test.c b/test/avdtp/avdtp_sink_test.c similarity index 98% rename from test/avdtp/avdtp_test.c rename to test/avdtp/avdtp_sink_test.c index 300d5fcbe..2186b40a2 100644 --- a/test/avdtp/avdtp_test.c +++ b/test/avdtp/avdtp_sink_test.c @@ -133,9 +133,12 @@ typedef struct { } avdtp_media_codec_configuration_sbc_t; // mac 2011: static bd_addr_t remote = {0x04, 0x0C, 0xCE, 0xE4, 0x85, 0xD3}; -// pts: static bd_addr_t remote = {0x00, 0x1B, 0xDC, 0x08, 0x0A, 0xA5}; +// pts: +static bd_addr_t remote = {0x00, 0x1B, 0xDC, 0x08, 0x0A, 0xA5}; // mac 2013: static bd_addr_t remote = {0x84, 0x38, 0x35, 0x65, 0xd1, 0x15}; -static bd_addr_t remote = {0x84, 0x38, 0x35, 0x65, 0xd1, 0x15}; + +// bt dongle: -u 02-02 static bd_addr_t remote = {0x00, 0x02, 0x72, 0xDC, 0x31, 0xC1}; + static uint16_t con_handle = 0; static uint8_t sdp_avdtp_sink_service_buffer[150]; static avdtp_sep_t sep; @@ -642,7 +645,7 @@ int btstack_main(int argc, const char * argv[]){ //#ifndef SMG_BI local_stream_endpoint = avdtp_sink_create_stream_endpoint(AVDTP_SINK, AVDTP_AUDIO); - local_stream_endpoint->sep.seid = 5; + local_stream_endpoint->sep.seid = 1; avdtp_sink_register_media_transport_category(local_stream_endpoint->sep.seid); avdtp_sink_register_media_codec_category(local_stream_endpoint->sep.seid, AVDTP_AUDIO, AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities)); //#endif @@ -650,17 +653,18 @@ int btstack_main(int argc, const char * argv[]){ // avdtp_sink_register_content_protection_category(seid, 2, 2, NULL, 0); avdtp_sink_register_media_handler(&handle_l2cap_media_data_packet); - + printf("reistered media handler\n"); // Initialize SDP sdp_init(); memset(sdp_avdtp_sink_service_buffer, 0, sizeof(sdp_avdtp_sink_service_buffer)); a2dp_sink_create_sdp_record(sdp_avdtp_sink_service_buffer, 0x10001, 1, NULL, NULL); sdp_register_service(sdp_avdtp_sink_service_buffer); - gap_set_local_name("BTstack AVDTP Test"); + gap_set_local_name("BTstack A2DP Sink Test"); gap_discoverable_control(1); gap_set_class_of_device(0x200408); - + printf("sdp, gap done\n"); + // turn on! hci_power_control(HCI_POWER_ON); diff --git a/test/avdtp/avdtp_source_test.c b/test/avdtp/avdtp_source_test.c new file mode 100644 index 000000000..d7826c1e7 --- /dev/null +++ b/test/avdtp/avdtp_source_test.c @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2016 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + + +#include +#include +#include +#include +#include +#include + +#include "btstack_config.h" +#include "btstack_debug.h" +#include "btstack_event.h" +#include "btstack_memory.h" +#include "btstack_run_loop.h" +#include "gap.h" +#include "hci.h" +#include "hci_cmd.h" +#include "hci_dump.h" +#include "l2cap.h" +#include "stdin_support.h" +#include "avdtp_source.h" + +#include "btstack_sbc.h" +#include "avdtp_util.h" + +typedef struct { + // bitmaps + uint8_t sampling_frequency_bitmap; + uint8_t channel_mode_bitmap; + uint8_t block_length_bitmap; + uint8_t subbands_bitmap; + uint8_t allocation_method_bitmap; + uint8_t min_bitpool_value; + uint8_t max_bitpool_value; +} adtvp_media_codec_information_sbc_t; + +typedef struct { + int reconfigure; + int num_channels; + int sampling_frequency; + int channel_mode; + int block_length; + int subbands; + int allocation_method; + int min_bitpool_value; + int max_bitpool_value; + int frames_per_buffer; +} avdtp_media_codec_configuration_sbc_t; + + +// mac 2011: static bd_addr_t remote = {0x04, 0x0C, 0xCE, 0xE4, 0x85, 0xD3}; +// pts: static bd_addr_t remote = {0x00, 0x1B, 0xDC, 0x08, 0x0A, 0xA5}; +// mac 2013: static bd_addr_t remote = {0x84, 0x38, 0x35, 0x65, 0xd1, 0x15}; +// phone 2013: static bd_addr_t remote = {0xD8, 0xBB, 0x2C, 0xDF, 0xF0, 0xF2}; +// minijambox: +static bd_addr_t remote = { 0x00, 0x21, 0x3c, 0xac, 0xf7, 0x38}; +// bt dongle: -u 02-04-01 +// static bd_addr_t remote = {0x00, 0x15, 0x83, 0x5F, 0x9D, 0x46}; + +static uint16_t con_handle = 0; +static uint16_t media_con_handle = 0; +static uint8_t sdp_avdtp_source_service_buffer[150]; +static avdtp_sep_t sep; + +static adtvp_media_codec_information_sbc_t sbc_capability; +static avdtp_media_codec_configuration_sbc_t sbc_configuration; +static avdtp_stream_endpoint_t * local_stream_endpoint; + +static uint16_t remote_configuration_bitmap; +static avdtp_capabilities_t remote_configuration; + +static const char * avdtp_si_name[] = { + "ERROR", + "AVDTP_SI_DISCOVER", + "AVDTP_SI_GET_CAPABILITIES", + "AVDTP_SI_SET_CONFIGURATION", + "AVDTP_SI_GET_CONFIGURATION", + "AVDTP_SI_RECONFIGURE," + "AVDTP_SI_OPEN", + "AVDTP_SI_START", + "AVDTP_SI_CLOSE", + "AVDTP_SI_SUSPEND", + "AVDTP_SI_ABORT", + "AVDTP_SI_SECURITY_CONTROL", + "AVDTP_SI_GET_ALL_CAPABILITIES", + "AVDTP_SI_DELAY_REPORT" +}; + +typedef enum { + AVDTP_APPLICATION_IDLE, + AVDTP_APPLICATION_W2_DISCOVER_SEPS, + AVDTP_APPLICATION_W2_GET_CAPABILITIES, + AVDTP_APPLICATION_W2_GET_ALL_CAPABILITIES, + AVDTP_APPLICATION_W2_SET_CONFIGURATION, + AVDTP_APPLICATION_W2_SUSPEND_STREAM_WITH_SEID, + AVDTP_APPLICATION_W2_RECONFIGURE_WITH_SEID, + AVDTP_APPLICATION_W2_OPEN_STREAM_WITH_SEID, + AVDTP_APPLICATION_W2_START_STREAM_WITH_SEID, + AVDTP_APPLICATION_W2_ABORT_STREAM_WITH_SEID, + AVDTP_APPLICATION_W2_STOP_STREAM_WITH_SEID, + AVDTP_APPLICATION_W2_GET_CONFIGURATION, + AVDTP_APPLICATION_STREAMING_OPENED +} avdtp_application_state_t; + +avdtp_application_state_t app_state = AVDTP_APPLICATION_IDLE; + +static btstack_packet_callback_registration_t hci_event_callback_registration; + +static const char * avdtp_si2str(uint16_t index){ + if (index <= 0 || index > sizeof(avdtp_si_name)) return avdtp_si_name[0]; + return avdtp_si_name[index]; +} + +static void dump_sbc_capability(adtvp_media_codec_information_sbc_t media_codec_sbc){ + printf(" --- avdtp source --- Received media codec capability:\n"); + printf(" - sampling_frequency: 0x%02x\n", media_codec_sbc.sampling_frequency_bitmap); + printf(" - channel_mode: 0x%02x\n", media_codec_sbc.channel_mode_bitmap); + printf(" - block_length: 0x%02x\n", media_codec_sbc.block_length_bitmap); + printf(" - subbands: 0x%02x\n", media_codec_sbc.subbands_bitmap); + printf(" - allocation_method: 0x%02x\n", media_codec_sbc.allocation_method_bitmap); + printf(" - bitpool_value [%d, %d] \n", media_codec_sbc.min_bitpool_value, media_codec_sbc.max_bitpool_value); + printf("\n"); +} + +static void dump_sbc_configuration(avdtp_media_codec_configuration_sbc_t configuration){ + printf(" --- avdtp source --- Received media codec configuration:\n"); + printf(" - num_channels: %d\n", configuration.num_channels); + printf(" - sampling_frequency: %d\n", configuration.sampling_frequency); + printf(" - channel_mode: %d\n", configuration.channel_mode); + printf(" - block_length: %d\n", configuration.block_length); + printf(" - subbands: %d\n", configuration.subbands); + printf(" - allocation_method: %d\n", configuration.allocation_method); + printf(" - bitpool_value [%d, %d] \n", configuration.min_bitpool_value, configuration.max_bitpool_value); + printf("\n"); +} + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + bd_addr_t event_addr; + uint8_t signal_identifier; + uint8_t status; + + switch (packet_type) { + + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_PIN_CODE_REQUEST: + // inform about pin code request + printf("Pin code request - using '0000'\n"); + hci_event_pin_code_request_get_bd_addr(packet, event_addr); + hci_send_cmd(&hci_pin_code_request_reply, &event_addr, 4, "0000"); + break; + case HCI_EVENT_DISCONNECTION_COMPLETE: + // connection closed -> quit test app + printf("\n --- avdtp source --- HCI_EVENT_DISCONNECTION_COMPLETE ---\n"); + break; + case HCI_EVENT_AVDTP_META: + switch (packet[2]){ + case AVDTP_SUBEVENT_SIGNALING_CONNECTION_ESTABLISHED: + con_handle = avdtp_subevent_signaling_connection_established_get_con_handle(packet); + status = avdtp_subevent_signaling_connection_established_get_status(packet); + if (status != 0){ + printf(" --- avdtp source --- AVDTP_SUBEVENT_SIGNALING_CONNECTION cpould not be established, status %d ---\n", status); + break; + } + printf(" --- avdtp source --- AVDTP_SUBEVENT_SIGNALING_CONNECTION_ESTABLISHED, con handle 0x%02x ---\n", con_handle); + break; + case AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED: + media_con_handle = avdtp_subevent_streaming_connection_established_get_con_handle(packet); + status = avdtp_subevent_streaming_connection_established_get_status(packet); + if (status != 0){ + printf(" --- avdtp source --- AVDTP_SUBEVENT_STREAMING_CONNECTION could not be established, status %d ---\n", status); + break; + } + app_state = AVDTP_APPLICATION_STREAMING_OPENED; + printf(" --- avdtp source --- AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED, con handle 0x%02x ---\n", media_con_handle); + break; + + case AVDTP_SUBEVENT_SIGNALING_SEP_FOUND: + if (app_state != AVDTP_APPLICATION_W2_DISCOVER_SEPS) return; + sep.seid = avdtp_subevent_signaling_sep_found_get_seid(packet); + sep.in_use = avdtp_subevent_signaling_sep_found_get_in_use(packet); + sep.media_type = avdtp_subevent_signaling_sep_found_get_media_type(packet); + sep.type = avdtp_subevent_signaling_sep_found_get_sep_type(packet); + printf(" --- avdtp source --- Found sep: seid %u, in_use %d, media type %d, sep type %d (1-SNK)\n", sep.seid, sep.in_use, sep.media_type, sep.type); + break; + case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CAPABILITY: + app_state = AVDTP_APPLICATION_IDLE; + sbc_capability.sampling_frequency_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_sampling_frequency_bitmap(packet); + sbc_capability.channel_mode_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_channel_mode_bitmap(packet); + sbc_capability.block_length_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_block_length_bitmap(packet); + sbc_capability.subbands_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_subbands_bitmap(packet); + sbc_capability.allocation_method_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_allocation_method_bitmap(packet); + sbc_capability.min_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_capability_get_min_bitpool_value(packet); + sbc_capability.max_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_capability_get_max_bitpool_value(packet); + dump_sbc_capability(sbc_capability); + break; + case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION:{ + app_state = AVDTP_APPLICATION_IDLE; + sbc_configuration.reconfigure = avdtp_subevent_signaling_media_codec_sbc_configuration_get_reconfigure(packet); + sbc_configuration.num_channels = avdtp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet); + sbc_configuration.sampling_frequency = avdtp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet); + sbc_configuration.channel_mode = avdtp_subevent_signaling_media_codec_sbc_configuration_get_channel_mode(packet); + sbc_configuration.block_length = avdtp_subevent_signaling_media_codec_sbc_configuration_get_block_length(packet); + sbc_configuration.subbands = avdtp_subevent_signaling_media_codec_sbc_configuration_get_subbands(packet); + sbc_configuration.allocation_method = avdtp_subevent_signaling_media_codec_sbc_configuration_get_allocation_method(packet); + sbc_configuration.min_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_configuration_get_min_bitpool_value(packet); + sbc_configuration.max_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_configuration_get_max_bitpool_value(packet); + sbc_configuration.frames_per_buffer = sbc_configuration.subbands * sbc_configuration.block_length; + dump_sbc_configuration(sbc_configuration); + + // if (sbc_configuration.reconfigure){} + break; + } + case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CAPABILITY: + printf(" --- avdtp source --- received non SBC codec. not implemented\n"); + break; + + case AVDTP_SUBEVENT_SIGNALING_ACCEPT: + app_state = AVDTP_APPLICATION_IDLE; + signal_identifier = avdtp_subevent_signaling_accept_get_signal_identifier(packet); + status = avdtp_subevent_signaling_accept_get_status(packet); + printf(" --- avdtp source --- Accepted %s, status %d\n", avdtp_si2str(signal_identifier), status); + if (app_state != AVDTP_APPLICATION_STREAMING_OPENED) return; + + switch (signal_identifier){ + case AVDTP_SI_START: + break; + case AVDTP_SI_CLOSE: + break; + case AVDTP_SI_SUSPEND: + break; + case AVDTP_SI_ABORT: + break; + default: + break; + } + break; + case AVDTP_SUBEVENT_SIGNALING_REJECT: + signal_identifier = avdtp_subevent_signaling_reject_get_signal_identifier(packet); + printf(" --- avdtp source --- Rejected %s\n", avdtp_si2str(signal_identifier)); + break; + case AVDTP_SUBEVENT_SIGNALING_GENERAL_REJECT: + signal_identifier = avdtp_subevent_signaling_general_reject_get_signal_identifier(packet); + printf(" --- avdtp source --- Rejected %s\n", avdtp_si2str(signal_identifier)); + break; + default: + printf(" --- avdtp source --- not implemented\n"); + break; + } + break; + default: + break; + } + break; + default: + // other packet type + break; + } +} + +static void show_usage(void){ + bd_addr_t iut_address; + gap_local_bd_addr(iut_address); + printf("\n--- Bluetooth AVDTP SOURCE Test Console %s ---\n", bd_addr_to_str(iut_address)); + printf("c - create connection to addr %s\n", bd_addr_to_str(remote)); + printf("C - disconnect\n"); + printf("d - discover stream endpoints\n"); + printf("g - get capabilities\n"); + printf("a - get all capabilities\n"); + printf("s - set configuration\n"); + printf("f - get configuration\n"); + printf("R - reconfigure stream with %d\n", sep.seid); + printf("o - open stream with seid %d\n", sep.seid); + printf("m - start stream with %d\n", sep.seid); + printf("x - start data stream\n"); + printf("X - stop data stream\n"); + printf("A - abort stream with %d\n", sep.seid); + printf("S - stop stream with %d\n", sep.seid); + printf("P - suspend stream with %d\n", sep.seid); + printf("Ctrl-c - exit\n"); + printf("---\n"); +} + +static const uint8_t media_sbc_codec_capabilities[] = { + 0xFF,//(AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, + 0xFF,//(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, + 2, 53 +}; + +static const uint8_t media_sbc_codec_configuration[] = { + (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, + (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, + 2, 53 +}; + +static const uint8_t media_sbc_codec_reconfiguration[] = { + (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, + (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_SNR, + 2, 53 +}; + +static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ + UNUSED(ds); + UNUSED(callback_type); + + int cmd = btstack_stdin_read(); + + sep.seid = 1; + switch (cmd){ + case 'c': + printf("Creating L2CAP Connection to %s, PSM_AVDTP\n", bd_addr_to_str(remote)); + avdtp_source_connect(remote); + break; + case 'C': + printf("Disconnect not implemented\n"); + avdtp_source_disconnect(con_handle); + break; + case 'd': + app_state = AVDTP_APPLICATION_W2_DISCOVER_SEPS; + avdtp_source_discover_stream_endpoints(con_handle); + break; + case 'g': + app_state = AVDTP_APPLICATION_W2_GET_CAPABILITIES; + avdtp_source_get_capabilities(con_handle, sep.seid); + break; + case 'a': + app_state = AVDTP_APPLICATION_W2_GET_ALL_CAPABILITIES; + avdtp_source_get_all_capabilities(con_handle, sep.seid); + break; + case 'f': + app_state = AVDTP_APPLICATION_W2_GET_CONFIGURATION; + avdtp_source_get_configuration(con_handle, sep.seid); + break; + case 's': + app_state = AVDTP_APPLICATION_W2_SET_CONFIGURATION; + remote_configuration_bitmap = store_bit16(remote_configuration_bitmap, AVDTP_MEDIA_CODEC, 1); + remote_configuration.media_codec.media_type = AVDTP_AUDIO; + remote_configuration.media_codec.media_codec_type = AVDTP_CODEC_SBC; + remote_configuration.media_codec.media_codec_information_len = sizeof(media_sbc_codec_configuration); + remote_configuration.media_codec.media_codec_information = media_sbc_codec_configuration; + avdtp_source_set_configuration(con_handle, local_stream_endpoint->sep.seid, sep.seid, remote_configuration_bitmap, remote_configuration); + break; + case 'R': + app_state = AVDTP_APPLICATION_W2_RECONFIGURE_WITH_SEID; + remote_configuration_bitmap = store_bit16(remote_configuration_bitmap, AVDTP_MEDIA_CODEC, 1); + remote_configuration.media_codec.media_type = AVDTP_AUDIO; + remote_configuration.media_codec.media_codec_type = AVDTP_CODEC_SBC; + remote_configuration.media_codec.media_codec_information_len = sizeof(media_sbc_codec_reconfiguration); + remote_configuration.media_codec.media_codec_information = media_sbc_codec_reconfiguration; + avdtp_source_reconfigure(con_handle, sep.seid, remote_configuration_bitmap, remote_configuration); + break; + case 'o': + app_state = AVDTP_APPLICATION_W2_OPEN_STREAM_WITH_SEID; + avdtp_source_open_stream(con_handle, sep.seid); + break; + case 'm': + app_state = AVDTP_APPLICATION_W2_START_STREAM_WITH_SEID; + avdtp_source_start_stream(con_handle, sep.seid); + break; + case 'A': + app_state = AVDTP_APPLICATION_W2_ABORT_STREAM_WITH_SEID; + avdtp_source_abort_stream(con_handle, sep.seid); + break; + case 'S': + app_state = AVDTP_APPLICATION_W2_STOP_STREAM_WITH_SEID; + avdtp_source_stop_stream(con_handle, sep.seid); + break; + case 'P': + app_state = AVDTP_APPLICATION_W2_SUSPEND_STREAM_WITH_SEID; + avdtp_source_suspend(con_handle, sep.seid); + break; + case 'x': + printf("start streaming sine\n"); + avdtp_source_stream_data_start(media_con_handle); + break; + case 'X': + printf("stop streaming sine\n"); + avdtp_source_stream_data_stop(media_con_handle); + break; + + case '\n': + case '\r': + break; + default: + show_usage(); + break; + + } +} + + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + UNUSED(argc); + (void)argv; + + /* Register for HCI events */ + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + l2cap_init(); + // Initialize AVDTP Sink + avdtp_source_init(); + avdtp_source_register_packet_handler(&packet_handler); + +//#ifndef SMG_BI + local_stream_endpoint = avdtp_source_create_stream_endpoint(AVDTP_SOURCE, AVDTP_AUDIO); + local_stream_endpoint->sep.seid = 2; + avdtp_source_register_media_transport_category(local_stream_endpoint->sep.seid); + avdtp_source_register_media_codec_category(local_stream_endpoint->sep.seid, AVDTP_AUDIO, AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities)); +//#endif + // uint8_t cp_type_lsb, uint8_t cp_type_msb, const uint8_t * cp_type_value, uint8_t cp_type_value_len + // avdtp_source_register_content_protection_category(seid, 2, 2, NULL, 0); + + // Initialize SDP + sdp_init(); + memset(sdp_avdtp_source_service_buffer, 0, sizeof(sdp_avdtp_source_service_buffer)); + a2dp_sink_create_sdp_record(sdp_avdtp_source_service_buffer, 0x10002, 1, NULL, NULL); + sdp_register_service(sdp_avdtp_source_service_buffer); + + gap_set_local_name("BTstack A2DP Source Test"); + gap_discoverable_control(1); + gap_set_class_of_device(0x200408); + + // turn on! + hci_power_control(HCI_POWER_ON); + + btstack_stdin_setup(stdin_process); + return 0; +} diff --git a/test/avdtp/sine_encode_decode_ring_buffer_test.c b/test/avdtp/sine_encode_decode_ring_buffer_test.c new file mode 100644 index 000000000..43686f212 --- /dev/null +++ b/test/avdtp/sine_encode_decode_ring_buffer_test.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2016 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#include +#include +#include +#include +#include + +#include "btstack_ring_buffer.h" +#include "btstack_sbc.h" +#include "wav_util.h" +#include "avdtp.h" +#include "avdtp_source.h" +#include "stdin_support.h" + +#define NUM_CHANNELS 2 +#define SAMPLE_RATE 44100 +#define BYTES_PER_AUDIO_SAMPLE (2*NUM_CHANNELS) +#define LATENCY 300 // ms + +#ifndef M_PI +#define M_PI 3.14159265 +#endif +#define TABLE_SIZE_441HZ 100 + +typedef struct { + int16_t source[TABLE_SIZE_441HZ]; + int left_phase; + int right_phase; +} paTestData; + +static uint32_t fill_audio_ring_buffer_timeout = 50; //ms +static paTestData sin_data; +// static int total_num_samples = 0; + +static char * output_wav_filename = "test_output_ring_sine.wav"; +// static char * input_wav_filename = "test_input_sine.wav"; + +static btstack_sbc_decoder_state_t state; +static btstack_sbc_mode_t mode = SBC_MODE_STANDARD; + +static avdtp_stream_endpoint_t * local_stream_endpoint; + + +static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, int sample_rate, void * context){ + UNUSED(sample_rate); + UNUSED(context); + wav_writer_write_int16(num_samples*num_channels, data); +} + + +static void fill_audio_ring_buffer(void *userData, int num_samples_to_write, avdtp_stream_endpoint_t * stream_endpoint){ + paTestData *data = (paTestData*)userData; + int count = 0; + while (btstack_ring_buffer_bytes_free(&stream_endpoint->audio_ring_buffer) >= BYTES_PER_AUDIO_SAMPLE && count < num_samples_to_write){ + uint8_t write_data[BYTES_PER_AUDIO_SAMPLE]; + *(int16_t*)&write_data[0] = data->source[data->left_phase]; + *(int16_t*)&write_data[2] = data->source[data->right_phase]; + + btstack_ring_buffer_write(&stream_endpoint->audio_ring_buffer, write_data, BYTES_PER_AUDIO_SAMPLE); + count++; + + data->left_phase += 1; + if (data->left_phase >= TABLE_SIZE_441HZ){ + data->left_phase -= TABLE_SIZE_441HZ; + } + data->right_phase += 1; + if (data->right_phase >= TABLE_SIZE_441HZ){ + data->right_phase -= TABLE_SIZE_441HZ; + } + } +} + +static void store_sbc_frame_for_transmission(uint8_t * sbc_frame, int sbc_frame_size, avdtp_stream_endpoint_t * stream_endpoint){ + if (btstack_ring_buffer_bytes_free(&stream_endpoint->sbc_ring_buffer) >= (sbc_frame_size + 1)){ + // printf(" store_sbc_frame_for_transmission\n"); + uint8_t size_buffer = sbc_frame_size; + btstack_ring_buffer_write(&stream_endpoint->sbc_ring_buffer, &size_buffer, 1); + btstack_ring_buffer_write(&stream_endpoint->sbc_ring_buffer, sbc_frame, sbc_frame_size); + } else { + printf("No space in sbc buffer\n"); + } +} + + +static void avdtp_source_stream_endpoint_run(avdtp_stream_endpoint_t * stream_endpoint){ + // performe sbc encoding + int total_num_bytes_read = 0; + int num_audio_samples_to_read = btstack_sbc_encoder_num_audio_frames(); + int audio_bytes_to_read = num_audio_samples_to_read * BYTES_PER_AUDIO_SAMPLE; + + printf("run: audio samples %u, audio_bytes_to_read: %d\n", num_audio_samples_to_read, audio_bytes_to_read); + printf(" audio buf, bytes available: %d\n", btstack_ring_buffer_bytes_available(&stream_endpoint->audio_ring_buffer)); + printf(" sbc buf, bytes free: %d\n", btstack_ring_buffer_bytes_free(&stream_endpoint->sbc_ring_buffer)); + + while (btstack_ring_buffer_bytes_available(&stream_endpoint->audio_ring_buffer) >= audio_bytes_to_read + && btstack_ring_buffer_bytes_free(&stream_endpoint->sbc_ring_buffer) >= 120){ // TODO use real value + + uint32_t number_of_bytes_read = 0; + uint8_t pcm_frame[256*BYTES_PER_AUDIO_SAMPLE]; + btstack_ring_buffer_read(&stream_endpoint->audio_ring_buffer, pcm_frame, audio_bytes_to_read, &number_of_bytes_read); + // printf(" num audio bytes read %d\n", number_of_bytes_read); + btstack_sbc_encoder_process_data((int16_t *) pcm_frame); + + uint16_t sbc_frame_bytes = btstack_sbc_encoder_sbc_buffer_length(); + printf("decode %d bytes\n", sbc_frame_bytes); + total_num_bytes_read += number_of_bytes_read; + + store_sbc_frame_for_transmission(btstack_sbc_encoder_sbc_buffer(), sbc_frame_bytes, stream_endpoint); + btstack_sbc_decoder_process_data(&state, 0, btstack_sbc_encoder_sbc_buffer(), sbc_frame_bytes); + } +} + +static void test_fill_audio_ring_buffer_timeout_handler(btstack_timer_source_t * timer){ + avdtp_stream_endpoint_t * stream_endpoint = btstack_run_loop_get_timer_context(timer); + btstack_run_loop_set_timer(&stream_endpoint->fill_audio_ring_buffer_timer, fill_audio_ring_buffer_timeout); // 2 seconds timeout + btstack_run_loop_add_timer(&stream_endpoint->fill_audio_ring_buffer_timer); + uint32_t now = btstack_run_loop_get_time_ms(); + + uint32_t update_period_ms = fill_audio_ring_buffer_timeout; + if (stream_endpoint->time_audio_data_sent > 0){ + update_period_ms = now - stream_endpoint->time_audio_data_sent; + } + uint32_t num_samples = (update_period_ms * 44100) / 1000; + stream_endpoint->acc_num_missed_samples += (update_period_ms * 44100) % 1000; + + if (stream_endpoint->acc_num_missed_samples >= 1000){ + num_samples++; + stream_endpoint->acc_num_missed_samples -= 1000; + } + + fill_audio_ring_buffer(&sin_data, num_samples, stream_endpoint); + stream_endpoint->time_audio_data_sent = now; + + avdtp_source_stream_endpoint_run(stream_endpoint); +} + +static void test_fill_audio_ring_buffer_timer_start(avdtp_stream_endpoint_t * stream_endpoint){ + btstack_run_loop_remove_timer(&stream_endpoint->fill_audio_ring_buffer_timer); + btstack_run_loop_set_timer_handler(&stream_endpoint->fill_audio_ring_buffer_timer, test_fill_audio_ring_buffer_timeout_handler); + btstack_run_loop_set_timer_context(&stream_endpoint->fill_audio_ring_buffer_timer, stream_endpoint); + btstack_run_loop_set_timer(&stream_endpoint->fill_audio_ring_buffer_timer, fill_audio_ring_buffer_timeout); // 50 ms timeout + btstack_run_loop_add_timer(&stream_endpoint->fill_audio_ring_buffer_timer); +} + +static void test_fill_audio_ring_buffer_timer_stop(avdtp_stream_endpoint_t * stream_endpoint){ + btstack_run_loop_remove_timer(&stream_endpoint->fill_audio_ring_buffer_timer); +} + +static void stream_data_start(void){ + test_fill_audio_ring_buffer_timer_start(local_stream_endpoint); +} + +static void stream_data_stop(void){ + test_fill_audio_ring_buffer_timer_stop(local_stream_endpoint); + wav_writer_close(); +} + +static void show_usage(void){ + printf("\n--- Streaming with ring buffer Test Console ---\n"); + printf("x - start data stream\n"); + printf("X - stop data stream\n"); + printf("Ctrl-c - exit\n"); + printf("---\n"); +} + +static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ + UNUSED(ds); + UNUSED(callback_type); + + int cmd = btstack_stdin_read(); + + switch (cmd){ + case 'x': + printf("start streaming sine\n"); + stream_data_start(); + break; + case 'X': + printf("stop streaming sine\n"); + stream_data_stop(); + break; + + case '\n': + case '\r': + break; + default: + show_usage(); + break; + + } +} + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + (void) argc; + (void) argv; + local_stream_endpoint = avdtp_source_create_stream_endpoint(AVDTP_SOURCE, AVDTP_AUDIO); + btstack_sbc_encoder_init(&(local_stream_endpoint->sbc_encoder_state), SBC_MODE_STANDARD, 16, 8, 2, 44100, 53); + + /* initialise sinusoidal wavetable */ + int i; + for (i=0; i +#include +#include +#include +#include + +#include "btstack_ring_buffer.h" +#include "btstack_sbc.h" +#include "wav_util.h" +#include "avdtp.h" +#include "avdtp_source.h" +#include "stdin_support.h" + +#define NUM_CHANNELS 2 +#define SAMPLE_RATE 44100 +#define BYTES_PER_AUDIO_SAMPLE (2*NUM_CHANNELS) +#define LATENCY 300 // ms + +#ifndef M_PI +#define M_PI 3.14159265 +#endif +#define TABLE_SIZE_441HZ 100 + +typedef struct { + int16_t source[TABLE_SIZE_441HZ]; + int left_phase; + int right_phase; +} paTestData; + +static uint8_t pcm_frame[2*8*16*2]; + +static paTestData sin_data; +// static int total_num_samples = 0; + +static char * output_wav_filename = "test_output_sine.wav"; +// static char * input_wav_filename = "test_input_sine.wav"; + +static btstack_sbc_decoder_state_t state; +static btstack_sbc_mode_t mode = SBC_MODE_STANDARD; +static btstack_sbc_encoder_state_t sbc_encoder_state; + +static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, int sample_rate, void * context){ + UNUSED(sample_rate); + UNUSED(context); + wav_writer_write_int16(num_samples*num_channels, data); +} + +static void fill_sine_frame(void *userData, int num_samples_to_write){ + paTestData *data = (paTestData*)userData; + int count = 0; + int offset = 0; + while (count < num_samples_to_write){ + uint8_t write_data[BYTES_PER_AUDIO_SAMPLE]; + *(int16_t*)&write_data[0] = data->source[data->left_phase]; + *(int16_t*)&write_data[2] = data->source[data->right_phase]; + + memcpy(pcm_frame+offset, write_data, BYTES_PER_AUDIO_SAMPLE); + offset += BYTES_PER_AUDIO_SAMPLE; + count++; + + data->left_phase += 1; + if (data->left_phase >= TABLE_SIZE_441HZ){ + data->left_phase -= TABLE_SIZE_441HZ; + } + data->right_phase += 1; + if (data->right_phase >= TABLE_SIZE_441HZ){ + data->right_phase -= TABLE_SIZE_441HZ; + } + } +} + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + (void) argc; + (void) argv; + btstack_sbc_encoder_init(&sbc_encoder_state, SBC_MODE_STANDARD, 16, 8, 2, 44100, 53); + + /* initialise sinusoidal wavetable */ + int i; + for (i=0; i 0 && fc >= corruption_step && fc%corruption_step == 0){ memset(audio_frame_in, 50, audio_samples_per_frame * 2); } @@ -151,7 +151,7 @@ void process_wav_file_with_plc(const char * in_filename, const char * out_filena wav_writer_open(out_filename, 1, 8000); wav_reader_open(in_filename); - while (wav_reader_read_int16(audio_samples_per_frame, audio_frame_in)){ + while (wav_reader_read_int16(audio_samples_per_frame, audio_frame_in) == 0){ int16_t audio_frame_out[audio_samples_per_frame]; btstack_cvsd_plc_process_data(&plc_state, audio_frame_in, audio_samples_per_frame, audio_frame_out); wav_writer_write_int16(audio_samples_per_frame, audio_frame_out); @@ -167,7 +167,7 @@ void mark_bad_frames_wav_file(const char * in_filename, const char * out_filenam CHECK_EQUAL(wav_writer_open(out_filename, 1, 8000), 0); CHECK_EQUAL(wav_reader_open(in_filename), 0); - while (wav_reader_read_int16(audio_samples_per_frame, audio_frame_in)){ + while (wav_reader_read_int16(audio_samples_per_frame, audio_frame_in) == 0){ int16_t audio_frame_out[audio_samples_per_frame]; btstack_cvsd_plc_mark_bad_frame(&plc_state, audio_frame_in, audio_samples_per_frame, audio_frame_out); wav_writer_write_int16(audio_samples_per_frame, audio_frame_out); diff --git a/test/sbc/Makefile b/test/sbc/Makefile index f543fa4ca..8e000522b 100644 --- a/test/sbc/Makefile +++ b/test/sbc/Makefile @@ -35,14 +35,14 @@ COMMON += \ COMMON_OBJ = $(COMMON:.c=.o) -SBC_TESTS = sbc_decoder_test sbc_encoder_test +SBC_TESTS = sbc_decoder_test msbc_encoder_test all: ${SBC_TESTS} sbc_decoder_test: ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${COMMON_OBJ} sbc_decoder_test.o ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ -sbc_encoder_test: ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${COMMON_OBJ} sbc_encoder_test.o +msbc_encoder_test: ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${COMMON_OBJ} msbc_encoder_test.o ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ test: all diff --git a/test/sbc/sbc_encoder_test.c b/test/sbc/msbc_encoder_test.c similarity index 94% rename from test/sbc/sbc_encoder_test.c rename to test/sbc/msbc_encoder_test.c index e1b79c9bf..1bef5e577 100644 --- a/test/sbc/sbc_encoder_test.c +++ b/test/sbc/msbc_encoder_test.c @@ -80,12 +80,12 @@ int main (int argc, const char * argv[]){ } hfp_msbc_init(); - int num_samples = hfp_msbc_num_audio_samples_per_frame() * 2; + int num_samples = hfp_msbc_num_audio_samples_per_frame(); while (1){ if (hfp_msbc_can_encode_audio_frame_now()){ - int bytes_read = wav_reader_read_int16(num_samples, read_buffer); - if (bytes_read < num_samples) break; + int error = wav_reader_read_int16(num_samples, read_buffer); + if (error) break; hfp_msbc_encode_audio_frame(read_buffer); } diff --git a/test/sbc/sbc_decoder_test.c b/test/sbc/sbc_decoder_test.c index cab32e13d..e63704f6e 100644 --- a/test/sbc/sbc_decoder_test.c +++ b/test/sbc/sbc_decoder_test.c @@ -64,7 +64,7 @@ static int wav_writer_opened = 0; static char wav_filename[1000]; static void show_usage(void){ - printf("\n\nUsage: ./sbc_decoder_test input_file msbc plc_enabled corrupt_frame_period \n\n"); + printf("\n\nUsage: ./sbc_decoder_test input_file [0-sbc|1-msbc] plc_enabled corrupt_frame_period \n\n"); } static ssize_t __read(int fd, void *buf, size_t count){ From 8ebc0d8ad65890e17ae1d1db630630a5735ddf47 Mon Sep 17 00:00:00 2001 From: Milanka Ringwald Date: Mon, 20 Mar 2017 11:49:14 +0100 Subject: [PATCH 09/12] avdtp source: fix compile --- src/classic/avdtp_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classic/avdtp_util.c b/src/classic/avdtp_util.c index aca3707e2..3097591f1 100644 --- a/src/classic/avdtp_util.c +++ b/src/classic/avdtp_util.c @@ -442,7 +442,7 @@ void avdtp_prepare_capabilities(avdtp_signaling_packet_t * signaling_packet, uin // printf_hexdump(signaling_packet->command, signaling_packet->size); for (i = 1; i < 9; i++){ int registered_category = get_bit16(registered_service_categories, i); - if (!registered_category && (identifier == AVDTP_SI_SET_CONFIGURATION || identifier == identifier == AVDTP_SI_RECONFIGURE){ + if (!registered_category && (identifier == AVDTP_SI_SET_CONFIGURATION || identifier == AVDTP_SI_RECONFIGURE){ // TODO: introduce bitmap of mandatory categories if (i == 1){ registered_category = 1; From e033427c27f73d1387d17f16cc3e75a32715175f Mon Sep 17 00:00:00 2001 From: Milanka Ringwald Date: Mon, 20 Mar 2017 11:53:08 +0100 Subject: [PATCH 10/12] avdtp: fix compile --- src/classic/avdtp_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classic/avdtp_util.c b/src/classic/avdtp_util.c index 3097591f1..730d05f4f 100644 --- a/src/classic/avdtp_util.c +++ b/src/classic/avdtp_util.c @@ -442,7 +442,7 @@ void avdtp_prepare_capabilities(avdtp_signaling_packet_t * signaling_packet, uin // printf_hexdump(signaling_packet->command, signaling_packet->size); for (i = 1; i < 9; i++){ int registered_category = get_bit16(registered_service_categories, i); - if (!registered_category && (identifier == AVDTP_SI_SET_CONFIGURATION || identifier == AVDTP_SI_RECONFIGURE){ + if (!registered_category && (identifier == AVDTP_SI_SET_CONFIGURATION || identifier == AVDTP_SI_RECONFIGURE)){ // TODO: introduce bitmap of mandatory categories if (i == 1){ registered_category = 1; From fa84a2d42f14663e2ff778ce353da94ad56b9a50 Mon Sep 17 00:00:00 2001 From: Milanka Ringwald Date: Mon, 20 Mar 2017 16:27:24 +0100 Subject: [PATCH 11/12] avdtp: source sends audio at const. speed --- src/classic/avdtp.c | 18 +++++++++--------- src/classic/avdtp_source.c | 2 +- test/avdtp/avdtp_source_test.c | 11 ++++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/classic/avdtp.c b/src/classic/avdtp.c index 31cae26a4..2b35f6ea6 100644 --- a/src/classic/avdtp.c +++ b/src/classic/avdtp.c @@ -440,26 +440,26 @@ void avdtp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet //send sbc uint8_t rtp_version = 2; uint8_t padding = 0; - uint8_t extention = 0; + uint8_t extension = 0; uint8_t csrc_count = 0; uint8_t marker = 0; - uint8_t payload_type = 0; + uint8_t payload_type = 0x60; uint16_t sequence_number = stream_endpoint->sequence_number; uint32_t timestamp = btstack_run_loop_get_time_ms(); - uint32_t ssrc = 0; + uint32_t ssrc = 0x11223344; // rtp header (min size 12B) int pos = 0; int mtu = l2cap_get_remote_mtu_for_local_cid(stream_endpoint->l2cap_media_cid); uint8_t media_packet[mtu]; - media_packet[pos++] = (rtp_version << 7) && (padding << 6) && (padding << 5) && (extention << 4) && csrc_count; - media_packet[pos++] = (marker << 7) && payload_type; - little_endian_store_16(media_packet, pos, sequence_number); + media_packet[pos++] = (rtp_version << 6) | (padding << 5) | (extension << 4) | csrc_count; + media_packet[pos++] = (marker << 1) | payload_type; + big_endian_store_16(media_packet, pos, sequence_number); pos += 2; - little_endian_store_32(media_packet, pos, timestamp); + big_endian_store_32(media_packet, pos, timestamp); pos += 4; - little_endian_store_32(media_packet, pos, ssrc); // only used for multicast + big_endian_store_32(media_packet, pos, ssrc); // only used for multicast pos += 4; // media payload @@ -483,7 +483,7 @@ void avdtp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet num_frames++; // printf("send sbc frame: timestamp %d, seq. nr %d\n", timestamp, stream_endpoint->sequence_number); } - media_packet[sbc_header_index] = (fragmentation << 7) && (starting_packet << 6) && (last_packet << 5) && num_frames; + media_packet[sbc_header_index] = (fragmentation << 7) | (starting_packet << 6) | (last_packet << 5) | num_frames; stream_endpoint->sequence_number++; l2cap_send(stream_endpoint->l2cap_media_cid, media_packet, pos); if (btstack_ring_buffer_bytes_available(&stream_endpoint->sbc_ring_buffer)){ diff --git a/src/classic/avdtp_source.c b/src/classic/avdtp_source.c index 1ae08534e..b152e6a6e 100644 --- a/src/classic/avdtp_source.c +++ b/src/classic/avdtp_source.c @@ -271,7 +271,7 @@ typedef struct { int right_phase; } paTestData; -static uint32_t fill_audio_ring_buffer_timeout = 25; //ms +static uint32_t fill_audio_ring_buffer_timeout = 50; //ms static paTestData sin_data; static int total_num_samples = 0; static char * wav_filename = "test_output_sine.wav"; diff --git a/test/avdtp/avdtp_source_test.c b/test/avdtp/avdtp_source_test.c index d7826c1e7..5cf98e213 100644 --- a/test/avdtp/avdtp_source_test.c +++ b/test/avdtp/avdtp_source_test.c @@ -88,8 +88,9 @@ typedef struct { // pts: static bd_addr_t remote = {0x00, 0x1B, 0xDC, 0x08, 0x0A, 0xA5}; // mac 2013: static bd_addr_t remote = {0x84, 0x38, 0x35, 0x65, 0xd1, 0x15}; // phone 2013: static bd_addr_t remote = {0xD8, 0xBB, 0x2C, 0xDF, 0xF0, 0xF2}; -// minijambox: -static bd_addr_t remote = { 0x00, 0x21, 0x3c, 0xac, 0xf7, 0x38}; +// minijambox: static bd_addr_t remote = {0x00, 0x21, 0x3c, 0xac, 0xf7, 0x38}; +// head phones: +static bd_addr_t remote = {0x00, 0x18, 0x09, 0x28, 0x50, 0x18}; // bt dongle: -u 02-04-01 // static bd_addr_t remote = {0x00, 0x15, 0x83, 0x5F, 0x9D, 0x46}; @@ -323,19 +324,19 @@ static void show_usage(void){ static const uint8_t media_sbc_codec_capabilities[] = { 0xFF,//(AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, 0xFF,//(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, - 2, 53 + 2, 35 }; static const uint8_t media_sbc_codec_configuration[] = { (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, - 2, 53 + 2, 35 }; static const uint8_t media_sbc_codec_reconfiguration[] = { (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_SNR, - 2, 53 + 2, 35 }; static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ From 53f7f1199665d5f652e681a5f1abbe82476dea3f Mon Sep 17 00:00:00 2001 From: Milanka Ringwald Date: Mon, 20 Mar 2017 17:03:46 +0100 Subject: [PATCH 12/12] avdpt: performance test for encoder, decoder --- port/libusb/.gitignore | 1 + test/avdtp/.gitignore | 1 + test/avdtp/Makefile | 6 +- test/avdtp/avdtp_source_test.c | 12 +- .../sine_encode_decode_performance_test.c | 143 ++++++++++++++++++ 5 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 test/avdtp/sine_encode_decode_performance_test.c diff --git a/port/libusb/.gitignore b/port/libusb/.gitignore index f3950a0b7..ea8d1bf00 100644 --- a/port/libusb/.gitignore +++ b/port/libusb/.gitignore @@ -37,3 +37,4 @@ spp_streamer spp_streamer sco_input* sco_output* +le_streamer_client \ No newline at end of file diff --git a/test/avdtp/.gitignore b/test/avdtp/.gitignore index bf6f763fb..cf01bc6f6 100644 --- a/test/avdtp/.gitignore +++ b/test/avdtp/.gitignore @@ -3,6 +3,7 @@ avdtp_sink_test portaudio_test sine_encode_decode_test sine_encode_decode_ring_buffer_test +sine_encode_decode_performance_test *.sbc *.wav diff --git a/test/avdtp/Makefile b/test/avdtp/Makefile index 6c716ecfc..f8c4d449c 100644 --- a/test/avdtp/Makefile +++ b/test/avdtp/Makefile @@ -71,7 +71,7 @@ AVDTP_SINK += \ avdtp_sink.c \ btstack_ring_buffer.c \ -AVDTP_TESTS = avdtp_source_test avdtp_sink_test portaudio_test sine_encode_decode_ring_buffer_test sine_encode_decode_test +AVDTP_TESTS = avdtp_source_test avdtp_sink_test portaudio_test sine_encode_decode_ring_buffer_test sine_encode_decode_test sine_encode_decode_performance_test CORE_OBJ = $(CORE:.c=.o) COMMON_OBJ = $(COMMON:.c=.o) @@ -96,6 +96,10 @@ sine_encode_decode_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCO sine_encode_decode_ring_buffer_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} sine_encode_decode_ring_buffer_test.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + +sine_encode_decode_performance_test: ${CORE_OBJ} ${COMMON_OBJ} ${SBC_DECODER_OBJ} ${SBC_ENCODER_OBJ} ${AVDTP_SINK_OBJ} sine_encode_decode_performance_test.c + ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ + test: all diff --git a/test/avdtp/avdtp_source_test.c b/test/avdtp/avdtp_source_test.c index 5cf98e213..c372c5243 100644 --- a/test/avdtp/avdtp_source_test.c +++ b/test/avdtp/avdtp_source_test.c @@ -88,9 +88,9 @@ typedef struct { // pts: static bd_addr_t remote = {0x00, 0x1B, 0xDC, 0x08, 0x0A, 0xA5}; // mac 2013: static bd_addr_t remote = {0x84, 0x38, 0x35, 0x65, 0xd1, 0x15}; // phone 2013: static bd_addr_t remote = {0xD8, 0xBB, 0x2C, 0xDF, 0xF0, 0xF2}; -// minijambox: static bd_addr_t remote = {0x00, 0x21, 0x3c, 0xac, 0xf7, 0x38}; -// head phones: -static bd_addr_t remote = {0x00, 0x18, 0x09, 0x28, 0x50, 0x18}; +// minijambox: +static bd_addr_t remote = {0x00, 0x21, 0x3c, 0xac, 0xf7, 0x38}; +// head phones: static bd_addr_t remote = {0x00, 0x18, 0x09, 0x28, 0x50, 0x18}; // bt dongle: -u 02-04-01 // static bd_addr_t remote = {0x00, 0x15, 0x83, 0x5F, 0x9D, 0x46}; @@ -324,19 +324,19 @@ static void show_usage(void){ static const uint8_t media_sbc_codec_capabilities[] = { 0xFF,//(AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, 0xFF,//(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, - 2, 35 + 2, 53 }; static const uint8_t media_sbc_codec_configuration[] = { (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, - 2, 35 + 2, 53 }; static const uint8_t media_sbc_codec_reconfiguration[] = { (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_SNR, - 2, 35 + 2, 53 }; static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ diff --git a/test/avdtp/sine_encode_decode_performance_test.c b/test/avdtp/sine_encode_decode_performance_test.c new file mode 100644 index 000000000..323d4ee06 --- /dev/null +++ b/test/avdtp/sine_encode_decode_performance_test.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#include +#include +#include +#include +#include + +#include "btstack_sbc.h" +#include "avdtp.h" +#include "avdtp_source.h" +#include "stdin_support.h" + +#define NUM_CHANNELS 2 +#define SAMPLE_RATE 44100 +#define BYTES_PER_AUDIO_SAMPLE (2*NUM_CHANNELS) +#define LATENCY 300 // ms + +#ifndef M_PI +#define M_PI 3.14159265 +#endif +#define TABLE_SIZE_441HZ 100 + +typedef struct { + int16_t source[TABLE_SIZE_441HZ]; + int left_phase; + int right_phase; +} paTestData; + +static uint8_t pcm_frame[2*8*16*2]; + +static paTestData sin_data; + +static btstack_sbc_mode_t mode = SBC_MODE_STANDARD; +static btstack_sbc_encoder_state_t sbc_encoder_state; +static btstack_sbc_decoder_state_t sbc_decoder_state; + +static void handle_pcm_data(int16_t * data, int num_samples, int num_channels, int sample_rate, void * context){ + UNUSED(sample_rate); + UNUSED(context); + UNUSED(data); + UNUSED(num_samples); + UNUSED(num_channels); +} + +static void fill_sine_frame(void *userData, int num_samples_to_write){ + paTestData *data = (paTestData*)userData; + int count = 0; + int offset = 0; + while (count < num_samples_to_write){ + uint8_t write_data[BYTES_PER_AUDIO_SAMPLE]; + *(int16_t*)&write_data[0] = data->source[data->left_phase]; + *(int16_t*)&write_data[2] = data->source[data->right_phase]; + + memcpy(pcm_frame+offset, write_data, BYTES_PER_AUDIO_SAMPLE); + offset += BYTES_PER_AUDIO_SAMPLE; + count++; + + data->left_phase += 1; + if (data->left_phase >= TABLE_SIZE_441HZ){ + data->left_phase -= TABLE_SIZE_441HZ; + } + data->right_phase += 1; + if (data->right_phase >= TABLE_SIZE_441HZ){ + data->right_phase -= TABLE_SIZE_441HZ; + } + } +} + +int btstack_main(int argc, const char * argv[]); +int btstack_main(int argc, const char * argv[]){ + (void) argc; + (void) argv; + btstack_sbc_encoder_init(&sbc_encoder_state, SBC_MODE_STANDARD, 16, 8, 2, 44100, 53); + + /* initialise sinusoidal wavetable */ + int i; + for (i=0; i