refactor package structure and make improvements regarding design
This commit is contained in:
0
apps/__init__.py
Normal file
0
apps/__init__.py
Normal file
@@ -33,8 +33,7 @@ import numpy as np
|
|||||||
import pyee
|
import pyee
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
sys.path.append('../utils')
|
from leaudio import LeAudioEncoder
|
||||||
from utils.le_audio_encoder import LeAudioEncoder
|
|
||||||
|
|
||||||
from bumble.colors import color
|
from bumble.colors import color
|
||||||
from bumble import company_ids
|
from bumble import company_ids
|
||||||
@@ -49,6 +48,8 @@ import bumble.device
|
|||||||
import bumble.transport
|
import bumble.transport
|
||||||
import bumble.utils
|
import bumble.utils
|
||||||
|
|
||||||
|
from leaudio import read_wav_file, generate_sine_data
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Logging
|
# Logging
|
||||||
@@ -66,30 +67,6 @@ AURACAST_DEFAULT_ATT_MTU = 256
|
|||||||
iso_index:int = 0
|
iso_index:int = 0
|
||||||
|
|
||||||
|
|
||||||
def generate_sine_wave_iso_frames(frequency, sampling_rate, duration):
|
|
||||||
num_samples = int(sampling_rate * duration)
|
|
||||||
|
|
||||||
t = np.linspace(0, duration, num_samples, False)
|
|
||||||
|
|
||||||
sine_wave = np.sin(2 * np.pi * frequency * t)
|
|
||||||
|
|
||||||
# Scale the sine wave to the 16-bit range (-32768 to 32767)
|
|
||||||
scaled_sine_wave = sine_wave * 8191.5
|
|
||||||
|
|
||||||
# Convert to 16-bit integer format
|
|
||||||
int16_sine_wave = scaled_sine_wave.astype(np.int16)
|
|
||||||
|
|
||||||
iso_frame = bytearray()
|
|
||||||
|
|
||||||
for num in int16_sine_wave:
|
|
||||||
|
|
||||||
iso_frame.append(num & 0xFF) # Extract lower 8 bits
|
|
||||||
|
|
||||||
iso_frame.append((num >> 8) & 0xFF) # Extract upper 8 bit
|
|
||||||
|
|
||||||
return iso_frame
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Scan For Broadcasts
|
# Scan For Broadcasts
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -747,29 +724,6 @@ async def run_receive(
|
|||||||
await terminated.wait()
|
await terminated.wait()
|
||||||
|
|
||||||
|
|
||||||
def read_wav_file(filename):
|
|
||||||
rate, data = wav.read(filename)
|
|
||||||
num_channels = data.ndim
|
|
||||||
|
|
||||||
if num_channels == 1:
|
|
||||||
left_channel = data[:]
|
|
||||||
else:
|
|
||||||
left_channel = data[:, 1]
|
|
||||||
|
|
||||||
print(len(left_channel))
|
|
||||||
upsampled_data = signal.resample(left_channel, int(
|
|
||||||
48000 / 41000 * left_channel.shape[0]))
|
|
||||||
|
|
||||||
# wav.write("upsampled_stereo_file.wav", app_specific_codec.sampling_frequency.hz, upsampled_data.astype(data.dtype))
|
|
||||||
print("Sample rate:", rate)
|
|
||||||
print("Number channels:", num_channels)
|
|
||||||
print("Audio data (left):", left_channel)
|
|
||||||
print("Bitdepth:", data.dtype.itemsize * 8)
|
|
||||||
|
|
||||||
return upsampled_data.astype(np.int16)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def run_broadcast(
|
async def run_broadcast(
|
||||||
transport: str, broadcast_id: int, broadcast_code: str | None, wav_file_path: str
|
transport: str, broadcast_id: int, broadcast_code: str | None, wav_file_path: str
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -781,25 +735,18 @@ async def run_broadcast(
|
|||||||
print(color('Periodic advertising not supported', 'red'))
|
print(color('Periodic advertising not supported', 'red'))
|
||||||
return
|
return
|
||||||
|
|
||||||
# encoder = lc3.Encoder(
|
|
||||||
# frame_duration_us=10000,
|
|
||||||
# sample_rate_hz=48000,
|
|
||||||
# num_channels=2,
|
|
||||||
# input_sample_rate_hz=wav.getframerate(),
|
|
||||||
# )
|
|
||||||
|
|
||||||
# create snoop file
|
# create snoop file
|
||||||
f = open("log.btsnoop", "wb")
|
f = open("log.btsnoop", "wb")
|
||||||
Snooper = BtSnooper(f)
|
Snooper = BtSnooper(f)
|
||||||
device.host.snooper = Snooper
|
device.host.snooper = Snooper
|
||||||
encoder.setup_encoders(48000,10000,1)
|
encoder.setup_encoders(48000,10000,1)
|
||||||
frames = list[bytes]()
|
frames = list[bytes]()
|
||||||
sine = generate_sine_wave_iso_frames(1000,48000,0.01)
|
sine = generate_sine_data(1000,48000,0.01)
|
||||||
print(len(sine))
|
print(len(sine))
|
||||||
|
|
||||||
sample_size = 480
|
sample_size = 480
|
||||||
print(wav_file_path)
|
print(wav_file_path)
|
||||||
upsampled_left_channel = read_wav_file(wav_file_path)
|
upsampled_left_channel = read_wav_file(wav_file_path,48000)
|
||||||
num_runs = len(upsampled_left_channel) // sample_size
|
num_runs = len(upsampled_left_channel) // sample_size
|
||||||
TEST_SINE = 0
|
TEST_SINE = 0
|
||||||
for i in range(num_runs):
|
for i in range(num_runs):
|
||||||
@@ -935,102 +882,12 @@ def run_async(async_command: Coroutine) -> None:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Main
|
# Main
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@click.group()
|
|
||||||
@click.pass_context
|
|
||||||
def auracast(ctx):
|
|
||||||
ctx.ensure_object(dict)
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
@auracast.command('scan')
|
@click.option('transport','-p', type=str)
|
||||||
|
@click.option('wav_file_path','-w' , type=str)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--filter-duplicates', is_flag=True, default=False, help='Filter duplicates'
|
'--broadcast_id',
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--sync-timeout',
|
|
||||||
metavar='SYNC_TIMEOUT',
|
|
||||||
type=float,
|
|
||||||
default=AURACAST_DEFAULT_SYNC_TIMEOUT,
|
|
||||||
help='Sync timeout (in seconds)',
|
|
||||||
)
|
|
||||||
@click.argument('transport')
|
|
||||||
@click.pass_context
|
|
||||||
def scan(ctx, filter_duplicates, sync_timeout, transport):
|
|
||||||
"""Scan for public broadcasts"""
|
|
||||||
run_async(run_scan(filter_duplicates, sync_timeout, transport))
|
|
||||||
|
|
||||||
|
|
||||||
@auracast.command('assist')
|
|
||||||
@click.option(
|
|
||||||
'--broadcast-name',
|
|
||||||
metavar='BROADCAST_NAME',
|
|
||||||
help='Broadcast Name to tune to',
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--source-id',
|
|
||||||
metavar='SOURCE_ID',
|
|
||||||
type=int,
|
|
||||||
help='Source ID (for remove-source command)',
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--command',
|
|
||||||
type=click.Choice(
|
|
||||||
['monitor-state', 'add-source', 'modify-source', 'remove-source']
|
|
||||||
),
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
@click.argument('transport')
|
|
||||||
@click.argument('address')
|
|
||||||
@click.pass_context
|
|
||||||
def assist(ctx, broadcast_name, source_id, command, transport, address):
|
|
||||||
"""Scan for broadcasts on behalf of a audio server"""
|
|
||||||
run_async(run_assist(broadcast_name, source_id, command, transport, address))
|
|
||||||
|
|
||||||
|
|
||||||
@auracast.command('pair')
|
|
||||||
@click.argument('transport')
|
|
||||||
@click.argument('address')
|
|
||||||
@click.pass_context
|
|
||||||
def pair(ctx, transport, address):
|
|
||||||
"""Pair with an audio server"""
|
|
||||||
run_async(run_pair(transport, address))
|
|
||||||
|
|
||||||
|
|
||||||
@auracast.command('receive')
|
|
||||||
@click.argument('transport')
|
|
||||||
@click.argument('broadcast_id', type=int)
|
|
||||||
@click.option(
|
|
||||||
'--broadcast-code',
|
|
||||||
metavar='BROADCAST_CODE',
|
|
||||||
type=str,
|
|
||||||
help='Broadcast encryption code in hex format',
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--sync-timeout',
|
|
||||||
metavar='SYNC_TIMEOUT',
|
|
||||||
type=float,
|
|
||||||
default=AURACAST_DEFAULT_SYNC_TIMEOUT,
|
|
||||||
help='Sync timeout (in seconds)',
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--subgroup',
|
|
||||||
metavar='SUBGROUP',
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help='Index of Subgroup',
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
def receive(ctx, transport, broadcast_id, broadcast_code, sync_timeout, subgroup):
|
|
||||||
"""Receive a broadcast source"""
|
|
||||||
run_async(
|
|
||||||
run_receive(transport, broadcast_id, broadcast_code, sync_timeout, subgroup)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@auracast.command('broadcast')
|
|
||||||
@click.argument('transport')
|
|
||||||
@click.argument('wav_file_path', type=str)
|
|
||||||
@click.option(
|
|
||||||
'--broadcast-id',
|
|
||||||
metavar='BROADCAST_ID',
|
metavar='BROADCAST_ID',
|
||||||
type=int,
|
type=int,
|
||||||
default=123456,
|
default=123456,
|
||||||
@@ -1042,9 +899,10 @@ def receive(ctx, transport, broadcast_id, broadcast_code, sync_timeout, subgroup
|
|||||||
type=str,
|
type=str,
|
||||||
help='Broadcast encryption code in hex format',
|
help='Broadcast encryption code in hex format',
|
||||||
)
|
)
|
||||||
@click.pass_context
|
|
||||||
def broadcast(ctx, transport, broadcast_id, broadcast_code, wav_file_path):
|
def broadcast(transport, broadcast_id, broadcast_code, wav_file_path):
|
||||||
"""Start a broadcast as a source."""
|
"""Start a broadcast as a source."""
|
||||||
|
#ctx.ensure_object(dict)
|
||||||
run_async(
|
run_async(
|
||||||
run_broadcast(
|
run_broadcast(
|
||||||
transport=transport,
|
transport=transport,
|
||||||
@@ -1056,8 +914,9 @@ def broadcast(ctx, transport, broadcast_id, broadcast_code, wav_file_path):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'ERROR').upper())
|
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'ERROR').upper())
|
||||||
auracast()
|
broadcast()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -1065,8 +924,9 @@ if __name__ == "__main__":
|
|||||||
main() # pylint: disable=no-value-for-parameter
|
main() # pylint: disable=no-value-for-parameter
|
||||||
|
|
||||||
|
|
||||||
# NOTES for the IOT747
|
# ####### NOTES for the IOT747
|
||||||
# Baudrate is 9600
|
# Set Baudrate to 9600
|
||||||
|
# SCAN 2 OFF 2
|
||||||
# open F0F1F2F3F4F5 BROAD 2 0 1e40
|
# open F0F1F2F3F4F5 BROAD 2 0 1e40
|
||||||
# MUSIC 91 PLAY
|
# MUSIC 91 PLAY
|
||||||
|
|
||||||
|
|||||||
@@ -38,15 +38,17 @@ from bumble.snoop import BtSnooper
|
|||||||
import functools
|
import functools
|
||||||
from bumble.profiles.ascs import AudioStreamControlServiceProxy
|
from bumble.profiles.ascs import AudioStreamControlServiceProxy
|
||||||
from bumble.hci import HCI_IsoDataPacket, HCI_LE_1M_PHY, HCI_LE_2M_PHY
|
from bumble.hci import HCI_IsoDataPacket, HCI_LE_1M_PHY, HCI_LE_2M_PHY
|
||||||
import scipy.io.wavfile as wav
|
|
||||||
import logging
|
import logging
|
||||||
from utils.le_audio_encoder import LeAudioEncoder
|
from leaudio import LeAudioEncoder
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
from bumble import hci
|
from bumble import hci
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from leaudio import generate_sine_data, read_wav_file
|
||||||
|
|
||||||
|
|
||||||
app_specific_codec = CodecSpecificConfiguration(
|
app_specific_codec = CodecSpecificConfiguration(
|
||||||
sampling_frequency=SamplingFrequency.FREQ_24000,
|
sampling_frequency=SamplingFrequency.FREQ_24000,
|
||||||
@@ -76,51 +78,8 @@ def clean_reset(com_port):
|
|||||||
print(f"Error opening or accessing {com_port}: {e}")
|
print(f"Error opening or accessing {com_port}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def read_wav_file(filename):
|
|
||||||
|
|
||||||
rate, data = wav.read(filename)
|
|
||||||
num_channels = data.ndim
|
|
||||||
|
|
||||||
if num_channels == 1:
|
|
||||||
left_channel = data[:]
|
|
||||||
else:
|
|
||||||
left_channel = data[:, 1]
|
|
||||||
|
|
||||||
print(len(left_channel))
|
|
||||||
upsampled_data = signal.resample(left_channel, int(
|
|
||||||
app_specific_codec.sampling_frequency.hz / 41000 * left_channel.shape[0]))
|
|
||||||
|
|
||||||
# wav.write("upsampled_stereo_file.wav", app_specific_codec.sampling_frequency.hz, upsampled_data.astype(data.dtype))
|
|
||||||
print("Sample rate:", rate)
|
|
||||||
print("Number channels:", num_channels)
|
|
||||||
print("Audio data (left):", left_channel)
|
|
||||||
print("Bitdepth:", data.dtype.itemsize * 8)
|
|
||||||
|
|
||||||
return upsampled_data.astype(np.int16)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_sine_wave_iso_frames(frequency, sampling_rate, duration):
|
|
||||||
num_samples = int(sampling_rate * duration)
|
|
||||||
|
|
||||||
t = np.linspace(0, duration, num_samples, False)
|
|
||||||
|
|
||||||
sine_wave = np.sin(2 * np.pi * frequency * t)
|
|
||||||
|
|
||||||
# Scale the sine wave to the 16-bit range (-32768 to 32767)
|
|
||||||
scaled_sine_wave = sine_wave * 8191.5
|
|
||||||
|
|
||||||
# Convert to 16-bit integer format
|
|
||||||
int16_sine_wave = scaled_sine_wave.astype(np.int16)
|
|
||||||
|
|
||||||
iso_frame = bytearray()
|
|
||||||
|
|
||||||
for num in int16_sine_wave:
|
|
||||||
|
|
||||||
iso_frame.append(num & 0xFF) # Extract lower 8 bits
|
|
||||||
|
|
||||||
iso_frame.append((num >> 8) & 0xFF) # Extract upper 8 bit
|
|
||||||
|
|
||||||
return iso_frame
|
|
||||||
|
|
||||||
|
|
||||||
class Listener(Device.Listener):
|
class Listener(Device.Listener):
|
||||||
@@ -452,7 +411,7 @@ async def client() -> None:
|
|||||||
app_specific_codec.frame_duration.us / 1000 / 1000)
|
app_specific_codec.frame_duration.us / 1000 / 1000)
|
||||||
if TEST_SINE == 0:
|
if TEST_SINE == 0:
|
||||||
if os.path.isfile(sound_file):
|
if os.path.isfile(sound_file):
|
||||||
upsampled_left_channel = read_wav_file(sound_file)
|
upsampled_left_channel = read_wav_file(sound_file,app_specific_codec.sampling_frequency.hz)
|
||||||
else:
|
else:
|
||||||
raise FileNotFoundError(f"The file {sound_file} does not exist.")
|
raise FileNotFoundError(f"The file {sound_file} does not exist.")
|
||||||
|
|
||||||
@@ -466,7 +425,7 @@ async def client() -> None:
|
|||||||
pcm_data = upsampled_left_channel[i *
|
pcm_data = upsampled_left_channel[i *
|
||||||
sample_size:i*sample_size+sample_size]
|
sample_size:i*sample_size+sample_size]
|
||||||
else:
|
else:
|
||||||
pcm_data = generate_sine_wave_iso_frames(
|
pcm_data = generate_sine_data(
|
||||||
1000, app_specific_codec.sampling_frequency.hz, app_specific_codec.frame_duration.us / 1000000)
|
1000, app_specific_codec.sampling_frequency.hz, app_specific_codec.frame_duration.us / 1000000)
|
||||||
|
|
||||||
data = encoder.encode(
|
data = encoder.encode(
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from leaudio.encoder import LeAudioEncoder
|
||||||
|
from leaudio.utils import read_wav_file, generate_sine_data
|
||||||
|
|||||||
121
leaudio/encoder.py
Normal file
121
leaudio/encoder.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
import wasmtime
|
||||||
|
import ctypes
|
||||||
|
from typing import List, cast
|
||||||
|
import wasmtime.loader
|
||||||
|
import leaudio.liblc3 as liblc3 # type: ignore
|
||||||
|
import enum
|
||||||
|
|
||||||
|
store = wasmtime.loader.store
|
||||||
|
|
||||||
|
_memory = cast(wasmtime.Memory, liblc3.memory)
|
||||||
|
|
||||||
|
STACK_POINTER = _memory.data_len(store)
|
||||||
|
|
||||||
|
_memory.grow(store, 1)
|
||||||
|
|
||||||
|
# Mapping wasmtime memory to linear address
|
||||||
|
|
||||||
|
memory = (ctypes.c_ubyte * _memory.data_len(store)).from_address(
|
||||||
|
|
||||||
|
ctypes.addressof(_memory.data_ptr(store).contents) # type: ignore
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Liblc3PcmFormat(enum.IntEnum):
|
||||||
|
|
||||||
|
S16 = 0
|
||||||
|
|
||||||
|
S24 = 1
|
||||||
|
|
||||||
|
S24_3LE = 2
|
||||||
|
|
||||||
|
FLOAT = 3
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PCM_SAMPLE_RATE = 48000
|
||||||
|
MAX_DECODER_SIZE = liblc3.lc3_decoder_size(10000, DEFAULT_PCM_SAMPLE_RATE)
|
||||||
|
MAX_ENCODER_SIZE = liblc3.lc3_encoder_size(10000, DEFAULT_PCM_SAMPLE_RATE)
|
||||||
|
|
||||||
|
DECODER_STACK_POINTER = STACK_POINTER
|
||||||
|
ENCODER_STACK_POINTER = DECODER_STACK_POINTER + MAX_DECODER_SIZE * 2
|
||||||
|
DECODE_BUFFER_STACK_POINTER = ENCODER_STACK_POINTER + MAX_ENCODER_SIZE * 2
|
||||||
|
ENCODE_BUFFER_STACK_POINTER = DECODE_BUFFER_STACK_POINTER + 8192
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PCM_FORMAT = Liblc3PcmFormat
|
||||||
|
|
||||||
|
DEFAULT_PCM_BYTES_PER_SAMPLE = 2
|
||||||
|
|
||||||
|
|
||||||
|
class LeAudioEncoder:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.encoders: List[int] = []
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setup_encoders(self, sample_rate: int, frame_duration_us: int, num_channels: int) -> None:
|
||||||
|
"""Setup LE audio encoders
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sample_rate (int): Sample rate in Hz
|
||||||
|
frame_duration_us (int): Frame duration in microseconds
|
||||||
|
num_channels (int): Number of channels
|
||||||
|
"""
|
||||||
|
self.encoders[:num_channels] = [
|
||||||
|
liblc3.lc3_setup_encoder(
|
||||||
|
frame_duration_us,
|
||||||
|
sample_rate,
|
||||||
|
0, # Input sample rate
|
||||||
|
ENCODER_STACK_POINTER + MAX_ENCODER_SIZE * i,
|
||||||
|
)
|
||||||
|
for i in range(num_channels)
|
||||||
|
]
|
||||||
|
|
||||||
|
def encode(
|
||||||
|
self,
|
||||||
|
sdu_length: int,
|
||||||
|
num_channels: int,
|
||||||
|
input_stride: int,
|
||||||
|
input_data: bytes,
|
||||||
|
) -> bytes:
|
||||||
|
"""Encode a LE audio frame
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sdu_length (int): Length of the SDU
|
||||||
|
num_channels (int): Number of channels
|
||||||
|
input_stride (int): Stride of the input data
|
||||||
|
input_data (bytes): Input data to encode
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bytes: Encoded data
|
||||||
|
"""
|
||||||
|
if not input_data:
|
||||||
|
return b""
|
||||||
|
|
||||||
|
input_buffer_offset = ENCODE_BUFFER_STACK_POINTER
|
||||||
|
input_buffer_size = len(input_data)
|
||||||
|
|
||||||
|
# Copy into wasm memory
|
||||||
|
memory[input_buffer_offset : input_buffer_offset + input_buffer_size] = input_data
|
||||||
|
|
||||||
|
output_buffer_offset = input_buffer_offset + input_buffer_size
|
||||||
|
output_buffer_size = sdu_length
|
||||||
|
output_frame_size = output_buffer_size // num_channels
|
||||||
|
|
||||||
|
for i in range(num_channels):
|
||||||
|
result = liblc3.lc3_encode(
|
||||||
|
self.encoders[i],
|
||||||
|
0,
|
||||||
|
input_buffer_offset + DEFAULT_PCM_BYTES_PER_SAMPLE * i,
|
||||||
|
input_stride,
|
||||||
|
output_frame_size,
|
||||||
|
output_buffer_offset + output_frame_size * i,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result != 0:
|
||||||
|
raise RuntimeError(f"lc3_encode failed, result={result}")
|
||||||
|
|
||||||
|
# Extract encoded data from the output buffer
|
||||||
|
return bytes(memory[output_buffer_offset : output_buffer_offset + output_buffer_size])
|
||||||
BIN
leaudio/liblc3.wasm
Normal file
BIN
leaudio/liblc3.wasm
Normal file
Binary file not shown.
49
leaudio/utils.py
Normal file
49
leaudio/utils.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from scipy import signal
|
||||||
|
import scipy.io.wavfile as wav
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def read_wav_file(filename,target_sample_rate):
|
||||||
|
|
||||||
|
rate, data = wav.read(filename)
|
||||||
|
num_channels = data.ndim
|
||||||
|
|
||||||
|
if num_channels == 1:
|
||||||
|
left_channel = data[:]
|
||||||
|
else:
|
||||||
|
left_channel = data[:, 1]
|
||||||
|
|
||||||
|
print(len(left_channel))
|
||||||
|
upsampled_data = signal.resample(left_channel, int(
|
||||||
|
target_sample_rate / rate * left_channel.shape[0]))
|
||||||
|
|
||||||
|
# wav.write("upsampled_stereo_file.wav", app_specific_codec.sampling_frequency.hz, upsampled_data.astype(data.dtype))
|
||||||
|
print("Sample rate:", rate)
|
||||||
|
print("Number channels:", num_channels)
|
||||||
|
print("Audio data (left):", left_channel)
|
||||||
|
print("Bitdepth:", data.dtype.itemsize * 8)
|
||||||
|
|
||||||
|
return upsampled_data.astype(np.int16)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sine_data(frequency, sampling_rate, duration):
|
||||||
|
num_samples = int(sampling_rate * duration)
|
||||||
|
|
||||||
|
t = np.linspace(0, duration, num_samples, False)
|
||||||
|
|
||||||
|
sine_wave = np.sin(2 * np.pi * frequency * t)
|
||||||
|
|
||||||
|
# Scale the sine wave to the 16-bit range (-32768 to 32767)
|
||||||
|
scaled_sine_wave = sine_wave * 8191.5
|
||||||
|
|
||||||
|
# Convert to 16-bit integer format
|
||||||
|
int16_sine_wave = scaled_sine_wave.astype(np.int16)
|
||||||
|
|
||||||
|
iso_frame = bytearray()
|
||||||
|
|
||||||
|
for num in int16_sine_wave:
|
||||||
|
|
||||||
|
iso_frame.append(num & 0xFF) # Extract lower 8 bits
|
||||||
|
|
||||||
|
iso_frame.append((num >> 8) & 0xFF) # Extract upper 8 bit
|
||||||
|
|
||||||
|
return iso_frame
|
||||||
@@ -8,19 +8,21 @@ dependencies = ["bumble @ git+https://github.com/markusjellitsch/bumble.git@iso
|
|||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools"]
|
requires = ["setuptools>=61", "wheel", "setuptools_scm>=8"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
|
||||||
where = ["."]
|
|
||||||
include = ["leaudio" , "apps", "utils*"]
|
|
||||||
namespaces = true
|
|
||||||
|
|
||||||
[tool.setuptools.package-dir]
|
|
||||||
"leaudio" = "."
|
|
||||||
"leaudio.apps" = "apps"
|
|
||||||
|
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
unicast_client = "leaudio.apps.bap_unicast_client:main"
|
unicast_client = "leaudio.apps.bap_unicast_client:main"
|
||||||
broadcast_source = "leaudio.apps.bap_broadcast_source:main"
|
broadcast_source = "leaudio.apps.bap_broadcast_source:main"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = [
|
||||||
|
"leaudio",
|
||||||
|
"leaudio.apps"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.package-dir]
|
||||||
|
"leaudio" = "leaudio"
|
||||||
|
"leaudio.apps" = "apps"
|
||||||
|
|||||||
Reference in New Issue
Block a user