start implementing lc3 precoded support
This commit is contained in:
@@ -55,6 +55,7 @@ class AuracastBigConfig:
|
|||||||
audio_source: str = 'file:./auracast/announcement_48_10_96000_en.wav'
|
audio_source: str = 'file:./auracast/announcement_48_10_96000_en.wav'
|
||||||
input_format: str = 'auto'
|
input_format: str = 'auto'
|
||||||
loop_wav: bool = True
|
loop_wav: bool = True
|
||||||
|
precode_wav: bool = False
|
||||||
iso_que_len: int = 64
|
iso_que_len: int = 64
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -296,7 +296,14 @@ async def init_audio(
|
|||||||
for i, big in enumerate(bigs.values()):
|
for i, big in enumerate(bigs.values()):
|
||||||
audio_source = big_config[i].audio_source
|
audio_source = big_config[i].audio_source
|
||||||
input_format = big_config[i].input_format
|
input_format = big_config[i].input_format
|
||||||
|
|
||||||
|
if big_config[i].audio_source.endswith('.lc3'):
|
||||||
|
pass
|
||||||
|
elif big_config[i].precode_wav:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
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 = big_config[i].loop_wav
|
audio_input.rewind = big_config[i].loop_wav
|
||||||
pcm_format = await audio_input.open()
|
pcm_format = await audio_input.open()
|
||||||
|
|
||||||
@@ -328,10 +335,17 @@ async def init_audio(
|
|||||||
big['encoder'] = encoder
|
big['encoder'] = encoder
|
||||||
|
|
||||||
class Streamer():
|
class Streamer():
|
||||||
def __init__(self, bigs):
|
def __init__(
|
||||||
|
self,
|
||||||
|
bigs,
|
||||||
|
global_config : auracast_config.AuracastGlobalConfig,
|
||||||
|
big_config: List[auracast_config.AuracastBigConfig]
|
||||||
|
):
|
||||||
self.task = None
|
self.task = None
|
||||||
self.is_streaming = False
|
self.is_streaming = False
|
||||||
self.bigs = bigs
|
self.bigs = bigs
|
||||||
|
self.global_config = global_config
|
||||||
|
self.big_config = big_config
|
||||||
|
|
||||||
def start_streaming(self):
|
def start_streaming(self):
|
||||||
if not self.is_streaming:
|
if not self.is_streaming:
|
||||||
@@ -348,13 +362,122 @@ class Streamer():
|
|||||||
self.task = None
|
self.task = None
|
||||||
|
|
||||||
async def stream(self):
|
async def stream(self):
|
||||||
# TODO: do some pre buffering so the stream is stable from the beginning. One half iso queue would be appropriate
|
|
||||||
|
bigs = self.bigs
|
||||||
|
big_config = self.big_config
|
||||||
|
global_config = self.global_config
|
||||||
|
# init
|
||||||
|
for i, big in enumerate(bigs.values()):
|
||||||
|
audio_source = big_config[i].audio_source
|
||||||
|
input_format = big_config[i].input_format
|
||||||
|
|
||||||
|
# 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 = itertools.cycle(lc3_frames)
|
||||||
|
big['lc3_frames'] = lc3_frames
|
||||||
|
|
||||||
|
# precoded lc3 file
|
||||||
|
elif big_config[i].audio_source.endswith('.lc3'):
|
||||||
|
big['precoded'] = True
|
||||||
|
|
||||||
|
with open (big_config[i].audio_source, 'r') as f:
|
||||||
|
lc3_frames = f.read()
|
||||||
|
|
||||||
|
if big_config[i].loop_wav:
|
||||||
|
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
|
||||||
|
|
||||||
|
audio_input = await audio_io.create_audio_input(audio_source, input_format)
|
||||||
|
audio_input.rewind = False
|
||||||
|
pcm_format = await audio_input.open()
|
||||||
|
|
||||||
|
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_frames = b''
|
||||||
|
async for pcm_frame in audio_input.frames(lc3_frame_samples):
|
||||||
|
lc3_frames += encoder.encode(
|
||||||
|
pcm_frame, num_bytes=global_config.octets_per_frame, bit_depth=pcm_bit_depth
|
||||||
|
)
|
||||||
|
|
||||||
|
# have a look at itertools.islice
|
||||||
|
if big_config[i].loop_wav:
|
||||||
|
lc3_frames = itertools.cycle(lc3_frames)
|
||||||
|
big['lc3_frames'] = lc3_frames
|
||||||
|
|
||||||
|
# anything else, e.g. realtime stream from device
|
||||||
|
else:
|
||||||
|
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
|
||||||
|
|
||||||
|
big['pcm_bit_depth'] = pcm_bit_depth
|
||||||
|
big['lc3_frame_samples'] = lc3_frame_samples
|
||||||
|
big['audio_input'] = audio_input
|
||||||
|
big['encoder'] = encoder
|
||||||
|
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
|
||||||
|
big['lc3_bytes_per_frame'] = lc3_bytes_per_frame
|
||||||
|
|
||||||
|
# TODO: Maybe do some pre buffering so the stream is stable from the beginning. One half iso queue would be appropriate
|
||||||
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
|
||||||
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
|
||||||
|
lc3_frame = b""
|
||||||
|
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:
|
||||||
pcm_frame = await anext(big['audio_input'].frames(big['lc3_frame_samples']), None)
|
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
|
if pcm_frame is None: # Not all streams may stop at the same time
|
||||||
stream_finished[i] = True
|
stream_finished[i] = True
|
||||||
@@ -363,6 +486,7 @@ class Streamer():
|
|||||||
lc3_frame = big['encoder'].encode(
|
lc3_frame = big['encoder'].encode(
|
||||||
pcm_frame, num_bytes=big['lc3_bytes_per_frame'], bit_depth=big['pcm_bit_depth']
|
pcm_frame, num_bytes=big['lc3_bytes_per_frame'], bit_depth=big['pcm_bit_depth']
|
||||||
)
|
)
|
||||||
|
|
||||||
await big['iso_queue'].write(lc3_frame)
|
await big['iso_queue'].write(lc3_frame)
|
||||||
|
|
||||||
if all(stream_finished): # Take into account that multiple files have different lengths
|
if all(stream_finished): # Take into account that multiple files have different lengths
|
||||||
@@ -387,12 +511,7 @@ async def broadcast(global_conf: auracast_config.AuracastGlobalConfig, big_conf:
|
|||||||
global_conf,
|
global_conf,
|
||||||
big_conf
|
big_conf
|
||||||
)
|
)
|
||||||
await init_audio(
|
streamer = Streamer(bigs, global_conf, big_conf)
|
||||||
bigs,
|
|
||||||
global_conf,
|
|
||||||
big_conf
|
|
||||||
)
|
|
||||||
streamer = Streamer(bigs)
|
|
||||||
streamer.start_streaming()
|
streamer.start_streaming()
|
||||||
|
|
||||||
await asyncio.wait([streamer.task])
|
await asyncio.wait([streamer.task])
|
||||||
@@ -429,10 +548,10 @@ if __name__ == "__main__":
|
|||||||
#auracast_config.broadcast_es,
|
#auracast_config.broadcast_es,
|
||||||
#auracast_config.broadcast_it,
|
#auracast_config.broadcast_it,
|
||||||
]
|
]
|
||||||
#for big in bigs: # TODO. investigate this further
|
for big in bigs: # TODO. investigate this further
|
||||||
# big.code = 'ff'*16 # returns hci/HCI_ENCRYPTION_MODE_NOT_ACCEPTABLE_ERROR
|
#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.code = '78 e5 dc f1 34 ab 42 bf c1 92 ef dd 3a fd 67 ae'
|
||||||
|
big.precode_wav = True
|
||||||
|
|
||||||
# 16kHz works reliably with 3 streams
|
# 16kHz works reliably with 3 streams
|
||||||
# 24kHz is only working with 2 streams - probably airtime constraint
|
# 24kHz is only working with 2 streams - probably airtime constraint
|
||||||
|
|||||||
Reference in New Issue
Block a user