Skip to content

Commit 4c730ce

Browse files
committed
adding experimental stuff for no reason
1 parent f547cc1 commit 4c730ce

20 files changed

+10302
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Copyright 2021-2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# -----------------------------------------------------------------------------
16+
# Imports
17+
# -----------------------------------------------------------------------------
18+
import asyncio
19+
import sys
20+
21+
import bumble.logging
22+
from bumble.a2dp import (
23+
A2DP_SBC_CODEC_TYPE,
24+
SbcMediaCodecInformation,
25+
SbcPacketSource,
26+
make_audio_source_service_sdp_records,
27+
)
28+
from bumble.avdtp import (
29+
AVDTP_AUDIO_MEDIA_TYPE,
30+
Listener,
31+
MediaCodecCapabilities,
32+
MediaPacketPump,
33+
Protocol,
34+
find_avdtp_service_with_connection,
35+
)
36+
from bumble.colors import color
37+
from bumble.core import PhysicalTransport
38+
from bumble.device import Device
39+
from bumble.transport import open_transport
40+
41+
42+
# -----------------------------------------------------------------------------
43+
def sdp_records():
44+
service_record_handle = 0x00010001
45+
return {
46+
service_record_handle: make_audio_source_service_sdp_records(
47+
service_record_handle
48+
)
49+
}
50+
51+
52+
# -----------------------------------------------------------------------------
53+
def codec_capabilities():
54+
# NOTE: this shouldn't be hardcoded, but should be inferred from the input file
55+
# instead
56+
return MediaCodecCapabilities(
57+
media_type=AVDTP_AUDIO_MEDIA_TYPE,
58+
media_codec_type=A2DP_SBC_CODEC_TYPE,
59+
media_codec_information=SbcMediaCodecInformation(
60+
sampling_frequency=SbcMediaCodecInformation.SamplingFrequency.SF_44100,
61+
channel_mode=SbcMediaCodecInformation.ChannelMode.JOINT_STEREO,
62+
block_length=SbcMediaCodecInformation.BlockLength.BL_16,
63+
subbands=SbcMediaCodecInformation.Subbands.S_8,
64+
allocation_method=SbcMediaCodecInformation.AllocationMethod.LOUDNESS,
65+
minimum_bitpool_value=2,
66+
maximum_bitpool_value=53,
67+
),
68+
)
69+
70+
71+
# -----------------------------------------------------------------------------
72+
def on_avdtp_connection(read_function, protocol):
73+
packet_source = SbcPacketSource(read_function, protocol.l2cap_channel.peer_mtu)
74+
packet_pump = MediaPacketPump(packet_source.packets)
75+
protocol.add_source(codec_capabilities(), packet_pump)
76+
77+
78+
# -----------------------------------------------------------------------------
79+
async def stream_packets(read_function, protocol):
80+
# Discover all endpoints on the remote device
81+
endpoints = await protocol.discover_remote_endpoints()
82+
for endpoint in endpoints:
83+
print('@@@', endpoint)
84+
85+
# Select a sink
86+
sink = protocol.find_remote_sink_by_codec(
87+
AVDTP_AUDIO_MEDIA_TYPE, A2DP_SBC_CODEC_TYPE
88+
)
89+
if sink is None:
90+
print(color('!!! no SBC sink found', 'red'))
91+
return
92+
print(f'### Selected sink: {sink.seid}')
93+
94+
# Stream the packets
95+
packet_source = SbcPacketSource(read_function, protocol.l2cap_channel.peer_mtu)
96+
packet_pump = MediaPacketPump(packet_source.packets)
97+
source = protocol.add_source(codec_capabilities(), packet_pump)
98+
stream = await protocol.create_stream(source, sink)
99+
await stream.start()
100+
await asyncio.sleep(5)
101+
await stream.stop()
102+
await asyncio.sleep(5)
103+
await stream.start()
104+
await asyncio.sleep(5)
105+
await stream.stop()
106+
await stream.close()
107+
108+
109+
# -----------------------------------------------------------------------------
110+
async def main() -> None:
111+
if len(sys.argv) < 4:
112+
print(
113+
'Usage: run_a2dp_source.py <device-config> <transport-spec> <sbc-file> '
114+
'[<bluetooth-address>]'
115+
)
116+
print(
117+
'example: run_a2dp_source.py classic1.json usb:0 test.sbc E1:CA:72:48:C4:E8'
118+
)
119+
return
120+
121+
print('<<< connecting to HCI...')
122+
async with await open_transport(sys.argv[2]) as hci_transport:
123+
print('<<< connected')
124+
125+
# Create a device
126+
device = Device.from_config_file_with_hci(
127+
sys.argv[1], hci_transport.source, hci_transport.sink
128+
)
129+
device.classic_enabled = True
130+
131+
# Setup the SDP to expose the SRC service
132+
device.sdp_service_records = sdp_records()
133+
134+
# Start
135+
await device.power_on()
136+
137+
with open(sys.argv[3], 'rb') as sbc_file:
138+
# NOTE: this should be using asyncio file reading, but blocking reads are
139+
# good enough for testing
140+
async def read(byte_count):
141+
return sbc_file.read(byte_count)
142+
143+
if len(sys.argv) > 4:
144+
# Connect to a peer
145+
target_address = sys.argv[4]
146+
print(f'=== Connecting to {target_address}...')
147+
connection = await device.connect(
148+
target_address, transport=PhysicalTransport.BR_EDR
149+
)
150+
print(f'=== Connected to {connection.peer_address}!')
151+
152+
# Request authentication
153+
print('*** Authenticating...')
154+
await connection.authenticate()
155+
print('*** Authenticated')
156+
157+
# Enable encryption
158+
print('*** Enabling encryption...')
159+
await connection.encrypt()
160+
print('*** Encryption on')
161+
162+
# Look for an A2DP service
163+
avdtp_version = await find_avdtp_service_with_connection(connection)
164+
if not avdtp_version:
165+
print(color('!!! no A2DP service found'))
166+
return
167+
168+
# Create a client to interact with the remote device
169+
protocol = await Protocol.connect(connection, avdtp_version)
170+
171+
# Start streaming
172+
await stream_packets(read, protocol)
173+
else:
174+
# Create a listener to wait for AVDTP connections
175+
listener = Listener.for_device(device=device, version=(1, 2))
176+
listener.on(
177+
'connection', lambda protocol: on_avdtp_connection(read, protocol)
178+
)
179+
180+
# Become connectable and wait for a connection
181+
await device.set_discoverable(True)
182+
await device.set_connectable(True)
183+
184+
await hci_transport.source.wait_for_termination()
185+
186+
187+
# -----------------------------------------------------------------------------
188+
bumble.logging.setup_basic_logging('DEBUG')
189+
asyncio.run(main())

linux-reprod/aacp.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import socket
2+
import threading
3+
import time
4+
import sys
5+
from prompt_toolkit import PromptSession
6+
from prompt_toolkit.patch_stdout import patch_stdout
7+
8+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
9+
sock.connect("./aacp.sock")
10+
11+
def listen():
12+
while True:
13+
try:
14+
res = sock.recv(1024)
15+
if not res:
16+
break
17+
print(f"Response: {res.hex()}")
18+
except OSError:
19+
break
20+
21+
22+
threading.Thread(target=listen, daemon=True).start()
23+
print("Connected.")
24+
25+
26+
def send_line(line: str):
27+
line = line.strip()
28+
if not line:
29+
return
30+
try:
31+
if " " in line:
32+
byts = bytes(int(b, 16) for b in line.split())
33+
else:
34+
if len(line) % 2 != 0:
35+
line = "0" + line
36+
byts = bytes.fromhex(line)
37+
sock.send(byts)
38+
except Exception as e:
39+
print(f"Invalid line '{line}': {e}")
40+
41+
42+
# Optional file argument
43+
file_arg = sys.argv[1] if len(sys.argv) > 1 else None
44+
45+
if file_arg:
46+
try:
47+
with open(file_arg, "r") as f:
48+
for line in f:
49+
send_line(line)
50+
time.sleep(0.2) # 200ms delay
51+
except FileNotFoundError:
52+
print(f"File not found: {file_arg}")
53+
54+
# Always enter interactive mode after file
55+
print("Type hex bytes (Ctrl-L to clear, 'quit' to exit)...")
56+
session = PromptSession()
57+
with patch_stdout():
58+
while True:
59+
try:
60+
data = session.prompt("> ").strip()
61+
except (EOFError, KeyboardInterrupt):
62+
break
63+
64+
if not data:
65+
continue
66+
if data.lower() == "quit":
67+
break
68+
69+
send_line(data)
70+
71+
sock.close()
72+
print("Connection closed.")

linux-reprod/att.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import socket
2+
import threading
3+
import time
4+
import sys
5+
from prompt_toolkit import PromptSession
6+
from prompt_toolkit.patch_stdout import patch_stdout
7+
8+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
9+
sock.connect("./att.sock")
10+
11+
def listen():
12+
while True:
13+
try:
14+
res = sock.recv(1024)
15+
if not res:
16+
break
17+
print(f"Response: {res.hex()}")
18+
except OSError:
19+
break
20+
21+
22+
threading.Thread(target=listen, daemon=True).start()
23+
print("Connected.")
24+
25+
26+
def send_line(line: str):
27+
line = line.strip()
28+
if not line:
29+
return
30+
try:
31+
if " " in line:
32+
byts = bytes(int(b, 16) for b in line.split())
33+
else:
34+
if len(line) % 2 != 0:
35+
line = "0" + line
36+
byts = bytes.fromhex(line)
37+
sock.send(byts)
38+
except Exception as e:
39+
print(f"Invalid line '{line}': {e}")
40+
41+
42+
# Optional file argument
43+
file_arg = sys.argv[1] if len(sys.argv) > 1 else None
44+
45+
if file_arg:
46+
try:
47+
with open(file_arg, "r") as f:
48+
for line in f:
49+
send_line(line)
50+
time.sleep(0.2) # 200ms delay
51+
except FileNotFoundError:
52+
print(f"File not found: {file_arg}")
53+
54+
# Always enter interactive mode after file
55+
print("Type hex bytes (Ctrl-L to clear, 'quit' to exit)...")
56+
session = PromptSession()
57+
with patch_stdout():
58+
while True:
59+
try:
60+
data = session.prompt("> ").strip()
61+
except (EOFError, KeyboardInterrupt):
62+
break
63+
64+
if not data:
65+
continue
66+
if data.lower() == "quit":
67+
break
68+
69+
send_line(data)
70+
71+
sock.close()
72+
print("Connection closed.")

0 commit comments

Comments
 (0)