xref: /aosp_15_r20/external/liblc3/python/lc3.py (revision 49fe348c0058011ee60b6957cdd9d52742df84bc)
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