add support for lc3 files and raw bytes
This commit is contained in:
Vendored
+6
-1
@@ -3,10 +3,15 @@
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Setup project for development",
|
||||
"type": "shell",
|
||||
"command": "./venv/bin/python -m pip install -e ."
|
||||
},
|
||||
{
|
||||
"label": "pip install -e bumble",
|
||||
"type": "shell",
|
||||
"command": "./venv/bin/python -m pip install -e ../bumble --config-settings editable_mode=compat"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class AuracastBigConfig:
|
||||
program_info: str = 'Some Announcements'
|
||||
audio_source: str = 'file:./auracast/announcement_48_10_96000_en.wav'
|
||||
input_format: str = 'auto'
|
||||
loop_wav: bool = True
|
||||
loop: bool = True
|
||||
precode_wav: bool = False
|
||||
iso_que_len: int = 64
|
||||
|
||||
@@ -66,7 +66,7 @@ broadcast_de = AuracastBigConfig(
|
||||
name = 'Broadcast0',
|
||||
language='deu',
|
||||
program_info = 'Announcements German',
|
||||
audio_source = 'file:./auracast/announcement_48_10_96000_de.wav',
|
||||
audio_source = 'file:./auracast/testdata/announcement_de.wav',
|
||||
)
|
||||
|
||||
broadcast_en = AuracastBigConfig(
|
||||
@@ -75,7 +75,7 @@ broadcast_en = AuracastBigConfig(
|
||||
name = 'Broadcast1',
|
||||
language='eng',
|
||||
program_info = 'Announcements English',
|
||||
audio_source = 'file:./auracast/announcement_48_10_96000_en.wav',
|
||||
audio_source = 'file:./auracast/testdata/announcement_en.wav',
|
||||
)
|
||||
|
||||
broadcast_fr = AuracastBigConfig(
|
||||
@@ -84,7 +84,7 @@ broadcast_fr = AuracastBigConfig(
|
||||
name = 'Broadcast2',
|
||||
language='fra',
|
||||
program_info = 'Announcements French',
|
||||
audio_source = 'file:./auracast/announcement_48_10_96000_fr.wav',
|
||||
audio_source = 'file:./auracast/testdata/announcement_fr.wav',
|
||||
)
|
||||
|
||||
broadcast_es = AuracastBigConfig(
|
||||
@@ -93,7 +93,7 @@ broadcast_es = AuracastBigConfig(
|
||||
name = 'Broadcast3',
|
||||
language='spa',
|
||||
program_info = 'Announcements Spanish',
|
||||
audio_source = 'file:./auracast/announcement_48_10_96_es.wav',
|
||||
audio_source = 'file:./auracast/testdata/announcement_es.wav',
|
||||
)
|
||||
|
||||
broadcast_it = AuracastBigConfig(
|
||||
@@ -102,5 +102,5 @@ broadcast_it = AuracastBigConfig(
|
||||
name = 'Broadcast4',
|
||||
language='ita',
|
||||
program_info = 'Announcements Italian',
|
||||
audio_source = 'file:./auracast/announcement_48_10_96_it.wav',
|
||||
audio_source = 'file:./auracast/testdata/announcement_it.wav',
|
||||
)
|
||||
+67
-22
@@ -94,6 +94,36 @@ class ModWaveAudioInput(audio_io.ThreadedAudioInput):
|
||||
|
||||
audio_io.WaveAudioInput = ModWaveAudioInput
|
||||
|
||||
|
||||
def read_lc3_file(filepath):
|
||||
filepath = filepath.replace('file:', '')
|
||||
with open(filepath, 'rb') as f_lc3:
|
||||
header = struct.unpack('=HHHHHHHI', f_lc3.read(18))
|
||||
if header[0] != 0xcc1c:
|
||||
raise ValueError('Invalid bitstream file')
|
||||
|
||||
# found in liblc3 - decoder.py
|
||||
samplerate = header[2] * 100
|
||||
nchannels = header[4]
|
||||
frame_duration = header[5] * 10
|
||||
stream_length = header[7]
|
||||
#lc3_frame_size = struct.unpack('=H', f_lc3.read(2))[0]
|
||||
logging.info('Loaded lc3 file: %s', filepath)
|
||||
logging.info('samplerate: %s', samplerate)
|
||||
logging.info('nchannels %s', nchannels)
|
||||
logging.info('frame_duration %s', frame_duration)
|
||||
logging.info('stream_length %s', stream_length)
|
||||
|
||||
lc3_bytes= b''
|
||||
while True:
|
||||
b = f_lc3.read(2)
|
||||
if b == b'':
|
||||
break
|
||||
lc3_frame_size = struct.unpack('=H', b)[0]
|
||||
lc3_bytes += f_lc3.read(lc3_frame_size)
|
||||
|
||||
return lc3_bytes
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -303,8 +333,8 @@ async def init_audio(
|
||||
pass
|
||||
else:
|
||||
audio_input = await audio_io.create_audio_input(audio_source, input_format)
|
||||
|
||||
audio_input.rewind = big_config[i].loop_wav
|
||||
|
||||
audio_input.rewind = big_config[i].loop
|
||||
pcm_format = await audio_input.open()
|
||||
|
||||
#try:
|
||||
@@ -335,8 +365,18 @@ async def init_audio(
|
||||
big['encoder'] = encoder
|
||||
|
||||
class Streamer():
|
||||
"""
|
||||
Streamer class that supports multiple input formats. See bumble for streaming from wav or device
|
||||
Added functionallity on top of bumble:
|
||||
- precode wav files,
|
||||
- lc3 coded files
|
||||
- just use a .lc3 file as audio_source
|
||||
- lc3 coded from ram
|
||||
- use a bytestring b'' as audio_source
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
self,
|
||||
bigs,
|
||||
global_config : auracast_config.AuracastGlobalConfig,
|
||||
big_config: List[auracast_config.AuracastBigConfig]
|
||||
@@ -362,7 +402,6 @@ class Streamer():
|
||||
self.task = None
|
||||
|
||||
async def stream(self):
|
||||
|
||||
bigs = self.bigs
|
||||
big_config = self.big_config
|
||||
global_config = self.global_config
|
||||
@@ -374,26 +413,28 @@ class Streamer():
|
||||
# precoded lc3 from ram
|
||||
if isinstance(big_config[i].audio_source, bytes):
|
||||
big['precoded'] = True
|
||||
lc3_frames = big_config[i].audio_source,
|
||||
|
||||
if big_config[i].loop_wav:
|
||||
lc3_frames = iter(big_config[i].audio_source)
|
||||
|
||||
if big_config[i].loop:
|
||||
lc3_frames = itertools.cycle(lc3_frames)
|
||||
big['lc3_frames'] = lc3_frames
|
||||
|
||||
# precoded lc3 file
|
||||
elif big_config[i].audio_source.endswith('.lc3'):
|
||||
big['precoded'] = True
|
||||
filename = big_config[i].audio_source.replace('file:', '')
|
||||
|
||||
with open (big_config[i].audio_source, 'r') as f:
|
||||
lc3_frames = f.read()
|
||||
lc3_bytes = read_lc3_file(filename)
|
||||
lc3_frames = iter(lc3_bytes)
|
||||
|
||||
if big_config[i].loop_wav:
|
||||
if big_config[i].loop:
|
||||
lc3_frames = itertools.cycle(lc3_frames)
|
||||
big['lc3_frames'] = lc3_frames
|
||||
|
||||
# use wav files and code them entirely before streaming
|
||||
elif big_config[i].precode_wav and big_config[i].audio_source.endswith('.wav'):
|
||||
big['precoded'] = True
|
||||
big['precoded'] = True
|
||||
|
||||
audio_input = await audio_io.create_audio_input(audio_source, input_format)
|
||||
audio_input.rewind = False
|
||||
@@ -416,22 +457,23 @@ class Streamer():
|
||||
input_sample_rate_hz=pcm_format.sample_rate,
|
||||
)
|
||||
lc3_frame_samples = encoder.get_frame_samples() # number of the pcm samples per lc3 frame
|
||||
|
||||
lc3_frames = b''
|
||||
|
||||
lc3_bytes = b''
|
||||
async for pcm_frame in audio_input.frames(lc3_frame_samples):
|
||||
lc3_frames += encoder.encode(
|
||||
lc3_bytes += encoder.encode(
|
||||
pcm_frame, num_bytes=global_config.octets_per_frame, bit_depth=pcm_bit_depth
|
||||
)
|
||||
lc3_frames = iter(lc3_bytes)
|
||||
|
||||
# have a look at itertools.islice
|
||||
if big_config[i].loop_wav:
|
||||
if big_config[i].loop:
|
||||
lc3_frames = itertools.cycle(lc3_frames)
|
||||
big['lc3_frames'] = lc3_frames
|
||||
big['lc3_frames'] = lc3_frames
|
||||
|
||||
# anything else, e.g. realtime stream from device
|
||||
else:
|
||||
# anything else, e.g. realtime stream from device (bumble)
|
||||
else:
|
||||
audio_input = await audio_io.create_audio_input(audio_source, input_format)
|
||||
audio_input.rewind = big_config[i].loop_wav
|
||||
audio_input.rewind = big_config[i].loop
|
||||
pcm_format = await audio_input.open()
|
||||
|
||||
#try:
|
||||
@@ -457,8 +499,8 @@ class Streamer():
|
||||
big['lc3_frame_samples'] = lc3_frame_samples
|
||||
big['audio_input'] = audio_input
|
||||
big['encoder'] = encoder
|
||||
big['precoded'] = False
|
||||
|
||||
big['precoded'] = False
|
||||
|
||||
# Need for coded an uncoded audio
|
||||
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
|
||||
@@ -475,6 +517,7 @@ class Streamer():
|
||||
|
||||
if big['precoded']:# everything was already lc3 coded beforehand
|
||||
lc3_frame = b""
|
||||
# list(itertools.islice(iterator, 3))
|
||||
for _ in range(big['lc3_bytes_per_frame']): # have a look at itertools.islice
|
||||
lc3_frame += next(big['lc3_frames']).to_bytes() # TODO: use async ?
|
||||
else:
|
||||
@@ -548,11 +591,13 @@ if __name__ == "__main__":
|
||||
#auracast_config.broadcast_es,
|
||||
#auracast_config.broadcast_it,
|
||||
]
|
||||
for big in bigs: # TODO. investigate this further
|
||||
for big in bigs: # TODO: encrypted streams are not working
|
||||
#big.code = 'ff'*16 # returns hci/HCI_ENCRYPTION_MODE_NOT_ACCEPTABLE_ERROR
|
||||
#big.code = '78 e5 dc f1 34 ab 42 bf c1 92 ef dd 3a fd 67 ae'
|
||||
big.precode_wav = True
|
||||
|
||||
big.audio_source = big.audio_source.replace('.wav', '_10_16_32.lc3') #lc3 precoded files
|
||||
big.audio_source = read_lc3_file(big.audio_source) # load files in advance
|
||||
|
||||
# 16kHz works reliably with 3 streams
|
||||
# 24kHz is only working with 2 streams - probably airtime constraint
|
||||
# TODO: with more than three broadcasters (16kHz) no advertising (no primary channels is present anymore)
|
||||
|
||||
@@ -131,7 +131,7 @@ async def main():
|
||||
#auracast_config.broadcast_it,
|
||||
]
|
||||
for conf in big_conf:
|
||||
conf.loop_wav = False
|
||||
conf.loop = False
|
||||
|
||||
# look into:
|
||||
#async with MyAPI() as api:
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
# use liblc3
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
frame_dur_ms=10
|
||||
srate=16000
|
||||
bps=32000
|
||||
|
||||
if __name__ == '__main__':
|
||||
workdir = os.path.dirname(__file__)
|
||||
os.chdir(workdir)
|
||||
files = os.listdir(workdir)
|
||||
filtered = [file for file in files if file.endswith('.wav')]
|
||||
|
||||
for file in filtered:
|
||||
cmd = [
|
||||
'elc3',
|
||||
'-b', f'{bps}',
|
||||
'-m', f'{frame_dur_ms}' ,
|
||||
'-r', f'{srate}',
|
||||
f'{file}', f'{file.replace('.wav', '')}_{frame_dur_ms}_{srate//1000}_{bps//1000}.lc3'
|
||||
]
|
||||
print("Executing: ", " ".join(cmd))
|
||||
ret = subprocess.run(cmd, check=True)
|
||||
print(ret.returncode, ret.stdout, ret.stderr)
|
||||
Reference in New Issue
Block a user