mirror of
https://github.com/google/liblc3.git
synced 2026-04-18 13:44:49 +00:00
python: Add python library wrapper
This commit is contained in:
96
python/tools/decoder.py
Executable file
96
python/tools/decoder.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import lc3
|
||||
import struct
|
||||
import sys
|
||||
import wave
|
||||
|
||||
parser = argparse.ArgumentParser(description='LC3 Decoder')
|
||||
|
||||
parser.add_argument(
|
||||
'lc3_file', nargs='?',
|
||||
help='Input bitstream file, default is stdin',
|
||||
type=argparse.FileType('rb'), default=sys.stdin.buffer)
|
||||
|
||||
parser.add_argument(
|
||||
'wav_file', nargs='?',
|
||||
help='Output wave file, default is stdout',
|
||||
type=argparse.FileType('wb'), default=sys.stdout.buffer)
|
||||
|
||||
parser.add_argument(
|
||||
'--bitdepth',
|
||||
help='Output bitdepth, default is 16 bits',
|
||||
type=int, choices=[16, 24], default=16)
|
||||
|
||||
parser.add_argument(
|
||||
'--libpath', help='LC3 Library path')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- LC3 File input ---
|
||||
|
||||
f_lc3 = args.lc3_file
|
||||
|
||||
header = struct.unpack('=HHHHHHHI', f_lc3.read(18))
|
||||
if header[0] != 0xcc1c:
|
||||
raise ValueError('Invalid bitstream file')
|
||||
|
||||
samplerate = header[2] * 100
|
||||
nchannels = header[4]
|
||||
frame_duration = header[5] / 100
|
||||
stream_length = header[7]
|
||||
|
||||
# --- Setup output ---
|
||||
|
||||
bitdepth = args.bitdepth
|
||||
pcm_size = nchannels * (bitdepth // 8)
|
||||
|
||||
f_wav = args.wav_file
|
||||
wavfile = wave.open(f_wav)
|
||||
wavfile.setnchannels(nchannels)
|
||||
wavfile.setsampwidth(bitdepth // 8)
|
||||
wavfile.setframerate(samplerate)
|
||||
wavfile.setnframes(stream_length)
|
||||
|
||||
# --- Setup decoder ---
|
||||
|
||||
dec = lc3.Decoder(
|
||||
frame_duration, samplerate, nchannels, libpath=args.libpath)
|
||||
frame_length = dec.get_frame_samples()
|
||||
encoded_length = stream_length + dec.get_delay_samples()
|
||||
|
||||
# --- Decoding loop ---
|
||||
|
||||
for i in range(0, encoded_length, frame_length):
|
||||
|
||||
lc3_frame_size = struct.unpack('=H', f_lc3.read(2))[0]
|
||||
pcm = dec.decode(f_lc3.read(lc3_frame_size), bitdepth=bitdepth)
|
||||
|
||||
pcm = pcm[max(encoded_length - stream_length - i, 0) * pcm_size:
|
||||
min(encoded_length - i, frame_length) * pcm_size]
|
||||
|
||||
wavfile.writeframesraw(pcm)
|
||||
|
||||
# --- Cleanup ---
|
||||
|
||||
wavfile.close()
|
||||
|
||||
for f in (f_lc3, f_wav):
|
||||
if f is not sys.stdout.buffer:
|
||||
f.close()
|
||||
88
python/tools/encoder.py
Executable file
88
python/tools/encoder.py
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import lc3
|
||||
import struct
|
||||
import sys
|
||||
import wave
|
||||
|
||||
parser = argparse.ArgumentParser(description='LC3 Encoder')
|
||||
|
||||
parser.add_argument(
|
||||
'wav_file', nargs='?',
|
||||
help='Input wave file, default is stdin',
|
||||
type=argparse.FileType('rb'), default=sys.stdin.buffer)
|
||||
|
||||
parser.add_argument(
|
||||
'lc3_file', nargs='?',
|
||||
help='Output bitstream file, default is stdout',
|
||||
type=argparse.FileType('wb'), default=sys.stdout.buffer)
|
||||
|
||||
parser.add_argument(
|
||||
'--bitrate', help='Bitrate in bps', type=int, required=True)
|
||||
|
||||
parser.add_argument(
|
||||
'--frame_duration', help='Frame duration in ms', type=float, default=10)
|
||||
|
||||
parser.add_argument(
|
||||
'--libpath', help='LC3 Library path')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- WAV File input ---
|
||||
|
||||
f_wav = args.wav_file
|
||||
wavfile = wave.open(f_wav, 'rb')
|
||||
|
||||
samplerate = wavfile.getframerate()
|
||||
nchannels = wavfile.getnchannels()
|
||||
bitdepth = wavfile.getsampwidth() * 8
|
||||
stream_length = wavfile.getnframes()
|
||||
|
||||
# --- Setup encoder ---
|
||||
|
||||
enc = lc3.Encoder(
|
||||
args.frame_duration, samplerate, nchannels, libpath=args.libpath)
|
||||
frame_size = enc.get_frame_bytes(args.bitrate)
|
||||
frame_length = enc.get_frame_samples()
|
||||
bitrate = enc.resolve_bitrate(frame_size)
|
||||
|
||||
# --- Setup output ---
|
||||
|
||||
f_lc3 = args.lc3_file
|
||||
f_lc3.write(struct.pack(
|
||||
'=HHHHHHHI', 0xcc1c, 18,
|
||||
samplerate // 100, bitrate // 100, nchannels,
|
||||
int(args.frame_duration * 100), 0, stream_length))
|
||||
|
||||
# --- Encoding loop ---
|
||||
|
||||
for i in range(0, stream_length, frame_length):
|
||||
|
||||
f_lc3.write(struct.pack('=H', frame_size))
|
||||
|
||||
pcm = wavfile.readframes(frame_length)
|
||||
f_lc3.write(enc.encode(pcm, frame_size, bitdepth=bitdepth))
|
||||
|
||||
# --- Cleanup ---
|
||||
|
||||
wavfile.close()
|
||||
|
||||
for f in (f_wav, f_lc3):
|
||||
if f is not sys.stdout.buffer:
|
||||
f.close()
|
||||
92
python/tools/specgram.py
Executable file
92
python/tools/specgram.py
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import lc3
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import scipy.signal as signal
|
||||
|
||||
matplotlib.use('QtAgg')
|
||||
|
||||
parser = argparse.ArgumentParser(description='LC3 Encoder')
|
||||
|
||||
parser.add_argument(
|
||||
'--frame_duration', help='Frame duration (ms)', type=float, default=10)
|
||||
|
||||
parser.add_argument(
|
||||
'--sample_rate', help='Sampling frequency (Hz)', type=float, default=48000)
|
||||
|
||||
parser.add_argument(
|
||||
'--hrmode', help='Enable high-resolution mode', action='store_true')
|
||||
|
||||
parser.add_argument(
|
||||
'--bitrate', help='Bitrate (bps)', type=int, default=96000)
|
||||
|
||||
parser.add_argument(
|
||||
'--libpath', help='LC3 Library path')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- Setup encoder + decoder ---
|
||||
|
||||
fs = args.sample_rate
|
||||
|
||||
enc = lc3.Encoder(
|
||||
args.frame_duration, fs, hrmode=args.hrmode, libpath=args.libpath)
|
||||
dec = lc3.Decoder(
|
||||
args.frame_duration, fs, hrmode=args.hrmode, libpath=args.libpath)
|
||||
|
||||
frame_len = enc.get_frame_samples()
|
||||
delay_len = enc.get_delay_samples()
|
||||
|
||||
# --- Generate 10 seconds chirp ---
|
||||
|
||||
n = 10 * int(fs)
|
||||
t = np.arange(n - (n % frame_len)) / fs
|
||||
s = signal.chirp(t, f0=10, f1=fs/2, t1=t[-1], phi=-90, method='linear')
|
||||
|
||||
# --- Encoding / decoding loop ---
|
||||
|
||||
frame_size = enc.get_frame_bytes(args.bitrate)
|
||||
bitrate = enc.resolve_bitrate(frame_size)
|
||||
|
||||
y = np.empty(len(s) + frame_len)
|
||||
|
||||
for i in range(0, len(s), frame_len):
|
||||
y[i:i+frame_len] = dec.decode(enc.encode(s[i:i+frame_len], frame_size))
|
||||
|
||||
y[len(s):] = dec.decode(enc.encode(np.zeros(frame_len), frame_size))
|
||||
y = y[delay_len:len(s)+delay_len]
|
||||
|
||||
# --- Plot spectrograms ---
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
|
||||
|
||||
NFFT = 512
|
||||
for (ax, s) in [(ax1, s), (ax2, y)]:
|
||||
ax.specgram(s, Fs=fs, NFFT=NFFT, pad_to=4*NFFT, noverlap=NFFT//2,
|
||||
vmin=-160, vmax=0)
|
||||
|
||||
ax1.set_title('Input signal')
|
||||
ax1.set_ylabel('Frequency (Hz)')
|
||||
|
||||
ax2.set_title(('Processed signal (%.1f kbps)' % (bitrate/1000)))
|
||||
ax2.set_ylabel('Frequency (Hz)')
|
||||
|
||||
plt.show()
|
||||
Reference in New Issue
Block a user