Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bluetooth: AVRCP: connection and disconnection of AVRCP #75637

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
88 changes: 88 additions & 0 deletions include/zephyr/bluetooth/classic/avrcp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/** @file
* @brief Audio Video Remote Control Profile header.
*/

/*
* Copyright (c) 2015-2016 Intel Corporation
* Copyright (C) 2024 Xiaomi Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_

#ifdef __cplusplus
extern "C" {
#endif

/** @brief AVRCP structure */
struct bt_avrcp;

struct bt_avrcp_cb {
/** @brief An AVRCP connection has been established.
*
* This callback notifies the application of an avrcp connection,
* i.e., an AVCTP L2CAP connection.
*
* @param avrcp AVRCP connection object.
*/
void (*connected)(struct bt_avrcp *avrcp);
/** @brief An AVRCP connection has been disconnected.
*
* This callback notifies the application that an avrcp connection
* has been disconnected.
*
* @param avrcp AVRCP connection object.
*/
void (*disconnected)(struct bt_avrcp *avrcp);
};

/** @brief Connect AVRCP.
*
* This function is to be called after the conn parameter is obtained by
* performing a GAP procedure. The API is to be used to establish AVRCP
* connection between devices.
*
* @param conn Pointer to bt_conn structure.
*
* @return pointer to struct bt_avrcp in case of success or NULL in case
* of error.
*/
struct bt_avrcp *bt_avrcp_connect(struct bt_conn *conn);

/** @brief Disconnect AVRCP.
*
* This function close AVCTP L2CAP connection.
*
* @param avrcp The AVRCP instance.
*
* @return 0 in case of success or error code in case of error.
*/
int bt_avrcp_disconnect(struct bt_avrcp *avrcp);

/** @brief Register callback.
*
* Register AVRCP callbacks to monitor the state and interact with the remote device.
*
* @param cb The callback function.
*
* @return 0 in case of success or error code in case of error.
*/
int bt_avrcp_register_cb(struct bt_avrcp_cb *cb);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a singleton (e.g. you don't have a linked list pointer in the struct), I think const struct bt_avrcp_cb *cb is probably more correct. That said, have you considered whether you want to keep this a singleton, i.e. that there aren't any valid use cases where an app might be interested in registering multiple instances of the callbacks?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, technically the Bluetooth Spec allows multiple instances, however, none of the applications did so.
As for the target side, one AVRCP instance can control multiple players, thus multiple instances is meaningless.
As for the controller side, we don't really need two distinct control panels on one product.
I saw both A2DP and HFP assume singleton, I choose to follow. 'const' is not used as well at these profiles.


/** @brief Disconnect AVRCP Unit Info.
*
* This function obtains information that pertains to the unit as a whole.
*
* @param avrcp The AVRCP instance.
*
* @return 0 in case of success or error code in case of error.
*/
int bt_avrcp_get_unit_info(struct bt_avrcp *avrcp);

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_ */
2 changes: 2 additions & 0 deletions include/zephyr/bluetooth/uuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -5195,6 +5195,8 @@ struct bt_uuid_128 {
#define BT_UUID_HCRP_NOTE BT_UUID_DECLARE_16(BT_UUID_HCRP_NOTE_VAL)
#define BT_UUID_AVCTP_VAL 0x0017
#define BT_UUID_AVCTP BT_UUID_DECLARE_16(BT_UUID_AVCTP_VAL)
#define BT_UUID_AVCTP_BROWSING_VAL 0x0018
#define BT_UUID_AVCTP_BROWSING BT_UUID_DECLARE_16(BT_UUID_AVCTP_BROWSING_VAL)
#define BT_UUID_AVDTP_VAL 0x0019
#define BT_UUID_AVDTP BT_UUID_DECLARE_16(BT_UUID_AVDTP_VAL)
#define BT_UUID_CMTP_VAL 0x001b
Expand Down
8 changes: 8 additions & 0 deletions subsys/bluetooth/Kconfig.logging
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,14 @@ module = BT_A2DP
module-str = "Bluetooth A2DP"
source "subsys/logging/Kconfig.template.log_config_inherit"

module = BT_AVCTP
module-str = "Bluetooth AVCTP"
source "subsys/logging/Kconfig.template.log_config_inherit"

module = BT_AVRCP
module-str = "Bluetooth AVRCP"
source "subsys/logging/Kconfig.template.log_config_inherit"

module = BT_SDP
module-str = "Bluetooth Service Discovery Protocol (SDP)"
source "subsys/logging/Kconfig.template.log_config_inherit"
Expand Down
2 changes: 2 additions & 0 deletions subsys/bluetooth/host/classic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ zephyr_library_link_libraries(subsys__bluetooth)

zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c a2dp_codec_sbc.c)
zephyr_library_sources_ifdef(CONFIG_BT_AVDTP avdtp.c)
zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c)
zephyr_library_sources_ifdef(CONFIG_BT_AVCTP avctp.c)
zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c)

zephyr_library_sources_ifdef(
Expand Down
26 changes: 26 additions & 0 deletions subsys/bluetooth/host/classic/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,32 @@ config BT_A2DP_SINK

endif # BT_A2DP

config BT_AVCTP
bool "Bluetooth AVCTP protocol support"
help
This option enables Bluetooth AVCTP support

config BT_AVRCP
bool "Bluetooth AVRCP Profile [EXPERIMENTAL]"
select BT_AVCTP
select EXPERIMENTAL
help
This option enables the AVRCP profile

if BT_AVRCP

config BT_AVRCP_TARGET
bool "Bluetooth AVRCP Profile Target Function"
help
This option enables the AVRCP profile target function

config BT_AVRCP_CONTROLLER
bool "Bluetooth AVRCP Profile Controller Function"
help
This option enables the AVRCP profile controller function

endif # BT_AVRCP

config BT_PAGE_TIMEOUT
hex "Bluetooth Page Timeout"
default 0x2000
Expand Down
221 changes: 221 additions & 0 deletions subsys/bluetooth/host/classic/avctp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/** @file
* @brief Audio Video Control Transport Protocol
*/

/*
* Copyright (c) 2015-2016 Intel Corporation
* Copyright (C) 2024 Xiaomi Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <string.h>
#include <strings.h>
#include <errno.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>

#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/classic/sdp.h>

#include "avctp_internal.h"
#include "host/hci_core.h"
#include "host/conn_internal.h"
#include "l2cap_br_internal.h"

#define LOG_LEVEL CONFIG_BT_AVCTP_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_avctp);

#define AVCTP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_avctp, br_chan.chan)

static struct bt_avctp_event_cb *event_cb;

static void avctp_l2cap_connected(struct bt_l2cap_chan *chan)
{
struct bt_avctp *session;

if (!chan) {
LOG_ERR("Invalid AVCTP chan");
return;
}

session = AVCTP_CHAN(chan);
LOG_DBG("chan %p session %p", chan, session);

if (session->ops && session->ops->connected) {
session->ops->connected(session);
}
}

static void avctp_l2cap_disconnected(struct bt_l2cap_chan *chan)
{
struct bt_avctp *session = AVCTP_CHAN(chan);

LOG_DBG("chan %p session %p", chan, session);
session->br_chan.chan.conn = NULL;

if (session->ops && session->ops->disconnected) {
session->ops->disconnected(session);
}
}

static void avctp_l2cap_encrypt_changed(struct bt_l2cap_chan *chan, uint8_t status)
{
LOG_DBG("");
}

static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct net_buf *rsp;
struct bt_avctp *session = AVCTP_CHAN(chan);
struct bt_avctp_header *hdr = (void *)buf->data;
uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr);
bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(hdr);

if (buf->len < sizeof(*hdr)) {
LOG_ERR("invalid AVCTP header received");
return -EINVAL;
}

switch (hdr->pid) {
#if defined(CONFIG_BT_AVRCP)
case sys_cpu_to_be16(BT_SDP_AV_REMOTE_SVCLASS):
break;
#endif
default:
LOG_ERR("unsupported AVCTP PID received: 0x%04x", sys_be16_to_cpu(hdr->pid));
if (cr == BT_AVCTP_CMD) {
rsp = bt_avctp_create_pdu(session, BT_AVCTP_RESPONSE,
BT_AVCTP_PKT_TYPE_SINGLE, BT_AVCTP_IPID_INVALID,
&tid, hdr->pid);
if (!rsp) {
return -ENOMEM;
}
return bt_avctp_send(session, rsp);
}
return 0; /* No need to report to the upper layer */
}

return session->ops->recv(session, buf);
}

int bt_avctp_connect(struct bt_conn *conn, struct bt_avctp *session)
{
static const struct bt_l2cap_chan_ops ops = {
.connected = avctp_l2cap_connected,
.disconnected = avctp_l2cap_disconnected,
.encrypt_change = avctp_l2cap_encrypt_changed,
.recv = avctp_l2cap_recv,
};

if (!session) {
return -EINVAL;
}

session->br_chan.rx.mtu = BT_L2CAP_RX_MTU;
session->br_chan.chan.ops = &ops;
session->br_chan.required_sec_level = BT_SECURITY_L2;

return bt_l2cap_chan_connect(conn, &session->br_chan.chan, BT_L2CAP_PSM_AVCTP);
}

int bt_avctp_disconnect(struct bt_avctp *session)
{
if (!session) {
return -EINVAL;
}

LOG_DBG("session %p", session);

return bt_l2cap_chan_disconnect(&session->br_chan.chan);
}

struct net_buf *bt_avctp_create_pdu(struct bt_avctp *session, bt_avctp_cr_t cr,
bt_avctp_pkt_type_t pkt_type, bt_avctp_ipid_t ipid,
uint8_t *tid, uint16_t pid)
{
struct net_buf *buf;
struct bt_avctp_header *hdr;

LOG_DBG("");

buf = bt_l2cap_create_pdu(NULL, 0);
if (!buf) {
LOG_ERR("No buff available");
return buf;
}

hdr = net_buf_add(buf, sizeof(*hdr));
BT_AVCTP_HDR_SET_TRANSACTION_LABLE(hdr, *tid);
BT_AVCTP_HDR_SET_PACKET_TYPE(hdr, pkt_type);
BT_AVCTP_HDR_SET_CR(hdr, cr);
BT_AVCTP_HDR_SET_IPID(hdr, ipid);
hdr->pid = pid;

if (cr == BT_AVCTP_CMD) {
*tid = (*tid + 1) & 0x0F; /* Incremented by one */
}

LOG_DBG("cr:0x%lX, tid:0x%02lX", BT_AVCTP_HDR_GET_CR(hdr),
BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr));
return buf;
}

int bt_avctp_send(struct bt_avctp *session, struct net_buf *buf)
{
int err;

err = bt_l2cap_chan_send(&session->br_chan.chan, buf);
if (err < 0) {
net_buf_unref(buf);
LOG_ERR("L2CAP send fail err = %d", err);
return err;
}

return err;
}

int bt_avctp_register(struct bt_avctp_event_cb *cb)
{
LOG_DBG("");

if (event_cb) {
return -EALREADY;
}

event_cb = cb;

return 0;
}

static int avctp_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server,
struct bt_l2cap_chan **chan)
{
/* TODO */

return -ENOTSUP;
}

int bt_avctp_init(void)
{
int err;
static struct bt_l2cap_server avctp_l2cap = {
.psm = BT_L2CAP_PSM_AVCTP,
.sec_level = BT_SECURITY_L2,
.accept = avctp_l2cap_accept,
};

LOG_DBG("");

/* Register AVCTP PSM with L2CAP */
err = bt_l2cap_br_server_register(&avctp_l2cap);
if (err < 0) {
LOG_ERR("AVCTP L2CAP registration failed %d", err);
}

return err;
}
Loading
Loading