mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
add support for multiple concurrent broadcasts
This commit is contained in:
1230
apps/auracast.py
1230
apps/auracast.py
File diff suppressed because it is too large
Load Diff
@@ -35,8 +35,6 @@ from bumble.hci import (
|
|||||||
HCI_READ_BUFFER_SIZE_COMMAND,
|
HCI_READ_BUFFER_SIZE_COMMAND,
|
||||||
HCI_READ_LOCAL_NAME_COMMAND,
|
HCI_READ_LOCAL_NAME_COMMAND,
|
||||||
HCI_SUCCESS,
|
HCI_SUCCESS,
|
||||||
HCI_VERSION_NAMES,
|
|
||||||
LMP_VERSION_NAMES,
|
|
||||||
CodecID,
|
CodecID,
|
||||||
HCI_Command,
|
HCI_Command,
|
||||||
HCI_Command_Complete_Event,
|
HCI_Command_Complete_Event,
|
||||||
@@ -54,6 +52,7 @@ from bumble.hci import (
|
|||||||
HCI_Read_Local_Supported_Codecs_V2_Command,
|
HCI_Read_Local_Supported_Codecs_V2_Command,
|
||||||
HCI_Read_Local_Version_Information_Command,
|
HCI_Read_Local_Version_Information_Command,
|
||||||
LeFeature,
|
LeFeature,
|
||||||
|
SpecificationVersion,
|
||||||
map_null_terminated_utf8_string,
|
map_null_terminated_utf8_string,
|
||||||
)
|
)
|
||||||
from bumble.host import Host
|
from bumble.host import Host
|
||||||
@@ -289,14 +288,20 @@ async def async_main(
|
|||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
color(' HCI Version: ', 'green'),
|
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(
|
print(
|
||||||
color(' LMP Version: ', 'green'),
|
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
|
# Get the Classic info
|
||||||
await get_classic_info(host)
|
await get_classic_info(host)
|
||||||
|
|||||||
@@ -546,5 +546,6 @@ class SoundDeviceAudioInput(ThreadedAudioInput):
|
|||||||
return bytes(pcm_buffer)
|
return bytes(pcm_buffer)
|
||||||
|
|
||||||
def _close(self):
|
def _close(self):
|
||||||
self._stream.stop()
|
if self._stream:
|
||||||
self._stream = None
|
self._stream.stop()
|
||||||
|
self._stream = None
|
||||||
|
|||||||
@@ -864,8 +864,8 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|||||||
|
|
||||||
EVENT_STATE_CHANGE = "state_change"
|
EVENT_STATE_CHANGE = "state_change"
|
||||||
EVENT_ESTABLISHMENT = "establishment"
|
EVENT_ESTABLISHMENT = "establishment"
|
||||||
|
EVENT_ESTABLISHMENT_ERROR = "establishment_error"
|
||||||
EVENT_CANCELLATION = "cancellation"
|
EVENT_CANCELLATION = "cancellation"
|
||||||
EVENT_ERROR = "error"
|
|
||||||
EVENT_LOSS = "loss"
|
EVENT_LOSS = "loss"
|
||||||
EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
|
EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
|
||||||
EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
|
EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
|
||||||
@@ -998,7 +998,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.state = self.State.ERROR
|
self.state = self.State.ERROR
|
||||||
self.emit(self.EVENT_ERROR)
|
self.emit(self.EVENT_ESTABLISHMENT_ERROR)
|
||||||
|
|
||||||
def on_loss(self):
|
def on_loss(self):
|
||||||
self.state = self.State.LOST
|
self.state = self.State.LOST
|
||||||
|
|||||||
@@ -207,22 +207,44 @@ def metadata(
|
|||||||
|
|
||||||
HCI_VENDOR_OGF = 0x3F
|
HCI_VENDOR_OGF = 0x3F
|
||||||
|
|
||||||
# HCI Version
|
# Specification Version
|
||||||
HCI_VERSION_BLUETOOTH_CORE_1_0B = 0
|
class SpecificationVersion(utils.OpenIntEnum):
|
||||||
HCI_VERSION_BLUETOOTH_CORE_1_1 = 1
|
BLUETOOTH_CORE_1_0B = 0
|
||||||
HCI_VERSION_BLUETOOTH_CORE_1_2 = 2
|
BLUETOOTH_CORE_1_1 = 1
|
||||||
HCI_VERSION_BLUETOOTH_CORE_2_0_EDR = 3
|
BLUETOOTH_CORE_1_2 = 2
|
||||||
HCI_VERSION_BLUETOOTH_CORE_2_1_EDR = 4
|
BLUETOOTH_CORE_2_0_EDR = 3
|
||||||
HCI_VERSION_BLUETOOTH_CORE_3_0_HS = 5
|
BLUETOOTH_CORE_2_1_EDR = 4
|
||||||
HCI_VERSION_BLUETOOTH_CORE_4_0 = 6
|
BLUETOOTH_CORE_3_0_HS = 5
|
||||||
HCI_VERSION_BLUETOOTH_CORE_4_1 = 7
|
BLUETOOTH_CORE_4_0 = 6
|
||||||
HCI_VERSION_BLUETOOTH_CORE_4_2 = 8
|
BLUETOOTH_CORE_4_1 = 7
|
||||||
HCI_VERSION_BLUETOOTH_CORE_5_0 = 9
|
BLUETOOTH_CORE_4_2 = 8
|
||||||
HCI_VERSION_BLUETOOTH_CORE_5_1 = 10
|
BLUETOOTH_CORE_5_0 = 9
|
||||||
HCI_VERSION_BLUETOOTH_CORE_5_2 = 11
|
BLUETOOTH_CORE_5_1 = 10
|
||||||
HCI_VERSION_BLUETOOTH_CORE_5_3 = 12
|
BLUETOOTH_CORE_5_2 = 11
|
||||||
HCI_VERSION_BLUETOOTH_CORE_5_4 = 13
|
BLUETOOTH_CORE_5_3 = 12
|
||||||
HCI_VERSION_BLUETOOTH_CORE_6_0 = 14
|
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_NAMES = {
|
||||||
HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
|
HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
|
||||||
@@ -240,9 +262,10 @@ HCI_VERSION_NAMES = {
|
|||||||
HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3',
|
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_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_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
|
LMP_VERSION_NAMES = HCI_VERSION_NAMES
|
||||||
|
|
||||||
# HCI Packet types
|
# HCI Packet types
|
||||||
|
|||||||
@@ -338,7 +338,12 @@ class BroadcastAudioScanService(gatt.TemplateService):
|
|||||||
b"12", # TEST
|
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(
|
def on_broadcast_audio_scan_control_point_write(
|
||||||
self, connection: device.Connection, value: bytes
|
self, connection: device.Connection, value: bytes
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import enum
|
|||||||
|
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from bumble import core, data_types, gatt
|
||||||
from bumble.profiles import le_audio
|
from bumble.profiles import le_audio
|
||||||
|
|
||||||
|
|
||||||
@@ -46,3 +47,18 @@ class PublicBroadcastAnnouncement:
|
|||||||
return cls(
|
return cls(
|
||||||
features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
|
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
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ The `--output` option specifies where to send the decoded audio samples.
|
|||||||
The following outputs are supported:
|
The following outputs are supported:
|
||||||
|
|
||||||
### Sound Device
|
### 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.
|
is the integer ID of one of the available sound devices.
|
||||||
When invoked with `--output "device:?"`, a list of available devices and
|
When invoked with `--output "device:?"`, a list of available devices and
|
||||||
their IDs is printed out.
|
their IDs is printed out.
|
||||||
@@ -115,17 +116,24 @@ standard output (currently always as float32 PCM samples)
|
|||||||
|
|
||||||
### FFPlay
|
### FFPlay
|
||||||
With `--output ffplay`, the decoded audio samples are piped to `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
|
### File
|
||||||
With `--output <filename>` or `--output file:<filename>`, the decoded audio
|
With `--output <filename>` or `--output file:<filename>`, the decoded audio
|
||||||
samples are written to a file (currently always as float32 PCM)
|
samples are written to a file (currently always as float32 PCM)
|
||||||
|
|
||||||
## `transmit`
|
## `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
|
The `--input` and `--input-format` options specify what audio input
|
||||||
source to transmit.
|
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:
|
The following inputs are supported:
|
||||||
|
|
||||||
### Sound Device
|
### Sound Device
|
||||||
@@ -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
|
Use the `--input-format <FORMAT>` option to specify the format of the audio
|
||||||
samples in raw PCM files. `<FORMAT>` is expressed as:
|
samples in raw PCM files. `<FORMAT>` is expressed as:
|
||||||
`<sample-type>,<sample-rate>,<channels>`
|
`<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`
|
||||||
Scan for public broadcasts.
|
Scan for public broadcasts.
|
||||||
@@ -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 `receive` command has been tested to successfully receive broadcasts from
|
||||||
the following transmitters:
|
the following transmitters:
|
||||||
|
|
||||||
|
* Android's "Audio Sharing"
|
||||||
* JBL GO 4
|
* JBL GO 4
|
||||||
* Flairmesh FlooGoo FMA120
|
* Flairmesh FlooGoo FMA120
|
||||||
* Eppfun AK3040Pro Max
|
* Eppfun AK3040Pro Max
|
||||||
@@ -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.
|
that will let the speaker recognize the broadcast as a compatible source.
|
||||||
|
|
||||||
The manufacturer ID for JBL is 87.
|
The manufacturer ID for JBL is 87.
|
||||||
Using an option like `--manufacturer-data 87:00000000000000000000000000000000dffd` should work (tested on the
|
Using an option like `--manufacturer-data 87:00000000000000000000000000000000dffd` should work
|
||||||
JBL GO 4. The `dffd` value at the end of the payload may be different on other models?).
|
(tested on the JBL GO 4.
|
||||||
|
The `dffd` value at the end of the payload may be different on other models?).
|
||||||
|
|
||||||
|
|
||||||
### Others
|
### Others
|
||||||
|
|
||||||
|
* Android
|
||||||
* Nexum Audio VOCE and USB dongle
|
* Nexum Audio VOCE and USB dongle
|
||||||
19
examples/auracast_broadcasts.toml
Normal file
19
examples/auracast_broadcasts.toml
Normal file
@@ -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"
|
||||||
@@ -32,6 +32,7 @@ dependencies = [
|
|||||||
"pyserial-asyncio >= 0.5; platform_system!='Emscripten'",
|
"pyserial-asyncio >= 0.5; platform_system!='Emscripten'",
|
||||||
"pyserial >= 3.5; platform_system!='Emscripten'",
|
"pyserial >= 3.5; platform_system!='Emscripten'",
|
||||||
"pyusb >= 1.2; platform_system!='Emscripten'",
|
"pyusb >= 1.2; platform_system!='Emscripten'",
|
||||||
|
"tomli ~= 2.2.1; platform_system!='Emscripten'",
|
||||||
"websockets >= 15.0.1; platform_system!='Emscripten'",
|
"websockets >= 15.0.1; platform_system!='Emscripten'",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user