feat: A2DP bluetooth audio working

This commit is contained in:
2026-06-10 16:00:42 +02:00
parent dff5a4a4b1
commit ca79678823
18 changed files with 1312 additions and 4340 deletions
+230
View File
@@ -0,0 +1,230 @@
/*
* Avrcp.cpp
* Bluetooth AVRCP -- minimal Target role over AVCTP (PSM 0x0017)
* Copyright (c) 2026 Daniel Hammer
*/
#include "Avrcp.hpp"
#include "A2dp.hpp"
#include "L2cap.hpp"
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp>
using namespace Kt;
namespace Drivers::USB::Bluetooth::Avrcp {
// =========================================================================
// Constants
// =========================================================================
// AV/C command types (commands) / response codes (responses)
constexpr uint8_t AVC_CTYPE_CONTROL = 0x0;
constexpr uint8_t AVC_CTYPE_STATUS = 0x1;
constexpr uint8_t AVC_CTYPE_NOTIFY = 0x3;
constexpr uint8_t AVC_RSP_NOT_IMPL = 0x8;
constexpr uint8_t AVC_RSP_ACCEPTED = 0x9;
constexpr uint8_t AVC_RSP_REJECTED = 0xA;
constexpr uint8_t AVC_RSP_STABLE = 0xC;
constexpr uint8_t AVC_RSP_INTERIM = 0xF;
// AV/C opcodes
constexpr uint8_t AVC_OP_VENDOR = 0x00;
constexpr uint8_t AVC_OP_UNIT_INFO = 0x30;
constexpr uint8_t AVC_OP_SUBUNIT_INFO = 0x31;
constexpr uint8_t AVC_OP_PASSTHROUGH = 0x7C;
// AVRCP vendor-dependent PDUs (BT SIG company id 00 19 58)
constexpr uint8_t PDU_GET_CAPABILITIES = 0x10;
constexpr uint8_t PDU_GET_PLAY_STATUS = 0x30;
constexpr uint8_t PDU_REGISTER_NOTIFY = 0x31;
constexpr uint8_t PDU_SET_ABS_VOLUME = 0x50;
// AVRCP notification events
constexpr uint8_t EVT_PLAYBACK_STATUS = 0x01;
constexpr uint8_t EVT_TRACK_CHANGED = 0x02;
constexpr uint8_t EVT_VOLUME_CHANGED = 0x0D;
// =========================================================================
// Send helpers
// =========================================================================
// AVCTP single-packet response: byte0 = transaction<<4 | pktType<<2 |
// C/R(1=response)<<1 | IPID; bytes 1-2 = PID big-endian.
static void SendAvctp(uint16_t cid, uint8_t transaction, uint16_t pid,
bool invalidPid, const uint8_t* avc, uint16_t avcLen) {
uint8_t buf[96] = {};
if (3u + avcLen > sizeof(buf)) return;
buf[0] = (uint8_t)((transaction << 4) | 0x02 | (invalidPid ? 0x01 : 0));
buf[1] = (uint8_t)(pid >> 8);
buf[2] = (uint8_t)(pid & 0xFF);
if (avc && avcLen) memcpy(&buf[3], avc, avcLen);
L2cap::SendData(cid, buf, (uint16_t)(3 + avcLen));
}
// AVRCP vendor-dependent response frame:
// [rsp][subunit 0x48 panel][opcode 0x00][company 00 19 58][pdu][pkt 0][len BE][params]
static void SendVendorRsp(uint16_t cid, uint8_t transaction, uint8_t rspCode,
uint8_t pdu, const uint8_t* p, uint16_t n) {
uint8_t avc[64] = {};
if (10u + n > sizeof(avc)) return;
avc[0] = rspCode;
avc[1] = 0x48; // PANEL subunit
avc[2] = AVC_OP_VENDOR;
avc[3] = 0x00; avc[4] = 0x19; avc[5] = 0x58; // BT SIG company id
avc[6] = pdu;
avc[7] = 0x00; // packet type: single
avc[8] = (uint8_t)(n >> 8);
avc[9] = (uint8_t)(n & 0xFF);
if (p && n) memcpy(&avc[10], p, n);
SendAvctp(cid, transaction, 0x110E, false, avc, (uint16_t)(10 + n));
}
// =========================================================================
// ProcessAvctp
// =========================================================================
void ProcessAvctp(uint16_t localCid, const uint8_t* d, uint16_t len) {
if (len < 3) return;
uint8_t transaction = (d[0] >> 4) & 0x0F;
uint8_t pktType = (d[0] >> 2) & 0x03;
bool isCommand = ((d[0] >> 1) & 0x01) == 0;
uint16_t pid = ((uint16_t)d[1] << 8) | d[2];
if (!isCommand) return; // a response to something we sent
if (pktType != 0) return; // only single-packet AVCTP supported
if (pid != 0x110E) {
// Unsupported PID: respond with the IPID bit set, no message body.
SendAvctp(localCid, transaction, pid, true, nullptr, 0);
return;
}
const uint8_t* avc = d + 3;
uint16_t alen = (uint16_t)(len - 3);
if (alen < 3) return;
uint8_t ctype = avc[0] & 0x0F;
uint8_t opcode = avc[2];
switch (opcode) {
case AVC_OP_UNIT_INFO: {
// Fixed-format reply: PANEL subunit, BT SIG company id.
uint8_t rsp[8] = {AVC_RSP_STABLE, 0xFF, AVC_OP_UNIT_INFO,
0x07, 0x48, 0x00, 0x19, 0x58};
SendAvctp(localCid, transaction, pid, false, rsp, sizeof(rsp));
break;
}
case AVC_OP_SUBUNIT_INFO: {
uint8_t rsp[8] = {AVC_RSP_STABLE, 0xFF, AVC_OP_SUBUNIT_INFO,
0x07, 0x48, 0xFF, 0xFF, 0xFF};
SendAvctp(localCid, transaction, pid, false, rsp, sizeof(rsp));
break;
}
case AVC_OP_PASSTHROUGH: {
// Accept every pass-through (play/pause/etc.). Echo the frame
// with the response code swapped in. Wiring the operation ids
// to the Music app is a later feature; ACCEPTED keeps the
// controller-side state machine happy.
uint8_t rsp[16] = {};
uint16_t cp = (alen > sizeof(rsp)) ? (uint16_t)sizeof(rsp) : alen;
memcpy(rsp, avc, cp);
rsp[0] = AVC_RSP_ACCEPTED;
SendAvctp(localCid, transaction, pid, false, rsp, cp);
if (alen >= 4) {
KernelLogStream(INFO, "BT-AVRCP") << "pass-through op="
<< base::hex << (uint64_t)(avc[3] & 0x7F)
<< ((avc[3] & 0x80) ? " release" : " press") << base::dec;
}
break;
}
case AVC_OP_VENDOR: {
// [ctype][subunit][00][company 3][pdu][pkt][len BE][params]
if (alen < 10) break;
if (avc[3] != 0x00 || avc[4] != 0x19 || avc[5] != 0x58) {
uint8_t rsp[8] = {};
memcpy(rsp, avc, 8);
rsp[0] = AVC_RSP_NOT_IMPL;
SendAvctp(localCid, transaction, pid, false, rsp, 8);
break;
}
uint8_t pdu = avc[6];
uint16_t plen = ((uint16_t)avc[8] << 8) | avc[9];
const uint8_t* p = &avc[10];
if (10u + plen > alen) plen = (uint16_t)(alen - 10);
if (pdu == PDU_GET_CAPABILITIES && ctype == AVC_CTYPE_STATUS && plen >= 1) {
if (p[0] == 0x02) { // CompanyID list
uint8_t rp[5] = {0x02, 0x01, 0x00, 0x19, 0x58};
SendVendorRsp(localCid, transaction, AVC_RSP_STABLE,
pdu, rp, sizeof(rp));
} else if (p[0] == 0x03) { // EventsSupported
uint8_t rp[5] = {0x03, 0x03, EVT_PLAYBACK_STATUS,
EVT_TRACK_CHANGED, EVT_VOLUME_CHANGED};
SendVendorRsp(localCid, transaction, AVC_RSP_STABLE,
pdu, rp, sizeof(rp));
} else {
uint8_t err = 0x01; // invalid parameter
SendVendorRsp(localCid, transaction, AVC_RSP_REJECTED,
pdu, &err, 1);
}
} else if (pdu == PDU_GET_PLAY_STATUS && ctype == AVC_CTYPE_STATUS) {
// song length / position unknown (0xFFFFFFFF), status by state
uint8_t rp[9] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
(uint8_t)(A2dp::IsStreaming() ? 0x01 : 0x00)};
SendVendorRsp(localCid, transaction, AVC_RSP_STABLE,
pdu, rp, sizeof(rp));
} else if (pdu == PDU_REGISTER_NOTIFY && ctype == AVC_CTYPE_NOTIFY && plen >= 1) {
// INTERIM response with the current value. (A CHANGED
// follow-up on actual change is a later feature.)
if (p[0] == EVT_VOLUME_CHANGED) {
uint8_t rp[2] = {EVT_VOLUME_CHANGED,
(uint8_t)((A2dp::GetVolume() * 127) / 100)};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
} else if (p[0] == EVT_PLAYBACK_STATUS) {
uint8_t rp[2] = {EVT_PLAYBACK_STATUS,
(uint8_t)(A2dp::IsStreaming() ? 0x01 : 0x00)};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
} else if (p[0] == EVT_TRACK_CHANGED) {
uint8_t rp[9] = {EVT_TRACK_CHANGED,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
} else {
uint8_t err = 0x01;
SendVendorRsp(localCid, transaction, AVC_RSP_REJECTED,
pdu, &err, 1);
}
} else if (pdu == PDU_SET_ABS_VOLUME && ctype == AVC_CTYPE_CONTROL && plen >= 1) {
uint8_t vol = p[0] & 0x7F;
A2dp::SetVolume(((int)vol * 100) / 127);
SendVendorRsp(localCid, transaction, AVC_RSP_ACCEPTED,
pdu, &vol, 1);
KernelLogStream(INFO, "BT-AVRCP") << "absolute volume -> "
<< (uint64_t)(((int)vol * 100) / 127) << "%";
} else {
uint8_t err = 0x00; // invalid command
SendVendorRsp(localCid, transaction, AVC_RSP_REJECTED,
pdu, &err, 1);
}
break;
}
default: {
uint8_t rsp[8] = {};
uint16_t cp = (alen > sizeof(rsp)) ? (uint16_t)sizeof(rsp) : alen;
memcpy(rsp, avc, cp);
rsp[0] = AVC_RSP_NOT_IMPL;
SendAvctp(localCid, transaction, pid, false, rsp, cp);
break;
}
}
}
}