1*49fe348cSAndroid Build Coastguard Worker# 2*49fe348cSAndroid Build Coastguard Worker# Copyright 2024 Google LLC 3*49fe348cSAndroid Build Coastguard Worker# 4*49fe348cSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*49fe348cSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*49fe348cSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*49fe348cSAndroid Build Coastguard Worker# 8*49fe348cSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*49fe348cSAndroid Build Coastguard Worker# 10*49fe348cSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*49fe348cSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*49fe348cSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*49fe348cSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*49fe348cSAndroid Build Coastguard Worker# limitations under the License. 15*49fe348cSAndroid Build Coastguard Worker# 16*49fe348cSAndroid Build Coastguard Worker 17*49fe348cSAndroid Build Coastguard Workerimport array 18*49fe348cSAndroid Build Coastguard Workerimport ctypes 19*49fe348cSAndroid Build Coastguard Workerimport enum 20*49fe348cSAndroid Build Coastguard Workerimport glob 21*49fe348cSAndroid Build Coastguard Workerimport os 22*49fe348cSAndroid Build Coastguard Worker 23*49fe348cSAndroid Build Coastguard Workerfrom ctypes import c_bool, c_byte, c_int, c_uint, c_size_t, c_void_p 24*49fe348cSAndroid Build Coastguard Workerfrom ctypes.util import find_library 25*49fe348cSAndroid Build Coastguard Worker 26*49fe348cSAndroid Build Coastguard Worker 27*49fe348cSAndroid Build Coastguard Workerclass _Base: 28*49fe348cSAndroid Build Coastguard Worker 29*49fe348cSAndroid Build Coastguard Worker def __init__(self, frame_duration, samplerate, nchannels, **kwargs): 30*49fe348cSAndroid Build Coastguard Worker 31*49fe348cSAndroid Build Coastguard Worker self.hrmode = False 32*49fe348cSAndroid Build Coastguard Worker self.dt_us = int(frame_duration * 1000) 33*49fe348cSAndroid Build Coastguard Worker self.sr_hz = int(samplerate) 34*49fe348cSAndroid Build Coastguard Worker self.sr_pcm_hz = self.sr_hz 35*49fe348cSAndroid Build Coastguard Worker self.nchannels = nchannels 36*49fe348cSAndroid Build Coastguard Worker 37*49fe348cSAndroid Build Coastguard Worker libpath = None 38*49fe348cSAndroid Build Coastguard Worker 39*49fe348cSAndroid Build Coastguard Worker for k in kwargs.keys(): 40*49fe348cSAndroid Build Coastguard Worker if k == 'hrmode': 41*49fe348cSAndroid Build Coastguard Worker self.hrmode = bool(kwargs[k]) 42*49fe348cSAndroid Build Coastguard Worker elif k == 'pcm_samplerate': 43*49fe348cSAndroid Build Coastguard Worker self.sr_pcm_hz = int(kwargs[k]) 44*49fe348cSAndroid Build Coastguard Worker elif k == 'libpath': 45*49fe348cSAndroid Build Coastguard Worker libpath = kwargs[k] 46*49fe348cSAndroid Build Coastguard Worker else: 47*49fe348cSAndroid Build Coastguard Worker raise ValueError("Invalid keyword argument: " + k) 48*49fe348cSAndroid Build Coastguard Worker 49*49fe348cSAndroid Build Coastguard Worker if self.dt_us not in [2500, 5000, 7500, 10000]: 50*49fe348cSAndroid Build Coastguard Worker raise ValueError( 51*49fe348cSAndroid Build Coastguard Worker "Invalid frame duration: %.1f ms" % frame_duration) 52*49fe348cSAndroid Build Coastguard Worker 53*49fe348cSAndroid Build Coastguard Worker allowed_samplerate = [8000, 16000, 24000, 32000, 48000] \ 54*49fe348cSAndroid Build Coastguard Worker if not self.hrmode else [48000, 96000] 55*49fe348cSAndroid Build Coastguard Worker 56*49fe348cSAndroid Build Coastguard Worker if self.sr_hz not in allowed_samplerate: 57*49fe348cSAndroid Build Coastguard Worker raise ValueError("Invalid sample rate: %d Hz" % samplerate) 58*49fe348cSAndroid Build Coastguard Worker 59*49fe348cSAndroid Build Coastguard Worker if libpath is None: 60*49fe348cSAndroid Build Coastguard Worker mesonpy_lib = glob.glob(os.path.join(os.path.dirname(__file__), '.lc3.mesonpy.libs', '*lc3*')) 61*49fe348cSAndroid Build Coastguard Worker 62*49fe348cSAndroid Build Coastguard Worker if mesonpy_lib: 63*49fe348cSAndroid Build Coastguard Worker libpath = mesonpy_lib[0] 64*49fe348cSAndroid Build Coastguard Worker else: 65*49fe348cSAndroid Build Coastguard Worker libpath = find_library("lc3") 66*49fe348cSAndroid Build Coastguard Worker if not libpath: 67*49fe348cSAndroid Build Coastguard Worker raise Exception("LC3 library not found") 68*49fe348cSAndroid Build Coastguard Worker 69*49fe348cSAndroid Build Coastguard Worker lib = ctypes.cdll.LoadLibrary(libpath) 70*49fe348cSAndroid Build Coastguard Worker 71*49fe348cSAndroid Build Coastguard Worker try: 72*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_frame_samples \ 73*49fe348cSAndroid Build Coastguard Worker and lib.lc3_hr_frame_block_bytes \ 74*49fe348cSAndroid Build Coastguard Worker and lib.lc3_hr_resolve_bitrate \ 75*49fe348cSAndroid Build Coastguard Worker and lib.lc3_hr_delay_samples 76*49fe348cSAndroid Build Coastguard Worker 77*49fe348cSAndroid Build Coastguard Worker except AttributeError: 78*49fe348cSAndroid Build Coastguard Worker 79*49fe348cSAndroid Build Coastguard Worker if self.hrmode: 80*49fe348cSAndroid Build Coastguard Worker raise Exception('High-Resolution interface not available') 81*49fe348cSAndroid Build Coastguard Worker 82*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_frame_samples = \ 83*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz: \ 84*49fe348cSAndroid Build Coastguard Worker lib.lc3_frame_samples(dt_us, sr_hz) 85*49fe348cSAndroid Build Coastguard Worker 86*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_frame_block_bytes = \ 87*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz, nchannels, bitrate: \ 88*49fe348cSAndroid Build Coastguard Worker nchannels * lib.lc3_frame_bytes(dt_us, bitrate // 2) 89*49fe348cSAndroid Build Coastguard Worker 90*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_resolve_bitrate = \ 91*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz, nbytes: \ 92*49fe348cSAndroid Build Coastguard Worker lib.lc3_resolve_bitrate(dt_us, nbytes) 93*49fe348cSAndroid Build Coastguard Worker 94*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_delay_samples = \ 95*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz: \ 96*49fe348cSAndroid Build Coastguard Worker lib.lc3_delay_samples(dt_us, sr_hz) 97*49fe348cSAndroid Build Coastguard Worker 98*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_frame_samples.argtypes = [c_bool, c_int, c_int] 99*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_frame_block_bytes.argtypes = \ 100*49fe348cSAndroid Build Coastguard Worker [c_bool, c_int, c_int, c_int, c_int] 101*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_resolve_bitrate.argtypes = [c_bool, c_int, c_int, c_int] 102*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_delay_samples.argtypes = [c_bool, c_int, c_int] 103*49fe348cSAndroid Build Coastguard Worker self.lib = lib 104*49fe348cSAndroid Build Coastguard Worker 105*49fe348cSAndroid Build Coastguard Worker libc = ctypes.cdll.LoadLibrary(find_library("c")) 106*49fe348cSAndroid Build Coastguard Worker 107*49fe348cSAndroid Build Coastguard Worker self.malloc = libc.malloc 108*49fe348cSAndroid Build Coastguard Worker self.malloc.argtypes = [c_size_t] 109*49fe348cSAndroid Build Coastguard Worker self.malloc.restype = c_void_p 110*49fe348cSAndroid Build Coastguard Worker 111*49fe348cSAndroid Build Coastguard Worker self.free = libc.free 112*49fe348cSAndroid Build Coastguard Worker self.free.argtypes = [c_void_p] 113*49fe348cSAndroid Build Coastguard Worker 114*49fe348cSAndroid Build Coastguard Worker def get_frame_samples(self): 115*49fe348cSAndroid Build Coastguard Worker """ 116*49fe348cSAndroid Build Coastguard Worker Returns the number of PCM samples in an LC3 frame 117*49fe348cSAndroid Build Coastguard Worker """ 118*49fe348cSAndroid Build Coastguard Worker ret = self.lib.lc3_hr_frame_samples( 119*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_pcm_hz) 120*49fe348cSAndroid Build Coastguard Worker if ret < 0: 121*49fe348cSAndroid Build Coastguard Worker raise ValueError("Bad parameters") 122*49fe348cSAndroid Build Coastguard Worker return ret 123*49fe348cSAndroid Build Coastguard Worker 124*49fe348cSAndroid Build Coastguard Worker def get_frame_bytes(self, bitrate): 125*49fe348cSAndroid Build Coastguard Worker """ 126*49fe348cSAndroid Build Coastguard Worker Returns the size of LC3 frame blocks, from bitrate in bit per seconds. 127*49fe348cSAndroid Build Coastguard Worker A target `bitrate` equals 0 or `INT32_MAX` returns respectively 128*49fe348cSAndroid Build Coastguard Worker the minimum and maximum allowed size. 129*49fe348cSAndroid Build Coastguard Worker """ 130*49fe348cSAndroid Build Coastguard Worker ret = self.lib.lc3_hr_frame_block_bytes( 131*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_hz, self.nchannels, bitrate) 132*49fe348cSAndroid Build Coastguard Worker if ret < 0: 133*49fe348cSAndroid Build Coastguard Worker raise ValueError("Bad parameters") 134*49fe348cSAndroid Build Coastguard Worker return ret 135*49fe348cSAndroid Build Coastguard Worker 136*49fe348cSAndroid Build Coastguard Worker def resolve_bitrate(self, nbytes): 137*49fe348cSAndroid Build Coastguard Worker """ 138*49fe348cSAndroid Build Coastguard Worker Returns the bitrate in bits per seconds, from the size of LC3 frames. 139*49fe348cSAndroid Build Coastguard Worker """ 140*49fe348cSAndroid Build Coastguard Worker ret = self.lib.lc3_hr_resolve_bitrate( 141*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_hz, nbytes) 142*49fe348cSAndroid Build Coastguard Worker if ret < 0: 143*49fe348cSAndroid Build Coastguard Worker raise ValueError("Bad parameters") 144*49fe348cSAndroid Build Coastguard Worker return ret 145*49fe348cSAndroid Build Coastguard Worker 146*49fe348cSAndroid Build Coastguard Worker def get_delay_samples(self): 147*49fe348cSAndroid Build Coastguard Worker """ 148*49fe348cSAndroid Build Coastguard Worker Returns the algorithmic delay, as a number of samples. 149*49fe348cSAndroid Build Coastguard Worker """ 150*49fe348cSAndroid Build Coastguard Worker ret = self.lib.lc3_hr_delay_samples( 151*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_pcm_hz) 152*49fe348cSAndroid Build Coastguard Worker if ret < 0: 153*49fe348cSAndroid Build Coastguard Worker raise ValueError("Bad parameters") 154*49fe348cSAndroid Build Coastguard Worker return ret 155*49fe348cSAndroid Build Coastguard Worker 156*49fe348cSAndroid Build Coastguard Worker @staticmethod 157*49fe348cSAndroid Build Coastguard Worker def _resolve_pcm_format(bitdepth): 158*49fe348cSAndroid Build Coastguard Worker PCM_FORMAT_S16 = 0 159*49fe348cSAndroid Build Coastguard Worker PCM_FORMAT_S24 = 1 160*49fe348cSAndroid Build Coastguard Worker PCM_FORMAT_S24_3LE = 2 161*49fe348cSAndroid Build Coastguard Worker PCM_FORMAT_FLOAT = 3 162*49fe348cSAndroid Build Coastguard Worker 163*49fe348cSAndroid Build Coastguard Worker match bitdepth: 164*49fe348cSAndroid Build Coastguard Worker case 16: return (PCM_FORMAT_S16, ctypes.c_int16) 165*49fe348cSAndroid Build Coastguard Worker case 24: return (PCM_FORMAT_S24_3LE, 3 * ctypes.c_byte) 166*49fe348cSAndroid Build Coastguard Worker case None: return (PCM_FORMAT_FLOAT, ctypes.c_float) 167*49fe348cSAndroid Build Coastguard Worker case _: raise ValueError("Could not interpret PCM bitdepth") 168*49fe348cSAndroid Build Coastguard Worker 169*49fe348cSAndroid Build Coastguard Worker 170*49fe348cSAndroid Build Coastguard Workerclass Encoder(_Base): 171*49fe348cSAndroid Build Coastguard Worker """ 172*49fe348cSAndroid Build Coastguard Worker LC3 Encoder wrapper 173*49fe348cSAndroid Build Coastguard Worker 174*49fe348cSAndroid Build Coastguard Worker The `frame_duration` expressed in milliseconds is any of 2.5, 5.0, 7.5 175*49fe348cSAndroid Build Coastguard Worker or 10.0. The `samplerate`, in Hertz, is any of 8000, 16000, 24000, 32000 176*49fe348cSAndroid Build Coastguard Worker or 48000, unless High-Resolution mode is enabled. In High-Resolution mode, 177*49fe348cSAndroid Build Coastguard Worker the `samplerate` is 48000 or 96000. 178*49fe348cSAndroid Build Coastguard Worker 179*49fe348cSAndroid Build Coastguard Worker By default, one channel is processed. When `nchannels` is greater than one, 180*49fe348cSAndroid Build Coastguard Worker the PCM input stream is read interleaved and consecutives LC3 frames are 181*49fe348cSAndroid Build Coastguard Worker output, for each channel. 182*49fe348cSAndroid Build Coastguard Worker 183*49fe348cSAndroid Build Coastguard Worker Keyword arguments: 184*49fe348cSAndroid Build Coastguard Worker hrmode : Enable High-Resolution mode, default is `False`. 185*49fe348cSAndroid Build Coastguard Worker sr_pcm_hz : Input PCM samplerate, enable downsampling of input. 186*49fe348cSAndroid Build Coastguard Worker libpath : LC3 library path and name 187*49fe348cSAndroid Build Coastguard Worker """ 188*49fe348cSAndroid Build Coastguard Worker 189*49fe348cSAndroid Build Coastguard Worker class c_encoder_t(c_void_p): 190*49fe348cSAndroid Build Coastguard Worker pass 191*49fe348cSAndroid Build Coastguard Worker 192*49fe348cSAndroid Build Coastguard Worker def __init__(self, frame_duration, samplerate, nchannels=1, **kwargs): 193*49fe348cSAndroid Build Coastguard Worker 194*49fe348cSAndroid Build Coastguard Worker super().__init__(frame_duration, samplerate, nchannels, **kwargs) 195*49fe348cSAndroid Build Coastguard Worker 196*49fe348cSAndroid Build Coastguard Worker lib = self.lib 197*49fe348cSAndroid Build Coastguard Worker 198*49fe348cSAndroid Build Coastguard Worker try: 199*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_encoder_size \ 200*49fe348cSAndroid Build Coastguard Worker and lib.lc3_hr_setup_encoder 201*49fe348cSAndroid Build Coastguard Worker 202*49fe348cSAndroid Build Coastguard Worker except AttributeError: 203*49fe348cSAndroid Build Coastguard Worker 204*49fe348cSAndroid Build Coastguard Worker assert not self.hrmode 205*49fe348cSAndroid Build Coastguard Worker 206*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_encoder_size = \ 207*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz: \ 208*49fe348cSAndroid Build Coastguard Worker lib.lc3_encoder_size(dt_us, sr_hz) 209*49fe348cSAndroid Build Coastguard Worker 210*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_setup_encoder = \ 211*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz, sr_pcm_hz, mem: \ 212*49fe348cSAndroid Build Coastguard Worker lib.lc3_setup_encoder(dt_us, sr_hz, sr_pcm_hz, mem) 213*49fe348cSAndroid Build Coastguard Worker 214*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_encoder_size.argtypes = [c_bool, c_int, c_int] 215*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_encoder_size.restype = c_uint 216*49fe348cSAndroid Build Coastguard Worker 217*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_setup_encoder.argtypes = \ 218*49fe348cSAndroid Build Coastguard Worker [c_bool, c_int, c_int, c_int, c_void_p] 219*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_setup_encoder.restype = self.c_encoder_t 220*49fe348cSAndroid Build Coastguard Worker 221*49fe348cSAndroid Build Coastguard Worker lib.lc3_encode.argtypes = \ 222*49fe348cSAndroid Build Coastguard Worker [self.c_encoder_t, c_int, c_void_p, c_int, c_int, c_void_p] 223*49fe348cSAndroid Build Coastguard Worker 224*49fe348cSAndroid Build Coastguard Worker def new_encoder(): return lib.lc3_hr_setup_encoder( 225*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_hz, self.sr_pcm_hz, 226*49fe348cSAndroid Build Coastguard Worker self.malloc(lib.lc3_hr_encoder_size( 227*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_pcm_hz))) 228*49fe348cSAndroid Build Coastguard Worker 229*49fe348cSAndroid Build Coastguard Worker self.__encoders = [new_encoder() for i in range(nchannels)] 230*49fe348cSAndroid Build Coastguard Worker 231*49fe348cSAndroid Build Coastguard Worker def __del__(self): 232*49fe348cSAndroid Build Coastguard Worker 233*49fe348cSAndroid Build Coastguard Worker try: 234*49fe348cSAndroid Build Coastguard Worker (self.free(encoder) for encoder in self.__encoders) 235*49fe348cSAndroid Build Coastguard Worker finally: 236*49fe348cSAndroid Build Coastguard Worker return 237*49fe348cSAndroid Build Coastguard Worker 238*49fe348cSAndroid Build Coastguard Worker def encode(self, pcm, nbytes, bitdepth=None): 239*49fe348cSAndroid Build Coastguard Worker """ 240*49fe348cSAndroid Build Coastguard Worker Encode LC3 frame(s), for each channel. 241*49fe348cSAndroid Build Coastguard Worker 242*49fe348cSAndroid Build Coastguard Worker The `pcm` input is given in two ways. When no `bitdepth` is defined, 243*49fe348cSAndroid Build Coastguard Worker it's a vector of floating point values from -1 to 1, coding the sample 244*49fe348cSAndroid Build Coastguard Worker levels. When `bitdepth` is defined, `pcm` is interpreted as a byte-like 245*49fe348cSAndroid Build Coastguard Worker object, each sample coded on `bitdepth` bits (16 or 24). 246*49fe348cSAndroid Build Coastguard Worker The machine endianness, or little endian, is used for 16 or 24 bits 247*49fe348cSAndroid Build Coastguard Worker width, respectively. 248*49fe348cSAndroid Build Coastguard Worker In both cases, the `pcm` vector data is padded with zeros when 249*49fe348cSAndroid Build Coastguard Worker its length is less than the required input samples for the encoder. 250*49fe348cSAndroid Build Coastguard Worker Channels concatenation of encoded LC3 frames, of `nbytes`, is returned. 251*49fe348cSAndroid Build Coastguard Worker """ 252*49fe348cSAndroid Build Coastguard Worker 253*49fe348cSAndroid Build Coastguard Worker nchannels = self.nchannels 254*49fe348cSAndroid Build Coastguard Worker frame_samples = self.get_frame_samples() 255*49fe348cSAndroid Build Coastguard Worker 256*49fe348cSAndroid Build Coastguard Worker (pcm_fmt, pcm_t) = self._resolve_pcm_format(bitdepth) 257*49fe348cSAndroid Build Coastguard Worker pcm_len = nchannels * frame_samples 258*49fe348cSAndroid Build Coastguard Worker 259*49fe348cSAndroid Build Coastguard Worker if bitdepth is None: 260*49fe348cSAndroid Build Coastguard Worker pcm_buffer = array.array('f', pcm) 261*49fe348cSAndroid Build Coastguard Worker 262*49fe348cSAndroid Build Coastguard Worker # Invert test to catch NaN 263*49fe348cSAndroid Build Coastguard Worker if not abs(sum(pcm)) / frame_samples < 2: 264*49fe348cSAndroid Build Coastguard Worker raise ValueError("Out of range PCM input") 265*49fe348cSAndroid Build Coastguard Worker 266*49fe348cSAndroid Build Coastguard Worker padding = max(pcm_len - frame_samples, 0) 267*49fe348cSAndroid Build Coastguard Worker pcm_buffer.extend(array.array('f', [0] * padding)) 268*49fe348cSAndroid Build Coastguard Worker 269*49fe348cSAndroid Build Coastguard Worker else: 270*49fe348cSAndroid Build Coastguard Worker padding = max(pcm_len * ctypes.sizeof(pcm_t) - len(pcm), 0) 271*49fe348cSAndroid Build Coastguard Worker pcm_buffer = bytearray(pcm) + bytearray(padding) 272*49fe348cSAndroid Build Coastguard Worker 273*49fe348cSAndroid Build Coastguard Worker data_buffer = (c_byte * nbytes)() 274*49fe348cSAndroid Build Coastguard Worker data_offset = 0 275*49fe348cSAndroid Build Coastguard Worker 276*49fe348cSAndroid Build Coastguard Worker for (ich, encoder) in enumerate(self.__encoders): 277*49fe348cSAndroid Build Coastguard Worker 278*49fe348cSAndroid Build Coastguard Worker pcm_offset = ich * ctypes.sizeof(pcm_t) 279*49fe348cSAndroid Build Coastguard Worker pcm = (pcm_t * (pcm_len - ich)).from_buffer(pcm_buffer, pcm_offset) 280*49fe348cSAndroid Build Coastguard Worker 281*49fe348cSAndroid Build Coastguard Worker data_size = nbytes // nchannels + int(ich < nbytes % nchannels) 282*49fe348cSAndroid Build Coastguard Worker data = (c_byte * data_size).from_buffer(data_buffer, data_offset) 283*49fe348cSAndroid Build Coastguard Worker data_offset += data_size 284*49fe348cSAndroid Build Coastguard Worker 285*49fe348cSAndroid Build Coastguard Worker ret = self.lib.lc3_encode( 286*49fe348cSAndroid Build Coastguard Worker encoder, pcm_fmt, pcm, nchannels, len(data), data) 287*49fe348cSAndroid Build Coastguard Worker if ret < 0: 288*49fe348cSAndroid Build Coastguard Worker raise ValueError("Bad parameters") 289*49fe348cSAndroid Build Coastguard Worker 290*49fe348cSAndroid Build Coastguard Worker return bytes(data_buffer) 291*49fe348cSAndroid Build Coastguard Worker 292*49fe348cSAndroid Build Coastguard Worker 293*49fe348cSAndroid Build Coastguard Workerclass Decoder(_Base): 294*49fe348cSAndroid Build Coastguard Worker """ 295*49fe348cSAndroid Build Coastguard Worker LC3 Decoder wrapper 296*49fe348cSAndroid Build Coastguard Worker 297*49fe348cSAndroid Build Coastguard Worker The `frame_duration` expressed in milliseconds is any of 2.5, 5.0, 7.5 298*49fe348cSAndroid Build Coastguard Worker or 10.0. The `samplerate`, in Hertz, is any of 8000, 16000, 24000, 32000 299*49fe348cSAndroid Build Coastguard Worker or 48000, unless High-Resolution mode is enabled. In High-Resolution 300*49fe348cSAndroid Build Coastguard Worker mode, the `samplerate` is 48000 or 96000. 301*49fe348cSAndroid Build Coastguard Worker 302*49fe348cSAndroid Build Coastguard Worker By default, one channel is processed. When `nchannels` is greater than one, 303*49fe348cSAndroid Build Coastguard Worker the PCM input stream is read interleaved and consecutives LC3 frames are 304*49fe348cSAndroid Build Coastguard Worker output, for each channel. 305*49fe348cSAndroid Build Coastguard Worker 306*49fe348cSAndroid Build Coastguard Worker Keyword arguments: 307*49fe348cSAndroid Build Coastguard Worker hrmode : Enable High-Resolution mode, default is `False`. 308*49fe348cSAndroid Build Coastguard Worker sr_pcm_hz : Output PCM samplerate, enable upsampling of output. 309*49fe348cSAndroid Build Coastguard Worker libpath : LC3 library path and name 310*49fe348cSAndroid Build Coastguard Worker """ 311*49fe348cSAndroid Build Coastguard Worker 312*49fe348cSAndroid Build Coastguard Worker class c_decoder_t(c_void_p): 313*49fe348cSAndroid Build Coastguard Worker pass 314*49fe348cSAndroid Build Coastguard Worker 315*49fe348cSAndroid Build Coastguard Worker def __init__(self, frame_duration, samplerate, nchannels=1, **kwargs): 316*49fe348cSAndroid Build Coastguard Worker 317*49fe348cSAndroid Build Coastguard Worker super().__init__(frame_duration, samplerate, nchannels, **kwargs) 318*49fe348cSAndroid Build Coastguard Worker 319*49fe348cSAndroid Build Coastguard Worker lib = self.lib 320*49fe348cSAndroid Build Coastguard Worker 321*49fe348cSAndroid Build Coastguard Worker try: 322*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_decoder_size \ 323*49fe348cSAndroid Build Coastguard Worker and lib.lc3_hr_setup_decoder 324*49fe348cSAndroid Build Coastguard Worker 325*49fe348cSAndroid Build Coastguard Worker except AttributeError: 326*49fe348cSAndroid Build Coastguard Worker 327*49fe348cSAndroid Build Coastguard Worker assert not self.hrmode 328*49fe348cSAndroid Build Coastguard Worker 329*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_decoder_size = \ 330*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz: \ 331*49fe348cSAndroid Build Coastguard Worker lib.lc3_decoder_size(dt_us, sr_hz) 332*49fe348cSAndroid Build Coastguard Worker 333*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_setup_decoder = \ 334*49fe348cSAndroid Build Coastguard Worker lambda hrmode, dt_us, sr_hz, sr_pcm_hz, mem: \ 335*49fe348cSAndroid Build Coastguard Worker lib.lc3_setup_decoder(dt_us, sr_hz, sr_pcm_hz, mem) 336*49fe348cSAndroid Build Coastguard Worker 337*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_decoder_size.argtypes = [c_bool, c_int, c_int] 338*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_decoder_size.restype = c_uint 339*49fe348cSAndroid Build Coastguard Worker 340*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_setup_decoder.argtypes = \ 341*49fe348cSAndroid Build Coastguard Worker [c_bool, c_int, c_int, c_int, c_void_p] 342*49fe348cSAndroid Build Coastguard Worker lib.lc3_hr_setup_decoder.restype = self.c_decoder_t 343*49fe348cSAndroid Build Coastguard Worker 344*49fe348cSAndroid Build Coastguard Worker lib.lc3_decode.argtypes = \ 345*49fe348cSAndroid Build Coastguard Worker [self.c_decoder_t, c_void_p, c_int, c_int, c_void_p, c_int] 346*49fe348cSAndroid Build Coastguard Worker 347*49fe348cSAndroid Build Coastguard Worker def new_decoder(): return lib.lc3_hr_setup_decoder( 348*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_hz, self.sr_pcm_hz, 349*49fe348cSAndroid Build Coastguard Worker self.malloc(lib.lc3_hr_decoder_size( 350*49fe348cSAndroid Build Coastguard Worker self.hrmode, self.dt_us, self.sr_pcm_hz))) 351*49fe348cSAndroid Build Coastguard Worker 352*49fe348cSAndroid Build Coastguard Worker self.__decoders = [new_decoder() for i in range(nchannels)] 353*49fe348cSAndroid Build Coastguard Worker 354*49fe348cSAndroid Build Coastguard Worker def __del__(self): 355*49fe348cSAndroid Build Coastguard Worker 356*49fe348cSAndroid Build Coastguard Worker try: 357*49fe348cSAndroid Build Coastguard Worker (self.free(decoder) for decoder in self.__decoders) 358*49fe348cSAndroid Build Coastguard Worker finally: 359*49fe348cSAndroid Build Coastguard Worker return 360*49fe348cSAndroid Build Coastguard Worker 361*49fe348cSAndroid Build Coastguard Worker def decode(self, data, bitdepth=None): 362*49fe348cSAndroid Build Coastguard Worker """ 363*49fe348cSAndroid Build Coastguard Worker Decode an LC3 frame 364*49fe348cSAndroid Build Coastguard Worker 365*49fe348cSAndroid Build Coastguard Worker The input `data` is the channels concatenation of LC3 frames in a 366*49fe348cSAndroid Build Coastguard Worker byte-like object. Interleaved PCM samples are returned according to 367*49fe348cSAndroid Build Coastguard Worker the `bitdepth` indication. 368*49fe348cSAndroid Build Coastguard Worker When no `bitdepth` is defined, it's a vector of floating point values 369*49fe348cSAndroid Build Coastguard Worker from -1 to 1, coding the sample levels. When `bitdepth` is defined, 370*49fe348cSAndroid Build Coastguard Worker it returns a byte array, each sample coded on `bitdepth` bits. 371*49fe348cSAndroid Build Coastguard Worker The machine endianness, or little endian, is used for 16 or 24 bits 372*49fe348cSAndroid Build Coastguard Worker width, respectively. 373*49fe348cSAndroid Build Coastguard Worker """ 374*49fe348cSAndroid Build Coastguard Worker 375*49fe348cSAndroid Build Coastguard Worker nchannels = self.nchannels 376*49fe348cSAndroid Build Coastguard Worker frame_samples = self.get_frame_samples() 377*49fe348cSAndroid Build Coastguard Worker 378*49fe348cSAndroid Build Coastguard Worker (pcm_fmt, pcm_t) = self._resolve_pcm_format(bitdepth) 379*49fe348cSAndroid Build Coastguard Worker pcm_len = nchannels * self.get_frame_samples() 380*49fe348cSAndroid Build Coastguard Worker pcm_buffer = (pcm_t * pcm_len)() 381*49fe348cSAndroid Build Coastguard Worker 382*49fe348cSAndroid Build Coastguard Worker data_buffer = bytearray(data) 383*49fe348cSAndroid Build Coastguard Worker data_offset = 0 384*49fe348cSAndroid Build Coastguard Worker 385*49fe348cSAndroid Build Coastguard Worker for (ich, decoder) in enumerate(self.__decoders): 386*49fe348cSAndroid Build Coastguard Worker pcm_offset = ich * ctypes.sizeof(pcm_t) 387*49fe348cSAndroid Build Coastguard Worker pcm = (pcm_t * (pcm_len - ich)).from_buffer(pcm_buffer, pcm_offset) 388*49fe348cSAndroid Build Coastguard Worker 389*49fe348cSAndroid Build Coastguard Worker data_size = len(data_buffer) // nchannels + \ 390*49fe348cSAndroid Build Coastguard Worker int(ich < len(data_buffer) % nchannels) 391*49fe348cSAndroid Build Coastguard Worker data = (c_byte * data_size).from_buffer(data_buffer, data_offset) 392*49fe348cSAndroid Build Coastguard Worker data_offset += data_size 393*49fe348cSAndroid Build Coastguard Worker 394*49fe348cSAndroid Build Coastguard Worker ret = self.lib.lc3_decode( 395*49fe348cSAndroid Build Coastguard Worker decoder, data, len(data), pcm_fmt, pcm, self.nchannels) 396*49fe348cSAndroid Build Coastguard Worker if ret < 0: 397*49fe348cSAndroid Build Coastguard Worker raise ValueError("Bad parameters") 398*49fe348cSAndroid Build Coastguard Worker 399*49fe348cSAndroid Build Coastguard Worker return array.array('f', pcm_buffer) \ 400*49fe348cSAndroid Build Coastguard Worker if bitdepth is None else bytes(pcm_buffer) 401