refractoring
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,7 +4,6 @@ coverage/ # Coverage results after running tests with coverage tools
|
|||||||
.dist-info/ # Wheel metadata (use poetry build to handle this)
|
.dist-info/ # Wheel metadata (use poetry build to handle this)
|
||||||
*.egg-info/ # Egg info directory (automatically created by pip)
|
*.egg-info/ # Egg info directory (automatically created by pip)
|
||||||
auracast.egg-info/
|
auracast.egg-info/
|
||||||
.vscode/ # IDE configuration (edit in VS Code)
|
|
||||||
|
|
||||||
# Ignore these file types and extensions
|
# Ignore these file types and extensions
|
||||||
*.pyc # Compiled Python files (.pyc, .pyo are automatically ignored by git)
|
*.pyc # Compiled Python files (.pyc, .pyo are automatically ignored by git)
|
||||||
@@ -16,11 +15,11 @@ venv/
|
|||||||
env/
|
env/
|
||||||
|
|
||||||
# Ignore any IDE configurations or project-specific metadata
|
# Ignore any IDE configurations or project-specific metadata
|
||||||
.vscode/**
|
|
||||||
.pycharm/**
|
.pycharm/**
|
||||||
*.iml
|
*.iml
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
|
.vscode/settings.json
|
||||||
|
|
||||||
# Ignore test results and logs (adjust to your specific testing framework)
|
# Ignore test results and logs (adjust to your specific testing framework)
|
||||||
/testresults/**
|
/testresults/**
|
||||||
@@ -35,4 +34,5 @@ env/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
# Exclude .env file from all platforms
|
# Exclude .env file from all platforms
|
||||||
*/.env
|
*/.env
|
||||||
|
|
||||||
|
|||||||
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python Debugger: current file",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": true,
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
.vscode/tasks.json
vendored
Normal file
12
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "pip install -e bumble",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pip install -e ../bumble --config-settings editable_mode=compat"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@ class AuracastBigConfig:
|
|||||||
name: str = 'Broadcast0'
|
name: str = 'Broadcast0'
|
||||||
program_info: str = 'Some Announcements'
|
program_info: str = 'Some Announcements'
|
||||||
audio_source: str = 'file:./auracast/announcement_48_10_96000_en.wav'
|
audio_source: str = 'file:./auracast/announcement_48_10_96000_en.wav'
|
||||||
|
iso_que_len: int = 64
|
||||||
loop_wav: bool = True
|
loop_wav: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,228 +97,243 @@ def run_async(async_command: Coroutine) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def setup_broadcast(
|
async def setup_broadcast(
|
||||||
|
device,
|
||||||
global_config : auracast_config.AuracastGlobalConfig,
|
global_config : auracast_config.AuracastGlobalConfig,
|
||||||
big_config: List[auracast_config.AuracastBigConfig]
|
big_config: List[auracast_config.AuracastBigConfig]
|
||||||
|
) -> dict:
|
||||||
|
|
||||||
) -> None:
|
bap_sampling_freq = getattr(bap.SamplingFrequency, f"FREQ_{global_config.auracast_sampling_rate_hz}")
|
||||||
async with create_device(global_config) as device:
|
bigs = {}
|
||||||
if not device.supports_le_periodic_advertising:
|
for i, conf in enumerate(big_config):
|
||||||
logger.error(color('Periodic advertising not supported', 'red'))
|
bigs[f'big{i}'] = {}
|
||||||
return
|
# Config advertising set
|
||||||
|
bigs[f'big{i}']['basic_audio_announcement'] = bap.BasicAudioAnnouncement(
|
||||||
bap_sampling_freq = getattr(bap.SamplingFrequency, f"FREQ_{global_config.auracast_sampling_rate_hz}")
|
presentation_delay=global_config.presentation_delay_us,
|
||||||
bigs = {}
|
subgroups=[
|
||||||
for i, conf in enumerate(big_config):
|
bap.BasicAudioAnnouncement.Subgroup(
|
||||||
bigs[f'big{i}'] = {}
|
codec_id=hci.CodingFormat(codec_id=hci.CodecID.LC3),
|
||||||
# Config advertising set
|
codec_specific_configuration=bap.CodecSpecificConfiguration(
|
||||||
bigs[f'big{i}']['basic_audio_announcement'] = bap.BasicAudioAnnouncement(
|
sampling_frequency=bap_sampling_freq,
|
||||||
presentation_delay=global_config.presentation_delay_us,
|
frame_duration=bap.FrameDuration.DURATION_10000_US,
|
||||||
subgroups=[
|
octets_per_codec_frame=global_config.octets_per_frame,
|
||||||
bap.BasicAudioAnnouncement.Subgroup(
|
),
|
||||||
codec_id=hci.CodingFormat(codec_id=hci.CodecID.LC3),
|
metadata=le_audio.Metadata(
|
||||||
codec_specific_configuration=bap.CodecSpecificConfiguration(
|
[
|
||||||
sampling_frequency=bap_sampling_freq,
|
le_audio.Metadata.Entry(
|
||||||
frame_duration=bap.FrameDuration.DURATION_10000_US,
|
tag=le_audio.Metadata.Tag.LANGUAGE, data=conf.language.encode()
|
||||||
octets_per_codec_frame=global_config.octets_per_frame,
|
|
||||||
),
|
|
||||||
metadata=le_audio.Metadata(
|
|
||||||
[
|
|
||||||
le_audio.Metadata.Entry(
|
|
||||||
tag=le_audio.Metadata.Tag.LANGUAGE, data=conf.language.encode()
|
|
||||||
),
|
|
||||||
le_audio.Metadata.Entry(
|
|
||||||
tag=le_audio.Metadata.Tag.PROGRAM_INFO, data=conf.program_info.encode()
|
|
||||||
),
|
|
||||||
le_audio.Metadata.Entry(
|
|
||||||
tag=le_audio.Metadata.Tag.BROADCAST_NAME, data=conf.name.encode()
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
bis=[
|
|
||||||
bap.BasicAudioAnnouncement.BIS(
|
|
||||||
index=1,
|
|
||||||
codec_specific_configuration=bap.CodecSpecificConfiguration(
|
|
||||||
audio_channel_allocation=bap.AudioLocation.FRONT_LEFT
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
le_audio.Metadata.Entry(
|
||||||
)
|
tag=le_audio.Metadata.Tag.PROGRAM_INFO, data=conf.program_info.encode()
|
||||||
],
|
),
|
||||||
)
|
le_audio.Metadata.Entry(
|
||||||
logger.info('Setup Advertising')
|
tag=le_audio.Metadata.Tag.BROADCAST_NAME, data=conf.name.encode()
|
||||||
advertising_manufacturer_data = (
|
),
|
||||||
b''
|
]
|
||||||
if global_config.manufacturer_data is None
|
),
|
||||||
else bytes(
|
bis=[
|
||||||
core.AdvertisingData(
|
bap.BasicAudioAnnouncement.BIS(
|
||||||
[
|
index=1,
|
||||||
(
|
codec_specific_configuration=bap.CodecSpecificConfiguration(
|
||||||
core.AdvertisingData.MANUFACTURER_SPECIFIC_DATA,
|
audio_channel_allocation=bap.AudioLocation.FRONT_LEFT
|
||||||
struct.pack('<H', global_config.manufacturer_data[0])
|
),
|
||||||
+ global_config.manufacturer_data[1],
|
),
|
||||||
)
|
],
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
logger.info('Setup Advertising')
|
||||||
|
advertising_manufacturer_data = (
|
||||||
|
b''
|
||||||
|
if global_config.manufacturer_data is None
|
||||||
|
else bytes(
|
||||||
|
core.AdvertisingData(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
core.AdvertisingData.MANUFACTURER_SPECIFIC_DATA,
|
||||||
|
struct.pack('<H', global_config.manufacturer_data[0])
|
||||||
|
+ global_config.manufacturer_data[1],
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
bigs[f'big{i}']['broadcast_audio_announcement'] = bap.BroadcastAudioAnnouncement(conf.id)
|
)
|
||||||
advertising_set = await device.create_advertising_set(
|
bigs[f'big{i}']['broadcast_audio_announcement'] = bap.BroadcastAudioAnnouncement(conf.id)
|
||||||
random_address=conf.random_address,
|
advertising_set = await device.create_advertising_set(
|
||||||
advertising_parameters=bumble.device.AdvertisingParameters(
|
random_address=conf.random_address,
|
||||||
advertising_event_properties=bumble.device.AdvertisingEventProperties(
|
advertising_parameters=bumble.device.AdvertisingParameters(
|
||||||
is_connectable=False
|
advertising_event_properties=bumble.device.AdvertisingEventProperties(
|
||||||
),
|
is_connectable=False
|
||||||
primary_advertising_interval_min=round(100),
|
|
||||||
primary_advertising_interval_max=round(200),
|
|
||||||
advertising_sid=i,
|
|
||||||
primary_advertising_phy=hci.Phy.LE_1M, # 2m phy config throws error - because for primary advertising channels, 1mbit is only supported
|
|
||||||
secondary_advertising_phy=hci.Phy.LE_2M, # this is the secondary advertising beeing send on non advertising channels (extendend advertising)
|
|
||||||
#advertising_tx_power= # tx power in dbm (max 20)
|
|
||||||
#secondary_advertising_max_skip=10,
|
|
||||||
),
|
),
|
||||||
advertising_data=(
|
primary_advertising_interval_min=round(100),
|
||||||
bigs[f'big{i}']['broadcast_audio_announcement'].get_advertising_data()
|
primary_advertising_interval_max=round(200),
|
||||||
+ bytes(
|
advertising_sid=i,
|
||||||
core.AdvertisingData(
|
primary_advertising_phy=hci.Phy.LE_1M, # 2m phy config throws error - because for primary advertising channels, 1mbit is only supported
|
||||||
[(core.AdvertisingData.BROADCAST_NAME, conf.name.encode())]
|
secondary_advertising_phy=hci.Phy.LE_2M, # this is the secondary advertising beeing send on non advertising channels (extendend advertising)
|
||||||
)
|
#advertising_tx_power= # tx power in dbm (max 20)
|
||||||
|
#secondary_advertising_max_skip=10,
|
||||||
|
),
|
||||||
|
advertising_data=(
|
||||||
|
bigs[f'big{i}']['broadcast_audio_announcement'].get_advertising_data()
|
||||||
|
+ bytes(
|
||||||
|
core.AdvertisingData(
|
||||||
|
[(core.AdvertisingData.BROADCAST_NAME, conf.name.encode())]
|
||||||
)
|
)
|
||||||
+ advertising_manufacturer_data
|
|
||||||
),
|
|
||||||
periodic_advertising_parameters=bumble.device.PeriodicAdvertisingParameters(
|
|
||||||
periodic_advertising_interval_min=round(80),
|
|
||||||
periodic_advertising_interval_max=round(160),
|
|
||||||
),
|
|
||||||
periodic_advertising_data=bigs[f'big{i}']['basic_audio_announcement'].get_advertising_data(),
|
|
||||||
auto_restart=True,
|
|
||||||
auto_start=True,
|
|
||||||
)
|
|
||||||
bigs[f'big{i}']['advertising_set'] = advertising_set
|
|
||||||
|
|
||||||
logging.info('Start Periodic Advertising')
|
|
||||||
await advertising_set.start_periodic()
|
|
||||||
|
|
||||||
logging.info('Setup BIG')
|
|
||||||
if global_config.qos_config.iso_int_multiple_10ms == 1:
|
|
||||||
frame_enable = 0
|
|
||||||
else:
|
|
||||||
frame_enable = 1
|
|
||||||
|
|
||||||
big = await device.create_big(
|
|
||||||
bigs[f'big{i}']['advertising_set'],
|
|
||||||
parameters=bumble.device.BigParameters(
|
|
||||||
num_bis=1,
|
|
||||||
sdu_interval=global_config.qos_config.iso_int_multiple_10ms*10000, # Is the same as iso interval
|
|
||||||
max_sdu=global_config.octets_per_frame,
|
|
||||||
max_transport_latency=global_config.qos_config.max_transport_latency_ms,
|
|
||||||
rtn=global_config.qos_config.number_of_retransmissions,
|
|
||||||
broadcast_code=(
|
|
||||||
bytes.fromhex(conf.code) if conf.code else None
|
|
||||||
),
|
|
||||||
framing=frame_enable # needed if iso interval is not frame interval of codedc
|
|
||||||
),
|
|
||||||
)
|
|
||||||
bigs[f'big{i}']['big'] = big
|
|
||||||
|
|
||||||
for bis_link in big.bis_links:
|
|
||||||
await bis_link.setup_data_path(
|
|
||||||
direction=bis_link.Direction.HOST_TO_CONTROLLER
|
|
||||||
)
|
)
|
||||||
|
+ advertising_manufacturer_data
|
||||||
|
),
|
||||||
|
periodic_advertising_parameters=bumble.device.PeriodicAdvertisingParameters(
|
||||||
|
periodic_advertising_interval_min=round(80),
|
||||||
|
periodic_advertising_interval_max=round(160),
|
||||||
|
),
|
||||||
|
periodic_advertising_data=bigs[f'big{i}']['basic_audio_announcement'].get_advertising_data(),
|
||||||
|
auto_restart=True,
|
||||||
|
auto_start=True,
|
||||||
|
)
|
||||||
|
bigs[f'big{i}']['advertising_set'] = advertising_set
|
||||||
|
|
||||||
iso_queue = bumble.device.IsoPacketStream(big.bis_links[0], 64)
|
logging.info('Start Periodic Advertising')
|
||||||
|
await advertising_set.start_periodic()
|
||||||
|
|
||||||
logging.info('Setup ISO Data Path')
|
logging.info('Setup BIG')
|
||||||
|
if global_config.qos_config.iso_int_multiple_10ms == 1:
|
||||||
|
frame_enable = 0
|
||||||
|
else:
|
||||||
|
frame_enable = 1
|
||||||
|
|
||||||
bigs[f'big{i}']['iso_queue'] = iso_queue
|
big = await device.create_big(
|
||||||
|
bigs[f'big{i}']['advertising_set'],
|
||||||
|
parameters=bumble.device.BigParameters(
|
||||||
|
num_bis=1,
|
||||||
|
sdu_interval=global_config.qos_config.iso_int_multiple_10ms*10000, # Is the same as iso interval
|
||||||
|
max_sdu=global_config.octets_per_frame,
|
||||||
|
max_transport_latency=global_config.qos_config.max_transport_latency_ms,
|
||||||
|
rtn=global_config.qos_config.number_of_retransmissions,
|
||||||
|
broadcast_code=(
|
||||||
|
bytes.fromhex(conf.code) if conf.code else None
|
||||||
|
),
|
||||||
|
framing=frame_enable # needed if iso interval is not frame interval of codedc
|
||||||
|
),
|
||||||
|
)
|
||||||
|
bigs[f'big{i}']['big'] = big
|
||||||
|
|
||||||
logging.debug(f'big{i} parameters are:')
|
for bis_link in big.bis_links:
|
||||||
logging.debug('%s', pprint.pformat(vars(big)))
|
await bis_link.setup_data_path(
|
||||||
logging.debug(f'Finished setup of big{i}.')
|
direction=bis_link.Direction.HOST_TO_CONTROLLER
|
||||||
|
|
||||||
await asyncio.sleep(i+1) # Wait for advertising to set up
|
|
||||||
|
|
||||||
logging.info("Broadcasting...")
|
|
||||||
|
|
||||||
def on_flow():
|
|
||||||
data_packet_queue = iso_queue.data_packet_queue
|
|
||||||
print(
|
|
||||||
f'\rPACKETS: pending={data_packet_queue.pending}, '
|
|
||||||
f'queued={data_packet_queue.queued}, '
|
|
||||||
f'completed={data_packet_queue.completed}',
|
|
||||||
end='',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if global_conf.debug:
|
iso_queue = bumble.device.IsoPacketStream(big.bis_links[0], conf.iso_que_len)
|
||||||
bigs[f'big{0}']['iso_queue'][0].data_packet_queue.on('flow', on_flow)
|
|
||||||
|
logging.info('Setup ISO Data Path')
|
||||||
|
|
||||||
|
bigs[f'big{i}']['iso_queue'] = iso_queue
|
||||||
|
|
||||||
|
logging.debug(f'big{i} parameters are:')
|
||||||
|
logging.debug('%s', pprint.pformat(vars(big)))
|
||||||
|
logging.debug(f'Finished setup of big{i}.')
|
||||||
|
|
||||||
|
await asyncio.sleep(i+1) # Wait for advertising to set up
|
||||||
|
|
||||||
|
def on_flow():
|
||||||
|
data_packet_queue = iso_queue.data_packet_queue
|
||||||
|
print(
|
||||||
|
f'\rPACKETS: pending={data_packet_queue.pending}, '
|
||||||
|
f'queued={data_packet_queue.queued}, '
|
||||||
|
f'completed={data_packet_queue.completed}',
|
||||||
|
end='',
|
||||||
|
)
|
||||||
|
|
||||||
|
if global_conf.debug:
|
||||||
|
bigs[f'big{0}']['iso_queue'].data_packet_queue.on('flow', on_flow)
|
||||||
|
|
||||||
|
return bigs
|
||||||
|
|
||||||
|
async def setup_audio(
|
||||||
|
bigs,
|
||||||
|
global_config : auracast_config.AuracastGlobalConfig,
|
||||||
|
big_config: List[auracast_config.AuracastBigConfig]
|
||||||
|
):
|
||||||
|
for i, big in enumerate(bigs.values()):
|
||||||
|
audio_source = big_config[i].audio_source
|
||||||
|
input_format = 'auto'
|
||||||
|
audio_input = await audio_io.create_audio_input(audio_source, input_format)
|
||||||
|
audio_input.rewind = big_config[i].loop_wav
|
||||||
|
pcm_format = await audio_input.open()
|
||||||
|
|
||||||
|
#try:
|
||||||
|
if pcm_format.channels != 1:
|
||||||
|
print("Only 1 channels PCM configurations are supported")
|
||||||
|
return
|
||||||
|
if pcm_format.sample_type == audio_io.PcmFormat.SampleType.INT16:
|
||||||
|
pcm_bit_depth = 16
|
||||||
|
elif pcm_format.sample_type == audio_io.PcmFormat.SampleType.FLOAT32:
|
||||||
|
pcm_bit_depth = None
|
||||||
|
else:
|
||||||
|
print("Only INT16 and FLOAT32 sample types are supported")
|
||||||
|
return
|
||||||
|
encoder = lc3.Encoder(
|
||||||
|
frame_duration_us=global_config.frame_duration_us,
|
||||||
|
sample_rate_hz=global_config.auracast_sampling_rate_hz,
|
||||||
|
num_channels=1,
|
||||||
|
input_sample_rate_hz=pcm_format.sample_rate,
|
||||||
|
)
|
||||||
|
lc3_frame_samples = encoder.get_frame_samples() # number of the pcm samples per lc3 frame
|
||||||
|
lc3_frame_size = global_config.octets_per_frame #encoder.get_frame_bytes(bitrate)
|
||||||
|
lc3_bytes_per_frame = lc3_frame_size #* 2 #multiplied by number of channels
|
||||||
|
|
||||||
|
big['pcm_bit_depth'] = pcm_bit_depth
|
||||||
|
big['lc3_bytes_per_frame'] = lc3_bytes_per_frame
|
||||||
|
big['lc3_frame_samples'] = lc3_frame_samples
|
||||||
|
big['audio_input'] = audio_input
|
||||||
|
big['encoder'] = encoder
|
||||||
|
|
||||||
|
async def streamer(bigs):
|
||||||
|
# TODO: do some pre buffering so the stream is stable from the beginning. One half iso queue would be appropriate
|
||||||
|
logging.info("Streaming audio...")
|
||||||
|
while True:
|
||||||
|
stream_finished = [False for _ in range(len(bigs))]
|
||||||
for i, big in enumerate(bigs.values()):
|
for i, big in enumerate(bigs.values()):
|
||||||
audio_source = big_config[i].audio_source
|
pcm_frame = await anext(big['audio_input'].frames(big['lc3_frame_samples']), None)
|
||||||
input_format = 'auto'
|
if pcm_frame is None: # Not all streams may stop at the same time
|
||||||
audio_input = await audio_io.create_audio_input(audio_source, input_format)
|
stream_finished[i] = True
|
||||||
audio_input.rewind = big_config[i].loop_wav
|
continue
|
||||||
pcm_format = await audio_input.open()
|
|
||||||
|
|
||||||
#try:
|
lc3_frame = big['encoder'].encode(
|
||||||
if pcm_format.channels != 1:
|
pcm_frame, num_bytes=big['lc3_bytes_per_frame'], bit_depth=big['pcm_bit_depth']
|
||||||
print("Only 1 channels PCM configurations are supported")
|
|
||||||
return
|
|
||||||
if pcm_format.sample_type == audio_io.PcmFormat.SampleType.INT16:
|
|
||||||
pcm_bit_depth = 16
|
|
||||||
elif pcm_format.sample_type == audio_io.PcmFormat.SampleType.FLOAT32:
|
|
||||||
pcm_bit_depth = None
|
|
||||||
else:
|
|
||||||
print("Only INT16 and FLOAT32 sample types are supported")
|
|
||||||
return
|
|
||||||
encoder = lc3.Encoder(
|
|
||||||
frame_duration_us=global_config.frame_duration_us,
|
|
||||||
sample_rate_hz=global_config.auracast_sampling_rate_hz,
|
|
||||||
num_channels=1,
|
|
||||||
input_sample_rate_hz=pcm_format.sample_rate,
|
|
||||||
)
|
)
|
||||||
lc3_frame_samples = encoder.get_frame_samples() # number of the pcm samples per lc3 frame
|
await big['iso_queue'].write(lc3_frame)
|
||||||
lc3_frame_size = global_config.octets_per_frame #encoder.get_frame_bytes(bitrate)
|
|
||||||
lc3_bytes_per_frame = lc3_frame_size #* 2 #multiplied by number of channels
|
|
||||||
|
|
||||||
bigs[f'big{i}']['lc3_frame_samples'] = lc3_frame_samples
|
if all(stream_finished): # Take into account that multiple files have different lengths
|
||||||
bigs[f'big{i}']['audio_input'] = audio_input
|
print('All streams finished, stopping streamer')
|
||||||
bigs[f'big{i}']['encoder'] = encoder
|
break
|
||||||
|
|
||||||
async def streamer(bigs):
|
|
||||||
# TODO: do some pre buffering so the stream is stable from the beginning. One half iso queue would be appropriate
|
|
||||||
while True:
|
|
||||||
stream_finished = [False for _ in range(len(bigs))]
|
|
||||||
for i, big in enumerate(bigs.values()):
|
|
||||||
pcm_frame = await anext(big['audio_input'].frames(big['lc3_frame_samples']), None)
|
|
||||||
if pcm_frame is None: # Not all streams may stop at the same time
|
|
||||||
stream_finished[i] = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
lc3_frame = big['encoder'].encode(
|
#return streamer(bigs)
|
||||||
pcm_frame, num_bytes=lc3_bytes_per_frame, bit_depth=pcm_bit_depth
|
#await stream # running until stream ends
|
||||||
)
|
|
||||||
await big['iso_queue'].write(lc3_frame) # iso_queue.write(lc3_frame)
|
|
||||||
|
|
||||||
|
|
||||||
if all(stream_finished): # TODO: Take into account that multiple files have different lengths
|
|
||||||
print('All streams finished, stopping streamer')
|
|
||||||
break
|
|
||||||
|
|
||||||
stream = streamer(bigs)
|
|
||||||
|
|
||||||
await stream # running until stream ends
|
|
||||||
|
|
||||||
return bigs
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Main
|
# Main
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
async def broadcast(global_conf: auracast_config.AuracastGlobalConfig, big_conf: List[auracast_config.AuracastBigConfig]):
|
async def broadcast(global_conf: auracast_config.AuracastGlobalConfig, big_conf: List[auracast_config.AuracastBigConfig]):
|
||||||
"""Start a broadcast as a source."""
|
"""Start a broadcast."""
|
||||||
ret = await setup_broadcast(
|
async with create_device(global_conf) as device:
|
||||||
global_conf,
|
if not device.supports_le_periodic_advertising:
|
||||||
big_conf
|
logger.error(color('Periodic advertising not supported', 'red'))
|
||||||
)
|
return
|
||||||
|
|
||||||
|
bigs = await setup_broadcast( # the bigs dictionary contains all the global configurations
|
||||||
|
device,
|
||||||
|
global_conf,
|
||||||
|
big_conf
|
||||||
|
)
|
||||||
|
await setup_audio(
|
||||||
|
bigs,
|
||||||
|
global_conf,
|
||||||
|
big_conf
|
||||||
|
)
|
||||||
|
|
||||||
|
await streamer(bigs)
|
||||||
|
|
||||||
# make a second coroutine to run the streaming - maybe even use the streamer coroutine
|
# make a second coroutine to run the streaming - maybe even use the streamer coroutine
|
||||||
# start it without await and go into a infinite loop were further instrucations via a ui can be given ?
|
# start it without await and go into a infinite loop were further instrucations via a ui can be given ?
|
||||||
|
|
||||||
@@ -342,7 +357,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# global_conf.transport='usb:2fe3:000b' #nrf52dongle hci_usb # TODO: iso packet over usb not supported
|
# global_conf.transport='usb:2fe3:000b' #nrf52dongle hci_usb # TODO: iso packet over usb not supported
|
||||||
|
|
||||||
|
|
||||||
# TODO: How can we use other iso interval than 10ms ?(medium or low rel) ? - nrf53audio receiver repports I2S tx underrun
|
# TODO: How can we use other iso interval than 10ms ?(medium or low rel) ? - nrf53audio receiver repports I2S tx underrun
|
||||||
#global_conf.qos_config = auracast_config.qos_config_mono_medium_rel
|
#global_conf.qos_config = auracast_config.qos_config_mono_medium_rel
|
||||||
global_conf.qos_config = auracast_config.qos_config_mono_high_rel
|
global_conf.qos_config = auracast_config.qos_config_mono_high_rel
|
||||||
@@ -366,7 +381,7 @@ if __name__ == "__main__":
|
|||||||
global_conf.auracast_sampling_rate_hz = 16000
|
global_conf.auracast_sampling_rate_hz = 16000
|
||||||
global_conf.octets_per_frame = 40 # 32kbps@16kHz
|
global_conf.octets_per_frame = 40 # 32kbps@16kHz
|
||||||
#global_conf.debug = True
|
#global_conf.debug = True
|
||||||
|
|
||||||
run_async(
|
run_async(
|
||||||
broadcast(
|
broadcast(
|
||||||
global_conf,
|
global_conf,
|
||||||
@@ -382,4 +397,3 @@ if __name__ == "__main__":
|
|||||||
# (realtime audio network uncoded)
|
# (realtime audio network uncoded)
|
||||||
|
|
||||||
# TODO: add support for playing new files will keeping the advertising running
|
# TODO: add support for playing new files will keeping the advertising running
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ version = "0.0.1"
|
|||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumble @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/bumble.git@e027bcb57a0f29c82e3c02c8bb8691dcb91eac62",
|
"bumble @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/bumble_mirror.git@e027bcb57a0f29c82e3c02c8bb8691dcb91eac62",
|
||||||
"lc3 @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/liblc3.git@7558637303106c7ea971e7bb8cedf379d3e08bcc",
|
"lc3 @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/liblc3.git@7558637303106c7ea971e7bb8cedf379d3e08bcc",
|
||||||
"sounddevice",
|
"sounddevice",
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user