Fix multiple streams from precoded file

This commit is contained in:
pstruebi
2025-07-03 15:40:16 +02:00
parent d8e60a5da7
commit 74598be98b
2 changed files with 56 additions and 53 deletions

View File

@@ -58,25 +58,26 @@ class AuracastBigConfig(BaseModel):
class AuracastBigConfigDeu(AuracastBigConfig): class AuracastBigConfigDeu(AuracastBigConfig):
id: int = 12 id: int = 12
random_address: str = 'F1:F1:F2:F3:F4:F5' random_address: str = 'F1:F1:F2:F3:F4:F5'
name: str = 'Broadcast0' name: str = 'Hörsaal A'
language: str ='deu' language: str ='deu'
program_info: str = 'Announcements German' program_info: str = 'Vorlesung DE'
audio_source: str = 'file:./testdata/announcement_de.wav' audio_source: str = 'file:./testdata/announcement_de.wav'
class AuracastBigConfigEng(AuracastBigConfig): class AuracastBigConfigEng(AuracastBigConfig):
id: int = 123 id: int = 123
random_address: str = 'F2:F1:F2:F3:F4:F5' random_address: str = 'F2:F1:F2:F3:F4:F5'
name: str = 'Broadcast1' name: str = 'Lecture Hall A'
language: str ='eng' language: str ='eng'
program_info: str = 'Announcements English' program_info: str = 'Lecture EN'
audio_source: str = 'file:./testdata/announcement_en.wav' audio_source: str = 'file:./testdata/announcement_en.wav'
class AuracastBigConfigFra(AuracastBigConfig): class AuracastBigConfigFra(AuracastBigConfig):
id: int = 1234 id: int = 1234
random_address: str = 'F3:F1:F2:F3:F4:F5' random_address: str = 'F3:F1:F2:F3:F4:F5'
name: str = 'Broadcast2' # French
name: str = 'Auditoire A'
language: str ='fra' language: str ='fra'
program_info: str = 'Announcements French' program_info: str = 'Auditoire FR'
audio_source: str = 'file:./testdata/announcement_fr.wav' audio_source: str = 'file:./testdata/announcement_fr.wav'
class AuracastBigConfigSpa(AuracastBigConfig): class AuracastBigConfigSpa(AuracastBigConfig):

View File

@@ -395,6 +395,7 @@ class Streamer():
# precoded lc3 from ram # precoded lc3 from ram
elif isinstance(big_config[i].audio_source, bytes): elif isinstance(big_config[i].audio_source, bytes):
big['precoded'] = True big['precoded'] = True
big['lc3_bytes_per_frame'] = global_config.octets_per_frame
lc3_frames = iter(big_config[i].audio_source) lc3_frames = iter(big_config[i].audio_source)
@@ -405,6 +406,7 @@ class Streamer():
# precoded lc3 file # precoded lc3 file
elif big_config[i].audio_source.endswith('.lc3'): elif big_config[i].audio_source.endswith('.lc3'):
big['precoded'] = True big['precoded'] = True
big['lc3_bytes_per_frame'] = global_config.octets_per_frame
filename = big_config[i].audio_source.replace('file:', '') filename = big_config[i].audio_source.replace('file:', '')
lc3_bytes = read_lc3_file(filename) lc3_bytes = read_lc3_file(filename)
@@ -417,6 +419,7 @@ class Streamer():
# use wav files and code them entirely before streaming # use wav files and code them entirely before streaming
elif big_config[i].precode_wav and big_config[i].audio_source.endswith('.wav'): elif big_config[i].precode_wav and big_config[i].audio_source.endswith('.wav'):
big['precoded'] = True big['precoded'] = True
big['lc3_bytes_per_frame'] = global_config.octets_per_frame
audio_input = await audio_io.create_audio_input(audio_source, input_format) audio_input = await audio_io.create_audio_input(audio_source, input_format)
audio_input.rewind = False audio_input.rewind = False
@@ -540,52 +543,52 @@ class Streamer():
big['precoded'] = False big['precoded'] = False
logging.info("Streaming audio...") logging.info("Streaming audio...")
bigs = self.bigs bigs = self.bigs
self.is_streaming = True self.is_streaming = True
# One streamer fits all # One streamer fits all
while self.is_streaming: while self.is_streaming:
stream_finished = [False for _ in range(len(bigs))] stream_finished = [False for _ in range(len(bigs))]
for i, big in enumerate(bigs.values()): for i, big in enumerate(bigs.values()):
if big['precoded']:# everything was already lc3 coded beforehand if big['precoded']:# everything was already lc3 coded beforehand
lc3_frame = bytes( lc3_frame = bytes(
itertools.islice(big['lc3_frames'], big['lc3_bytes_per_frame']) itertools.islice(big['lc3_frames'], big['lc3_bytes_per_frame'])
)
if lc3_frame == b'': # Not all streams may stop at the same time
stream_finished[i] = True
continue
else: # code lc3 on the fly
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
# Down-mix multi-channel PCM to mono for LC3 encoder if needed
if big.get('channels', 1) > 1:
if isinstance(pcm_frame, np.ndarray):
if pcm_frame.ndim > 1:
mono = pcm_frame.mean(axis=1).astype(pcm_frame.dtype)
pcm_frame = mono
else:
# Convert raw bytes to numpy, average channels, convert back
dtype = np.int16 if big['pcm_bit_depth'] == 16 else np.float32
samples = np.frombuffer(pcm_frame, dtype=dtype)
samples = samples.reshape(-1, big['channels']).mean(axis=1)
pcm_frame = samples.astype(dtype).tobytes()
lc3_frame = big['encoder'].encode(
pcm_frame, num_bytes=big['lc3_bytes_per_frame'], bit_depth=big['pcm_bit_depth']
) )
await big['iso_queue'].write(lc3_frame) if lc3_frame == b'': # Not all streams may stop at the same time
stream_finished[i] = True
continue
else: # code lc3 on the fly
pcm_frame = await anext(big['audio_input'].frames(big['lc3_frame_samples']), None)
if all(stream_finished): # Take into account that multiple files have different lengths if pcm_frame is None: # Not all streams may stop at the same time
logging.info('All streams finished, stopping streamer') stream_finished[i] = True
self.is_streaming = False continue
break
# Down-mix multi-channel PCM to mono for LC3 encoder if needed
if big.get('channels', 1) > 1:
if isinstance(pcm_frame, np.ndarray):
if pcm_frame.ndim > 1:
mono = pcm_frame.mean(axis=1).astype(pcm_frame.dtype)
pcm_frame = mono
else:
# Convert raw bytes to numpy, average channels, convert back
dtype = np.int16 if big['pcm_bit_depth'] == 16 else np.float32
samples = np.frombuffer(pcm_frame, dtype=dtype)
samples = samples.reshape(-1, big['channels']).mean(axis=1)
pcm_frame = samples.astype(dtype).tobytes()
lc3_frame = big['encoder'].encode(
pcm_frame, num_bytes=big['lc3_bytes_per_frame'], bit_depth=big['pcm_bit_depth']
)
await big['iso_queue'].write(lc3_frame)
if all(stream_finished): # Take into account that multiple files have different lengths
logging.info('All streams finished, stopping streamer')
self.is_streaming = False
break
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -634,12 +637,11 @@ if __name__ == "__main__":
) )
os.chdir(os.path.dirname(__file__)) os.chdir(os.path.dirname(__file__))
config = auracast_config.AuracastConfigGroup( config = auracast_config.AuracastConfigGroup(
bigs = [ bigs = [
auracast_config.AuracastBigConfigDeu(), auracast_config.AuracastBigConfigDeu(),
#auracast_config.AuracastBigConfigEng(), auracast_config.AuracastBigConfigEng(),
#auracast_config.AuracastBigConfigFra(), auracast_config.AuracastBigConfigFra(),
#auracast_config.AuracastBigConfigEs(), #auracast_config.AuracastBigConfigEs(),
#auracast_config.AuracastBigConfigIt(), #auracast_config.AuracastBigConfigIt(),
] ]
@@ -653,8 +655,8 @@ if __name__ == "__main__":
#config.transport='serial:/dev/serial/by-id/usb-SEGGER_J-Link_001057705357-if02,1000000,rtscts' # transport for nrf54l15dk #config.transport='serial:/dev/serial/by-id/usb-SEGGER_J-Link_001057705357-if02,1000000,rtscts' # transport for nrf54l15dk
#config.transport='serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_95A087EADB030B24-if00,115200,rtscts' #nrf52dongle hci_uart usb cdc #config.transport='serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_95A087EADB030B24-if00,115200,rtscts' #nrf52dongle hci_uart usb cdc
#config.transport='usb:2fe3:000b' #nrf52dongle hci_usb # TODO: iso packet over usb not supported #config.transport='usb:2fe3:000b' #nrf52dongle hci_usb # TODO: iso packet over usb not supported
config.transport= 'auto' # config.transport= 'auto'
#config.transport='serial:/dev/ttyAMA2,1000000,rtscts' # transport for raspberry pi config.transport='serial:/dev/ttyAMA3,1000000,rtscts' # transport for raspberry pi
for big in config.bigs: # TODO: encrypted streams are not working for big in config.bigs: # TODO: encrypted streams are not working