Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,200 changes: 737 additions & 463 deletions apps/auracast.py

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions apps/controller_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
HCI_READ_BUFFER_SIZE_COMMAND,
HCI_READ_LOCAL_NAME_COMMAND,
HCI_SUCCESS,
HCI_VERSION_NAMES,
LMP_VERSION_NAMES,
CodecID,
HCI_Command,
HCI_Command_Complete_Event,
Expand All @@ -54,6 +52,7 @@
HCI_Read_Local_Supported_Codecs_V2_Command,
HCI_Read_Local_Version_Information_Command,
LeFeature,
SpecificationVersion,
map_null_terminated_utf8_string,
)
from bumble.host import Host
Expand Down Expand Up @@ -289,14 +288,20 @@ async def async_main(
)
print(
color(' HCI Version: ', 'green'),
name_or_number(HCI_VERSION_NAMES, host.local_version.hci_version),
SpecificationVersion(host.local_version.hci_version).name,
)
print(
color(' HCI Subversion:', 'green'),
f'0x{host.local_version.hci_subversion:04x}',
)
print(color(' HCI Subversion:', 'green'), host.local_version.hci_subversion)
print(
color(' LMP Version: ', 'green'),
name_or_number(LMP_VERSION_NAMES, host.local_version.lmp_version),
SpecificationVersion(host.local_version.lmp_version).name,
)
print(
color(' LMP Subversion:', 'green'),
f'0x{host.local_version.lmp_subversion:04x}',
)
print(color(' LMP Subversion:', 'green'), host.local_version.lmp_subversion)

# Get the Classic info
await get_classic_info(host)
Expand Down
5 changes: 3 additions & 2 deletions bumble/audio/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,5 +546,6 @@ def _read(self, frame_size: int) -> bytes:
return bytes(pcm_buffer)

def _close(self):
self._stream.stop()
self._stream = None
if self._stream:
self._stream.stop()
self._stream = None
4 changes: 2 additions & 2 deletions bumble/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,8 +864,8 @@ class State(Enum):

EVENT_STATE_CHANGE = "state_change"
EVENT_ESTABLISHMENT = "establishment"
EVENT_ESTABLISHMENT_ERROR = "establishment_error"
EVENT_CANCELLATION = "cancellation"
EVENT_ERROR = "error"
EVENT_LOSS = "loss"
EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
Expand Down Expand Up @@ -998,7 +998,7 @@ def on_establishment(
return

self.state = self.State.ERROR
self.emit(self.EVENT_ERROR)
self.emit(self.EVENT_ESTABLISHMENT_ERROR)

def on_loss(self):
self.state = self.State.LOST
Expand Down
57 changes: 40 additions & 17 deletions bumble/hci.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,22 +207,44 @@ def metadata(

HCI_VENDOR_OGF = 0x3F

# HCI Version
HCI_VERSION_BLUETOOTH_CORE_1_0B = 0
HCI_VERSION_BLUETOOTH_CORE_1_1 = 1
HCI_VERSION_BLUETOOTH_CORE_1_2 = 2
HCI_VERSION_BLUETOOTH_CORE_2_0_EDR = 3
HCI_VERSION_BLUETOOTH_CORE_2_1_EDR = 4
HCI_VERSION_BLUETOOTH_CORE_3_0_HS = 5
HCI_VERSION_BLUETOOTH_CORE_4_0 = 6
HCI_VERSION_BLUETOOTH_CORE_4_1 = 7
HCI_VERSION_BLUETOOTH_CORE_4_2 = 8
HCI_VERSION_BLUETOOTH_CORE_5_0 = 9
HCI_VERSION_BLUETOOTH_CORE_5_1 = 10
HCI_VERSION_BLUETOOTH_CORE_5_2 = 11
HCI_VERSION_BLUETOOTH_CORE_5_3 = 12
HCI_VERSION_BLUETOOTH_CORE_5_4 = 13
HCI_VERSION_BLUETOOTH_CORE_6_0 = 14
# Specification Version
class SpecificationVersion(utils.OpenIntEnum):
BLUETOOTH_CORE_1_0B = 0
BLUETOOTH_CORE_1_1 = 1
BLUETOOTH_CORE_1_2 = 2
BLUETOOTH_CORE_2_0_EDR = 3
BLUETOOTH_CORE_2_1_EDR = 4
BLUETOOTH_CORE_3_0_HS = 5
BLUETOOTH_CORE_4_0 = 6
BLUETOOTH_CORE_4_1 = 7
BLUETOOTH_CORE_4_2 = 8
BLUETOOTH_CORE_5_0 = 9
BLUETOOTH_CORE_5_1 = 10
BLUETOOTH_CORE_5_2 = 11
BLUETOOTH_CORE_5_3 = 12
BLUETOOTH_CORE_5_4 = 13
BLUETOOTH_CORE_6_0 = 14
BLUETOOTH_CORE_6_1 = 15
BLUETOOTH_CORE_6_2 = 16

# For backwards compatibility only
HCI_VERSION_BLUETOOTH_CORE_1_0B = SpecificationVersion.BLUETOOTH_CORE_1_0B
HCI_VERSION_BLUETOOTH_CORE_1_1 = SpecificationVersion.BLUETOOTH_CORE_1_1
HCI_VERSION_BLUETOOTH_CORE_1_2 = SpecificationVersion.BLUETOOTH_CORE_1_2
HCI_VERSION_BLUETOOTH_CORE_2_0_EDR = SpecificationVersion.BLUETOOTH_CORE_2_0_EDR
HCI_VERSION_BLUETOOTH_CORE_2_1_EDR = SpecificationVersion.BLUETOOTH_CORE_2_1_EDR
HCI_VERSION_BLUETOOTH_CORE_3_0_HS = SpecificationVersion.BLUETOOTH_CORE_3_0_HS
HCI_VERSION_BLUETOOTH_CORE_4_0 = SpecificationVersion.BLUETOOTH_CORE_4_0
HCI_VERSION_BLUETOOTH_CORE_4_1 = SpecificationVersion.BLUETOOTH_CORE_4_1
HCI_VERSION_BLUETOOTH_CORE_4_2 = SpecificationVersion.BLUETOOTH_CORE_4_2
HCI_VERSION_BLUETOOTH_CORE_5_0 = SpecificationVersion.BLUETOOTH_CORE_5_0
HCI_VERSION_BLUETOOTH_CORE_5_1 = SpecificationVersion.BLUETOOTH_CORE_5_1
HCI_VERSION_BLUETOOTH_CORE_5_2 = SpecificationVersion.BLUETOOTH_CORE_5_2
HCI_VERSION_BLUETOOTH_CORE_5_3 = SpecificationVersion.BLUETOOTH_CORE_5_3
HCI_VERSION_BLUETOOTH_CORE_5_4 = SpecificationVersion.BLUETOOTH_CORE_5_4
HCI_VERSION_BLUETOOTH_CORE_6_0 = SpecificationVersion.BLUETOOTH_CORE_6_0
HCI_VERSION_BLUETOOTH_CORE_6_1 = SpecificationVersion.BLUETOOTH_CORE_6_1
HCI_VERSION_BLUETOOTH_CORE_6_2 = SpecificationVersion.BLUETOOTH_CORE_6_2

HCI_VERSION_NAMES = {
HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
Expand All @@ -240,9 +262,10 @@ def metadata(
HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3',
HCI_VERSION_BLUETOOTH_CORE_5_4: 'HCI_VERSION_BLUETOOTH_CORE_5_4',
HCI_VERSION_BLUETOOTH_CORE_6_0: 'HCI_VERSION_BLUETOOTH_CORE_6_0',
HCI_VERSION_BLUETOOTH_CORE_6_1: 'HCI_VERSION_BLUETOOTH_CORE_6_1',
HCI_VERSION_BLUETOOTH_CORE_6_2: 'HCI_VERSION_BLUETOOTH_CORE_6_2',
}

# LMP Version
LMP_VERSION_NAMES = HCI_VERSION_NAMES

# HCI Packet types
Expand Down
7 changes: 6 additions & 1 deletion bumble/profiles/bass.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,12 @@ def __init__(self):
b"12", # TEST
)

super().__init__([self.battery_level_characteristic])
super().__init__(
[
self.broadcast_audio_scan_control_point_characteristic,
self.broadcast_receive_state_characteristic,
]
)

def on_broadcast_audio_scan_control_point_write(
self, connection: device.Connection, value: bytes
Expand Down
16 changes: 16 additions & 0 deletions bumble/profiles/pbp.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from typing_extensions import Self

from bumble import core, data_types, gatt
from bumble.profiles import le_audio


Expand All @@ -46,3 +47,18 @@ def from_bytes(cls, data: bytes) -> Self:
return cls(
features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
)

def get_advertising_data(self) -> bytes:
return bytes(
core.AdvertisingData(
[
data_types.ServiceData16BitUUID(
gatt.GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE, bytes(self)
)
]
)
)

def __bytes__(self) -> bytes:
metadata_bytes = bytes(self.metadata)
return bytes([self.features, len(metadata_bytes)]) + metadata_bytes
24 changes: 18 additions & 6 deletions docs/mkdocs/src/apps_and_tools/auracast.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ The `--output` option specifies where to send the decoded audio samples.
The following outputs are supported:

### Sound Device
The `--output` argument is either `device`, to send the audio to the hosts's default sound device, or `device:<DEVICE_ID>` where `<DEVICE_ID>`
The `--output` argument is either `device`, to send the audio to the hosts's
default sound device, or `device:<DEVICE_ID>` where `<DEVICE_ID>`
is the integer ID of one of the available sound devices.
When invoked with `--output "device:?"`, a list of available devices and
their IDs is printed out.
Expand All @@ -115,17 +116,24 @@ standard output (currently always as float32 PCM samples)

### FFPlay
With `--output ffplay`, the decoded audio samples are piped to `ffplay`
in a child process. This option is only available if `ffplay` is a command that is available on the host.
in a child process. This option is only available if `ffplay` is a command
that is available on the host.

### File
With `--output <filename>` or `--output file:<filename>`, the decoded audio
samples are written to a file (currently always as float32 PCM)

## `transmit`
Broadcast an audio source as a transmitter.
Broadcast one or more audio sources as a transmitter.

The `--input` and `--input-format` options specify what audio input
source to transmit.

Optionally, you can use the `--broadcast-list` option,
specifying a TOML configuration file, as a convenient way to specify
audio source configurations for one or more audio sources.
See `examples/auracast_broadcasts.toml` for an example.

The following inputs are supported:

### Sound Device
Expand All @@ -146,7 +154,8 @@ are read from a .wav or raw PCM file.
Use the `--input-format <FORMAT>` option to specify the format of the audio
samples in raw PCM files. `<FORMAT>` is expressed as:
`<sample-type>,<sample-rate>,<channels>`
(the only supported <sample-type> currently is 'int16le' for 16 bit signed integers with little-endian byte order)
(the only supported <sample-type> currently is 'int16le' for 16 bit signed
integers with little-endian byte order)

## `scan`
Scan for public broadcasts.
Expand All @@ -164,6 +173,7 @@ be shared to allow better compatibiity with certain products.
The `receive` command has been tested to successfully receive broadcasts from
the following transmitters:

* Android's "Audio Sharing"
* JBL GO 4
* Flairmesh FlooGoo FMA120
* Eppfun AK3040Pro Max
Expand Down Expand Up @@ -193,10 +203,12 @@ Use the `--manufacturer-data` option of the `transmit` command in order to inclu
that will let the speaker recognize the broadcast as a compatible source.

The manufacturer ID for JBL is 87.
Using an option like `--manufacturer-data 87:00000000000000000000000000000000dffd` should work (tested on the
JBL GO 4. The `dffd` value at the end of the payload may be different on other models?).
Using an option like `--manufacturer-data 87:00000000000000000000000000000000dffd` should work
(tested on the JBL GO 4.
The `dffd` value at the end of the payload may be different on other models?).


### Others

* Android
* Nexum Audio VOCE and USB dongle
19 changes: 19 additions & 0 deletions examples/auracast_broadcasts.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[[broadcasts]]
name = "Broadcast 1"
id = 1234
language="en"
program_info="Jazz"
[[broadcasts.sources]]
input = "file:audio_1_48k.wav"
bitrate = 80000

[[broadcasts]]
name = "Broadcast 2"
id = 5678
language="fr"
program_info="Classical"
[[broadcasts.sources]]
input = "file:audio_2.wav"
[broadcasts.sources.manufacturer_data]
company_id = 87
data = "00000000000000000000000000000000dffd"
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies = [
"pyserial-asyncio >= 0.5; platform_system!='Emscripten'",
"pyserial >= 3.5; platform_system!='Emscripten'",
"pyusb >= 1.2; platform_system!='Emscripten'",
"tomli ~= 2.2.1; platform_system!='Emscripten'",
"websockets >= 15.0.1; platform_system!='Emscripten'",
]

Expand Down
Loading