Repository navigation
Conversation
Qualcomm's Adaptive encoder emits an eight-byte transport header in front of each codec frame. The parser decodes it - time to play, period code, packet type, channel mode and revision marker - and selects the payload size from the observed packet-type table. It also carries the control protocol constants of the research helper in research/aptx-adaptive-qemu, which is its only consumer today. The test covers the R2, R3 and R2.2 headers, the whole payload-size table and the values that must be rejected.
Everything behind the findings in aptx-adaptive.md: the Hexagon helper that drives Qualcomm's CAPI encoder module, its control channel, the btmon and air-analysis tools, the ABI 17 patch for the clean-room encoder, and the chronological handoff and status logs. The helper is a research artifact. It needs the vendor QDSP6 encoder modules and a Hexagon emulator at run time, so nothing here is built by default.
aptx-adaptive.md collects what a non-Qualcomm Linux host can do today: the clean-room encoder in the aptx slot (427 kbps HQ with ABR, 270 kbps low latency), the CAPI bridge and the container-header rule that decides whether audio comes out at all, the transport facts both paths depend on, and the open problems - aptX Lossless stays out of reach without the AudioReach feedback container.
The encoder implements 44.1, 48 and 96 kHz, but the rate is negotiated rather than configured: PipeWire offers one preferred rate through bluez5.default.rate and the encoder intersects it with the rates the peer advertises, falling back to 48 kHz. Record the measured forms for 48 kHz (435 kbps, 12 ms) and 96 kHz (616-646 kbps, 8 ms, HQ only), and the reason 44.1 kHz does not survive with this headset: the MOMENTUM 5 encodes 44.1 kHz as 0x40 in its rate byte while the encoder only recognises 0x08 for it, so a 44100 request falls back to 48000. The evidence is the peer's capability element; the bit is marked UNCERTAIN in the public sources.
Limitation found while testing sample rates: 44.1 kHz does not surviveFollowing up on this PR's documentation with a limitation that belongs in the What works. The encoder implements 44 100, 48 000 and 96 000 Hz, and the
Why 44.1 kHz fails. The sink advertises its sample rates in a single byte
Where a fix belongs. In the encoder's rate table (accept If it would be more useful there, I am happy to move this report to an issue on |
…dset limit The rate bit is an encoding mismatch rather than an unsupported rate: the sink sends 0x40 for 44.1 kHz while the encoder only knows 0x08, and the encoder also excludes 44.1 kHz on a 2.2 sink because that is the reference's Lossless configuration. Accepting both encodings and letting the 2.2 branch offer the rate makes the link negotiate 44.1 kHz: the outgoing element carries 0x42 and the stream ran 35.8 s at 11.97 ms, 668-byte packets and 443 kbps. Audible confirmation is still pending, so the section records what was measured on the wire.
Correction and follow-up: 44.1 kHz is reachable with a two-line encoder fixMy previous comment called 44.1 kHz unreachable with this headset. That was What was wrong. Two independent things kept 44.1 kHz out of the
What was measured with both changed (local patch on a30bbd8, nothing
Status. The result above is measured on the wire. Audible confirmation of |
Playback at 44.1 kHz was confirmed audibly, so the rate is usable rather than merely negotiated. The change lives in a fork of the encoder because upstream is read-only, and switching rates restarts the monitor, which briefly interrupts audio.
Confirmed by ear, and where the fix livesShort closure on the 44.1 kHz thread: the configuration with the rate-bit fix The two-line change is not being sent upstream: theacrat/aptx-linux is read-only All three rates are now reachable from the host configuration and switchable at
|
What this adds
Three commits on top of
master:include/aptxadaptive.h,src/aptx-adaptive-stream.c,test/- a parserfor the eight-byte aptX Adaptive transport header (time to play, period code,
packet type, channel mode, revision marker) plus the observed packet-type to
payload-size table. The test runs in Debug and Release, and it covers the R2,
R3 and R2.2 headers, the whole table and the values that must be rejected.
research/aptx-adaptive-qemu/- the record behind the results: theHexagon helper that drives Qualcomm's CAPI encoder module through a small
control protocol, the measurement tools (btmon and air analysis, a link
preflight, a stream checker, an offline helper probe), the SPA codec ABI 17
patch for the clean-room encoder, and the chronological handoff and status
logs including every negative result. Nothing here is built by default; the
helper needs vendor QDSP6 modules and a Hexagon emulator at run time.
aptx-adaptive.md, linked from the README - what a non-Qualcomm Linuxhost can do with an Adaptive sink today, the two host paths, the transport
rules they depend on, and the open problems.
Why
Headsets negotiate aptX Adaptive, and a Linux host has had no usable source for
it. This PR ships what was actually established on the hardware: a working
source configuration, the transport facts it depends on, and the negative
results, so the next person does not have to repeat the search.
The finding that decides whether audio comes out
The eight-byte Qualcomm container header must not be part of the A2DP
payload. With it, the link stalls one to two seconds in and the sink stops
acknowledging; without it the same encoder plays for minutes. Measured on a
Sennheiser MOMENTUM 5 (R2.2 sink):
Two host paths work:
aptxslot SPA codec pluginqemu-hexagonThe clean-room encoder is the recommended one. Its author has published it for
reuse, and
aptx-adaptive.mdcarries the whole recipe including the ABI 17patch needed for PipeWire 1.7.
How it was tested
CFLAGSandCXXFLAGSset to-Wall -Wextra -Werror -Wshadow: Debug and Release against both FFmpeg andlibfreeaptx. All four configurations build without a single warning and
ctestpasses both tests in each one.markdownlintover the tree: no findings.reuse lint: compliant(112/112 files).
captures and the encoder's own counters; the numbers are in the document and
in
STATUS.md.What does not work: aptX Lossless
The sink supports it - a FiiO BT11 reports
aptX Adaptive mode 19 (aptX Lossless)while connected to the same headset - but a non-Qualcomm host cannotproduce a stream it plays:
the AudioReach feedback loop, which a standalone container does not have. What
it emits instead (packet type 5, channel byte
0xa0, 760-byte frames at50 ms, about 121 kbps) is rejected by the sink after 1.9 s, and Qualcomm's own
reference toolchain does not accept it as a stream either.
1, 15 ms).
SetBitRate,SetCompression,SetExtendedPacketand everyprofile its init accepts leave the header unchanged, and the output
descriptor's
endalways equals the buffer limit, so the encoded lengthcannot be read back.
container-header finding, but they contain no R3 air decoder. The R3 tool is
a SlimBus converter and rejects OTA framing.
available here: the BT11 exposes USB HID and UAC1.0 only, with no HCI and no
stream read-back, and its host firmware partitions are not readable.
Unblocking it needs one of: a captured Lossless stream, a way into the vendor
host firmware, or an implementation of the AudioReach feedback container.
Relationship to #16
#16 is the earlier and longer-running record of this investigation from the same
branch. This PR is that material rebased onto current
master, plus what cameafter it (the container-header fix, the clean-room encoder, the Lossless wall),
with the lint and
-Werrorissues fixed. Happy to supersede #16 with this one,or to drop the
research/part entirely if you would rather take only theparser and the documentation.