Only audio is supported for now. Meaning you can use any bluetooth audio device. Such as Airpods or Beats!
The PS5 has no Bluetooth audio. Sony's own wireless headsets need a USB dongle. The Bluetooth chip in the console is for DualSense, not headphones.
ghost-toothAPI is a payload that plays the console's sound — game and system UI on a normal Bluetooth headset, Turn the headset off to stop.
This is v1. Same Bluetooth path is meant to take third-party controllers later, plus a PKG with an on-console pairing screen. That is not in this build.
First time
Pairing is saved. Next send reconnects that headset. TVs are never saved.
Optional:
Stop
Turn the headset off. That is the exit.
Stuck on "already linked"
Power-cycle the headset, send the ELF again. A killed payload can leave the radio thinking the headset is still connected. Do not send two copies at once.
SBC at 48 kHz if the headset allows it, otherwise 44.1. Bitpool 40, locked (not a 2–53 range — that made Beats Solo 3 sound like static).
If you close a game, the stream keeps going as silence and picks the mix back up when the next one starts. If audio drifts more than ~160 ms behind the picture, one packet is dropped.
Log is here:
Bonded headset key:
Delete
Short version: the PS5 wireless chip is two Bluetooth controllers on one USB device.
Because the kernel is still reading the same endpoints, some HCI events get stolen (ours, and some of the system's). The stack retries, ignores TVs, and keeps at most 3 ACL packets in flight on a controller that reports 7 buffers. Two copies of the payload must not run — they would split the same controller.
Audio is not a tap of the HDMI encoder. It is
A2DP is Source, SBC, no AVRCP, no HFP. Headset sees the name
Bug reports: paste the last page of
ghost-toothAPI
The PS5 has no Bluetooth audio. Sony's own wireless headsets need a USB dongle. The Bluetooth chip in the console is for DualSense, not headphones.
ghost-toothAPI is a payload that plays the console's sound — game and system UI on a normal Bluetooth headset, Turn the headset off to stop.
This is v1. Same Bluetooth path is meant to take third-party controllers later, plus a PKG with an on-console pairing screen. That is not in this build.
What you need
- Jailbroken PS5 with a payload loader (the usual ELF send on 9021)
- A Bluetooth headset that does A2DP (basically all of them)
- DualSense can stay on. That is the point.
Only tested on 5.50.
How to use it
First time
- Put the headset in pairing mode.
- Send
ghost-toothAPI.elf. - Wait for the notification
searching - put the headset in pairing mode. - It scans ~10 s, ignores TVs/soundbars, and picks headphones (name + class). Notification shows the name it is connecting to.
- When you see
audio on the headset - switch it off to stop, you are in.
Pairing is saved. Next send reconnects that headset. TVs are never saved.
Optional:
/data/ghost-toothAPI/headset.ini — leave it blank, or name=beats / address=AA:BB:CC:DD:EE:FF (address as printed in the log).Stop
Turn the headset off. That is the exit.
Stuck on "already linked"
Power-cycle the headset, send the ELF again. A killed payload can leave the radio thinking the headset is still connected. Do not send two copies at once.
Notifications are prefixed
ghost-toothAPI:. If something fails, it tells you to read the log.
What you get / what you don't
| Works | Does not |
|---|---|
| Game audio + system sounds (same mix Remote Play uses) | Mic / party chat from the headset |
| PS menu stays audible (not muted like the capture UI mix) | Headset volume / pause buttons (no AVRCP) |
| DualSense stays wireless | aptX / AAC / LDAC — SBC only |
| No kernel patch, no install | 3D audio beds — you get the stereo mix Remote Play already has |
SBC at 48 kHz if the headset allows it, otherwise 44.1. Bitpool 40, locked (not a 2–53 range — that made Beats Solo 3 sound like static).
If you close a game, the stream keeps going as silence and picks the mix back up when the next one starts. If audio drifts more than ~160 ms behind the picture, one packet is dropped.
If it breaks
Log is here:
Code:
/data/ghost-toothAPI/ghost-toothAPI.log
Bonded headset key:
Code:
/data/ghost-toothAPI/bond.key
Delete
bond.key if you want to pair a different headset (or if auth keeps failing — a bad key is deleted automatically, but you can do it by hand).| Notification | Meaning |
|---|---|
| already running | ELF is already up. Wait, or the last run crashed less than 15 seconds ago. |
| audio capture unavailable | Could not open the Remote Play capture path. Log has the error. |
| the Bluetooth controller did not answer | USB HCI setup failed. Log has the error. |
| no headphones found - pairing mode? | Scan only saw TVs/speakers. Headset needs to stay in pairing until it connects. |
| could not connect to the headset | Pair / auth / A2DP failed. Headset off, out of range, or already on a phone. |
| turn the headset off and on, then try again | Radio still has an old link. Power-cycle the headset. |
How it actually works
Short version: the PS5 wireless chip is two Bluetooth controllers on one USB device.
- Controller 1 is DualSense. We never touch it.
- Controller 0 is sitting there claimed by the same kernel driver. We open
/dev/ugen0.2and talk HCI on those pipes without detaching the driver. Detach any interface on that chip and DualSense dies.
Because the kernel is still reading the same endpoints, some HCI events get stolen (ours, and some of the system's). The stack retries, ignores TVs, and keeps at most 3 ACL packets in flight on a controller that reports 7 buffers. Two copies of the payload must not run — they would split the same controller.
Audio is not a tap of the HDMI encoder. It is
libSceAvcap2 opened the way Remote Play opens it (source 2, raw 48 kHz stereo float). That mix includes system sounds and is not muted when the PS menu is up. libSceIpmi has to be loaded first or the process dies with no signal.A2DP is Source, SBC, no AVRCP, no HFP. Headset sees the name
ghost-toothAPI.USB node:
Do not HCI Reset. Do not unbind interfaces.
Capture NIDs (resolved with
OpenAudio block is 0x38 bytes, source=2, mode=0. ReadAudio
State:
Build:
/dev/ugen0.2
Code:
Function 0 (this payload)
events 0x82, ACL in 0x81, ACL out 0x01
commands: control pipe, class-device, bRequest 0
Function 1 (system DualSense)
events 0x85, ACL in 0x84, ACL out 0x04
Do not HCI Reset. Do not unbind interfaces.
Capture NIDs (resolved with
kernel_dynlib_resolve, not dlsym by name):
Code:
svzPXluOz8U sceAvcap2Initialize
gojqghDU+1Y sceAvcap2Terminate
3nHpE7Dp5SE sceAvcap2OpenAudio
tR7gPe1i8hw sceAvcap2Close
8CGNCwBsItI sceAvcap2Start
Z5dKK0xnQ8g sceAvcap2Stop
WhsHggqdwcg sceAvcap2ReadAudio
OpenAudio block is 0x38 bytes, source=2, mode=0. ReadAudio
0x81950002 empty, 0x81950004 overrun, 0x81950008 session ended.State:
Code:
/data/ghost-toothAPI/ghost-toothAPI.log
/data/ghost-toothAPI/ghost-toothAPI.lock
/data/ghost-toothAPI/bond.key (6-byte addr + 16-byte key + 1-byte type)
/data/ghost-toothAPI/headset.ini (optional name= / address=)
Build:
ps5-payload-dev SDK, make → ghost-toothAPI.elf.What's next
- HID — third-party controllers on the same controller-0 path
- Companion PKG — pairing UI on the console, device list, not just notifications
Bug reports: paste the last page of
/data/ghost-toothAPI/ghost-toothAPI.log, headset model, and whether DualSense dropped. If DualSense dropped, say so first — that means something unbound USB, which this payload is not supposed to do.





