Files
MontaukOS/kernel/src/Drivers/USB/Bluetooth/A2dp.hpp
T
danielandClaude Fable 5 928fe0bbed fix: make BT A2DP output exclusive to one process
The A2DP output is a single unmixed PCM stream. A second process
opening audio while a stream was active would reconfigure the SBC
encoder and media clock under the owner and interleave both apps' PCM
into one ring, garbling playback (e.g. launching DOOM destabilized
Music). Add ClaimOutput/ReleaseOutput pid ownership: the first opener
gets the BT sink, later openers fall back to the HDA mixer, and only
the owner can tear the stream down. The scheduler releases ownership
on process exit so a killed app cannot leak the claim.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 12:41:17 +02:00

95 lines
3.7 KiB
C++

/*
* A2dp.hpp
* Bluetooth A2DP (Advanced Audio Distribution Profile)
* AVDTP signaling and SBC audio streaming
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::USB::Bluetooth::A2dp {
// =========================================================================
// A2DP stream states
// =========================================================================
enum class State {
Idle, // No A2DP connection
Discovering, // AVDTP discover in progress
Configured, // Stream endpoint configured
Open, // Stream open, ready for audio
Streaming // Actively streaming audio
};
// =========================================================================
// Public API
// =========================================================================
// Drive the A2DP source role after an ACL link is up: open the AVDTP
// signaling channel, negotiate an SBC stream, and open the media transport
// channel. Must be called from process context (it blocks on the AVDTP
// handshakes), NOT from an event/interrupt path. Leaves the stream Open.
bool StartSource(uint32_t timeoutMs = 5000);
// Called by L2CAP when an AVDTP channel becomes ready
void OnChannelReady(uint16_t l2capCid);
// Process an AVDTP signaling packet
void ProcessAvdtp(const uint8_t* data, uint16_t len);
// Process an SDP packet (on any SDP L2CAP channel). Dispatches by PDU:
// requests (the headset querying OUR services) are answered by the minimal
// SDP server; responses on our outgoing client channel complete DoSdpQuery.
void ProcessSdp(uint16_t localCid, const uint8_t* data, uint16_t len);
// Configure a stream for the given PCM parameters
bool ConfigureStream(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample);
// Start streaming
bool StartStream();
// Stop streaming. flushQueued drops any PCM still queued in the media
// ring: pass true when closing the stream (track change / app exit),
// false for a pause so resume continues gaplessly.
bool StopStream(bool flushQueued = true);
// Write PCM audio data to the Bluetooth audio stream. Copies into the
// media ring and returns immediately (never blocks); returns the number
// of bytes accepted (0 = ring full, retry later).
int WriteAudio(const uint8_t* pcmData, uint32_t pcmLen);
// The A2DP output is a single unmixed PCM stream, so at most one process
// may own the Bluetooth audio handle at a time. A second opener sharing
// it would reconfigure the SBC encoder and media clock under the first
// stream and interleave its raw PCM into the same ring (audible garble
// and dropouts), and its close would suspend the owner's stream.
//
// ClaimOutput returns true if `pid` now owns the output; false if it is
// already owned (the caller should fall back to the HDA mixer).
// ReleaseOutput stops the stream (dropping queued PCM) and frees the
// output when `pid` is the current owner; no-op otherwise. The
// scheduler also calls it on process exit so a killed app cannot leak
// ownership.
bool ClaimOutput(int pid);
void ReleaseOutput(int pid);
// Encode + send queued PCM, paced to the audio clock and gated on ACL TX
// readiness. Called from the idle-loop event pump and from WriteAudio;
// self-serializing, cheap no-op when not streaming.
void PumpMedia();
// Get current state
State GetState();
// Check if currently streaming
bool IsStreaming();
// Get volume (0-100)
int GetVolume();
// Set volume (0-100)
void SetVolume(int percent);
}