diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 8100247..4ecbc75 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 12 +#define MONTAUK_BUILD_NUMBER 13 diff --git a/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp b/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp index 8acda61..8879b73 100644 --- a/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/A2dp.cpp @@ -1387,6 +1387,21 @@ namespace Drivers::USB::Bluetooth::A2dp { uint32_t bytesPerFrame = samplesPerFrame * g_sbcEncoder.Channels * 2; int16_t framePcm[512]; + // Bundle as many SBC frames as fit in the media channel's MTU into + // each RTP packet. One frame per packet means 375 packets/s at + // 48 kHz -- each costing a 2-DH3 + ACK (~2.5 ms) on air, ~94% of the + // radio's airtime. With zero headroom, any retransmission burst, + // WiFi coexistence window (combo chip), or multipoint service to the + // phone delays frames past the sink's deadline -> concealment static + // and dropouts. Bundling to the MTU (5 frames at bitpool 53) cuts + // this to ~75 packets/s, ~28% airtime, like every stock stack does. + uint16_t maxPayload = 672; // default L2CAP MTU; never exceed buffer + if (auto* mch = L2cap::GetChannel(g_mediaCid)) { + if (mch->RemoteMtu >= 48 && mch->RemoteMtu < maxPayload) { + maxPayload = mch->RemoteMtu; + } + } + while (bytesPerFrame <= sizeof(framePcm) && g_pcmRate != 0) { if (g_state != State::Streaming) break; // torn down mid-pump @@ -1426,25 +1441,10 @@ namespace Drivers::USB::Bluetooth::A2dp { elapsed = audioMs; } - // Pull one frame from the ring (may wrap) and apply volume. - uint32_t tail = g_ringTail.load(std::memory_order_relaxed); - uint32_t idx = tail & (PCM_RING_SIZE - 1); - uint32_t firstPart = PCM_RING_SIZE - idx; - if (firstPart > bytesPerFrame) firstPart = bytesPerFrame; - memcpy(framePcm, &g_pcmRing[idx], firstPart); - memcpy((uint8_t*)framePcm + firstPart, &g_pcmRing[0], - bytesPerFrame - firstPart); - g_ringTail.store(tail + bytesPerFrame, std::memory_order_release); - - uint32_t numSamples = samplesPerFrame * g_sbcEncoder.Channels; - for (uint32_t i = 0; i < numSamples; i++) { - framePcm[i] = (int16_t)(((int32_t)framePcm[i] * g_volume) / 100); - } - // Build media packet: RTP-like header (12 bytes) + optional SCMS-T // content-protection header (1 byte) + SBC payload header (1 byte) - // + SBC frame - uint8_t mediaPkt[256] = {}; + // + up to 15 SBC frames, MTU permitting + uint8_t mediaPkt[768] = {}; mediaPkt[0] = 0x80; // V=2, P=0, X=0, CC=0 mediaPkt[1] = 0x60; // M=0, PT=96 mediaPkt[2] = (uint8_t)(g_seqNum >> 8); @@ -1460,26 +1460,61 @@ namespace Drivers::USB::Bluetooth::A2dp { // and the SBC payload header (0x00 = copy permitted, as BlueZ). uint32_t hdr = 12; if (g_sinkContentProtection) mediaPkt[hdr++] = 0x00; - mediaPkt[hdr++] = 1; // SBC payload header: number of SBC frames + uint32_t sbcHdrPos = hdr++; // SBC payload header: frame count - uint32_t encodedSize = Sbc::Encode(&g_sbcEncoder, framePcm, &mediaPkt[hdr]); - L2cap::SendData(g_mediaCid, mediaPkt, (uint16_t)(hdr + encodedSize)); + // Pull frames from the ring (may wrap), apply volume, encode. + // Frame size is constant for a fixed config; the size of the + // first encode bounds whether the next one still fits. + uint32_t off = hdr; + uint32_t nFrames = 0; + uint32_t frameLen = 0; + while (nFrames < 15) { + if (frameLen != 0 && off + frameLen > maxPayload) break; + uint32_t avail = g_ringHead.load(std::memory_order_acquire) + - g_ringTail.load(std::memory_order_relaxed); + if (avail < bytesPerFrame) break; + + uint32_t tail = g_ringTail.load(std::memory_order_relaxed); + uint32_t idx = tail & (PCM_RING_SIZE - 1); + uint32_t firstPart = PCM_RING_SIZE - idx; + if (firstPart > bytesPerFrame) firstPart = bytesPerFrame; + memcpy(framePcm, &g_pcmRing[idx], firstPart); + memcpy((uint8_t*)framePcm + firstPart, &g_pcmRing[0], + bytesPerFrame - firstPart); + g_ringTail.store(tail + bytesPerFrame, std::memory_order_release); + + uint32_t numSamples = samplesPerFrame * g_sbcEncoder.Channels; + for (uint32_t i = 0; i < numSamples; i++) { + framePcm[i] = (int16_t)(((int32_t)framePcm[i] * g_volume) / 100); + } + + frameLen = Sbc::Encode(&g_sbcEncoder, framePcm, &mediaPkt[off]); + off += frameLen; + nFrames++; + } + if (nFrames == 0) break; + mediaPkt[sbcHdrPos] = (uint8_t)nFrames; + + L2cap::SendData(g_mediaCid, mediaPkt, (uint16_t)off); g_seqNum++; - g_timestamp += samplesPerFrame; - g_sentSamples += samplesPerFrame; + g_timestamp += samplesPerFrame * nFrames; + g_sentSamples += samplesPerFrame * nFrames; g_lastSendMs = now; g_inUnderrun = false; - // Bring-up diagnostic: heartbeat every 2048 frames (~5.5 s of - // audio) proving the pipeline is healthy. - if ((g_seqNum & 0x7FF) == 0) { + // Bring-up diagnostic: heartbeat every 512 packets (~6.8 s of + // audio at 5 frames/packet) proving the pipeline is healthy. + if ((g_seqNum & 0x1FF) == 0) { + uint32_t left = g_ringHead.load(std::memory_order_acquire) + - g_ringTail.load(std::memory_order_relaxed); uint32_t ringMs = (g_pcmRate && g_sbcEncoder.Channels) - ? (uint32_t)((uint64_t)(fill - bytesPerFrame) * 1000 + ? (uint32_t)((uint64_t)left * 1000 / ((uint64_t)g_pcmRate * g_sbcEncoder.Channels * 2)) : 0; KernelLogStream(INFO, "BT-A2DP") << "media: seq=" << (uint64_t)g_seqNum + << " bundle=" << (uint64_t)nFrames << " ring=" << (uint64_t)ringMs << "ms" << " lead=" << (uint64_t)(audioMs > elapsed ? audioMs - elapsed : 0) << "ms" << " inflight=" << (uint64_t)Hci::AclTxInFlight() diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp index d8c074a..b19e6f4 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,13 @@ namespace Drivers::USB::Bluetooth::Hci { static uint8_t* g_aclTxRing[ACL_TX_SLOTS] = {}; static uint64_t g_aclTxRingPhys[ACL_TX_SLOTS] = {}; static uint8_t g_aclTxSlot = 0; + // Serializes the whole send (slot grab + packet build + xHCI bulk OUT + // enqueue). PumpMedia and DrainEvents have SEPARATE single-instance + // guards, so one core can be sending a media packet while another sends a + // signaling reply; without this lock the two race on g_aclTxSlot and on + // the bulk OUT TRB ring's enqueue/cycle state, garbling packets on air. + // IRQ-disabling spinlock so the holder also can't be preempted mid-send. + static kcp::Spinlock g_aclTxLock; // HCI command DMA buffer (separate from ACL to avoid conflicts) static uint8_t* g_cmdDmaBuf = nullptr; @@ -549,6 +557,17 @@ namespace Drivers::USB::Bluetooth::Hci { if (!g_initialized || !g_aclTxRing[0]) return false; if (len + sizeof(AclHeader) > 4096) return false; // Single page DMA buffer + g_aclTxLock.Acquire(); + + // Every slot still owned by the xHCI: reusing one would let this + // packet's memcpy race the in-flight DMA. Dropping is recoverable + // (L2CAP peers retransmit signaling; media just skips a frame), + // corruption on air is not. + if (AclTxInFlight() >= (uint32_t)ACL_TX_SLOTS) { + g_aclTxLock.Release(); + return false; + } + // Use the next TX ring slot so a rapid second send can't overwrite this // packet before its bulk OUT transfer DMAs it to the wire. uint8_t* txBuf = g_aclTxRing[g_aclTxSlot]; @@ -569,6 +588,7 @@ namespace Drivers::USB::Bluetooth::Hci { g_aclTxCount.fetch_add(1, std::memory_order_relaxed); Xhci::QueueBulkOutTransfer(g_slotId, txBuf, txPhys, totalLen); + g_aclTxLock.Release(); return true; }