| #!/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() |