mirror of
https://github.com/google/liblc3.git
synced 2026-04-16 12:45:31 +00:00
First release
This commit is contained in:
28
CONTRIBUTING.md
Normal file
28
CONTRIBUTING.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution;
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code Reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google/conduct/).
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
126
Makefile
Normal file
126
Makefile
Normal file
@@ -0,0 +1,126 @@
|
||||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
V ?= @
|
||||
|
||||
BUILD_DIR := build
|
||||
BIN_DIR := bin
|
||||
|
||||
|
||||
#
|
||||
# Set `gcc` as default compiler
|
||||
#
|
||||
|
||||
CC := $(if $(CC)=cc,gcc,$(CC))
|
||||
AS := $(if $(AS)=as,$(CC),$(AS))
|
||||
LD := $(if $(LD)=ld,$(CC),$(LD))
|
||||
|
||||
CFLAGS := $(if $(DEBUG),-O0 -g,-O3)
|
||||
CFLAGS += -std=c11 -Wall -Wextra
|
||||
|
||||
|
||||
#
|
||||
# Declarations
|
||||
#
|
||||
|
||||
lib_list :=
|
||||
bin_list :=
|
||||
|
||||
define add-lib
|
||||
$(eval $(1)_bin ?= $(1).a)
|
||||
$(eval $(1)_bin := $(addprefix $(BIN_DIR)/,$($(1)_bin)))
|
||||
|
||||
lib_list += $(1)
|
||||
LIB += $($(1)_bin)
|
||||
endef
|
||||
|
||||
define add-bin
|
||||
$(eval $(1)_bin ?= $(1))
|
||||
$(eval $(1)_bin := $(addprefix $(BIN_DIR)/,$($(1)_bin)))
|
||||
|
||||
$($(1)_bin): LDLIBS += $(if $(filter $(LIBC),bionic),\
|
||||
$(filter-out rt pthread,$($(1)_ldlibs)),$($(1)_ldlibs))
|
||||
$($(1)_bin): LDFLAGS += $($(1)_ldflags)
|
||||
|
||||
bin_list += $(1)
|
||||
BIN += $($(1)_bin)
|
||||
endef
|
||||
|
||||
define set-target
|
||||
$(eval $(1)_obj ?= $(patsubst %.c,%.o,$(filter %.c,$($(1)_src))) \
|
||||
$(patsubst %.s,%.o,$(filter %.s,$($(1)_src))))
|
||||
$(eval $(1)_obj := $(addprefix $(BUILD_DIR)/,$($(1)_obj)))
|
||||
$(eval $(1)_lib := $(foreach lib, $($(1)_lib), $($(lib)_bin)))
|
||||
|
||||
$($(1)_obj): INCLUDE += $($(1)_include)
|
||||
$($(1)_obj): DEFINE += $($(1)_define)
|
||||
$($(1)_obj): CFLAGS += $($(1)_cflags)
|
||||
|
||||
-include $($(1)_obj:.o=.d)
|
||||
|
||||
$($(1)_bin): $($(1)_lib)
|
||||
$($(1)_bin): $($(1)_obj)
|
||||
$($(1)_bin): $($(1)_dependencies)
|
||||
|
||||
.PHONY: $(1)
|
||||
$(1): $($(1)_bin)
|
||||
endef
|
||||
|
||||
.PHONY: default
|
||||
default:
|
||||
|
||||
include makefile.mk
|
||||
|
||||
|
||||
#
|
||||
# Rules
|
||||
#
|
||||
|
||||
MAKEFILE_DEPS := $(MAKEFILE_LIST)
|
||||
|
||||
$(foreach lib, $(lib_list), $(eval $(call set-target,$(lib))))
|
||||
$(foreach bin, $(bin_list), $(eval $(call set-target,$(bin))))
|
||||
|
||||
$(BUILD_DIR)/%.o: %.c $(MAKEFILE_DEPS)
|
||||
@echo " CC $(notdir $<)"
|
||||
$(V)mkdir -p $(dir $@)
|
||||
$(V)$(CC) $< -c $(CFLAGS) \
|
||||
$(addprefix -I,$(INCLUDE)) \
|
||||
$(addprefix -D,$(DEFINE)) -MMD -MF $(@:.o=.d) -o $@
|
||||
|
||||
$(BUILD_DIR)/%.o: %.s $(MAKEFILE_DEPS)
|
||||
@echo " AS $(notdir $<)"
|
||||
$(V)mkdir -p $(dir $@)
|
||||
$(V)$(AS) $< -c $(CFLAGS) \
|
||||
$(addprefix -I,$(INCLUDE)) \
|
||||
$(addprefix -D,$(DEFINE)) -MMD -MF $(@:.o=.d) -o $@
|
||||
|
||||
$(LIB): $(MAKEFILE_DEPS)
|
||||
@echo " AR $(notdir $@)"
|
||||
$(V)mkdir -p $(dir $@)
|
||||
$(V)$(AR) rcs $@ $(filter %.o,$^)
|
||||
|
||||
$(BIN): $(MAKEFILE_DEPS)
|
||||
@echo " LD $(notdir $@)"
|
||||
$(V)mkdir -p $(dir $@)
|
||||
$(V)$(LD) $(filter %.o,$^) $(filter %.a,$^) $(LDFLAGS) \
|
||||
$(addprefix -l,$(LDLIBS)) -o $@
|
||||
|
||||
clean:
|
||||
$(V)rm -rf $(BUILD_DIR)
|
||||
$(V)rm -rf $(BIN_DIR)
|
||||
|
||||
clean-all: clean
|
||||
102
README.md
Normal file
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Low Complexity Communication Codec (LC3)
|
||||
|
||||
The LC3 is an efficient low latency audio codec.
|
||||
|
||||
[_Low Complexity Communication Codec_](https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=502107&vId=542963)
|
||||
|
||||
## Overview
|
||||
|
||||
The directory layout is as follows :
|
||||
- include: Library interface
|
||||
- src: Source files
|
||||
- tools: Standalone encoder/decoder tools
|
||||
- test: Python implentation, used as reference for unit testing
|
||||
- build: Building outputs
|
||||
- bin: Compilation output
|
||||
|
||||
## How to build
|
||||
|
||||
The default toolchain used is GCC. Invoke `make` to build the library.
|
||||
|
||||
```sh
|
||||
$ make -j
|
||||
```
|
||||
|
||||
Compiled library `liblc3.a` will be found in `bin` directory.
|
||||
|
||||
#### Cross compilation
|
||||
|
||||
The cc, as, ld and ar can be selected with respective Makefile variables `CC`,
|
||||
`AS`, `LD` and `AR`. The `AS` and `LD` selections are optionnal, and fallback
|
||||
to `CC` selection when not defined.
|
||||
|
||||
The `LIBC` must be set to `bionic` for android cross-compilation. This switch
|
||||
prevent link with `pthread` and `rt` libraries, that is included in the
|
||||
bionic libc.
|
||||
|
||||
Following example build for android, using NDK toolset.
|
||||
|
||||
```sh
|
||||
$ make -j CC=path_to_android_ndk_prebuilt/toolchain-prefix-clang LIBC=bionic
|
||||
```
|
||||
|
||||
Compiled library will be found in `bin` directory.
|
||||
|
||||
## Tools
|
||||
|
||||
Tools can be all compiled, while involking `make` as follows :
|
||||
|
||||
```sh
|
||||
$ make tools
|
||||
```
|
||||
|
||||
The standalone encoder `elc3` take a `wave` file as input and encode it
|
||||
according given parameter. The LC3 binary file format used is the non
|
||||
standard format described by the reference encoder / decoder tools.
|
||||
The standalone decoder `dlc3` do the inverse operation.
|
||||
|
||||
Refer to `elc3 -h` or `dlc3 -h` for options.
|
||||
|
||||
Note that `elc3` output bitstream to standard output when output file is
|
||||
omitted. On the other side `dlc3` read from standard input when input output
|
||||
file are omitted.
|
||||
In such way you can easly test encoding / decoding loop with :
|
||||
|
||||
```sh
|
||||
$ ./elc3 <in.wav> -b <bitrate> | ./dlc3 > <out.wav>
|
||||
```
|
||||
|
||||
Adding Linux `aplay` tools, you will be able to instant hear the result :
|
||||
|
||||
```sh
|
||||
$ ./elc3 <in.wav> -b <bitrate> | ./dlc3 | aplay
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
A python implementation of the encoder is provided in `test` diretory.
|
||||
The C implementation is unitary validated against this implementation and
|
||||
intermediate values given in Appendix C of the specification.
|
||||
|
||||
#### Prerequisite
|
||||
|
||||
```sh
|
||||
# apt install python3 python3-dev python3-pip
|
||||
$ pip3 install scipy numpy
|
||||
```
|
||||
|
||||
#### Running test suite
|
||||
|
||||
```sh
|
||||
$ make test
|
||||
```
|
||||
|
||||
|
||||
## Conformance
|
||||
|
||||
The proposed encoder and decoder implementation have been fully tested and
|
||||
validated.
|
||||
|
||||
For more detail on conformance, refer to [_Bluetooth Conformance
|
||||
Documents and scripts_](https://www.bluetooth.com/specifications/specs/low-complexity-communication-codec-1-0/)
|
||||
|
||||
305
include/lc3.h
Normal file
305
include/lc3.h
Normal file
@@ -0,0 +1,305 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Low Complexity Communication Codec (LC3)
|
||||
*
|
||||
* This implementation conforms to :
|
||||
* Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*
|
||||
*
|
||||
* The LC3 is an efficient low latency audio codec.
|
||||
*
|
||||
* - Unlike most other codecs, the LC3 codec is focused on audio streaming
|
||||
* in constrained (on packet sizes and interval) tranport layer.
|
||||
* In this way, the LC3 does not handle :
|
||||
* VBR (Variable Bitrate), based on input signal complexity
|
||||
* ABR (Adaptative Bitrate). It does not rely on any bit reservoir,
|
||||
* a frame will be strictly encoded in the bytes budget given by
|
||||
* the user (or transport layer).
|
||||
*
|
||||
* However, the bitrate (bytes budget for encoding a frame) can be
|
||||
* freely changed at any time. But will not rely on signal complexity,
|
||||
* it can follow a temporary bandwidth increase or reduction.
|
||||
*
|
||||
* - Unlike classic codecs, the LC3 codecs does not run on fixed amount
|
||||
* of samples as input. It operates only on fixed frame duration, for
|
||||
* any supported samplerates (8 to 48 KHz). Two frames duration are
|
||||
* available 7.5ms and 10ms.
|
||||
*
|
||||
*
|
||||
* --- About 44.1 KHz samplerate ---
|
||||
*
|
||||
* The Bluetooth specification reference the 44.1 KHz samplerate, although
|
||||
* there is no support in the core algorithm of the codec of 44.1 KHz.
|
||||
* We can summarize the 44.1 KHz support by "you can put any samplerate
|
||||
* around the defined base samplerates". Please mind the following items :
|
||||
*
|
||||
* 1. The frame size will not be 7.5 ms or 10 ms, but is scaled
|
||||
* by 'supported samplerate' / 'input samplerate'
|
||||
*
|
||||
* 2. The bandwidth will be hard limited (to 20 KHz) if you select 48 KHz.
|
||||
* The encoded bandwidth will also be affected by the above inverse
|
||||
* factor of 20 KHz.
|
||||
*
|
||||
* Applied to 44.1 KHz, we get :
|
||||
*
|
||||
* 1. About 8.16 ms frame duration, instead of 7.5 ms
|
||||
* About 10.88 ms frame duration, instead of 10 ms
|
||||
*
|
||||
* 2. The bandwidth becomes limited to 18.375 KHz
|
||||
*
|
||||
*
|
||||
* --- How to encode / decode ---
|
||||
*
|
||||
* An encoder / decoder context needs to be setup. This context keeps states
|
||||
* on the current stream to proceed, and samples that overlapped across
|
||||
* frames.
|
||||
*
|
||||
* You have two ways to setup the encoder / decoder :
|
||||
*
|
||||
* - Using static memory allocation (this module does not rely on
|
||||
* any dynamic memory allocation). The types `lc3_xxcoder_mem_16k_t`,
|
||||
* and `lc3_xxcoder_mem_48k_t` have size of the memory needed for
|
||||
* encoding up to 16 KHz or 48 KHz.
|
||||
*
|
||||
* - Using dynamic memory allocation. The `lc3_xxcoder_size()` procedure
|
||||
* returns the needed memory size, for a given configuration. The memory
|
||||
* space must be aligned to a pointer size. As an example, you can setup
|
||||
* encoder like this :
|
||||
*
|
||||
* | enc = lc3_setup_encoder(frame_us, samplerate,
|
||||
* | malloc(lc3_encoder_size(frame_us, samplerate)));
|
||||
* | ...
|
||||
* | free(enc);
|
||||
*
|
||||
* Note :
|
||||
* - A NULL memory adress as input, will return a NULL encoder context.
|
||||
* - The returned encoder handle is set at the address of the allocated
|
||||
* memory space, you can directly free the handle.
|
||||
*
|
||||
* Next, call the `lc3_encode()` encoding procedure, for each frames.
|
||||
* To handle multichannel streams (Stereo or more), you can proceed with
|
||||
* interleaved channels PCM stream like this :
|
||||
*
|
||||
* | for(int ich = 0; ich < nch: ich++)
|
||||
* | lc3_encode(encoder[ich], pcm + ich, nch, ...);
|
||||
*
|
||||
* with `nch` as the number of channels in the PCM stream
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Antoine SOULIER, Tempow / Google LLC
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LC3_H
|
||||
#define __LC3_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lc3_private.h"
|
||||
|
||||
|
||||
/**
|
||||
* Limitations
|
||||
* - On the bitrate, in bps, of a stream
|
||||
* - On the size of the frames in bytes
|
||||
*/
|
||||
|
||||
#define LC3_MIN_BITRATE 16000
|
||||
#define LC3_MAX_BITRATE 320000
|
||||
|
||||
#define LC3_MIN_FRAME_BYTES 20
|
||||
#define LC3_MAX_FRAME_BYTES 400
|
||||
|
||||
|
||||
/**
|
||||
* Parameters check
|
||||
* LC3_CHECK_DT_US(us) True when frame duration in us is suitable
|
||||
* LC3_CHECK_SR_HZ(sr) True when samplerate in Hz is suitable
|
||||
*/
|
||||
|
||||
#define LC3_CHECK_DT_US(us) \
|
||||
( ((us) == 7500) || ((us) == 10000) )
|
||||
|
||||
#define LC3_CHECK_SR_HZ(sr) \
|
||||
( ((sr) == 8000) || ((sr) == 16000) || ((sr) == 24000) || \
|
||||
((sr) == 32000) || ((sr) == 48000) )
|
||||
|
||||
|
||||
/**
|
||||
* PCM Sample Format
|
||||
* S16 Signed 16 bits, in 16 bits words (int16_t)
|
||||
* S24 Signed 24 bits, using low three bytes of 32 bits words (int32_t).
|
||||
* The high byte sign extends the sample value (bits 31..24 set to b23).
|
||||
*/
|
||||
|
||||
enum lc3_pcm_format {
|
||||
LC3_PCM_FORMAT_S16,
|
||||
LC3_PCM_FORMAT_S24,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle
|
||||
*/
|
||||
|
||||
typedef struct lc3_encoder *lc3_encoder_t;
|
||||
typedef struct lc3_decoder *lc3_decoder_t;
|
||||
|
||||
|
||||
/**
|
||||
* Static memory of encoder context
|
||||
*
|
||||
* Propose types suitable for static memory allocation, supporting
|
||||
* any frame duration, and maximum samplerates 16k and 48k respectively
|
||||
* You can customize your type using the `LC3_ENCODER_MEM_T` or
|
||||
* `LC3_DECODER_MEM_T` macro.
|
||||
*/
|
||||
|
||||
typedef LC3_ENCODER_MEM_T(10000, 16000) lc3_encoder_mem_16k_t;
|
||||
typedef LC3_ENCODER_MEM_T(10000, 48000) lc3_encoder_mem_48k_t;
|
||||
|
||||
typedef LC3_DECODER_MEM_T(10000, 16000) lc3_decoder_mem_16k_t;
|
||||
typedef LC3_DECODER_MEM_T(10000, 48000) lc3_decoder_mem_48k_t;
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of PCM samples in a frame
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* sr_hz Samplerate in Hz, 8000, 16000, 24000, 32000 or 48000
|
||||
* return Number of PCM samples, -1 on bad parameters
|
||||
*/
|
||||
int lc3_frame_samples(int dt_us, int sr_hz);
|
||||
|
||||
/**
|
||||
* Return the size of frames, from bitrate
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* bitrate Target bitrate in bit per second
|
||||
* return The floor size in bytes of the frames, -1 on bad parameters
|
||||
*/
|
||||
int lc3_frame_bytes(int dt_us, int bitrate);
|
||||
|
||||
/**
|
||||
* Resolve the bitrate, from the size of frames
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* nbytes Size in bytes of the frames
|
||||
* return The according bitrate in bps, -1 on bad parameters
|
||||
*/
|
||||
int lc3_resolve_bitrate(int dt_us, int nbytes);
|
||||
|
||||
/**
|
||||
* Return algorithmic delay, as a number of samples
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* sr_hz Samplerate in Hz, 8000, 16000, 24000, 32000 or 48000
|
||||
* return Number of algorithmic delay samples, -1 on bad parameters
|
||||
*/
|
||||
int lc3_delay_samples(int dt_us, int sr_hz);
|
||||
|
||||
/**
|
||||
* Return size needed for an encoder
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* sr_hz Samplerate in Hz, 8000, 16000, 24000, 32000 or 48000
|
||||
* return Size of then encoder in bytes, 0 on bad parameters
|
||||
*
|
||||
* The `sr_hz` parameter is the samplerate of the PCM input stream,
|
||||
* and will match `sr_pcm_hz` of `lc3_setup_encoder()`.
|
||||
*/
|
||||
unsigned lc3_encoder_size(int dt_us, int sr_hz);
|
||||
|
||||
/**
|
||||
* Setup encoder
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* sr_hz Samplerate in Hz, 8000, 16000, 24000, 32000 or 48000
|
||||
* sr_pcm_hz Input samplerate, downsampling option of input, or 0
|
||||
* mem Encoder memory space, aligned to pointer type
|
||||
* return Encoder as an handle, NULL on bad parameters
|
||||
*
|
||||
* The `sr_pcm_hz` parameter is a downsampling option of PCM input,
|
||||
* the value `0` fallback to the samplerate of the encoded stream `sr_hz`.
|
||||
* When used, `sr_pcm_hz` is intended to be higher or equal to the encoder
|
||||
* samplerate `sr_hz`. The size of the context needed, given by
|
||||
* `lc3_encoder_size()` will be set accordingly to `sr_pcm_hz`.
|
||||
*/
|
||||
lc3_encoder_t lc3_setup_encoder(
|
||||
int dt_us, int sr_hz, int sr_pcm_hz, void *mem);
|
||||
|
||||
/**
|
||||
* Encode a frame
|
||||
* encoder Handle of the encoder
|
||||
* fmt PCM input format
|
||||
* pcm, stride Input PCM samples, and count between two consecutives
|
||||
* nbytes Target size, in bytes, of the frame (20 to 400)
|
||||
* out Output buffer of `nbytes` size
|
||||
* return 0: On success -1: Wrong parameters
|
||||
*/
|
||||
int lc3_encode(lc3_encoder_t encoder, enum lc3_pcm_format fmt,
|
||||
const void *pcm, int stride, int nbytes, void *out);
|
||||
|
||||
/**
|
||||
* Return size needed for an decoder
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* sr_hz Samplerate in Hz, 8000, 16000, 24000, 32000 or 48000
|
||||
* return Size of then decoder in bytes, 0 on bad parameters
|
||||
*
|
||||
* The `sr_hz` parameter is the samplerate of the PCM output stream,
|
||||
* and will match `sr_pcm_hz` of `lc3_setup_decoder()`.
|
||||
*/
|
||||
unsigned lc3_decoder_size(int dt_us, int sr_hz);
|
||||
|
||||
/**
|
||||
* Setup decoder
|
||||
* dt_us Frame duration in us, 7500 or 10000
|
||||
* sr_hz Samplerate in Hz, 8000, 16000, 24000, 32000 or 48000
|
||||
* sr_pcm_hz Output samplerate, upsampling option of output (or 0)
|
||||
* mem Decoder memory space, aligned to pointer type
|
||||
* return Decoder as an handle, NULL on bad parameters
|
||||
*
|
||||
* The `sr_pcm_hz` parameter is an upsampling option of PCM output,
|
||||
* the value `0` fallback to the samplerate of the decoded stream `sr_hz`.
|
||||
* When used, `sr_pcm_hz` is intended to be higher or equal to the decoder
|
||||
* samplerate `sr_hz`. The size of the context needed, given by
|
||||
* `lc3_decoder_size()` will be set accordingly to `sr_pcm_hz`.
|
||||
*/
|
||||
lc3_decoder_t lc3_setup_decoder(
|
||||
int dt_us, int sr_hz, int sr_pcm_hz, void *mem);
|
||||
|
||||
/**
|
||||
* Decode a frame
|
||||
* decoder Handle of the decoder
|
||||
* in, nbytes Input bitstream, and size in bytes, NULL performs PLC
|
||||
* fmt PCM output format
|
||||
* pcm, stride Output PCM samples, and count between two consecutives
|
||||
* return 0: On success 1: PLC operated -1: Wrong parameters
|
||||
*/
|
||||
int lc3_decode(lc3_decoder_t decoder, const void *in, int nbytes,
|
||||
enum lc3_pcm_format fmt, void *pcm, int stride);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LC3_H */
|
||||
156
include/lc3_private.h
Normal file
156
include/lc3_private.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __LC3_PRIVATE_H
|
||||
#define __LC3_PRIVATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
/**
|
||||
* Return number of samples, delayed samples and
|
||||
* encoded spectrum coefficients within a frame
|
||||
* For decoding, add number of samples of 18 ms history
|
||||
*/
|
||||
|
||||
#define __LC3_NS(dt_us, sr_hz) \
|
||||
((dt_us * sr_hz) / 1000 / 1000)
|
||||
|
||||
#define __LC3_ND(dt_us, sr_hz) \
|
||||
( (dt_us) == 7500 ? 23 * __LC3_NS(dt_us, sr_hz) / 30 \
|
||||
: 5 * __LC3_NS(dt_us, sr_hz) / 8 )
|
||||
|
||||
#define __LC3_NH(sr_hz) \
|
||||
( (18 * sr_hz) / 1000 )
|
||||
|
||||
|
||||
/**
|
||||
* Frame duration 7.5ms or 10ms
|
||||
*/
|
||||
|
||||
enum lc3_dt {
|
||||
LC3_DT_7M5,
|
||||
LC3_DT_10M,
|
||||
|
||||
LC3_NUM_DT
|
||||
};
|
||||
|
||||
/**
|
||||
* Sampling frequency
|
||||
*/
|
||||
|
||||
enum lc3_srate {
|
||||
LC3_SRATE_8K,
|
||||
LC3_SRATE_16K,
|
||||
LC3_SRATE_24K,
|
||||
LC3_SRATE_32K,
|
||||
LC3_SRATE_48K,
|
||||
|
||||
LC3_NUM_SRATE,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encoder state and memory
|
||||
*/
|
||||
|
||||
typedef struct lc3_attdet_analysis {
|
||||
float en1, an1;
|
||||
int p_att;
|
||||
} lc3_attdet_analysis_t;
|
||||
|
||||
struct lc3_ltpf_hp50_state {
|
||||
float s1, s2;
|
||||
};
|
||||
|
||||
typedef struct lc3_ltpf_analysis {
|
||||
bool active;
|
||||
int pitch;
|
||||
float nc[2];
|
||||
|
||||
struct lc3_ltpf_hp50_state hp50;
|
||||
float x_12k8[384];
|
||||
float x_6k4[178];
|
||||
int tc;
|
||||
} lc3_ltpf_analysis_t;
|
||||
|
||||
typedef struct lc3_spec_analysis {
|
||||
float nbits_off;
|
||||
int nbits_spare;
|
||||
} lc3_spec_analysis_t;
|
||||
|
||||
struct lc3_encoder {
|
||||
enum lc3_dt dt;
|
||||
enum lc3_srate sr, sr_pcm;
|
||||
|
||||
lc3_attdet_analysis_t attdet;
|
||||
lc3_ltpf_analysis_t ltpf;
|
||||
lc3_spec_analysis_t spec;
|
||||
|
||||
float *xs, *xf, s[0];
|
||||
};
|
||||
|
||||
#define LC3_ENCODER_BUFFER_COUNT(dt_us, sr_hz) \
|
||||
( 2*__LC3_NS(dt_us, sr_hz) + __LC3_ND(dt_us, sr_hz) )
|
||||
|
||||
#define LC3_ENCODER_MEM_T(dt_us, sr_hz) \
|
||||
struct { \
|
||||
struct lc3_encoder __e; \
|
||||
float __s[LC3_ENCODER_BUFFER_COUNT(dt_us, sr_hz)]; \
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decoder state and memory
|
||||
*/
|
||||
|
||||
typedef struct lc3_ltpf_synthesis {
|
||||
bool active;
|
||||
int pitch;
|
||||
float c[12][2], x[12];
|
||||
} lc3_ltpf_synthesis_t;
|
||||
|
||||
typedef struct lc3_plc_state {
|
||||
uint16_t seed;
|
||||
int count;
|
||||
float alpha;
|
||||
} lc3_plc_state_t;
|
||||
|
||||
struct lc3_decoder {
|
||||
enum lc3_dt dt;
|
||||
enum lc3_srate sr, sr_pcm;
|
||||
|
||||
lc3_ltpf_synthesis_t ltpf;
|
||||
lc3_plc_state_t plc;
|
||||
|
||||
float *xs, *xd, *xg, s[0];
|
||||
};
|
||||
|
||||
#define LC3_DECODER_BUFFER_COUNT(dt_us, sr_hz) \
|
||||
( __LC3_NH(sr_hz) + __LC3_NS(dt_us, sr_hz) + \
|
||||
__LC3_ND(dt_us, sr_hz) + __LC3_NS(dt_us, sr_hz) )
|
||||
|
||||
#define LC3_DECODER_MEM_T(dt_us, sr_hz) \
|
||||
struct { \
|
||||
struct lc3_decoder __d; \
|
||||
float __s[LC3_DECODER_BUFFER_COUNT(dt_us, sr_hz)]; \
|
||||
}
|
||||
|
||||
|
||||
#endif /* __LC3_PRIVATE_H */
|
||||
41
makefile.mk
Normal file
41
makefile.mk
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
INCLUDE += include
|
||||
SRC_DIR := src
|
||||
|
||||
liblc3_src += \
|
||||
$(SRC_DIR)/attdet.c \
|
||||
$(SRC_DIR)/bits.c \
|
||||
$(SRC_DIR)/bwdet.c \
|
||||
$(SRC_DIR)/energy.c \
|
||||
$(SRC_DIR)/lc3.c \
|
||||
$(SRC_DIR)/ltpf.c \
|
||||
$(SRC_DIR)/mdct.c \
|
||||
$(SRC_DIR)/plc.c \
|
||||
$(SRC_DIR)/sns.c \
|
||||
$(SRC_DIR)/spec.c \
|
||||
$(SRC_DIR)/tables.c \
|
||||
$(SRC_DIR)/tns.c
|
||||
|
||||
liblc3_cflags += -ffast-math
|
||||
|
||||
$(eval $(call add-lib,liblc3))
|
||||
|
||||
default: liblc3
|
||||
|
||||
-include tools/makefile.mk
|
||||
-include test/makefile.mk
|
||||
92
src/attdet.c
Normal file
92
src/attdet.c
Normal file
@@ -0,0 +1,92 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "attdet.h"
|
||||
|
||||
|
||||
/**
|
||||
* Time domain attack detector
|
||||
*/
|
||||
bool lc3_attdet_run(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int nbytes, struct lc3_attdet_analysis *attdet, const float *x)
|
||||
{
|
||||
/* --- Check enabling --- */
|
||||
|
||||
const int nbytes_ranges[LC3_NUM_DT][LC3_NUM_SRATE - LC3_SRATE_32K][2] = {
|
||||
[LC3_DT_7M5] = { { 61, 149 }, { 75, 149 } },
|
||||
[LC3_DT_10M] = { { 81, INT_MAX }, { 100, INT_MAX } },
|
||||
};
|
||||
|
||||
if (sr < LC3_SRATE_32K ||
|
||||
nbytes < nbytes_ranges[dt][sr - LC3_SRATE_32K][0] ||
|
||||
nbytes > nbytes_ranges[dt][sr - LC3_SRATE_32K][1] )
|
||||
return 0;
|
||||
|
||||
/* --- Filtering & Energy calculation --- */
|
||||
|
||||
int nblk = 4 - (dt == LC3_DT_7M5);
|
||||
float e[4];
|
||||
|
||||
for (int i = 0; i < nblk; i++) {
|
||||
e[i] = 0;
|
||||
|
||||
if (sr == LC3_SRATE_32K) {
|
||||
float xn2 = x[-4] + x[-3];
|
||||
float xn1 = x[-2] + x[-1];
|
||||
float xn, xf;
|
||||
|
||||
for (int j = 0; j < 40; j++, x += 2, xn2 = xn1, xn1 = xn) {
|
||||
xn = x[0] + x[1];
|
||||
xf = 0.375 * xn - 0.5 * xn1 + 0.125 * xn2;
|
||||
e[i] += xf * xf;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
float xn2 = x[-6] + x[-5] + x[-4];
|
||||
float xn1 = x[-3] + x[-2] + x[-1];
|
||||
float xn, xf;
|
||||
|
||||
for (int j = 0; j < 40; j++, x += 3, xn2 = xn1, xn1 = xn) {
|
||||
xn = x[0] + x[1] + x[2];
|
||||
xf = 0.375 * xn - 0.5 * xn1 + 0.125 * xn2;
|
||||
e[i] += xf * xf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Attack detection ---
|
||||
* The attack block `p_att` is defined as the normative value + 1,
|
||||
* in such way, it will be initialized to 0 */
|
||||
|
||||
int p_att = 0;
|
||||
float a[4];
|
||||
|
||||
for (int i = 0; i < nblk; i++) {
|
||||
a[i] = fmaxf(0.25 * attdet->an1, attdet->en1);
|
||||
attdet->en1 = e[i], attdet->an1 = a[i];
|
||||
|
||||
if (e[i] > 8.5 * a[i])
|
||||
p_att = i + 1;
|
||||
}
|
||||
|
||||
int att = attdet->p_att >= 1 + (nblk >> 1) || p_att > 0;
|
||||
attdet->p_att = p_att;
|
||||
|
||||
return att;
|
||||
}
|
||||
44
src/attdet.h
Normal file
44
src/attdet.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Time domain attack detector
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_ATTDET_H
|
||||
#define __LC3_ATTDET_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/**
|
||||
* Time domain attack detector
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* nbytes Size in bytes of the frame
|
||||
* attdet Context of the Attack Detector
|
||||
* x [-6..-1] Previous, [0..ns-1] Current samples
|
||||
* return 1: Attack detected 0: Otherwise
|
||||
*/
|
||||
bool lc3_attdet_run(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int nbytes, lc3_attdet_analysis_t *attdet, const float *x);
|
||||
|
||||
|
||||
#endif /* __LC3_ATTDET_H */
|
||||
375
src/bits.c
Normal file
375
src/bits.c
Normal file
@@ -0,0 +1,375 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "bits.h"
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Common
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static inline int ac_get(struct lc3_bits_buffer *);
|
||||
static inline void accu_load(struct lc3_bits_accu *, struct lc3_bits_buffer *);
|
||||
|
||||
/**
|
||||
* Arithmetic coder return range bits
|
||||
* ac Arithmetic coder
|
||||
* return 1 + log2(ac->range)
|
||||
*/
|
||||
static int ac_get_range_bits(const struct lc3_bits_ac *ac)
|
||||
{
|
||||
int nbits = 0;
|
||||
|
||||
for (unsigned r = ac->range; r; r >>= 1, nbits++);
|
||||
|
||||
return nbits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arithmetic coder return pending bits
|
||||
* ac Arithmetic coder
|
||||
* return Pending bits
|
||||
*/
|
||||
static int ac_get_pending_bits(const struct lc3_bits_ac *ac)
|
||||
{
|
||||
return 26 - ac_get_range_bits(ac) +
|
||||
((ac->cache >= 0) + ac->carry_count) * 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of bits left in the bitstream
|
||||
* bits Bitstream context
|
||||
* return >= 0: Number of bits left < 0: Overflow
|
||||
*/
|
||||
static int get_bits_left(const struct lc3_bits *bits)
|
||||
{
|
||||
const struct lc3_bits_buffer *buffer = &bits->buffer;
|
||||
const struct lc3_bits_accu *accu = &bits->accu;
|
||||
const struct lc3_bits_ac *ac = &bits->ac;
|
||||
|
||||
uintptr_t end = (uintptr_t)buffer->p_bw +
|
||||
(bits->mode == LC3_BITS_MODE_READ ? LC3_ACCU_BITS/8 : 0);
|
||||
|
||||
uintptr_t start = (uintptr_t)buffer->p_fw -
|
||||
(bits->mode == LC3_BITS_MODE_READ ? LC3_AC_BITS/8 : 0);
|
||||
|
||||
int n = end > start ? (int)(end - start) : -(int)(start - end);
|
||||
|
||||
return 8 * n - (accu->n + accu->nover + ac_get_pending_bits(ac));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup bitstream writing
|
||||
*/
|
||||
void lc3_setup_bits(struct lc3_bits *bits,
|
||||
enum lc3_bits_mode mode, void *buffer, int len)
|
||||
{
|
||||
*bits = (struct lc3_bits){
|
||||
.mode = mode,
|
||||
.accu = {
|
||||
.n = mode == LC3_BITS_MODE_READ ? LC3_ACCU_BITS : 0,
|
||||
},
|
||||
.ac = {
|
||||
.range = 0xffffff,
|
||||
.cache = -1
|
||||
},
|
||||
.buffer = {
|
||||
.start = (uint8_t *)buffer, .end = (uint8_t *)buffer + len,
|
||||
.p_fw = (uint8_t *)buffer, .p_bw = (uint8_t *)buffer + len,
|
||||
}
|
||||
};
|
||||
|
||||
if (mode == LC3_BITS_MODE_READ) {
|
||||
struct lc3_bits_ac *ac = &bits->ac;
|
||||
struct lc3_bits_accu *accu = &bits->accu;
|
||||
struct lc3_bits_buffer *buffer = &bits->buffer;
|
||||
|
||||
ac->low = ac_get(buffer) << 16;
|
||||
ac->low |= ac_get(buffer) << 8;
|
||||
ac->low |= ac_get(buffer);
|
||||
|
||||
accu_load(accu, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of bits left in the bitstream
|
||||
*/
|
||||
int lc3_get_bits_left(const struct lc3_bits *bits)
|
||||
{
|
||||
return LC3_MAX(get_bits_left(bits), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of bits left in the bitstream
|
||||
*/
|
||||
int lc3_check_bits(const struct lc3_bits *bits)
|
||||
{
|
||||
const struct lc3_bits_ac *ac = &bits->ac;
|
||||
|
||||
return -(get_bits_left(bits) < 0 || ac->error);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Writing
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Flush the bits accumulator
|
||||
* accu Bitstream accumulator
|
||||
* buffer Bitstream buffer
|
||||
*/
|
||||
static inline void accu_flush(
|
||||
struct lc3_bits_accu *accu, struct lc3_bits_buffer *buffer)
|
||||
{
|
||||
int nbytes = LC3_MIN(accu->n >> 3,
|
||||
LC3_MAX(buffer->p_bw - buffer->p_fw, 0));
|
||||
|
||||
accu->n -= 8 * nbytes;
|
||||
|
||||
for ( ; nbytes; accu->v >>= 8, nbytes--)
|
||||
*(--buffer->p_bw) = accu->v & 0xff;
|
||||
|
||||
if (accu->n >= 8)
|
||||
accu->n = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arithmetic coder put byte
|
||||
* buffer Bitstream buffer
|
||||
* byte Byte to output
|
||||
*/
|
||||
static inline void ac_put(struct lc3_bits_buffer *buffer, int byte)
|
||||
{
|
||||
if (buffer->p_fw < buffer->end)
|
||||
*(buffer->p_fw++) = byte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arithmetic coder range shift
|
||||
* ac Arithmetic coder
|
||||
* buffer Bitstream buffer
|
||||
*/
|
||||
static inline void ac_shift(
|
||||
struct lc3_bits_ac *ac, struct lc3_bits_buffer *buffer)
|
||||
{
|
||||
if (ac->low < 0xff0000 || ac->carry)
|
||||
{
|
||||
if (ac->cache >= 0)
|
||||
ac_put(buffer, ac->cache + ac->carry);
|
||||
|
||||
for ( ; ac->carry_count > 0; ac->carry_count--)
|
||||
ac_put(buffer, ac->carry ? 0x00 : 0xff);
|
||||
|
||||
ac->cache = ac->low >> 16;
|
||||
ac->carry = 0;
|
||||
}
|
||||
else
|
||||
ac->carry_count++;
|
||||
|
||||
ac->low = (ac->low << 8) & 0xffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arithmetic coder termination
|
||||
* ac Arithmetic coder
|
||||
* buffer Bitstream buffer
|
||||
* end_val/nbits End value and count of bits to terminate (1 to 8)
|
||||
*/
|
||||
static void ac_terminate(struct lc3_bits_ac *ac,
|
||||
struct lc3_bits_buffer *buffer)
|
||||
{
|
||||
int nbits = 25 - ac_get_range_bits(ac);
|
||||
unsigned mask = 0xffffff >> nbits;
|
||||
unsigned val = ac->low + mask;
|
||||
unsigned high = ac->low + ac->range;
|
||||
|
||||
bool over_val = val >> 24;
|
||||
bool over_high = high >> 24;
|
||||
|
||||
val = (val & 0xffffff) & ~mask;
|
||||
high = (high & 0xffffff);
|
||||
|
||||
if (over_val == over_high) {
|
||||
|
||||
if (val + mask >= high) {
|
||||
nbits++;
|
||||
mask >>= 1;
|
||||
val = ((ac->low + mask) & 0xffffff) & ~mask;
|
||||
}
|
||||
|
||||
ac->carry |= val < ac->low;
|
||||
}
|
||||
|
||||
ac->low = val;
|
||||
|
||||
for (; nbits > 8; nbits -= 8)
|
||||
ac_shift(ac, buffer);
|
||||
ac_shift(ac, buffer);
|
||||
|
||||
int end_val = ac->cache >> (8 - nbits);
|
||||
|
||||
if (ac->carry_count) {
|
||||
ac_put(buffer, ac->cache);
|
||||
for ( ; ac->carry_count > 1; ac->carry_count--)
|
||||
ac_put(buffer, 0xff);
|
||||
|
||||
end_val = nbits < 8 ? 0 : 0xff;
|
||||
}
|
||||
|
||||
if (buffer->p_fw < buffer->end) {
|
||||
*buffer->p_fw &= 0xff >> nbits;
|
||||
*buffer->p_fw |= end_val << (8 - nbits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush and terminate bitstream
|
||||
*/
|
||||
void lc3_flush_bits(struct lc3_bits *bits)
|
||||
{
|
||||
struct lc3_bits_ac *ac = &bits->ac;
|
||||
struct lc3_bits_accu *accu = &bits->accu;
|
||||
struct lc3_bits_buffer *buffer = &bits->buffer;
|
||||
|
||||
int nleft = buffer->p_bw - buffer->p_fw;
|
||||
for (int n = 8 * nleft - accu->n; n > 0; n -= 32)
|
||||
lc3_put_bits(bits, 0, LC3_MIN(n, 32));
|
||||
|
||||
accu_flush(accu, buffer);
|
||||
|
||||
ac_terminate(ac, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write from 1 to 32 bits,
|
||||
* exceeding the capacity of the accumulator
|
||||
*/
|
||||
void lc3_put_bits_generic(struct lc3_bits *bits, unsigned v, int n)
|
||||
{
|
||||
struct lc3_bits_accu *accu = &bits->accu;
|
||||
|
||||
/* --- Fulfill accumulator and flush -- */
|
||||
|
||||
int n1 = LC3_MIN(LC3_ACCU_BITS - accu->n, n);
|
||||
if (n1) {
|
||||
accu->v |= v << accu->n;
|
||||
accu->n = LC3_ACCU_BITS;
|
||||
}
|
||||
|
||||
accu_flush(accu, &bits->buffer);
|
||||
|
||||
/* --- Accumulate remaining bits -- */
|
||||
|
||||
accu->v = v >> n1;
|
||||
accu->n = n - n1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arithmetic coder renormalization
|
||||
*/
|
||||
void lc3_ac_write_renorm(struct lc3_bits *bits)
|
||||
{
|
||||
struct lc3_bits_ac *ac = &bits->ac;
|
||||
|
||||
for ( ; ac->range < 0x10000; ac->range <<= 8)
|
||||
ac_shift(ac, &bits->buffer);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Reading
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Arithmetic coder get byte
|
||||
* buffer Bitstream buffer
|
||||
* return Byte read, 0 on overflow
|
||||
*/
|
||||
static inline int ac_get(struct lc3_bits_buffer *buffer)
|
||||
{
|
||||
return buffer->p_fw < buffer->end ? *(buffer->p_fw++) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the accumulator
|
||||
* accu Bitstream accumulator
|
||||
* buffer Bitstream buffer
|
||||
*/
|
||||
static inline void accu_load(struct lc3_bits_accu *accu,
|
||||
struct lc3_bits_buffer *buffer)
|
||||
{
|
||||
int nbytes = LC3_MIN(accu->n >> 3, buffer->p_bw - buffer->start);
|
||||
|
||||
accu->n -= 8 * nbytes;
|
||||
|
||||
for ( ; nbytes; nbytes--) {
|
||||
accu->v >>= 8;
|
||||
accu->v |= *(--buffer->p_bw) << (LC3_ACCU_BITS - 8);
|
||||
}
|
||||
|
||||
if (accu->n >= 8) {
|
||||
accu->nover = LC3_MIN(accu->nover + accu->n, LC3_ACCU_BITS);
|
||||
accu->v >>= accu->n;
|
||||
accu->n = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from 1 to 32 bits,
|
||||
* exceeding the capacity of the accumulator
|
||||
*/
|
||||
unsigned lc3_get_bits_generic(struct lc3_bits *bits, int n)
|
||||
{
|
||||
struct lc3_bits_accu *accu = &bits->accu;
|
||||
struct lc3_bits_buffer *buffer = &bits->buffer;
|
||||
|
||||
/* --- Fulfill accumulator and read -- */
|
||||
|
||||
accu_load(accu, buffer);
|
||||
|
||||
int n1 = LC3_MIN(LC3_ACCU_BITS - accu->n, n);
|
||||
unsigned v = (accu->v >> accu->n) & ((1u << n1) - 1);
|
||||
accu->n += n1;
|
||||
|
||||
/* --- Second round --- */
|
||||
|
||||
int n2 = n - n1;
|
||||
|
||||
if (n2) {
|
||||
accu_load(accu, buffer);
|
||||
|
||||
v |= ((accu->v >> accu->n) & ((1u << n2) - 1)) << n1;
|
||||
accu->n += n2;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arithmetic coder renormalization
|
||||
*/
|
||||
void lc3_ac_read_renorm(struct lc3_bits *bits)
|
||||
{
|
||||
struct lc3_bits_ac *ac = &bits->ac;
|
||||
|
||||
for ( ; ac->range < 0x10000; ac->range <<= 8)
|
||||
ac->low = ((ac->low << 8) | ac_get(&bits->buffer)) & 0xffffff;
|
||||
}
|
||||
314
src/bits.h
Normal file
314
src/bits.h
Normal file
@@ -0,0 +1,314 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Bitstream management
|
||||
*
|
||||
* The bitstream is written by the 2 ends of the buffer :
|
||||
*
|
||||
* - Arthmetic coder put bits while increasing memory addresses
|
||||
* in the buffer (forward)
|
||||
*
|
||||
* - Plain bits are puts starting the end of the buffer, with memeory
|
||||
* addresses decreasing (backward)
|
||||
*
|
||||
* .---------------------------------------------------.
|
||||
* | > > > > > > > > > > : : < < < < < < < < < |
|
||||
* '---------------------------------------------------'
|
||||
* |---------------------> - - - - - - - - - - - - - ->|
|
||||
* |< - - - <-------------------|
|
||||
* Arithmetic coding Plain bits
|
||||
* `lc3_put_symbol()` `lc3_put_bits()`
|
||||
*
|
||||
* - The forward writing is protected against buffer overflow, it cannot
|
||||
* write after the buffer, but can overwrite plain bits previously
|
||||
* written in the buffer.
|
||||
*
|
||||
* - The backward writing is protected against overwrite of the arithmetic
|
||||
* coder bitstream. In such way, the backward bitstream is always limited
|
||||
* by the aritmetic coder bitstream, and can be overwritten by him.
|
||||
*
|
||||
* .---------------------------------------------------.
|
||||
* | > > > > > > > > > > : : < < < < < < < < < |
|
||||
* '---------------------------------------------------'
|
||||
* |---------------------> - - - - - - - - - - - - - ->|
|
||||
* |< - - - - - - - - - - - - - - <-------------------|
|
||||
* Arithmetic coding Plain bits
|
||||
* `lc3_get_symbol()` `lc3_get_bits()`
|
||||
*
|
||||
* - Reading is limited to read of the complementary end of the buffer.
|
||||
*
|
||||
* - The procedure `lc3_check_bits()` returns indication that read has been
|
||||
* made crossing the other bit plane.
|
||||
*
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LC3_BITS_H
|
||||
#define __LC3_BITS_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/**
|
||||
* Bitstream mode
|
||||
*/
|
||||
|
||||
enum lc3_bits_mode {
|
||||
LC3_BITS_MODE_READ,
|
||||
LC3_BITS_MODE_WRITE,
|
||||
};
|
||||
|
||||
/**
|
||||
* Arithmetic coder symbol interval
|
||||
* The model split the interval in 17 symbols
|
||||
*/
|
||||
|
||||
struct lc3_ac_symbol {
|
||||
uint16_t low : 16;
|
||||
uint16_t range : 16;
|
||||
};
|
||||
|
||||
struct lc3_ac_model {
|
||||
struct lc3_ac_symbol s[17];
|
||||
};
|
||||
|
||||
/**
|
||||
* Bitstream context
|
||||
*/
|
||||
|
||||
#define LC3_ACCU_BITS (int)(8 * sizeof(unsigned))
|
||||
|
||||
struct lc3_bits_accu {
|
||||
unsigned v;
|
||||
int n, nover;
|
||||
};
|
||||
|
||||
#define LC3_AC_BITS (int)(24)
|
||||
|
||||
struct lc3_bits_ac {
|
||||
unsigned low, range;
|
||||
int cache, carry, carry_count;
|
||||
bool error;
|
||||
};
|
||||
|
||||
struct lc3_bits_buffer {
|
||||
const uint8_t *start, *end;
|
||||
uint8_t *p_fw, *p_bw;
|
||||
};
|
||||
|
||||
typedef struct lc3_bits {
|
||||
enum lc3_bits_mode mode;
|
||||
struct lc3_bits_ac ac;
|
||||
struct lc3_bits_accu accu;
|
||||
struct lc3_bits_buffer buffer;
|
||||
} lc3_bits_t;
|
||||
|
||||
|
||||
/**
|
||||
* Setup bitstream reading/writing
|
||||
* bits Bitstream context
|
||||
* mode Either READ or WRITE mode
|
||||
* buffer, len Output buffer and length (in bytes)
|
||||
*/
|
||||
void lc3_setup_bits(lc3_bits_t *bits,
|
||||
enum lc3_bits_mode mode, void *buffer, int len);
|
||||
|
||||
/**
|
||||
* Return number of bits left in the bitstream
|
||||
* bits Bitstream context
|
||||
* return Number of bits left
|
||||
*/
|
||||
int lc3_get_bits_left(const lc3_bits_t *bits);
|
||||
|
||||
/**
|
||||
* Check if error occured on bitstream reading/writing
|
||||
* bits Bitstream context
|
||||
* return 0: Ok -1: Bitstream overflow or AC reading error
|
||||
*/
|
||||
int lc3_check_bits(const lc3_bits_t *bits);
|
||||
|
||||
/**
|
||||
* Put a bit
|
||||
* bits Bitstream context
|
||||
* v Bit value, 0 or 1
|
||||
*/
|
||||
static inline void lc3_put_bit(lc3_bits_t *bits, int v);
|
||||
|
||||
/**
|
||||
* Put from 1 to 32 bits
|
||||
* bits Bitstream context
|
||||
* v, n Value, in range 0 to 2^n - 1, and bits count (1 to 32)
|
||||
*/
|
||||
static inline void lc3_put_bits(lc3_bits_t *bits, unsigned v, int n);
|
||||
|
||||
/**
|
||||
* Put arithmetic coder symbol
|
||||
* bits Bitstream context
|
||||
* model, s Model distribution and symbol value
|
||||
*/
|
||||
static inline void lc3_put_symbol(lc3_bits_t *bits,
|
||||
const struct lc3_ac_model *model, unsigned s);
|
||||
|
||||
/**
|
||||
* Flush and terminate bitstream writing
|
||||
* bits Bitstream context
|
||||
*/
|
||||
void lc3_flush_bits(lc3_bits_t *bits);
|
||||
|
||||
/**
|
||||
* Get a bit
|
||||
* bits Bitstream context
|
||||
*/
|
||||
static inline int lc3_get_bit(lc3_bits_t *bits);
|
||||
|
||||
/**
|
||||
* Get from 1 to 32 bits
|
||||
* bits Bitstream context
|
||||
* n Number of bits to read (1 to 32)
|
||||
* return The value read
|
||||
*/
|
||||
static inline unsigned lc3_get_bits(lc3_bits_t *bits, int n);
|
||||
|
||||
/**
|
||||
* Get arithmetic coder symbol
|
||||
* bits Bitstream context
|
||||
* model Model distribution
|
||||
* return The value read
|
||||
*/
|
||||
static inline unsigned lc3_get_symbol(lc3_bits_t *bits,
|
||||
const struct lc3_ac_model *model);
|
||||
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Inline implementations
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
void lc3_put_bits_generic(lc3_bits_t *bits, unsigned v, int n);
|
||||
unsigned lc3_get_bits_generic(struct lc3_bits *bits, int n);
|
||||
|
||||
void lc3_ac_read_renorm(lc3_bits_t *bits);
|
||||
void lc3_ac_write_renorm(lc3_bits_t *bits);
|
||||
|
||||
|
||||
/**
|
||||
* Put a bit
|
||||
*/
|
||||
static inline void lc3_put_bit(lc3_bits_t *bits, int v)
|
||||
{
|
||||
lc3_put_bits(bits, v, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put from 1 to 32 bits
|
||||
*/
|
||||
static inline void lc3_put_bits(struct lc3_bits *bits, unsigned v, int n)
|
||||
{
|
||||
struct lc3_bits_accu *accu = &bits->accu;
|
||||
|
||||
if (accu->n + n <= LC3_ACCU_BITS) {
|
||||
accu->v |= v << accu->n;
|
||||
accu->n += n;
|
||||
} else {
|
||||
lc3_put_bits_generic(bits, v, n);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bit
|
||||
*/
|
||||
static inline int lc3_get_bit(lc3_bits_t *bits)
|
||||
{
|
||||
return lc3_get_bits(bits, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get from 1 to 32 bits
|
||||
*/
|
||||
static inline unsigned lc3_get_bits(struct lc3_bits *bits, int n)
|
||||
{
|
||||
struct lc3_bits_accu *accu = &bits->accu;
|
||||
|
||||
if (accu->n + n <= LC3_ACCU_BITS) {
|
||||
int v = (accu->v >> accu->n) & ((1u << n) - 1);
|
||||
return (accu->n += n), v;
|
||||
}
|
||||
else {
|
||||
return lc3_get_bits_generic(bits, n);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put arithmetic coder symbol
|
||||
*/
|
||||
static inline void lc3_put_symbol(
|
||||
struct lc3_bits *bits, const struct lc3_ac_model *model, unsigned s)
|
||||
{
|
||||
const struct lc3_ac_symbol *symbols = model->s;
|
||||
struct lc3_bits_ac *ac = &bits->ac;
|
||||
unsigned range = ac->range >> 10;
|
||||
|
||||
ac->low += range * symbols[s].low;
|
||||
ac->range = range * symbols[s].range;
|
||||
|
||||
ac->carry |= ac->low >> 24;
|
||||
ac->low &= 0xffffff;
|
||||
|
||||
if (ac->range < 0x10000)
|
||||
lc3_ac_write_renorm(bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get arithmetic coder symbol
|
||||
*/
|
||||
static inline unsigned lc3_get_symbol(
|
||||
lc3_bits_t *bits, const struct lc3_ac_model *model)
|
||||
{
|
||||
const struct lc3_ac_symbol *symbols = model->s;
|
||||
struct lc3_bits_ac *ac = &bits->ac;
|
||||
|
||||
unsigned range = (ac->range >> 10) & 0xffff;
|
||||
|
||||
ac->error |= (ac->low >= (range << 10));
|
||||
if (ac->error)
|
||||
ac->low = 0;
|
||||
|
||||
int s = 16;
|
||||
|
||||
if (ac->low < range * symbols[s].low) {
|
||||
s >>= 1;
|
||||
s -= ac->low < range * symbols[s].low ? 4 : -4;
|
||||
s -= ac->low < range * symbols[s].low ? 2 : -2;
|
||||
s -= ac->low < range * symbols[s].low ? 1 : -1;
|
||||
s -= ac->low < range * symbols[s].low;
|
||||
}
|
||||
|
||||
ac->low -= range * symbols[s].low;
|
||||
ac->range = range * symbols[s].range;
|
||||
|
||||
if (ac->range < 0x10000)
|
||||
lc3_ac_read_renorm(bits);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#endif /* __LC3_BITS_H */
|
||||
129
src/bwdet.c
Normal file
129
src/bwdet.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "bwdet.h"
|
||||
|
||||
|
||||
/**
|
||||
* Bandwidth detector
|
||||
*/
|
||||
enum lc3_bandwidth lc3_bwdet_run(
|
||||
enum lc3_dt dt, enum lc3_srate sr, const float *e)
|
||||
{
|
||||
/* Bandwidth regions (Table 3.6) */
|
||||
|
||||
struct region { int is : 8; int ie : 8; };
|
||||
|
||||
static const struct region bws_table[LC3_NUM_DT]
|
||||
[LC3_NUM_BANDWIDTH-1][LC3_NUM_BANDWIDTH-1] = {
|
||||
|
||||
[LC3_DT_7M5] = {
|
||||
{ { 51, 63+1 } },
|
||||
{ { 45, 55+1 }, { 58, 63+1 } },
|
||||
{ { 42, 51+1 }, { 53, 58+1 }, { 60, 63+1 } },
|
||||
{ { 40, 48+1 }, { 51, 55+1 }, { 57, 60+1 }, { 61, 63+1 } },
|
||||
},
|
||||
|
||||
[LC3_DT_10M] = {
|
||||
{ { 53, 63+1 } },
|
||||
{ { 47, 56+1 }, { 59, 63+1 } },
|
||||
{ { 44, 52+1 }, { 54, 59+1 }, { 60, 63+1 } },
|
||||
{ { 41, 49+1 }, { 51, 55+1 }, { 57, 60+1 }, { 61, 63+1 } },
|
||||
},
|
||||
};
|
||||
|
||||
static const int l_table[LC3_NUM_DT][LC3_NUM_BANDWIDTH-1] = {
|
||||
[LC3_DT_7M5] = { 4, 4, 3, 2 },
|
||||
[LC3_DT_10M] = { 4, 4, 3, 1 },
|
||||
};
|
||||
|
||||
/* --- Stage 1 ---
|
||||
* Determine bw0 candidate */
|
||||
|
||||
enum lc3_bandwidth bw0 = LC3_BANDWIDTH_NB;
|
||||
enum lc3_bandwidth bwn = (enum lc3_bandwidth)sr;
|
||||
|
||||
if (bwn <= bw0)
|
||||
return bwn;
|
||||
|
||||
const struct region *bwr = bws_table[dt][bwn-1];
|
||||
|
||||
for (enum lc3_bandwidth bw = bw0; bw < bwn; bw++) {
|
||||
int i = bwr[bw].is, ie = bwr[bw].ie;
|
||||
int n = ie - i;
|
||||
|
||||
float se = e[i];
|
||||
for (i++; i < ie; i++)
|
||||
se += e[i];
|
||||
|
||||
if (se >= (10 << (bw == LC3_BANDWIDTH_NB)) * n)
|
||||
bw0 = bw + 1;
|
||||
}
|
||||
|
||||
/* --- Stage 2 ---
|
||||
* Detect drop above cut-off frequency.
|
||||
* The Tc condition (13) is precalculated, as
|
||||
* Tc[] = 10 ^ (n / 10) , n = { 15, 23, 20, 20 } */
|
||||
|
||||
int hold = bw0 >= bwn;
|
||||
|
||||
if (!hold) {
|
||||
int i0 = bwr[bw0].is, l = l_table[dt][bw0];
|
||||
float tc = (const float []){
|
||||
31.62277660, 199.52623150, 100, 100 }[bw0];
|
||||
|
||||
for (int i = i0 - l + 1; !hold && i <= i0 + 1; i++) {
|
||||
hold = e[i-l] > tc * e[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return hold ? bw0 : bwn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of bits coding the bandwidth value
|
||||
*/
|
||||
int lc3_bwdet_get_nbits(enum lc3_srate sr)
|
||||
{
|
||||
return (sr > 0) + (sr > 1) + (sr > 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put bandwidth indication
|
||||
*/
|
||||
void lc3_bwdet_put_bw(lc3_bits_t *bits,
|
||||
enum lc3_srate sr, enum lc3_bandwidth bw)
|
||||
{
|
||||
int nbits_bw = lc3_bwdet_get_nbits(sr);
|
||||
if (nbits_bw > 0)
|
||||
lc3_put_bits(bits, bw, nbits_bw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bandwidth indication
|
||||
*/
|
||||
int lc3_bwdet_get_bw(lc3_bits_t *bits,
|
||||
enum lc3_srate sr, enum lc3_bandwidth *bw)
|
||||
{
|
||||
enum lc3_bandwidth max_bw = (enum lc3_bandwidth)sr;
|
||||
int nbits_bw = lc3_bwdet_get_nbits(sr);
|
||||
|
||||
*bw = nbits_bw > 0 ? lc3_get_bits(bits, nbits_bw) : LC3_BANDWIDTH_NB;
|
||||
return *bw > max_bw ? (*bw = max_bw), -1 : 0;
|
||||
}
|
||||
69
src/bwdet.h
Normal file
69
src/bwdet.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Bandwidth detector
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_BWDET_H
|
||||
#define __LC3_BWDET_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bits.h"
|
||||
|
||||
|
||||
/**
|
||||
* Bandwidth detector (cf. 3.3.5)
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* e Energy estimation per bands
|
||||
* return Return detected bandwitdth
|
||||
*/
|
||||
enum lc3_bandwidth lc3_bwdet_run(
|
||||
enum lc3_dt dt, enum lc3_srate sr, const float *e);
|
||||
|
||||
/**
|
||||
* Return number of bits coding the bandwidth value
|
||||
* sr Samplerate of the frame
|
||||
* return Number of bits coding the bandwidth value
|
||||
*/
|
||||
int lc3_bwdet_get_nbits(enum lc3_srate sr);
|
||||
|
||||
/**
|
||||
* Put bandwidth indication
|
||||
* bits Bitstream context
|
||||
* sr Samplerate of the frame
|
||||
* bw Bandwidth detected
|
||||
*/
|
||||
void lc3_bwdet_put_bw(lc3_bits_t *bits,
|
||||
enum lc3_srate sr, enum lc3_bandwidth bw);
|
||||
|
||||
/**
|
||||
* Get bandwidth indication
|
||||
* bits Bitstream context
|
||||
* sr Samplerate of the frame
|
||||
* bw Return bandwidth indication
|
||||
* return 0: Ok -1: Invalid bandwidth indication
|
||||
*/
|
||||
int lc3_bwdet_get_bw(lc3_bits_t *bits,
|
||||
enum lc3_srate sr, enum lc3_bandwidth *bw);
|
||||
|
||||
|
||||
#endif /* __LC3_BWDET_H */
|
||||
105
src/common.h
Normal file
105
src/common.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Common constants and types
|
||||
*/
|
||||
|
||||
#ifndef __LC3_COMMON_H
|
||||
#define __LC3_COMMON_H
|
||||
|
||||
#include <lc3.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/**
|
||||
* Macros
|
||||
* MIN/MAX Minimum and maximum between 2 values
|
||||
* CLIP Clip a value between low and high limits
|
||||
* ABS Return the absolute value
|
||||
*/
|
||||
|
||||
#define LC3_MIN(a, b) ( (a) < (b) ? (a) : (b) )
|
||||
#define LC3_MAX(a, b) ( (a) > (b) ? (a) : (b) )
|
||||
#define LC3_CLIP(v, min, max) LC3_MIN(LC3_MAX(v, min), max)
|
||||
|
||||
#define LC3_ABS(n) ( (n) < 0 ? -(n) : (n) )
|
||||
|
||||
|
||||
/**
|
||||
* Convert `dt` in us and `sr` in KHz
|
||||
*/
|
||||
|
||||
#define LC3_DT_US(dt) \
|
||||
( (3 + (dt)) * 2500 )
|
||||
|
||||
#define LC3_SRATE_KHZ(sr) \
|
||||
( (1 + (sr) + ((sr) == LC3_SRATE_48K)) * 8 )
|
||||
|
||||
|
||||
/**
|
||||
* Return number of samples, delayed samples and
|
||||
* encoded spectrum coefficients within a frame
|
||||
* For decoding, add number of samples of 18 ms history
|
||||
*/
|
||||
|
||||
#define LC3_NS(dt, sr) \
|
||||
( 20 * (3 + (dt)) * (1 + (sr) + ((sr) == LC3_SRATE_48K)) )
|
||||
|
||||
#define LC3_ND(dt, sr) \
|
||||
( (dt) == LC3_DT_7M5 ? 23 * LC3_NS(dt, sr) / 30 \
|
||||
: 5 * LC3_NS(dt, sr) / 8 )
|
||||
#define LC3_NE(dt, sr) \
|
||||
( 20 * (3 + (dt)) * (1 + (sr)) )
|
||||
|
||||
#define LC3_MAX_NE \
|
||||
LC3_NE(LC3_DT_10M, LC3_SRATE_48K)
|
||||
|
||||
#define LC3_NH(sr) \
|
||||
(18 * LC3_SRATE_KHZ(sr))
|
||||
|
||||
|
||||
/**
|
||||
* Bandwidth, mapped to Nyquist frequency of samplerates
|
||||
*/
|
||||
|
||||
enum lc3_bandwidth {
|
||||
LC3_BANDWIDTH_NB = LC3_SRATE_8K,
|
||||
LC3_BANDWIDTH_WB = LC3_SRATE_16K,
|
||||
LC3_BANDWIDTH_SSWB = LC3_SRATE_24K,
|
||||
LC3_BANDWIDTH_SWB = LC3_SRATE_32K,
|
||||
LC3_BANDWIDTH_FB = LC3_SRATE_48K,
|
||||
|
||||
LC3_NUM_BANDWIDTH,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Complex floating point number
|
||||
*/
|
||||
|
||||
struct lc3_complex
|
||||
{
|
||||
float re, im;
|
||||
};
|
||||
|
||||
|
||||
#endif /* __LC3_COMMON_H */
|
||||
70
src/energy.c
Normal file
70
src/energy.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "energy.h"
|
||||
#include "tables.h"
|
||||
|
||||
|
||||
/**
|
||||
* Energy estimation per band
|
||||
*/
|
||||
bool lc3_energy_compute(
|
||||
enum lc3_dt dt, enum lc3_srate sr, const float *x, float *e)
|
||||
{
|
||||
static const int n1_table[LC3_NUM_DT][LC3_NUM_SRATE] = {
|
||||
[LC3_DT_7M5] = { 56, 34, 27, 24, 22 },
|
||||
[LC3_DT_10M] = { 49, 28, 23, 20, 18 },
|
||||
};
|
||||
|
||||
/* First bands are 1 coefficient width */
|
||||
|
||||
int n1 = n1_table[dt][sr];
|
||||
float e_sum[2] = { 0, 0 };
|
||||
int iband;
|
||||
|
||||
for (iband = 0; iband < n1; iband++) {
|
||||
*e = x[iband] * x[iband];
|
||||
e_sum[0] += *(e++);
|
||||
}
|
||||
|
||||
/* Mean the square of coefficients within each band,
|
||||
* note that 7.5ms 8KHz frame has more bands than samples */
|
||||
|
||||
int nb = LC3_MIN(LC3_NUM_BANDS, LC3_NS(dt, sr));
|
||||
int iband_h = nb - 2*(2 - dt);
|
||||
const int *lim = lc3_band_lim[dt][sr];
|
||||
|
||||
for (int i = lim[iband]; iband < nb; iband++) {
|
||||
int ie = lim[iband+1];
|
||||
int n = ie - i;
|
||||
|
||||
float sx2 = x[i] * x[i];
|
||||
for (i++; i < ie; i++)
|
||||
sx2 += x[i] * x[i];
|
||||
|
||||
*e = sx2 / n;
|
||||
e_sum[iband >= iband_h] += *(e++);
|
||||
}
|
||||
|
||||
for (; iband < LC3_NUM_BANDS; iband++)
|
||||
*(e++) = 0;
|
||||
|
||||
/* Return the near nyquist flag */
|
||||
|
||||
return e_sum[1] > 30 * e_sum[0];
|
||||
}
|
||||
43
src/energy.h
Normal file
43
src/energy.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Energy estimation per band
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_ENERGY_H
|
||||
#define __LC3_ENERGY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/**
|
||||
* Energy estimation per band
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* x Input MDCT coefficient
|
||||
* e Energy estimation per bands
|
||||
* return True when high energy detected near Nyquist frequency
|
||||
*/
|
||||
bool lc3_energy_compute(
|
||||
enum lc3_dt dt, enum lc3_srate sr, const float *x, float *e);
|
||||
|
||||
|
||||
#endif /* __LC3_ENERGY_H */
|
||||
570
src/lc3.c
Normal file
570
src/lc3.c
Normal file
@@ -0,0 +1,570 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <lc3.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "bits.h"
|
||||
|
||||
#include "attdet.h"
|
||||
#include "bwdet.h"
|
||||
#include "ltpf.h"
|
||||
#include "mdct.h"
|
||||
#include "energy.h"
|
||||
#include "sns.h"
|
||||
#include "tns.h"
|
||||
#include "spec.h"
|
||||
#include "plc.h"
|
||||
|
||||
|
||||
/**
|
||||
* Frame side data
|
||||
*/
|
||||
|
||||
struct side_data {
|
||||
enum lc3_bandwidth bw;
|
||||
bool pitch_present;
|
||||
lc3_ltpf_data_t ltpf;
|
||||
lc3_sns_data_t sns;
|
||||
lc3_tns_data_t tns;
|
||||
lc3_spec_side_t spec;
|
||||
};
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* General
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Resolve frame duration in us
|
||||
* us Frame duration in us
|
||||
* return Frame duration identifier, or LC3_NUM_DT
|
||||
*/
|
||||
static enum lc3_dt resolve_dt(int us)
|
||||
{
|
||||
return us == 7500 ? LC3_DT_7M5 :
|
||||
us == 10000 ? LC3_DT_10M : LC3_NUM_DT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve samplerate in Hz
|
||||
* hz Samplerate in Hz
|
||||
* return Sample rate identifier, or LC3_NUM_SRATE
|
||||
*/
|
||||
static enum lc3_srate resolve_sr(int hz)
|
||||
{
|
||||
return hz == 8000 ? LC3_SRATE_8K : hz == 16000 ? LC3_SRATE_16K :
|
||||
hz == 24000 ? LC3_SRATE_24K : hz == 32000 ? LC3_SRATE_32K :
|
||||
hz == 48000 ? LC3_SRATE_48K : LC3_NUM_SRATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of PCM samples in a frame
|
||||
*/
|
||||
int lc3_frame_samples(int dt_us, int sr_hz)
|
||||
{
|
||||
enum lc3_dt dt = resolve_dt(dt_us);
|
||||
enum lc3_srate sr = resolve_sr(sr_hz);
|
||||
|
||||
if (dt >= LC3_NUM_DT || sr >= LC3_NUM_SRATE)
|
||||
return -1;
|
||||
|
||||
return LC3_NS(dt, sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of frames, from bitrate
|
||||
*/
|
||||
int lc3_frame_bytes(int dt_us, int bitrate)
|
||||
{
|
||||
if (resolve_dt(dt_us) >= LC3_NUM_DT)
|
||||
return -1;
|
||||
|
||||
if (bitrate < LC3_MIN_BITRATE)
|
||||
return LC3_MIN_FRAME_BYTES;
|
||||
|
||||
if (bitrate > LC3_MAX_BITRATE)
|
||||
return LC3_MAX_FRAME_BYTES;
|
||||
|
||||
int nbytes = ((unsigned)bitrate * dt_us) / (1000*1000*8);
|
||||
|
||||
return LC3_CLIP(nbytes, LC3_MIN_FRAME_BYTES, LC3_MAX_FRAME_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the bitrate, from the size of frames
|
||||
*/
|
||||
int lc3_resolve_bitrate(int dt_us, int nbytes)
|
||||
{
|
||||
if (resolve_dt(dt_us) >= LC3_NUM_DT)
|
||||
return -1;
|
||||
|
||||
if (nbytes < LC3_MIN_FRAME_BYTES)
|
||||
return LC3_MIN_BITRATE;
|
||||
|
||||
if (nbytes > LC3_MAX_FRAME_BYTES)
|
||||
return LC3_MAX_BITRATE;
|
||||
|
||||
int bitrate = ((unsigned)nbytes * (1000*1000*8) + dt_us/2) / dt_us;
|
||||
|
||||
return LC3_CLIP(bitrate, LC3_MIN_BITRATE, LC3_MAX_BITRATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return algorithmic delay, as a number of samples
|
||||
*/
|
||||
int lc3_delay_samples(int dt_us, int sr_hz)
|
||||
{
|
||||
enum lc3_dt dt = resolve_dt(dt_us);
|
||||
enum lc3_srate sr = resolve_sr(sr_hz);
|
||||
|
||||
if (dt >= LC3_NUM_DT || sr >= LC3_NUM_SRATE)
|
||||
return -1;
|
||||
|
||||
return (dt == LC3_DT_7M5 ? 8 : 5) * (LC3_SRATE_KHZ(sr) / 2);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Encoder
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Input PCM Samples from signed 16 bits
|
||||
* encoder Encoder state
|
||||
* pcm, stride Input PCM samples, and count between two consecutives
|
||||
*/
|
||||
static void load_s16(
|
||||
struct lc3_encoder *encoder, const void *_pcm, int stride)
|
||||
{
|
||||
const int16_t *pcm = _pcm;
|
||||
|
||||
enum lc3_dt dt = encoder->dt;
|
||||
enum lc3_srate sr = encoder->sr_pcm;
|
||||
float *xs = encoder->xs;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
for (int i = 0; i < ns; i++)
|
||||
xs[i] = pcm[i*stride];
|
||||
}
|
||||
|
||||
/**
|
||||
* Input PCM Samples from signed 24 bits
|
||||
* encoder Encoder state
|
||||
* pcm, stride Input PCM samples, and count between two consecutives
|
||||
*/
|
||||
static void load_s24(
|
||||
struct lc3_encoder *encoder, const void *_pcm, int stride)
|
||||
{
|
||||
const int32_t *pcm = _pcm;
|
||||
|
||||
enum lc3_dt dt = encoder->dt;
|
||||
enum lc3_srate sr = encoder->sr_pcm;
|
||||
float *xs = encoder->xs;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
for (int i = 0; i < ns; i++)
|
||||
xs[i] = ldexpf(pcm[i*stride], -8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frame Analysis
|
||||
* encoder Encoder state
|
||||
* nbytes Size in bytes of the frame
|
||||
* side, xq Return frame data
|
||||
*/
|
||||
static void analyze(struct lc3_encoder *encoder,
|
||||
int nbytes, struct side_data *side, int16_t *xq)
|
||||
{
|
||||
enum lc3_dt dt = encoder->dt;
|
||||
enum lc3_srate sr = encoder->sr;
|
||||
enum lc3_srate sr_pcm = encoder->sr_pcm;
|
||||
int ns = LC3_NS(dt, sr_pcm);
|
||||
int nd = LC3_ND(dt, sr_pcm);
|
||||
|
||||
float *xs = encoder->xs;
|
||||
float *xf = encoder->xf;
|
||||
|
||||
/* --- Temporal --- */
|
||||
|
||||
bool att = lc3_attdet_run(dt, sr_pcm, nbytes, &encoder->attdet, xs);
|
||||
|
||||
side->pitch_present =
|
||||
lc3_ltpf_analyse(dt, sr_pcm, &encoder->ltpf, xs, &side->ltpf);
|
||||
|
||||
/* --- Spectral --- */
|
||||
|
||||
float e[LC3_NUM_BANDS];
|
||||
|
||||
lc3_mdct_forward(dt, sr_pcm, sr, xs, xf);
|
||||
memmove(xs - nd, xs + ns-nd, nd * sizeof(float));
|
||||
|
||||
bool nn_flag = lc3_energy_compute(dt, sr, xf, e);
|
||||
if (nn_flag)
|
||||
lc3_ltpf_disable(&side->ltpf);
|
||||
|
||||
side->bw = lc3_bwdet_run(dt, sr, e);
|
||||
|
||||
lc3_sns_analyze(dt, sr, e, att, &side->sns, xf, xf);
|
||||
|
||||
lc3_tns_analyze(dt, side->bw, nn_flag, nbytes, &side->tns, xf);
|
||||
|
||||
lc3_spec_analyze(dt, sr,
|
||||
nbytes, side->pitch_present, &side->tns,
|
||||
&encoder->spec, xf, xq, &side->spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode bitstream
|
||||
* encoder Encoder state
|
||||
* side, xq The frame data
|
||||
* nbytes Target size of the frame (20 to 400)
|
||||
* buffer Output bitstream buffer of `nbytes` size
|
||||
*/
|
||||
static void encode(struct lc3_encoder *encoder,
|
||||
const struct side_data *side, int16_t *xq, int nbytes, void *buffer)
|
||||
{
|
||||
enum lc3_dt dt = encoder->dt;
|
||||
enum lc3_srate sr = encoder->sr;
|
||||
enum lc3_bandwidth bw = side->bw;
|
||||
float *xf = encoder->xf;
|
||||
|
||||
lc3_bits_t bits;
|
||||
|
||||
lc3_setup_bits(&bits, LC3_BITS_MODE_WRITE, buffer, nbytes);
|
||||
|
||||
lc3_bwdet_put_bw(&bits, sr, bw);
|
||||
|
||||
lc3_spec_put_side(&bits, dt, sr, &side->spec);
|
||||
|
||||
lc3_tns_put_data(&bits, &side->tns);
|
||||
|
||||
lc3_put_bit(&bits, side->pitch_present);
|
||||
|
||||
lc3_sns_put_data(&bits, &side->sns);
|
||||
|
||||
if (side->pitch_present)
|
||||
lc3_ltpf_put_data(&bits, &side->ltpf);
|
||||
|
||||
lc3_spec_encode(&bits,
|
||||
dt, sr, bw, nbytes, xq, &side->spec, xf);
|
||||
|
||||
lc3_flush_bits(&bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size needed for an encoder
|
||||
*/
|
||||
unsigned lc3_encoder_size(int dt_us, int sr_hz)
|
||||
{
|
||||
if (resolve_dt(dt_us) >= LC3_NUM_DT ||
|
||||
resolve_sr(sr_hz) >= LC3_NUM_SRATE)
|
||||
return 0;
|
||||
|
||||
return sizeof(struct lc3_encoder) +
|
||||
LC3_ENCODER_BUFFER_COUNT(dt_us, sr_hz) * sizeof(float);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup encoder
|
||||
*/
|
||||
struct lc3_encoder *lc3_setup_encoder(
|
||||
int dt_us, int sr_hz, int sr_pcm_hz, void *mem)
|
||||
{
|
||||
if (sr_pcm_hz <= 0)
|
||||
sr_pcm_hz = sr_hz;
|
||||
|
||||
enum lc3_dt dt = resolve_dt(dt_us);
|
||||
enum lc3_srate sr = resolve_sr(sr_hz);
|
||||
enum lc3_srate sr_pcm = resolve_sr(sr_pcm_hz);
|
||||
|
||||
if (dt >= LC3_NUM_DT || sr_pcm >= LC3_NUM_SRATE || sr > sr_pcm || !mem)
|
||||
return NULL;
|
||||
|
||||
struct lc3_encoder *encoder = mem;
|
||||
int ns = LC3_NS(dt, sr_pcm);
|
||||
int nd = LC3_ND(dt, sr_pcm);
|
||||
|
||||
*encoder = (struct lc3_encoder){
|
||||
.dt = dt, .sr = sr,
|
||||
.sr_pcm = sr_pcm,
|
||||
.xs = encoder->s + nd,
|
||||
.xf = encoder->s + nd+ns,
|
||||
};
|
||||
|
||||
memset(encoder->s, 0,
|
||||
LC3_ENCODER_BUFFER_COUNT(dt_us, sr_pcm_hz) * sizeof(float));
|
||||
|
||||
return encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a frame
|
||||
*/
|
||||
int lc3_encode(struct lc3_encoder *encoder, enum lc3_pcm_format fmt,
|
||||
const void *pcm, int stride, int nbytes, void *out)
|
||||
{
|
||||
static void (* const load[])(struct lc3_encoder *, const void *, int) = {
|
||||
[LC3_PCM_FORMAT_S16] = load_s16,
|
||||
[LC3_PCM_FORMAT_S24] = load_s24,
|
||||
};
|
||||
|
||||
/* --- Check parameters --- */
|
||||
|
||||
if (!encoder || nbytes < LC3_MIN_FRAME_BYTES
|
||||
|| nbytes > LC3_MAX_FRAME_BYTES)
|
||||
return -1;
|
||||
|
||||
/* --- Processing --- */
|
||||
|
||||
struct side_data side;
|
||||
int16_t xq[LC3_NE(encoder->dt, encoder->sr)];
|
||||
|
||||
load[fmt](encoder, pcm, stride);
|
||||
|
||||
analyze(encoder, nbytes, &side, xq);
|
||||
|
||||
encode(encoder, &side, xq, nbytes, out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Decoder
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Output PCM Samples to signed 16 bits
|
||||
* decoder Decoder state
|
||||
* pcm, stride Output PCM samples, and count between two consecutives
|
||||
*/
|
||||
static void store_s16(
|
||||
struct lc3_decoder *decoder, void *_pcm, int stride)
|
||||
{
|
||||
int16_t *pcm = _pcm;
|
||||
|
||||
enum lc3_dt dt = decoder->dt;
|
||||
enum lc3_srate sr = decoder->sr_pcm;
|
||||
float *xs = decoder->xs;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
for ( ; ns > 0; ns--, xs++, pcm += stride) {
|
||||
int s = *xs >= 0 ? (int)(*xs + 0.5f) : (int)(*xs - 0.5f);
|
||||
*pcm = LC3_CLIP(s, INT16_MIN, INT16_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output PCM Samples to signed 24 bits
|
||||
* decoder Decoder state
|
||||
* pcm, stride Output PCM samples, and count between two consecutives
|
||||
*/
|
||||
static void store_s24(
|
||||
struct lc3_decoder *decoder, void *_pcm, int stride)
|
||||
{
|
||||
int32_t *pcm = _pcm;
|
||||
const int32_t int24_max = (1 << 23) - 1;
|
||||
const int32_t int24_min = -(1 << 23);
|
||||
|
||||
enum lc3_dt dt = decoder->dt;
|
||||
enum lc3_srate sr = decoder->sr_pcm;
|
||||
float *xs = decoder->xs;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
for ( ; ns > 0; ns--, xs++, pcm += stride) {
|
||||
int32_t s = *xs >= 0 ? (int32_t)(ldexpf(*xs, 8) + 0.5f)
|
||||
: (int32_t)(ldexpf(*xs, 8) - 0.5f);
|
||||
*pcm = LC3_CLIP(s, int24_min, int24_max);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode bitstream
|
||||
* decoder Decoder state
|
||||
* data, nbytes Input bitstream buffer
|
||||
* side Return the side data
|
||||
* return 0: Ok < 0: Bitsream error detected
|
||||
*/
|
||||
static int decode(struct lc3_decoder *decoder,
|
||||
const void *data, int nbytes, struct side_data *side)
|
||||
{
|
||||
enum lc3_dt dt = decoder->dt;
|
||||
enum lc3_srate sr = decoder->sr;
|
||||
float *xf = decoder->xs;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
lc3_bits_t bits;
|
||||
int ret = 0;
|
||||
|
||||
lc3_setup_bits(&bits, LC3_BITS_MODE_READ, (void *)data, nbytes);
|
||||
|
||||
if ((ret = lc3_bwdet_get_bw(&bits, sr, &side->bw)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = lc3_spec_get_side(&bits, dt, sr, &side->spec)) < 0)
|
||||
return ret;
|
||||
|
||||
lc3_tns_get_data(&bits, dt, side->bw, nbytes, &side->tns);
|
||||
|
||||
side->pitch_present = lc3_get_bit(&bits);
|
||||
|
||||
if ((ret = lc3_sns_get_data(&bits, &side->sns)) < 0)
|
||||
return ret;
|
||||
|
||||
if (side->pitch_present)
|
||||
lc3_ltpf_get_data(&bits, &side->ltpf);
|
||||
|
||||
if ((ret = lc3_spec_decode(&bits, dt, sr,
|
||||
side->bw, nbytes, &side->spec, xf)) < 0)
|
||||
return ret;
|
||||
|
||||
memset(xf + ne, 0, (ns - ne) * sizeof(float));
|
||||
|
||||
return lc3_check_bits(&bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frame synthesis
|
||||
* decoder Decoder state
|
||||
* side Frame data, NULL performs PLC
|
||||
* nbytes Size in bytes of the frame
|
||||
*/
|
||||
static void synthesize(struct lc3_decoder *decoder,
|
||||
const struct side_data *side, int nbytes)
|
||||
{
|
||||
enum lc3_dt dt = decoder->dt;
|
||||
enum lc3_srate sr = decoder->sr;
|
||||
enum lc3_srate sr_pcm = decoder->sr_pcm;
|
||||
int ns = LC3_NS(dt, sr_pcm);
|
||||
int ne = LC3_NE(dt, sr);
|
||||
int nh = LC3_NH(sr_pcm);
|
||||
|
||||
float *xf = decoder->xs;
|
||||
float *xg = decoder->xg;
|
||||
float *xd = decoder->xd;
|
||||
float *xs = xf;
|
||||
|
||||
if (side) {
|
||||
enum lc3_bandwidth bw = side->bw;
|
||||
|
||||
lc3_plc_suspend(&decoder->plc);
|
||||
|
||||
lc3_tns_synthesize(dt, bw, &side->tns, xf);
|
||||
|
||||
lc3_sns_synthesize(dt, sr, &side->sns, xf, xg);
|
||||
|
||||
lc3_mdct_inverse(dt, sr_pcm, sr, xg, xd, xs);
|
||||
|
||||
} else {
|
||||
lc3_plc_synthesize(dt, sr, &decoder->plc, xg, xf);
|
||||
|
||||
memset(xf + ne, 0, (ns - ne) * sizeof(float));
|
||||
|
||||
lc3_mdct_inverse(dt, sr_pcm, sr, xf, xd, xs);
|
||||
}
|
||||
|
||||
lc3_ltpf_synthesize(dt, sr_pcm, nbytes, &decoder->ltpf,
|
||||
side && side->pitch_present ? &side->ltpf : NULL, xs);
|
||||
|
||||
memmove(xs - nh, xs - nh+ns, nh * sizeof(*xs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size needed for a decoder
|
||||
*/
|
||||
unsigned lc3_decoder_size(int dt_us, int sr_hz)
|
||||
{
|
||||
if (resolve_dt(dt_us) >= LC3_NUM_DT ||
|
||||
resolve_sr(sr_hz) >= LC3_NUM_SRATE)
|
||||
return 0;
|
||||
|
||||
return sizeof(struct lc3_decoder) +
|
||||
LC3_DECODER_BUFFER_COUNT(dt_us, sr_hz) * sizeof(float);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup decoder
|
||||
*/
|
||||
struct lc3_decoder *lc3_setup_decoder(
|
||||
int dt_us, int sr_hz, int sr_pcm_hz, void *mem)
|
||||
{
|
||||
if (sr_pcm_hz <= 0)
|
||||
sr_pcm_hz = sr_hz;
|
||||
|
||||
enum lc3_dt dt = resolve_dt(dt_us);
|
||||
enum lc3_srate sr = resolve_sr(sr_hz);
|
||||
enum lc3_srate sr_pcm = resolve_sr(sr_pcm_hz);
|
||||
|
||||
if (dt >= LC3_NUM_DT || sr_pcm >= LC3_NUM_SRATE || sr > sr_pcm || !mem)
|
||||
return NULL;
|
||||
|
||||
struct lc3_decoder *decoder = mem;
|
||||
int nh = LC3_NH(sr_pcm);
|
||||
int ns = LC3_NS(dt, sr_pcm);
|
||||
int nd = LC3_ND(dt, sr_pcm);
|
||||
|
||||
*decoder = (struct lc3_decoder){
|
||||
.dt = dt, .sr = sr,
|
||||
.sr_pcm = sr_pcm,
|
||||
.xs = decoder->s + nh,
|
||||
.xd = decoder->s + nh+ns,
|
||||
.xg = decoder->s + nh+ns+nd,
|
||||
};
|
||||
|
||||
lc3_plc_reset(&decoder->plc);
|
||||
|
||||
memset(decoder->s, 0,
|
||||
LC3_DECODER_BUFFER_COUNT(dt_us, sr_pcm_hz) * sizeof(float));
|
||||
|
||||
return decoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a frame
|
||||
*/
|
||||
int lc3_decode(struct lc3_decoder *decoder, const void *in, int nbytes,
|
||||
enum lc3_pcm_format fmt, void *pcm, int stride)
|
||||
{
|
||||
static void (* const store[])(struct lc3_decoder *, void *, int) = {
|
||||
[LC3_PCM_FORMAT_S16] = store_s16,
|
||||
[LC3_PCM_FORMAT_S24] = store_s24,
|
||||
};
|
||||
|
||||
/* --- Check parameters --- */
|
||||
|
||||
if (!decoder)
|
||||
return -1;
|
||||
|
||||
if (in && (nbytes < LC3_MIN_FRAME_BYTES ||
|
||||
nbytes > LC3_MAX_FRAME_BYTES ))
|
||||
return -1;
|
||||
|
||||
/* --- Processing --- */
|
||||
|
||||
struct side_data side;
|
||||
|
||||
int ret = !in || (decode(decoder, in, nbytes, &side) < 0);
|
||||
|
||||
synthesize(decoder, ret ? NULL : &side, nbytes);
|
||||
|
||||
store[fmt](decoder, pcm, stride);
|
||||
|
||||
return ret;
|
||||
}
|
||||
621
src/ltpf.c
Normal file
621
src/ltpf.c
Normal file
@@ -0,0 +1,621 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "ltpf.h"
|
||||
#include "tables.h"
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Resampling
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Resample to 12.8 KHz (cf. 3.3.9.3-4) Template
|
||||
* sr Samplerate source of the frame
|
||||
* hp50 State of the High-Pass 50 Hz filter
|
||||
* x [-d..-1] Previous, [0..ns-1] Current samples
|
||||
* y, n [0..n-1] Output `n` processed samples
|
||||
*
|
||||
* The number of previous samples `d` accessed on `x` is :
|
||||
* d: { 10, 20, 30, 40, 60 } - 1 for samplerates from 8KHz to 48KHz
|
||||
*/
|
||||
static inline void resample_12k8_template(const enum lc3_srate sr,
|
||||
struct lc3_ltpf_hp50_state *hp50, const float *x, float *y, int n)
|
||||
{
|
||||
/* --- Parameters ---
|
||||
* p: Resampling factor, from 4 to 24
|
||||
* w: Half width of polyphase filter
|
||||
*
|
||||
* bn, an: High-Pass Biquad coefficients,
|
||||
* with `bn` support of rescaling resampling factor.
|
||||
* Note that it's an High-Pass filter, so we have `b0 = b2`,
|
||||
* in the following steps we use `b0` as `b2`. */
|
||||
|
||||
const int p = 192 / LC3_SRATE_KHZ(sr);
|
||||
const int w = 5 * LC3_SRATE_KHZ(sr) / 8;
|
||||
|
||||
const int b_scale = p >> (sr == LC3_SRATE_8K);
|
||||
const float a1 = -1.965293373, b1 = -1.965589417 * b_scale;
|
||||
const float a2 = 0.965885461, b2 = 0.982794708 * b_scale;
|
||||
|
||||
/* --- Resampling ---
|
||||
* The value `15*8 * n` is divisible by all resampling factors `p`,
|
||||
* integer and fractionnal position can be determined at compilation
|
||||
* time while unrolling the loops by 8 samples.
|
||||
* The biquad filter implementation chosen in the `Direct Form 2`. */
|
||||
|
||||
const float *h = lc3_ltpf_h12k8 + 119;
|
||||
x -= w;
|
||||
|
||||
for (int i = 0; i < n; i += 8, x += 120/p)
|
||||
for (int j = 0; j < 15*8; j += 15) {
|
||||
float un, yn;
|
||||
int e, f, k;
|
||||
|
||||
e = j / p, f = j % p;
|
||||
for (un = 0, k = 1-w; k <= w; k++)
|
||||
un += x[e+k] * h[k*p - f];
|
||||
|
||||
yn = b2 * un + hp50->s1;
|
||||
hp50->s1 = b1 * un - a1 * yn + hp50->s2;
|
||||
hp50->s2 = b2 * un - a2 * yn;
|
||||
*(y++) = yn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LTPF Resample to 12.8 KHz implementations for each samplerates
|
||||
*/
|
||||
|
||||
static void resample_8k_12k8(
|
||||
struct lc3_ltpf_hp50_state *hp50, const float *x, float *y, int n)
|
||||
{
|
||||
resample_12k8_template(LC3_SRATE_8K, hp50, x, y, n);
|
||||
}
|
||||
|
||||
static void resample_16k_12k8(
|
||||
struct lc3_ltpf_hp50_state *hp50, const float *x, float *y, int n)
|
||||
{
|
||||
resample_12k8_template(LC3_SRATE_16K, hp50, x, y, n);
|
||||
}
|
||||
|
||||
static void resample_24k_12k8(
|
||||
struct lc3_ltpf_hp50_state *hp50, const float *x, float *y, int n)
|
||||
{
|
||||
resample_12k8_template(LC3_SRATE_24K, hp50, x, y, n);
|
||||
}
|
||||
|
||||
static void resample_32k_12k8(
|
||||
struct lc3_ltpf_hp50_state *hp50, const float *x, float *y, int n)
|
||||
{
|
||||
resample_12k8_template(LC3_SRATE_32K, hp50, x, y, n);
|
||||
}
|
||||
|
||||
static void resample_48k_12k8(
|
||||
struct lc3_ltpf_hp50_state *hp50, const float *x, float *y, int n)
|
||||
{
|
||||
resample_12k8_template(LC3_SRATE_48K, hp50, x, y, n);
|
||||
}
|
||||
|
||||
static void (* const resample_12k8[])
|
||||
(struct lc3_ltpf_hp50_state *, const float *, float *, int ) =
|
||||
{
|
||||
[LC3_SRATE_8K ] = resample_8k_12k8,
|
||||
[LC3_SRATE_16K] = resample_16k_12k8,
|
||||
[LC3_SRATE_24K] = resample_24k_12k8,
|
||||
[LC3_SRATE_32K] = resample_32k_12k8,
|
||||
[LC3_SRATE_48K] = resample_48k_12k8,
|
||||
};
|
||||
|
||||
/**
|
||||
* Resample to 6.4 KHz (cf. 3.3.9.3-4)
|
||||
* x [-3..-1] Previous, [0..n-1] Current samples
|
||||
* y, n [0..n-1] Output `n` processed samples
|
||||
*/
|
||||
static void resample_6k4(const float *x, float *y, int n)
|
||||
{
|
||||
static const float h[] = { 0.2819382921, 0.2353512128, 0.1236796411 };
|
||||
float xn2 = x[-3], xn1 = x[-2], x0 = x[-1], x1, x2;
|
||||
|
||||
for (const float *ye = y + n; y < ye; xn2 = x0, xn1 = x1, x0 = x2) {
|
||||
x1 = *(x++); x2 = *(x++);
|
||||
|
||||
*(y++) = x0 * h[0] + (xn1 + x1) * h[1] + (xn2 + x2) * h[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Analysis
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Return dot product of 2 vectors
|
||||
* a, b, n The 2 vectors of size `n`
|
||||
* return sum( a[i] * b[i] ), i = [0..n-1]
|
||||
*/
|
||||
static inline float dot(const float *a, const float *b, int n)
|
||||
{
|
||||
float v = 0;
|
||||
|
||||
while (n--)
|
||||
v += *(a++) * *(b++);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return vector of correlations
|
||||
* a, b, n The 2 vector of size `n` to correlate
|
||||
* y, nc Output the correlation vector of size `nc`
|
||||
*
|
||||
* The size `n` of input vectors must be multiple of 16
|
||||
*/
|
||||
static void correlate(
|
||||
const float *a, const float *b, int n, float *y, int nc)
|
||||
{
|
||||
for (const float *ye = y + nc; y < ye; )
|
||||
*(y++) = dot(a, b--, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the maximum value and returns its argument
|
||||
* x, n The input vector of size `n`
|
||||
* x_max Return the maximum value
|
||||
* return Return the argument of the maximum
|
||||
*/
|
||||
static int argmax(const float *x, int n, float *x_max)
|
||||
{
|
||||
int arg = 0;
|
||||
|
||||
*x_max = x[arg = 0];
|
||||
for (int i = 1; i < n; i++)
|
||||
if (*x_max < x[i])
|
||||
*x_max = x[arg = i];
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the maximum weithed value and returns its argument
|
||||
* x, n The input vector of size `n`
|
||||
* w_incr Increment of the weight
|
||||
* x_max, xw_max Return the maximum not weighted value
|
||||
* return Return the argument of the weigthed maximum
|
||||
*/
|
||||
static int argmax_weighted(
|
||||
const float *x, int n, float w_incr, float *x_max)
|
||||
{
|
||||
int arg;
|
||||
|
||||
float xw_max = (*x_max = x[arg = 0]);
|
||||
float w = 1 + w_incr;
|
||||
|
||||
for (int i = 1; i < n; i++, w += w_incr)
|
||||
if (xw_max < x[i] * w)
|
||||
xw_max = (*x_max = x[arg = i]) * w;
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate from pitch detected value (3.3.9.8)
|
||||
* x, n [-2..-1] Previous, [0..n] Current input
|
||||
* d The phase of interpolation (0 to 3)
|
||||
* return The interpolated vector
|
||||
*
|
||||
* The size `n` of vectors must be multiple of 4
|
||||
*/
|
||||
static void interpolate(const float *x, int n, int d, float *y)
|
||||
{
|
||||
static const float h4[][8] = {
|
||||
{ 2.09880463e-01, 5.83527575e-01, 2.09880463e-01 },
|
||||
{ 1.06999186e-01, 5.50075002e-01, 3.35690625e-01, 6.69885837e-03 },
|
||||
{ 3.96711478e-02, 4.59220930e-01, 4.59220930e-01, 3.96711478e-02 },
|
||||
{ 6.69885837e-03, 3.35690625e-01, 5.50075002e-01, 1.06999186e-01 },
|
||||
};
|
||||
|
||||
const float *h = h4[d];
|
||||
float x3 = x[-2], x2 = x[-1], x1, x0;
|
||||
|
||||
x1 = (*x++);
|
||||
for (const float *ye = y + n; y < ye; ) {
|
||||
*(y++) = (x0 = *(x++)) * h[0] + x1 * h[1] + x2 * h[2] + x3 * h[3];
|
||||
*(y++) = (x3 = *(x++)) * h[0] + x0 * h[1] + x1 * h[2] + x2 * h[3];
|
||||
*(y++) = (x2 = *(x++)) * h[0] + x3 * h[1] + x0 * h[2] + x1 * h[3];
|
||||
*(y++) = (x1 = *(x++)) * h[0] + x2 * h[1] + x3 * h[2] + x0 * h[3];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate autocorrelation (3.3.9.7)
|
||||
* x [-4..-1] Previous, [0..4] Current input
|
||||
* d The phase of interpolation (-3 to 3)
|
||||
* return The interpolated value
|
||||
*/
|
||||
static float interpolate_4(const float *x, int d)
|
||||
{
|
||||
static const float h4[][8] = {
|
||||
{ 1.53572770e-02, -4.72963246e-02, 8.35788573e-02, 8.98638285e-01,
|
||||
8.35788573e-02, -4.72963246e-02, 1.53572770e-02, },
|
||||
{ 2.74547165e-03, 4.59833449e-03, -7.54404636e-02, 8.17488686e-01,
|
||||
3.30182571e-01, -1.05835916e-01, 2.86823405e-02, -2.87456116e-03 },
|
||||
{ -3.00125103e-03, 2.95038503e-02, -1.30305021e-01, 6.03297008e-01,
|
||||
6.03297008e-01, -1.30305021e-01, 2.95038503e-02, -3.00125103e-03 },
|
||||
{ -2.87456116e-03, 2.86823405e-02, -1.05835916e-01, 3.30182571e-01,
|
||||
8.17488686e-01, -7.54404636e-02, 4.59833449e-03, 2.74547165e-03 },
|
||||
};
|
||||
|
||||
const float *h = h4[(4+d) % 4];
|
||||
|
||||
float y = d < 0 ? x[-4] * *(h++) :
|
||||
d > 0 ? x[ 4] * *(h+7) : 0;
|
||||
|
||||
y += x[-3] * h[0] + x[-2] * h[1] + x[-1] * h[2] + x[0] * h[3] +
|
||||
x[ 1] * h[4] + x[ 2] * h[5] + x[ 3] * h[6];
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pitch detection algorithm (3.3.9.5-6)
|
||||
* ltpf Context of analysis
|
||||
* x, n [-114..-17] Previous, [0..n-1] Current 6.4KHz samples
|
||||
* tc Return the pitch-lag estimation
|
||||
* return True when pitch present
|
||||
*/
|
||||
static bool detect_pitch(
|
||||
struct lc3_ltpf_analysis *ltpf, const float *x, int n, int *tc)
|
||||
{
|
||||
float rm1, rm2;
|
||||
float r[98];
|
||||
|
||||
const int r0 = 17, nr = 98;
|
||||
int k0 = LC3_MAX( 0, ltpf->tc-4);
|
||||
int nk = LC3_MIN(nr-1, ltpf->tc+4) - k0 + 1;
|
||||
|
||||
correlate(x, x - r0, n, r, nr);
|
||||
|
||||
int t1 = argmax_weighted(r, nr, -.5/(nr-1), &rm1);
|
||||
int t2 = k0 + argmax(r + k0, nk, &rm2);
|
||||
|
||||
const float *x1 = x - (r0 + t1);
|
||||
const float *x2 = x - (r0 + t2);
|
||||
|
||||
float nc1 = rm1 <= 0 ? 0 :
|
||||
rm1 / sqrtf(dot(x, x, n) * dot(x1, x1, n));
|
||||
|
||||
float nc2 = rm2 <= 0 ? 0 :
|
||||
rm2 / sqrtf(dot(x, x, n) * dot(x2, x2, n));
|
||||
|
||||
int t1sel = nc2 <= 0.85 * nc1;
|
||||
ltpf->tc = (t1sel ? t1 : t2);
|
||||
|
||||
*tc = r0 + ltpf->tc;
|
||||
return (t1sel ? nc1 : nc2) > 0.6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pitch-lag parameter (3.3.9.7)
|
||||
* x, n [-232..-28] Previous, [0..n-1] Current 12.8KHz samples
|
||||
* tc Pitch-lag estimation
|
||||
* pitch The pitch value, in fixed .4
|
||||
* return The bitstream pitch index value
|
||||
*/
|
||||
static int refine_pitch(const float *x, int n, int tc, int *pitch)
|
||||
{
|
||||
float r[17], rm;
|
||||
int e, f;
|
||||
|
||||
int r0 = LC3_MAX( 32, 2*tc - 4);
|
||||
int nr = LC3_MIN(228, 2*tc + 4) - r0 + 1;
|
||||
|
||||
correlate(x, x - (r0 - 4), n, r, nr + 8);
|
||||
|
||||
e = r0 + argmax(r + 4, nr, &rm);
|
||||
const float *re = r + (e - (r0 - 4));
|
||||
|
||||
float dm = interpolate_4(re, f = 0);
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
float d;
|
||||
|
||||
if (e >= 127 && ((i & 1) | (e >= 157)))
|
||||
continue;
|
||||
|
||||
if ((d = interpolate_4(re, i)) > dm)
|
||||
dm = d, f = i;
|
||||
|
||||
if (e > 32 && (d = interpolate_4(re, -i)) > dm)
|
||||
dm = d, f = -i;
|
||||
}
|
||||
|
||||
e -= (f < 0);
|
||||
f += 4*(f < 0);
|
||||
|
||||
*pitch = 4*e + f;
|
||||
return e < 127 ? 4*e + f - 128 :
|
||||
e < 157 ? 2*e + (f >> 1) + 126 : e + 283;
|
||||
}
|
||||
|
||||
/**
|
||||
* LTPF Analysis
|
||||
*/
|
||||
bool lc3_ltpf_analyse(enum lc3_dt dt, enum lc3_srate sr,
|
||||
struct lc3_ltpf_analysis *ltpf, const float *x, struct lc3_ltpf_data *data)
|
||||
{
|
||||
/* --- Resampling to 12.8 KHz --- */
|
||||
|
||||
int z_12k8 = sizeof(ltpf->x_12k8) / sizeof(float);
|
||||
int n_12k8 = dt == LC3_DT_7M5 ? 96 : 128;
|
||||
|
||||
memmove(ltpf->x_12k8, ltpf->x_12k8 + n_12k8,
|
||||
(z_12k8 - n_12k8) * sizeof(float));
|
||||
|
||||
float *x_12k8 = ltpf->x_12k8 + (z_12k8 - n_12k8);
|
||||
resample_12k8[sr](<pf->hp50, x, x_12k8, n_12k8);
|
||||
|
||||
x_12k8 -= (dt == LC3_DT_7M5 ? 44 : 24);
|
||||
|
||||
/* --- Resampling to 6.4 KHz --- */
|
||||
|
||||
int z_6k4 = sizeof(ltpf->x_6k4) / sizeof(float);
|
||||
int n_6k4 = n_12k8 >> 1;
|
||||
|
||||
memmove(ltpf->x_6k4, ltpf->x_6k4 + n_6k4,
|
||||
(z_6k4 - n_6k4) * sizeof(float));
|
||||
|
||||
float *x_6k4 = ltpf->x_6k4 + (z_6k4 - n_6k4);
|
||||
resample_6k4(x_12k8, x_6k4, n_6k4);
|
||||
|
||||
/* --- Pitch detection --- */
|
||||
|
||||
int tc, pitch = 0;
|
||||
float nc = 0;
|
||||
|
||||
bool pitch_present = detect_pitch(ltpf, x_6k4, n_6k4, &tc);
|
||||
|
||||
if (pitch_present) {
|
||||
float u[n_12k8], v[n_12k8];
|
||||
|
||||
data->pitch_index = refine_pitch(x_12k8, n_12k8, tc, &pitch);
|
||||
|
||||
interpolate(x_12k8, n_12k8, 0, u);
|
||||
interpolate(x_12k8 - (pitch >> 2), n_12k8, pitch & 3, v);
|
||||
|
||||
nc = dot(u, v, n_12k8) / sqrtf(dot(u, u, n_12k8) * dot(v, v, n_12k8));
|
||||
}
|
||||
|
||||
/* --- Activation --- */
|
||||
|
||||
if (ltpf->active) {
|
||||
int pitch_diff =
|
||||
LC3_MAX(pitch, ltpf->pitch) - LC3_MIN(pitch, ltpf->pitch);
|
||||
float nc_diff = nc - ltpf->nc[0];
|
||||
|
||||
data->active = pitch_present &&
|
||||
((nc > 0.9) || (nc > 0.84 && pitch_diff < 8 && nc_diff > -0.1));
|
||||
|
||||
} else {
|
||||
data->active = pitch_present &&
|
||||
( (dt == LC3_DT_10M || ltpf->nc[1] > 0.94) &&
|
||||
(ltpf->nc[0] > 0.94 && nc > 0.94) );
|
||||
}
|
||||
|
||||
ltpf->active = data->active;
|
||||
ltpf->pitch = pitch;
|
||||
ltpf->nc[1] = ltpf->nc[0];
|
||||
ltpf->nc[0] = nc;
|
||||
|
||||
return pitch_present;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Synthesis
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Synthesis filter template
|
||||
* ym [-w/2..0] Previous, [0..w-1] Current pitch samples
|
||||
* xm w-1 previous input samples
|
||||
* x, n Current samples as input, filtered as output
|
||||
* c, w Coefficients by pair (num, den), and count of pairs
|
||||
* fade Fading mode of filter -1: Out 1: In 0: None
|
||||
*/
|
||||
static inline void synthesize_template(const float *ym, const float *xm,
|
||||
float *x, int n, const float (*c)[2], const int w, int fade)
|
||||
{
|
||||
float g = (float)(fade <= 0);
|
||||
float g_incr = (float)((fade > 0) - (fade < 0)) / n;
|
||||
float u[w];
|
||||
int i;
|
||||
|
||||
ym -= (w >> 1);
|
||||
|
||||
/* --- Load previous samples --- */
|
||||
|
||||
for (i = 1-w; i < 0; i++) {
|
||||
float xi = *(xm++), yi = *(ym++);
|
||||
|
||||
u[i + w-1] = 0;
|
||||
for (int k = w-1; k+i >= 0; k--)
|
||||
u[i+k] += xi * c[k][0] - yi * c[k][1];
|
||||
}
|
||||
|
||||
u[w-1] = 0;
|
||||
|
||||
/* --- Process --- */
|
||||
|
||||
for (; i < n; i += w) {
|
||||
|
||||
for (int j = 0; j < w; j++, g += g_incr) {
|
||||
float xi = *x, yi = *(ym++);
|
||||
|
||||
for (int k = w-1; k >= 0; k--)
|
||||
u[(j+k)%w] += xi * c[k][0] - yi * c[k][1];
|
||||
|
||||
*(x++) = xi - g * u[j];
|
||||
u[j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesis filter for each samplerates (width of filter)
|
||||
*/
|
||||
|
||||
static void synthesize_4(const float *ym, const float *xm,
|
||||
float *x, int n, const float (*c)[2], int fade)
|
||||
{
|
||||
synthesize_template(ym, xm, x, n, c, 4, fade);
|
||||
}
|
||||
|
||||
static void synthesize_6(const float *ym, const float *xm,
|
||||
float *x, int n, const float (*c)[2], int fade)
|
||||
{
|
||||
synthesize_template(ym, xm, x, n, c, 6, fade);
|
||||
}
|
||||
|
||||
static void synthesize_8(const float *ym, const float *xm,
|
||||
float *x, int n, const float (*c)[2], int fade)
|
||||
{
|
||||
synthesize_template(ym, xm, x, n, c, 8, fade);
|
||||
}
|
||||
|
||||
static void synthesize_12(const float *ym, const float *xm,
|
||||
float *x, int n, const float (*c)[2], int fade)
|
||||
{
|
||||
synthesize_template(ym, xm, x, n, c, 12, fade);
|
||||
}
|
||||
|
||||
static void (* const synthesize[])(
|
||||
const float *, const float *, float *, int, const float (*)[2], int) =
|
||||
{
|
||||
[LC3_SRATE_8K ] = synthesize_4,
|
||||
[LC3_SRATE_16K] = synthesize_4,
|
||||
[LC3_SRATE_24K] = synthesize_6,
|
||||
[LC3_SRATE_32K] = synthesize_8,
|
||||
[LC3_SRATE_48K] = synthesize_12,
|
||||
};
|
||||
|
||||
/**
|
||||
* LTPF Synthesis
|
||||
*/
|
||||
void lc3_ltpf_synthesize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int nbytes, struct lc3_ltpf_synthesis *ltpf,
|
||||
const struct lc3_ltpf_data *data, float *x)
|
||||
{
|
||||
int dt_us = LC3_DT_US(dt);
|
||||
|
||||
/* --- Filter parameters --- */
|
||||
|
||||
int p_idx = data ? data->pitch_index : 0;
|
||||
int pitch =
|
||||
p_idx >= 440 ? (((p_idx ) - 283) << 2) :
|
||||
p_idx >= 380 ? (((p_idx >> 1) - 63) << 2) + (((p_idx & 1)) << 1) :
|
||||
(((p_idx >> 2) + 32) << 2) + (((p_idx & 3)) << 0) ;
|
||||
|
||||
pitch = (pitch * LC3_SRATE_KHZ(sr) * 10 + 64) / 128;
|
||||
|
||||
int nbits = (nbytes*8 * 10000 + (dt_us/2)) / dt_us;
|
||||
int g_idx = LC3_MAX(nbits / 80, 3 + (int)sr) - (3 + sr);
|
||||
bool active = data && data->active && g_idx < 4;
|
||||
|
||||
int w = LC3_MAX(4, LC3_SRATE_KHZ(sr) / 4);
|
||||
float c[w][2];
|
||||
|
||||
for (int i = 0; i < w; i++) {
|
||||
float g = active ? 0.4f - 0.05f * g_idx : 0;
|
||||
|
||||
c[i][0] = active ? 0.85f * g * lc3_ltpf_cnum[sr][g_idx][i] : 0;
|
||||
c[i][1] = active ? g * lc3_ltpf_cden[sr][pitch & 3][i] : 0;
|
||||
}
|
||||
|
||||
/* --- Transition handling --- */
|
||||
|
||||
int ns = LC3_NS(dt, sr);
|
||||
int nt = ns / (4 - (dt == LC3_DT_7M5));
|
||||
float xm[12];
|
||||
|
||||
if (active)
|
||||
memcpy(xm, x + nt-(w-1), (w-1) * sizeof(float));
|
||||
|
||||
if (!ltpf->active && active)
|
||||
synthesize[sr](x - pitch/4, ltpf->x, x, nt, c, 1);
|
||||
else if (ltpf->active && !active)
|
||||
synthesize[sr](x - ltpf->pitch/4, ltpf->x, x, nt, ltpf->c, -1);
|
||||
else if (ltpf->active && active && ltpf->pitch == pitch)
|
||||
synthesize[sr](x - pitch/4, ltpf->x, x, nt, c, 0);
|
||||
else if (ltpf->active && active) {
|
||||
synthesize[sr](x - ltpf->pitch/4, ltpf->x, x, nt, ltpf->c, -1);
|
||||
synthesize[sr](x - pitch/4, x - (w-1), x, nt, c, 1);
|
||||
}
|
||||
|
||||
/* --- Remainder --- */
|
||||
|
||||
memcpy(ltpf->x, x + ns-(w-1), (w-1) * sizeof(float));
|
||||
|
||||
if (active)
|
||||
synthesize[sr](x - pitch/4 + nt, xm, x + nt, ns-nt, c, 0);
|
||||
|
||||
/* --- Update state --- */
|
||||
|
||||
ltpf->active = active;
|
||||
ltpf->pitch = pitch;
|
||||
memcpy(ltpf->c, c, w * sizeof(ltpf->c[0]));
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Bitstream data
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* LTPF disable
|
||||
*/
|
||||
void lc3_ltpf_disable(struct lc3_ltpf_data *data)
|
||||
{
|
||||
data->active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of bits coding the bitstream data
|
||||
*/
|
||||
int lc3_ltpf_get_nbits(bool pitch)
|
||||
{
|
||||
return 1 + 10 * pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put bitstream data
|
||||
*/
|
||||
void lc3_ltpf_put_data(lc3_bits_t *bits,
|
||||
const struct lc3_ltpf_data *data)
|
||||
{
|
||||
lc3_put_bit(bits, data->active);
|
||||
lc3_put_bits(bits, data->pitch_index, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bitstream data
|
||||
*/
|
||||
void lc3_ltpf_get_data(lc3_bits_t *bits, struct lc3_ltpf_data *data)
|
||||
{
|
||||
data->active = lc3_get_bit(bits);
|
||||
data->pitch_index = lc3_get_bits(bits, 9);
|
||||
}
|
||||
107
src/ltpf.h
Normal file
107
src/ltpf.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Long Term Postfilter
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_LTPF_H
|
||||
#define __LC3_LTPF_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bits.h"
|
||||
|
||||
|
||||
/**
|
||||
* LTPF data
|
||||
*/
|
||||
|
||||
typedef struct lc3_ltpf_data {
|
||||
bool active;
|
||||
int pitch_index;
|
||||
} lc3_ltpf_data_t;
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Encoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* LTPF analysis
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* ltpf Context of analysis
|
||||
* allowed True when activation of LTPF is allowed
|
||||
* x [-d..-1] Previous, [0..ns-1] Current samples
|
||||
* data Return bitstream data
|
||||
* return True when pitch present, False otherwise
|
||||
*
|
||||
* The number of previous samples `d` accessed on `x` is :
|
||||
* d: { 10, 20, 30, 40, 60 } - 1 for samplerates from 8KHz to 48KHz
|
||||
*/
|
||||
bool lc3_ltpf_analyse(enum lc3_dt dt, enum lc3_srate sr,
|
||||
lc3_ltpf_analysis_t *ltpf, const float *x, lc3_ltpf_data_t *data);
|
||||
|
||||
/**
|
||||
* LTPF disable
|
||||
* data LTPF data, disabled activation on return
|
||||
*/
|
||||
void lc3_ltpf_disable(lc3_ltpf_data_t *data);
|
||||
|
||||
/**
|
||||
* Return number of bits coding the bitstream data
|
||||
* pitch True when pitch present, False otherwise
|
||||
* return Bit consumption, including the pitch present flag
|
||||
*/
|
||||
int lc3_ltpf_get_nbits(bool pitch);
|
||||
|
||||
/**
|
||||
* Put bitstream data
|
||||
* bits Bitstream context
|
||||
* data LTPF data
|
||||
*/
|
||||
void lc3_ltpf_put_data(lc3_bits_t *bits, const lc3_ltpf_data_t *data);
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Decoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Get bitstream data
|
||||
* bits Bitstream context
|
||||
* data Return bitstream data
|
||||
*/
|
||||
void lc3_ltpf_get_data(lc3_bits_t *bits, lc3_ltpf_data_t *data);
|
||||
|
||||
/**
|
||||
* LTPF synthesis
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* nbytes Size in bytes of the frame
|
||||
* ltpf Context of synthesis
|
||||
* data Bitstream data, NULL when pitch not present
|
||||
* x [-d..-1] Previous, [0..ns-1] Current, filtered as output
|
||||
*
|
||||
* The number of previous samples `d` accessed on `x` is about 18 ms
|
||||
*/
|
||||
void lc3_ltpf_synthesize(enum lc3_dt dt, enum lc3_srate sr, int nbytes,
|
||||
lc3_ltpf_synthesis_t *ltpf, const lc3_ltpf_data_t *data, float *x);
|
||||
|
||||
|
||||
#endif /* __LC3_LTPF_H */
|
||||
502
src/mdct.c
Normal file
502
src/mdct.c
Normal file
@@ -0,0 +1,502 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "tables.h"
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* FFT processing
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* FFT 5 Points template
|
||||
* s -1: Forward 1: Inverse
|
||||
* x, y Input and output coefficients, of size 5xn
|
||||
* n Number of interleaved transform to perform
|
||||
*/
|
||||
static inline void xfft_5(const float s,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
static const float cos1 = 0.3090169944; /* cos(-2Pi 1/5) */
|
||||
static const float cos2 = -0.8090169944; /* cos(-2Pi 2/5) */
|
||||
|
||||
static const float sin1 = -0.9510565163; /* sin(-2Pi 1/5) */
|
||||
static const float sin2 = -0.5877852523; /* sin(-2Pi 2/5) */
|
||||
|
||||
for (int i = 0; i < n; i++, x++, y+= 5) {
|
||||
|
||||
struct lc3_complex s14 =
|
||||
{ x[1*n].re + x[4*n].re, x[1*n].im + x[4*n].im };
|
||||
struct lc3_complex d14 =
|
||||
{ x[1*n].re - x[4*n].re, x[1*n].im - x[4*n].im };
|
||||
|
||||
struct lc3_complex s23 =
|
||||
{ x[2*n].re + x[3*n].re, x[2*n].im + x[3*n].im };
|
||||
struct lc3_complex d23 =
|
||||
{ x[2*n].re - x[3*n].re, x[2*n].im - x[3*n].im };
|
||||
|
||||
y[0].re = x[0].re + s14.re + s23.re;
|
||||
y[0].im = x[0].im + s14.im + s23.im;
|
||||
|
||||
y[1].re = x[0].re + s14.re * cos1 + s * d14.im * sin1
|
||||
+ s23.re * cos2 + s * d23.im * sin2;
|
||||
|
||||
y[1].im = x[0].im + s14.im * cos1 - s * d14.re * sin1
|
||||
+ s23.im * cos2 - s * d23.re * sin2;
|
||||
|
||||
y[2].re = x[0].re + s14.re * cos2 + s * d14.im * sin2
|
||||
+ s23.re * cos1 - s * d23.im * sin1;
|
||||
|
||||
y[2].im = x[0].im + s14.im * cos2 - s * d14.re * sin2
|
||||
+ s23.im * cos1 + s * d23.re * sin1;
|
||||
|
||||
y[3].re = x[0].re + s14.re * cos2 - s * d14.im * sin2
|
||||
+ s23.re * cos1 + s * d23.im * sin1;
|
||||
|
||||
y[3].im = x[0].im + s14.im * cos2 + s * d14.re * sin2
|
||||
+ s23.im * cos1 - s * d23.re * sin1;
|
||||
|
||||
y[4].re = x[0].re + s14.re * cos1 - s * d14.im * sin1
|
||||
+ s23.re * cos2 - s * d23.im * sin2;
|
||||
|
||||
y[4].im = x[0].im + s14.im * cos1 + s * d14.re * sin1
|
||||
+ s23.im * cos2 + s * d23.re * sin2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FFT Butterfly 3 Points template
|
||||
* s -1: Forward 1: Inverse
|
||||
* x, y Input and output coefficients
|
||||
* twiddles Twiddles factors, determine size of transform
|
||||
* n Number of interleaved transforms
|
||||
*/
|
||||
static inline void xfft_bf3(
|
||||
const float s, const struct lc3_fft_bf3_twiddles *twiddles,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
int n3 = twiddles->n3;
|
||||
const struct lc3_complex (*w0)[2] = twiddles->t;
|
||||
const struct lc3_complex (*w1)[2] = w0 + n3, (*w2)[2] = w1 + n3;
|
||||
|
||||
const struct lc3_complex *x0 = x, *x1 = x0 + n*n3, *x2 = x1 + n*n3;
|
||||
struct lc3_complex *y0 = y, *y1 = y0 + n3, *y2 = y1 + n3;
|
||||
|
||||
for (int i = 0; i < n; i++, y0 += 3*n3, y1 += 3*n3, y2 += 3*n3) {
|
||||
|
||||
for (int j = 0; j < n3; j++, x0++, x1++, x2++) {
|
||||
|
||||
y0[j].re = x0->re + x1->re * w0[j][0].re + s * x1->im * w0[j][0].im
|
||||
+ x2->re * w0[j][1].re + s * x2->im * w0[j][1].im;
|
||||
|
||||
y0[j].im = x0->im + x1->im * w0[j][0].re - s * x1->re * w0[j][0].im
|
||||
+ x2->im * w0[j][1].re - s * x2->re * w0[j][1].im;
|
||||
|
||||
y1[j].re = x0->re + x1->re * w1[j][0].re + s * x1->im * w1[j][0].im
|
||||
+ x2->re * w1[j][1].re + s * x2->im * w1[j][1].im;
|
||||
|
||||
y1[j].im = x0->im + x1->im * w1[j][0].re - s * x1->re * w1[j][0].im
|
||||
+ x2->im * w1[j][1].re - s * x2->re * w1[j][1].im;
|
||||
|
||||
y2[j].re = x0->re + x1->re * w2[j][0].re + s * x1->im * w2[j][0].im
|
||||
+ x2->re * w2[j][1].re + s * x2->im * w2[j][1].im;
|
||||
|
||||
y2[j].im = x0->im + x1->im * w2[j][0].re - s * x1->re * w2[j][0].im
|
||||
+ x2->im * w2[j][1].re - s * x2->re * w2[j][1].im;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FFT Butterfly 2 Points template
|
||||
* s -1: Forward 1: Inverse
|
||||
* twiddles Twiddles factors, determine size of transform
|
||||
* x, y Input and output coefficients
|
||||
* n Number of interleaved transforms
|
||||
*/
|
||||
static inline void xfft_bf2(
|
||||
const float s, const struct lc3_fft_bf2_twiddles *twiddles,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
int n2 = twiddles->n2;
|
||||
const struct lc3_complex *w = twiddles->t;
|
||||
|
||||
const struct lc3_complex *x0 = x, *x1 = x0 + n*n2;
|
||||
struct lc3_complex *y0 = y, *y1 = y0 + n2;
|
||||
|
||||
for (int i = 0; i < n; i++, y0 += 2*n2, y1 += 2*n2) {
|
||||
|
||||
for (int j = 0; j < n2; j++, x0++, x1++) {
|
||||
|
||||
y0[j].re = x0->re + x1->re * w[j].re + s * x1->im * w[j].im;
|
||||
y0[j].im = x0->im + x1->im * w[j].re - s * x1->re * w[j].im;
|
||||
|
||||
y1[j].re = x0->re - x1->re * w[j].re - s * x1->im * w[j].im;
|
||||
y1[j].im = x0->im - x1->im * w[j].re + s * x1->re * w[j].im;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward FFT 5 Points
|
||||
* x, y Input and output coefficients, of size 5xn
|
||||
* n Number of interleaved transform to perform
|
||||
*/
|
||||
static void ffft_5(const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
xfft_5(-1, x, y, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse FFT 5 Points
|
||||
* x, y Input and output coefficients, of size 5xn
|
||||
* n Number of interleaved transform to perform
|
||||
*/
|
||||
static void ifft_5(const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
xfft_5(1, x, y, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward FFT Butterfly 3 Points
|
||||
* twiddles Twiddles factors, determine size of transform
|
||||
* x, y Input and output coefficients
|
||||
* n Number of interleaved transforms
|
||||
*/
|
||||
static void ffft_bf3(const struct lc3_fft_bf3_twiddles *twiddles,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
xfft_bf3(-1, twiddles, x, y, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse FFT Butterfly 3 Points
|
||||
* twiddles Twiddles factors, determine size of transform
|
||||
* x, y Input and output coefficients
|
||||
* n Number of interleaved transforms
|
||||
*/
|
||||
static void ifft_bf3(const struct lc3_fft_bf3_twiddles *twiddles,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
xfft_bf3(1, twiddles, x, y, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward FFT Butterfly 2 Points
|
||||
* twiddles Twiddles factors, determine size of transform
|
||||
* x, y Input and output coefficients
|
||||
* n Number of interleaved transforms
|
||||
*/
|
||||
static void ffft_bf2(const struct lc3_fft_bf2_twiddles *twiddles,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
xfft_bf2(-1, twiddles, x, y, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* InverseIFFT Butterfly 2 Points
|
||||
* twiddles Twiddles factors, determine size of transform
|
||||
* x, y Input and output coefficients
|
||||
* n Number of interleaved transforms
|
||||
*/
|
||||
static void ifft_bf2(const struct lc3_fft_bf2_twiddles *twiddles,
|
||||
const struct lc3_complex *x, struct lc3_complex *y, int n)
|
||||
{
|
||||
xfft_bf2(1, twiddles, x, y, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform FFT
|
||||
* inverse True on inverse transform else forward
|
||||
* x, y0, y1 Input, and 2 scratch buffers of size `n`
|
||||
* n Number of points 30, 40, 60, 80, 90, 120, 160, 180, 240
|
||||
* return The buffer `y0` or `y1` that hold the result
|
||||
*
|
||||
* Input `x` can be the same as the `y0` second scratch buffer
|
||||
*/
|
||||
static struct lc3_complex *fft(
|
||||
bool inverse, const struct lc3_complex *x, int n,
|
||||
struct lc3_complex *y0, struct lc3_complex *y1)
|
||||
{
|
||||
struct lc3_complex *y[2] = { y1, y0 };
|
||||
int i2, i3, is = 0;
|
||||
|
||||
/* The number of points `n` can be decomposed as :
|
||||
*
|
||||
* n = 5^1 * 3^n3 * 2^n2
|
||||
*
|
||||
* for n = 40, 80, 160 n3 = 0, n2 = [3..5]
|
||||
* n = 30, 60, 120, 240 n3 = 1, n2 = [1..4]
|
||||
* n = 90, 180 n3 = 2, n2 = [1..2]
|
||||
*
|
||||
* Note that the expression `n & (n-1) == 0` is equivalent
|
||||
* to the check that `n` is a power of 2. */
|
||||
|
||||
(inverse ? ifft_5 : ffft_5)(x, y[is], n /= 5);
|
||||
|
||||
for (i3 = 0; n & (n-1); i3++, is ^= 1)
|
||||
(inverse ? ifft_bf3 : ffft_bf3)
|
||||
(lc3_fft_twiddles_bf3[i3], y[is], y[is ^ 1], n /= 3);
|
||||
|
||||
for (i2 = 0; n > 1; i2++, is ^= 1)
|
||||
(inverse ? ifft_bf2 : ffft_bf2)
|
||||
(lc3_fft_twiddles_bf2[i2][i3], y[is], y[is ^ 1], n >>= 1);
|
||||
|
||||
return y[is];
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* MDCT processing
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Windowing of samples before MDCT
|
||||
* dt, sr Duration and samplerate (size of the transform)
|
||||
* x [-nd..-1] Previous, [0..ns-1] Current samples
|
||||
* y Output `ns` windowed samples
|
||||
*
|
||||
* The number of previous samples `nd` accessed on `x` is :
|
||||
* nd: `ns` * 23/30 for 7.5ms frame duration
|
||||
* nd: `ns` * 5/ 8 for 10ms frame duration
|
||||
*/
|
||||
static void mdct_window(
|
||||
enum lc3_dt dt, enum lc3_srate sr, const float *x, float *y)
|
||||
{
|
||||
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
|
||||
|
||||
const float *w0 = lc3_mdct_win[dt][sr], *w1 = w0 + ns;
|
||||
const float *w2 = w1, *w3 = w2 + nd;
|
||||
|
||||
const float *x0 = x - nd, *x1 = x0 + ns;
|
||||
const float *x2 = x1, *x3 = x2 + nd;
|
||||
|
||||
float *y0 = y + ns/2, *y1 = y0;
|
||||
|
||||
while (x0 < x1)
|
||||
*(--y0) = *(x0++) * *(w0++) - *(--x1) * *(--w1);
|
||||
|
||||
for (const float *xe = x2 + ns-nd; x2 < xe; )
|
||||
*(y1++) = *(x2++) * *(w2++);
|
||||
|
||||
while (x2 < x3)
|
||||
*(y1++) = *(x2++) * *(w2++) + *(--x3) * *(--w3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-rotate MDCT coefficients of N/2 points, before FFT N/4 points FFT
|
||||
* def Size and twiddles factors
|
||||
* x, y Input and output coefficients
|
||||
*
|
||||
* `x` and y` can be the same buffer
|
||||
*/
|
||||
static void mdct_pre_fft(const struct lc3_mdct_rot_def *def,
|
||||
const float *x, struct lc3_complex *y)
|
||||
{
|
||||
int n4 = def->n4;
|
||||
|
||||
const float *x0 = x, *x1 = x0 + 2*n4;
|
||||
const struct lc3_complex *w0 = def->w, *w1 = w0 + n4;
|
||||
struct lc3_complex *y0 = y, *y1 = y0 + n4;
|
||||
|
||||
while (x0 < x1) {
|
||||
struct lc3_complex u, uw = *(w0++);
|
||||
u.re = - *(--x1) * uw.re + *x0 * uw.im;
|
||||
u.im = *(x0++) * uw.re + *x1 * uw.im;
|
||||
|
||||
struct lc3_complex v, vw = *(--w1);
|
||||
v.re = - *(--x1) * vw.im + *x0 * vw.re;
|
||||
v.im = - *(x0++) * vw.im - *x1 * vw.re;
|
||||
|
||||
*(y0++) = u;
|
||||
*(--y1) = v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-rotate FFT N/4 points coefficients, resulting MDCT N points
|
||||
* def Size and twiddles factors
|
||||
* x, y Input and output coefficients
|
||||
* scale Scale on output coefficients
|
||||
*
|
||||
* `x` and y` can be the same buffer
|
||||
*/
|
||||
static void mdct_post_fft(const struct lc3_mdct_rot_def *def,
|
||||
const struct lc3_complex *x, float *y, float scale)
|
||||
{
|
||||
int n4 = def->n4, n8 = n4 >> 1;
|
||||
|
||||
const struct lc3_complex *w0 = def->w + n8, *w1 = w0 - 1;
|
||||
const struct lc3_complex *x0 = x + n8, *x1 = x0 - 1;
|
||||
|
||||
float *y0 = y + n4, *y1 = y0;
|
||||
|
||||
for ( ; y1 > y; x0++, x1--, w0++, w1--) {
|
||||
|
||||
float u0 = (x0->im * w0->im + x0->re * w0->re) * scale;
|
||||
float u1 = (x1->re * w1->im - x1->im * w1->re) * scale;
|
||||
|
||||
float v0 = (x0->re * w0->im - x0->im * w0->re) * scale;
|
||||
float v1 = (x1->im * w1->im + x1->re * w1->re) * scale;
|
||||
|
||||
*(y0++) = u0; *(y0++) = u1;
|
||||
*(--y1) = v0; *(--y1) = v1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-rotate IMDCT coefficients of N points, before FFT N/4 points FFT
|
||||
* def Size and twiddles factors
|
||||
* x, y Input and output coefficients
|
||||
*
|
||||
* `x` and y` can be the same buffer
|
||||
*/
|
||||
static void imdct_pre_fft(const struct lc3_mdct_rot_def *def,
|
||||
const float *x, struct lc3_complex *y)
|
||||
{
|
||||
int n4 = def->n4;
|
||||
|
||||
const float *x0 = x, *x1 = x0 + 2*n4;
|
||||
|
||||
const struct lc3_complex *w0 = def->w, *w1 = w0 + n4;
|
||||
struct lc3_complex *y0 = y, *y1 = y0 + n4;
|
||||
|
||||
while (x0 < x1) {
|
||||
float u0 = *(x0++), u1 = *(--x1);
|
||||
float v0 = *(x0++), v1 = *(--x1);
|
||||
struct lc3_complex uw = *(w0++), vw = *(--w1);
|
||||
|
||||
(y0 )->re = - u1 * uw.re + u0 * uw.im;
|
||||
(y0++)->im = - u0 * uw.re - u1 * uw.im;
|
||||
|
||||
(--y1)->re = - v0 * vw.re + v1 * vw.im;
|
||||
( y1)->im = - v1 * vw.re - v0 * vw.im;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-rotate FFT N/4 points coefficients, resulting IMDCT N points
|
||||
* def Size and twiddles factors
|
||||
* x, y Input and output coefficients
|
||||
* scale Scale on output coefficients
|
||||
*
|
||||
* `x` and y` can be the same buffer
|
||||
*/
|
||||
static void imdct_post_fft(const struct lc3_mdct_rot_def *def,
|
||||
const struct lc3_complex *x, float *y, float scale)
|
||||
{
|
||||
int n4 = def->n4;
|
||||
|
||||
const struct lc3_complex *w0 = def->w, *w1 = w0 + n4;
|
||||
const struct lc3_complex *x0 = x, *x1 = x0 + n4;
|
||||
|
||||
float *y0 = y, *y1 = y0 + 2*n4;
|
||||
|
||||
while (x0 < x1) {
|
||||
struct lc3_complex uz = *(x0++), vz = *(--x1);
|
||||
struct lc3_complex uw = *(w0++), vw = *(--w1);
|
||||
|
||||
*(y0++) = (uz.im * uw.im - uz.re * uw.re) * scale;
|
||||
*(--y1) = (uz.im * uw.re + uz.re * uw.im) * scale;
|
||||
|
||||
*(--y1) = (vz.im * vw.im - vz.re * vw.re) * scale;
|
||||
*(y0++) = (vz.im * vw.re + vz.re * vw.im) * scale;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply windowing of samples
|
||||
* dt, sr Duration and samplerate
|
||||
* x, d Middle half of IMDCT coefficients and delayed samples
|
||||
* y, d Output samples and delayed ones
|
||||
*/
|
||||
static void imdct_window(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const float *x, float *d, float *y)
|
||||
{
|
||||
/* The full MDCT coefficients is given by symmetry :
|
||||
* T[ 0 .. n/4-1] = -half[n/4-1 .. 0 ]
|
||||
* T[ n/4 .. n/2-1] = half[0 .. n/4-1]
|
||||
* T[ n/2 .. 3n/4-1] = half[n/4 .. n/2-1]
|
||||
* T[3n/4 .. n-1] = half[n/2-1 .. n/4 ] */
|
||||
|
||||
int n4 = LC3_NS(dt, sr) >> 1, nd = LC3_ND(dt, sr);
|
||||
const float *w2 = lc3_mdct_win[dt][sr], *w0 = w2 + 3*n4, *w1 = w0;
|
||||
|
||||
const float *x0 = d + nd-n4, *x1 = x0;
|
||||
float *y0 = y + nd-n4, *y1 = y0, *y2 = d + nd, *y3 = d;
|
||||
|
||||
while (y0 > y) {
|
||||
*(--y0) = *(--x0) - *(x ) * *(w1++);
|
||||
*(y1++) = *(x1++) + *(x++) * *(--w0);
|
||||
}
|
||||
|
||||
while (y1 < y + nd) {
|
||||
*(y1++) = *(x1++) + *(x++) * *(--w0);
|
||||
}
|
||||
|
||||
while (y1 < y + 2*n4) {
|
||||
*(y1++) = *(x ) * *(--w0);
|
||||
*(--y2) = *(x++) * *(w2++);
|
||||
}
|
||||
|
||||
while (y2 > y3) {
|
||||
*(y3++) = *(x ) * *(--w0);
|
||||
*(--y2) = *(x++) * *(w2++);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward MDCT transformation
|
||||
*/
|
||||
void lc3_mdct_forward(enum lc3_dt dt, enum lc3_srate sr,
|
||||
enum lc3_srate sr_dst, const float *x, float *y)
|
||||
{
|
||||
const struct lc3_mdct_rot_def *rot = lc3_mdct_rot[dt][sr];
|
||||
int nf = LC3_NS(dt, sr_dst);
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
union { float *f; struct lc3_complex *z; } u = { .f = y };
|
||||
struct lc3_complex z[ns/2];
|
||||
|
||||
mdct_window(dt, sr, x, u.f);
|
||||
|
||||
mdct_pre_fft(rot, u.f, u.z);
|
||||
u.z = fft(false, u.z, ns/2, u.z, z);
|
||||
mdct_post_fft(rot, u.z, y, sqrtf( (2.f*nf) / (ns*ns) ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse MDCT transformation
|
||||
*/
|
||||
void lc3_mdct_inverse(enum lc3_dt dt, enum lc3_srate sr,
|
||||
enum lc3_srate sr_src, const float *x, float *d, float *y)
|
||||
{
|
||||
const struct lc3_mdct_rot_def *rot = lc3_mdct_rot[dt][sr];
|
||||
int nf = LC3_NS(dt, sr_src);
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
struct lc3_complex buffer[ns/2];
|
||||
struct lc3_complex *z = (struct lc3_complex *)y;
|
||||
union { float *f; struct lc3_complex *z; } u = { .z = buffer };
|
||||
|
||||
imdct_pre_fft(rot, x, z);
|
||||
z = fft(true, z, ns/2, z, u.z);
|
||||
imdct_post_fft(rot, z, u.f, sqrtf(2.f / nf));
|
||||
|
||||
imdct_window(dt, sr, u.f, d, y);
|
||||
}
|
||||
59
src/mdct.h
Normal file
59
src/mdct.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Compute LD-MDCT (Low Delay Modified Discret Cosinus Transform)
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_MDCT_H
|
||||
#define __LC3_MDCT_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/**
|
||||
* Forward MDCT transformation
|
||||
* dt, sr Duration and samplerate (size of the transform)
|
||||
* sr_dst Samplerate destination, scale transforam accordingly
|
||||
* x [-nd..-1] Previous, [0..ns-1] Current samples
|
||||
* y Output `ns` frequency coefficients
|
||||
*
|
||||
* The number of previous samples `nd` accessed on `x` is :
|
||||
* nd: `ns` * 23/30 for 7.5ms frame duration
|
||||
* nd: `ns` * 5/ 8 for 10ms frame duration
|
||||
*/
|
||||
void lc3_mdct_forward(enum lc3_dt dt, enum lc3_srate sr,
|
||||
enum lc3_srate sr_dst, const float *x, float *y);
|
||||
|
||||
/**
|
||||
* Inverse MDCT transformation
|
||||
* dt, sr Duration and samplerate (size of the transform)
|
||||
* sr_src Samplerate source, scale transforam accordingly
|
||||
* x, d Frequency coefficients and delayed buffer
|
||||
* y, d Output `ns` samples and `nd` delayed ones
|
||||
*
|
||||
* `x` and `y` can be the same buffer
|
||||
*/
|
||||
void lc3_mdct_inverse(enum lc3_dt dt, enum lc3_srate sr,
|
||||
enum lc3_srate sr_src, const float *x, float *d, float *y);
|
||||
|
||||
|
||||
#endif /* __LC3_MDCT_H */
|
||||
61
src/plc.c
Normal file
61
src/plc.c
Normal file
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "plc.h"
|
||||
|
||||
|
||||
/**
|
||||
* Reset Packet Loss Concealment state
|
||||
*/
|
||||
void lc3_plc_reset(struct lc3_plc_state *plc)
|
||||
{
|
||||
plc->seed = 24607;
|
||||
lc3_plc_suspend(plc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend PLC execution (Good frame received)
|
||||
*/
|
||||
void lc3_plc_suspend(struct lc3_plc_state *plc)
|
||||
{
|
||||
plc->count = 1;
|
||||
plc->alpha = 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesis of a PLC frame
|
||||
*/
|
||||
void lc3_plc_synthesize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
struct lc3_plc_state *plc, const float *x, float *y)
|
||||
{
|
||||
uint16_t seed = plc->seed;
|
||||
float alpha = plc->alpha;
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
alpha *= (plc->count < 4 ? 1.0f :
|
||||
plc->count < 8 ? 0.9f : 0.85f);
|
||||
|
||||
for (int i = 0; i < ne; i++) {
|
||||
seed = (16831 + seed * 12821) & 0xffff;
|
||||
y[i] = alpha * (seed & 0x8000 ? -x[i] : x[i]);
|
||||
}
|
||||
|
||||
plc->seed = seed;
|
||||
plc->alpha = alpha;
|
||||
plc->count++;
|
||||
}
|
||||
57
src/plc.h
Normal file
57
src/plc.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Packet Loss Concealment
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_PLC_H
|
||||
#define __LC3_PLC_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
/**
|
||||
* Reset PLC state
|
||||
* plc PLC State to reset
|
||||
*/
|
||||
void lc3_plc_reset(lc3_plc_state_t *plc);
|
||||
|
||||
/**
|
||||
* Suspend PLC synthesis (Error-free frame decoded)
|
||||
* plc PLC State
|
||||
*/
|
||||
void lc3_plc_suspend(lc3_plc_state_t *plc);
|
||||
|
||||
/**
|
||||
* Synthesis of a PLC frame
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* plc PLC State
|
||||
* x Last good spectral coefficients
|
||||
* y Return emulated ones
|
||||
*
|
||||
* `x` and `y` can be the same buffer
|
||||
*/
|
||||
void lc3_plc_synthesize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
lc3_plc_state_t *plc, const float *x, float *y);
|
||||
|
||||
|
||||
#endif /* __LC3_PLC_H */
|
||||
878
src/sns.c
Normal file
878
src/sns.c
Normal file
@@ -0,0 +1,878 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "sns.h"
|
||||
#include "tables.h"
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* DCT-16
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Matrix of DCT-16 coefficients
|
||||
*
|
||||
* M[n][k] = 2f cos( Pi k (2n + 1) / 2N )
|
||||
*
|
||||
* k = [0..N-1], n = [0..N-1], N = 16
|
||||
* f = sqrt(1/4N) for k=0, sqrt(1/2N) otherwise
|
||||
*/
|
||||
static const float dct16_m[16][16] = {
|
||||
|
||||
{ 2.50000000e-01, 3.51850934e-01, 3.46759961e-01, 3.38329500e-01,
|
||||
3.26640741e-01, 3.11806253e-01, 2.93968901e-01, 2.73300467e-01,
|
||||
2.50000000e-01, 2.24291897e-01, 1.96423740e-01, 1.66663915e-01,
|
||||
1.35299025e-01, 1.02631132e-01, 6.89748448e-02, 3.46542923e-02 },
|
||||
|
||||
{ 2.50000000e-01, 3.38329500e-01, 2.93968901e-01, 2.24291897e-01,
|
||||
1.35299025e-01, 3.46542923e-02, -6.89748448e-02, -1.66663915e-01,
|
||||
-2.50000000e-01, -3.11806253e-01, -3.46759961e-01, -3.51850934e-01,
|
||||
-3.26640741e-01, -2.73300467e-01, -1.96423740e-01, -1.02631132e-01 },
|
||||
|
||||
{ 2.50000000e-01, 3.11806253e-01, 1.96423740e-01, 3.46542923e-02,
|
||||
-1.35299025e-01, -2.73300467e-01, -3.46759961e-01, -3.38329500e-01,
|
||||
-2.50000000e-01, -1.02631132e-01, 6.89748448e-02, 2.24291897e-01,
|
||||
3.26640741e-01, 3.51850934e-01, 2.93968901e-01, 1.66663915e-01 },
|
||||
|
||||
{ 2.50000000e-01, 2.73300467e-01, 6.89748448e-02, -1.66663915e-01,
|
||||
-3.26640741e-01, -3.38329500e-01, -1.96423740e-01, 3.46542923e-02,
|
||||
2.50000000e-01, 3.51850934e-01, 2.93968901e-01, 1.02631132e-01,
|
||||
-1.35299025e-01, -3.11806253e-01, -3.46759961e-01, -2.24291897e-01 },
|
||||
|
||||
{ 2.50000000e-01, 2.24291897e-01, -6.89748448e-02, -3.11806253e-01,
|
||||
-3.26640741e-01, -1.02631132e-01, 1.96423740e-01, 3.51850934e-01,
|
||||
2.50000000e-01, -3.46542923e-02, -2.93968901e-01, -3.38329500e-01,
|
||||
-1.35299025e-01, 1.66663915e-01, 3.46759961e-01, 2.73300467e-01 },
|
||||
|
||||
{ 2.50000000e-01, 1.66663915e-01, -1.96423740e-01, -3.51850934e-01,
|
||||
-1.35299025e-01, 2.24291897e-01, 3.46759961e-01, 1.02631132e-01,
|
||||
-2.50000000e-01, -3.38329500e-01, -6.89748448e-02, 2.73300467e-01,
|
||||
3.26640741e-01, 3.46542923e-02, -2.93968901e-01, -3.11806253e-01 },
|
||||
|
||||
{ 2.50000000e-01, 1.02631132e-01, -2.93968901e-01, -2.73300467e-01,
|
||||
1.35299025e-01, 3.51850934e-01, 6.89748448e-02, -3.11806253e-01,
|
||||
-2.50000000e-01, 1.66663915e-01, 3.46759961e-01, 3.46542923e-02,
|
||||
-3.26640741e-01, -2.24291897e-01, 1.96423740e-01, 3.38329500e-01 },
|
||||
|
||||
{ 2.50000000e-01, 3.46542923e-02, -3.46759961e-01, -1.02631132e-01,
|
||||
3.26640741e-01, 1.66663915e-01, -2.93968901e-01, -2.24291897e-01,
|
||||
2.50000000e-01, 2.73300467e-01, -1.96423740e-01, -3.11806253e-01,
|
||||
1.35299025e-01, 3.38329500e-01, -6.89748448e-02, -3.51850934e-01 },
|
||||
|
||||
{ 2.50000000e-01, -3.46542923e-02, -3.46759961e-01, 1.02631132e-01,
|
||||
3.26640741e-01, -1.66663915e-01, -2.93968901e-01, 2.24291897e-01,
|
||||
2.50000000e-01, -2.73300467e-01, -1.96423740e-01, 3.11806253e-01,
|
||||
1.35299025e-01, -3.38329500e-01, -6.89748448e-02, 3.51850934e-01 },
|
||||
|
||||
{ 2.50000000e-01, -1.02631132e-01, -2.93968901e-01, 2.73300467e-01,
|
||||
1.35299025e-01, -3.51850934e-01, 6.89748448e-02, 3.11806253e-01,
|
||||
-2.50000000e-01, -1.66663915e-01, 3.46759961e-01, -3.46542923e-02,
|
||||
-3.26640741e-01, 2.24291897e-01, 1.96423740e-01, -3.38329500e-01 },
|
||||
|
||||
{ 2.50000000e-01, -1.66663915e-01, -1.96423740e-01, 3.51850934e-01,
|
||||
-1.35299025e-01, -2.24291897e-01, 3.46759961e-01, -1.02631132e-01,
|
||||
-2.50000000e-01, 3.38329500e-01, -6.89748448e-02, -2.73300467e-01,
|
||||
3.26640741e-01, -3.46542923e-02, -2.93968901e-01, 3.11806253e-01 },
|
||||
|
||||
{ 2.50000000e-01, -2.24291897e-01, -6.89748448e-02, 3.11806253e-01,
|
||||
-3.26640741e-01, 1.02631132e-01, 1.96423740e-01, -3.51850934e-01,
|
||||
2.50000000e-01, 3.46542923e-02, -2.93968901e-01, 3.38329500e-01,
|
||||
-1.35299025e-01, -1.66663915e-01, 3.46759961e-01, -2.73300467e-01 },
|
||||
|
||||
{ 2.50000000e-01, -2.73300467e-01, 6.89748448e-02, 1.66663915e-01,
|
||||
-3.26640741e-01, 3.38329500e-01, -1.96423740e-01, -3.46542923e-02,
|
||||
2.50000000e-01, -3.51850934e-01, 2.93968901e-01, -1.02631132e-01,
|
||||
-1.35299025e-01, 3.11806253e-01, -3.46759961e-01, 2.24291897e-01 },
|
||||
|
||||
{ 2.50000000e-01, -3.11806253e-01, 1.96423740e-01, -3.46542923e-02,
|
||||
-1.35299025e-01, 2.73300467e-01, -3.46759961e-01, 3.38329500e-01,
|
||||
-2.50000000e-01, 1.02631132e-01, 6.89748448e-02, -2.24291897e-01,
|
||||
3.26640741e-01, -3.51850934e-01, 2.93968901e-01, -1.66663915e-01 },
|
||||
|
||||
{ 2.50000000e-01, -3.38329500e-01, 2.93968901e-01, -2.24291897e-01,
|
||||
1.35299025e-01, -3.46542923e-02, -6.89748448e-02, 1.66663915e-01,
|
||||
-2.50000000e-01, 3.11806253e-01, -3.46759961e-01, 3.51850934e-01,
|
||||
-3.26640741e-01, 2.73300467e-01, -1.96423740e-01, 1.02631132e-01 },
|
||||
|
||||
{ 2.50000000e-01, -3.51850934e-01, 3.46759961e-01, -3.38329500e-01,
|
||||
3.26640741e-01, -3.11806253e-01, 2.93968901e-01, -2.73300467e-01,
|
||||
2.50000000e-01, -2.24291897e-01, 1.96423740e-01, -1.66663915e-01,
|
||||
1.35299025e-01, -1.02631132e-01, 6.89748448e-02, -3.46542923e-02 },
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Forward DCT-16 transformation
|
||||
* x, y Input and output 16 values
|
||||
*/
|
||||
static void dct16_forward(const float *x, float *y)
|
||||
{
|
||||
for (int i = 0, j; i < 16; i++)
|
||||
for (y[i] = 0, j = 0; j < 16; j++)
|
||||
y[i] += x[j] * dct16_m[j][i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse DCT-16 transformation
|
||||
* x, y Input and output 16 values
|
||||
*/
|
||||
static void dct16_inverse(const float *x, float *y)
|
||||
{
|
||||
for (int i = 0, j; i < 16; i++)
|
||||
for (y[i] = 0, j = 0; j < 16; j++)
|
||||
y[i] += x[j] * dct16_m[i][j];
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Scale factors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Scale factors
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* eb Energy estimation per bands
|
||||
* att 1: Attack detected 0: Otherwise
|
||||
* scf Output 16 scale factors
|
||||
*/
|
||||
static void compute_scale_factors(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const float *eb, bool att, float *scf)
|
||||
{
|
||||
/* Pre-emphasis gain table :
|
||||
* Ge[b] = 10 ^ (b * g_tilt) / 630 , b = [0..63] */
|
||||
|
||||
static const float ge_table[LC3_NUM_SRATE][LC3_NUM_BANDS] = {
|
||||
|
||||
[LC3_SRATE_8K] = { /* g_tilt = 14 */
|
||||
1.00000000e+00, 1.05250029e+00, 1.10775685e+00, 1.16591440e+00,
|
||||
1.22712524e+00, 1.29154967e+00, 1.35935639e+00, 1.43072299e+00,
|
||||
1.50583635e+00, 1.58489319e+00, 1.66810054e+00, 1.75567629e+00,
|
||||
1.84784980e+00, 1.94486244e+00, 2.04696827e+00, 2.15443469e+00,
|
||||
2.26754313e+00, 2.38658979e+00, 2.51188643e+00, 2.64376119e+00,
|
||||
2.78255940e+00, 2.92864456e+00, 3.08239924e+00, 3.24422608e+00,
|
||||
3.41454887e+00, 3.59381366e+00, 3.78248991e+00, 3.98107171e+00,
|
||||
4.19007911e+00, 4.41005945e+00, 4.64158883e+00, 4.88527357e+00,
|
||||
5.14175183e+00, 5.41169527e+00, 5.69581081e+00, 5.99484250e+00,
|
||||
6.30957344e+00, 6.64082785e+00, 6.98947321e+00, 7.35642254e+00,
|
||||
7.74263683e+00, 8.14912747e+00, 8.57695899e+00, 9.02725178e+00,
|
||||
9.50118507e+00, 1.00000000e+01, 1.05250029e+01, 1.10775685e+01,
|
||||
1.16591440e+01, 1.22712524e+01, 1.29154967e+01, 1.35935639e+01,
|
||||
1.43072299e+01, 1.50583635e+01, 1.58489319e+01, 1.66810054e+01,
|
||||
1.75567629e+01, 1.84784980e+01, 1.94486244e+01, 2.04696827e+01,
|
||||
2.15443469e+01, 2.26754313e+01, 2.38658979e+01, 2.51188643e+01 },
|
||||
|
||||
[LC3_SRATE_16K] = { /* g_tilt = 18 */
|
||||
1.00000000e+00, 1.06800043e+00, 1.14062492e+00, 1.21818791e+00,
|
||||
1.30102522e+00, 1.38949549e+00, 1.48398179e+00, 1.58489319e+00,
|
||||
1.69266662e+00, 1.80776868e+00, 1.93069773e+00, 2.06198601e+00,
|
||||
2.20220195e+00, 2.35195264e+00, 2.51188643e+00, 2.68269580e+00,
|
||||
2.86512027e+00, 3.05994969e+00, 3.26802759e+00, 3.49025488e+00,
|
||||
3.72759372e+00, 3.98107171e+00, 4.25178630e+00, 4.54090961e+00,
|
||||
4.84969343e+00, 5.17947468e+00, 5.53168120e+00, 5.90783791e+00,
|
||||
6.30957344e+00, 6.73862717e+00, 7.19685673e+00, 7.68624610e+00,
|
||||
8.20891416e+00, 8.76712387e+00, 9.36329209e+00, 1.00000000e+01,
|
||||
1.06800043e+01, 1.14062492e+01, 1.21818791e+01, 1.30102522e+01,
|
||||
1.38949549e+01, 1.48398179e+01, 1.58489319e+01, 1.69266662e+01,
|
||||
1.80776868e+01, 1.93069773e+01, 2.06198601e+01, 2.20220195e+01,
|
||||
2.35195264e+01, 2.51188643e+01, 2.68269580e+01, 2.86512027e+01,
|
||||
3.05994969e+01, 3.26802759e+01, 3.49025488e+01, 3.72759372e+01,
|
||||
3.98107171e+01, 4.25178630e+01, 4.54090961e+01, 4.84969343e+01,
|
||||
5.17947468e+01, 5.53168120e+01, 5.90783791e+01, 6.30957344e+01 },
|
||||
|
||||
[LC3_SRATE_24K] = { /* g_tilt = 22 */
|
||||
1.00000000e+00, 1.08372885e+00, 1.17446822e+00, 1.27280509e+00,
|
||||
1.37937560e+00, 1.49486913e+00, 1.62003281e+00, 1.75567629e+00,
|
||||
1.90267705e+00, 2.06198601e+00, 2.23463373e+00, 2.42173704e+00,
|
||||
2.62450630e+00, 2.84425319e+00, 3.08239924e+00, 3.34048498e+00,
|
||||
3.62017995e+00, 3.92329345e+00, 4.25178630e+00, 4.60778348e+00,
|
||||
4.99358789e+00, 5.41169527e+00, 5.86481029e+00, 6.35586411e+00,
|
||||
6.88803330e+00, 7.46476041e+00, 8.08977621e+00, 8.76712387e+00,
|
||||
9.50118507e+00, 1.02967084e+01, 1.11588399e+01, 1.20931568e+01,
|
||||
1.31057029e+01, 1.42030283e+01, 1.53922315e+01, 1.66810054e+01,
|
||||
1.80776868e+01, 1.95913107e+01, 2.12316686e+01, 2.30093718e+01,
|
||||
2.49359200e+01, 2.70237760e+01, 2.92864456e+01, 3.17385661e+01,
|
||||
3.43959997e+01, 3.72759372e+01, 4.03970086e+01, 4.37794036e+01,
|
||||
4.74450028e+01, 5.14175183e+01, 5.57226480e+01, 6.03882412e+01,
|
||||
6.54444792e+01, 7.09240702e+01, 7.68624610e+01, 8.32980665e+01,
|
||||
9.02725178e+01, 9.78309319e+01, 1.06022203e+02, 1.14899320e+02,
|
||||
1.24519708e+02, 1.34945600e+02, 1.46244440e+02, 1.58489319e+02 },
|
||||
|
||||
[LC3_SRATE_32K] = { /* g_tilt = 26 */
|
||||
1.00000000e+00, 1.09968890e+00, 1.20931568e+00, 1.32987103e+00,
|
||||
1.46244440e+00, 1.60823388e+00, 1.76855694e+00, 1.94486244e+00,
|
||||
2.13874364e+00, 2.35195264e+00, 2.58641621e+00, 2.84425319e+00,
|
||||
3.12779366e+00, 3.43959997e+00, 3.78248991e+00, 4.15956216e+00,
|
||||
4.57422434e+00, 5.03022373e+00, 5.53168120e+00, 6.08312841e+00,
|
||||
6.68954879e+00, 7.35642254e+00, 8.08977621e+00, 8.89623710e+00,
|
||||
9.78309319e+00, 1.07583590e+01, 1.18308480e+01, 1.30102522e+01,
|
||||
1.43072299e+01, 1.57335019e+01, 1.73019574e+01, 1.90267705e+01,
|
||||
2.09235283e+01, 2.30093718e+01, 2.53031508e+01, 2.78255940e+01,
|
||||
3.05994969e+01, 3.36499270e+01, 3.70044512e+01, 4.06933843e+01,
|
||||
4.47500630e+01, 4.92111475e+01, 5.41169527e+01, 5.95118121e+01,
|
||||
6.54444792e+01, 7.19685673e+01, 7.91430346e+01, 8.70327166e+01,
|
||||
9.57089124e+01, 1.05250029e+02, 1.15742288e+02, 1.27280509e+02,
|
||||
1.39968963e+02, 1.53922315e+02, 1.69266662e+02, 1.86140669e+02,
|
||||
2.04696827e+02, 2.25102829e+02, 2.47543082e+02, 2.72220379e+02,
|
||||
2.99357729e+02, 3.29200372e+02, 3.62017995e+02, 3.98107171e+02 },
|
||||
|
||||
[LC3_SRATE_48K] = { /* g_tilt = 30 */
|
||||
1.00000000e+00, 1.11588399e+00, 1.24519708e+00, 1.38949549e+00,
|
||||
1.55051578e+00, 1.73019574e+00, 1.93069773e+00, 2.15443469e+00,
|
||||
2.40409918e+00, 2.68269580e+00, 2.99357729e+00, 3.34048498e+00,
|
||||
3.72759372e+00, 4.15956216e+00, 4.64158883e+00, 5.17947468e+00,
|
||||
5.77969288e+00, 6.44946677e+00, 7.19685673e+00, 8.03085722e+00,
|
||||
8.96150502e+00, 1.00000000e+01, 1.11588399e+01, 1.24519708e+01,
|
||||
1.38949549e+01, 1.55051578e+01, 1.73019574e+01, 1.93069773e+01,
|
||||
2.15443469e+01, 2.40409918e+01, 2.68269580e+01, 2.99357729e+01,
|
||||
3.34048498e+01, 3.72759372e+01, 4.15956216e+01, 4.64158883e+01,
|
||||
5.17947468e+01, 5.77969288e+01, 6.44946677e+01, 7.19685673e+01,
|
||||
8.03085722e+01, 8.96150502e+01, 1.00000000e+02, 1.11588399e+02,
|
||||
1.24519708e+02, 1.38949549e+02, 1.55051578e+02, 1.73019574e+02,
|
||||
1.93069773e+02, 2.15443469e+02, 2.40409918e+02, 2.68269580e+02,
|
||||
2.99357729e+02, 3.34048498e+02, 3.72759372e+02, 4.15956216e+02,
|
||||
4.64158883e+02, 5.17947468e+02, 5.77969288e+02, 6.44946677e+02,
|
||||
7.19685673e+02, 8.03085722e+02, 8.96150502e+02, 1.00000000e+03 },
|
||||
};
|
||||
|
||||
float e[LC3_NUM_BANDS];
|
||||
|
||||
/* --- Copy and padding --- */
|
||||
|
||||
int nb = LC3_MIN(lc3_band_lim[dt][sr][LC3_NUM_BANDS], LC3_NUM_BANDS);
|
||||
int n2 = LC3_NUM_BANDS - nb;
|
||||
|
||||
for (int i2 = 0; i2 < n2; i2++)
|
||||
e[2*i2 + 0] = e[2*i2 + 1] = eb[i2];
|
||||
|
||||
memcpy(e + 2*n2, eb + n2, (nb - n2) * sizeof(float));
|
||||
|
||||
/* --- Smoothing, pre-emphasis and logarithm --- */
|
||||
|
||||
const float *ge = ge_table[sr];
|
||||
|
||||
float e0 = e[0], e1 = e[0], e2;
|
||||
float e_sum = 0;
|
||||
|
||||
for (int i = 0; i < LC3_NUM_BANDS-1; ) {
|
||||
e[i] = (e0 * 0.25 + e1 * 0.5 + (e2 = e[i+1]) * 0.25) * ge[i];
|
||||
e_sum += e[i++];
|
||||
|
||||
e[i] = (e1 * 0.25 + e2 * 0.5 + (e0 = e[i+1]) * 0.25) * ge[i];
|
||||
e_sum += e[i++];
|
||||
|
||||
e[i] = (e2 * 0.25 + e0 * 0.5 + (e1 = e[i+1]) * 0.25) * ge[i];
|
||||
e_sum += e[i++];
|
||||
}
|
||||
|
||||
e[LC3_NUM_BANDS-1] = (e0 * 0.25 + e1 * 0.75) * ge[LC3_NUM_BANDS-1];
|
||||
e_sum += e[LC3_NUM_BANDS-1];
|
||||
|
||||
float noise_floor = fmaxf(e_sum * (1e-4 / 64), 0x1p-32);
|
||||
|
||||
for (int i = 0; i < LC3_NUM_BANDS; i++)
|
||||
e[i] = log2f(fmaxf(e[i], noise_floor)) * 0.5;
|
||||
|
||||
/* --- Grouping & scaling --- */
|
||||
|
||||
float scf_sum;
|
||||
|
||||
scf[0] = (e[0] + e[4]) * 1./12 +
|
||||
(e[0] + e[3]) * 2./12 +
|
||||
(e[1] + e[2]) * 3./12 ;
|
||||
scf_sum = scf[0];
|
||||
|
||||
for (int i = 1; i < 15; i++) {
|
||||
scf[i] = (e[4*i-1] + e[4*i+4]) * 1./12 +
|
||||
(e[4*i ] + e[4*i+3]) * 2./12 +
|
||||
(e[4*i+1] + e[4*i+2]) * 3./12 ;
|
||||
scf_sum += scf[i];
|
||||
}
|
||||
|
||||
scf[15] = (e[59] + e[63]) * 1./12 +
|
||||
(e[60] + e[63]) * 2./12 +
|
||||
(e[61] + e[62]) * 3./12 ;
|
||||
scf_sum += scf[15];
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
scf[i] = 0.85 * (scf[i] - scf_sum * 1./16);
|
||||
|
||||
/* --- Attack handling --- */
|
||||
|
||||
if (!att)
|
||||
return;
|
||||
|
||||
float s0, s1 = scf[0], s2 = scf[1], s3 = scf[2], s4 = scf[3];
|
||||
float sn = s1 + s2;
|
||||
|
||||
scf[0] = (sn += s3) * 1./3;
|
||||
scf[1] = (sn += s4) * 1./4;
|
||||
scf_sum = scf[0] + scf[1];
|
||||
|
||||
for (int i = 2; i < 14; i++, sn -= s0) {
|
||||
s0 = s1, s1 = s2, s2 = s3, s3 = s4, s4 = scf[i+2];
|
||||
scf[i] = (sn += s4) * 1./5;
|
||||
scf_sum += scf[i];
|
||||
}
|
||||
|
||||
scf[14] = (sn ) * 1./4;
|
||||
scf[15] = (sn -= s1) * 1./3;
|
||||
scf_sum += scf[14] + scf[15];
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
scf[i] = (dt == LC3_DT_7M5 ? 0.3 : 0.5) *
|
||||
(scf[i] - scf_sum * 1./16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Codebooks
|
||||
* scf Input 16 scale factors
|
||||
* lf/hfcb_idx Output the low and high frequency codebooks index
|
||||
*/
|
||||
static void resolve_codebooks(const float *scf, int *lfcb_idx, int *hfcb_idx)
|
||||
{
|
||||
float dlfcb_max = 0, dhfcb_max = 0;
|
||||
*lfcb_idx = *hfcb_idx = 0;
|
||||
|
||||
for (int icb = 0; icb < 32; icb++) {
|
||||
const float *lfcb = lc3_sns_lfcb[icb];
|
||||
const float *hfcb = lc3_sns_hfcb[icb];
|
||||
float dlfcb = 0, dhfcb = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
dlfcb += (scf[ i] - lfcb[i]) * (scf[ i] - lfcb[i]);
|
||||
dhfcb += (scf[8+i] - hfcb[i]) * (scf[8+i] - hfcb[i]);
|
||||
}
|
||||
|
||||
if (icb == 0 || dlfcb < dlfcb_max)
|
||||
*lfcb_idx = icb, dlfcb_max = dlfcb;
|
||||
|
||||
if (icb == 0 || dhfcb < dhfcb_max)
|
||||
*hfcb_idx = icb, dhfcb_max = dhfcb;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit energy normalize pulse configuration
|
||||
* c Pulse configuration
|
||||
* cn Normalized pulse configuration
|
||||
*/
|
||||
static void normalize(const int *c, float *cn)
|
||||
{
|
||||
int c2_sum = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
c2_sum += c[i] * c[i];
|
||||
|
||||
float c_norm = 1.f / sqrtf(c2_sum);
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
cn[i] = c[i] * c_norm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-procedure of `quantize()`, add unit pulse
|
||||
* x, y, n Transformed residual, and vector of pulses with length
|
||||
* start, end Current number of pulses, limit to reach
|
||||
* corr, energy Correlation (x,y) and y energy, updated at output
|
||||
*/
|
||||
static void add_pulse(const float *x, int *y, int n,
|
||||
int start, int end, float *corr, float *energy)
|
||||
{
|
||||
for (int k = start; k < end; k++) {
|
||||
float best_c2 = (*corr + x[0]) * (*corr + x[0]);
|
||||
float best_e = *energy + 2*y[0] + 1;
|
||||
int nbest = 0;
|
||||
|
||||
for (int i = 1; i < n; i++) {
|
||||
float c2 = (*corr + x[i]) * (*corr + x[i]);
|
||||
float e = *energy + 2*y[i] + 1;
|
||||
|
||||
if (c2 * best_e > e * best_c2)
|
||||
best_c2 = c2, best_e = e, nbest = i;
|
||||
}
|
||||
|
||||
*corr += x[nbest];
|
||||
*energy += 2*y[nbest] + 1;
|
||||
y[nbest]++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quantization of codebooks residual
|
||||
* scf Input 16 scale factors, output quantized version
|
||||
* lf/hfcb_idx Codebooks index
|
||||
* c, cn Output 4 pulse configurations candidates, normalized
|
||||
* shape/gain_idx Output selected shape/gain indexes
|
||||
*/
|
||||
static void quantize(const float *scf, int lfcb_idx, int hfcb_idx,
|
||||
int (*c)[16], float (*cn)[16], int *shape_idx, int *gain_idx)
|
||||
{
|
||||
/* --- Residual --- */
|
||||
|
||||
const float *lfcb = lc3_sns_lfcb[lfcb_idx];
|
||||
const float *hfcb = lc3_sns_hfcb[hfcb_idx];
|
||||
float r[16], x[16];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
r[ i] = scf[ i] - lfcb[i];
|
||||
r[8+i] = scf[8+i] - hfcb[i];
|
||||
}
|
||||
|
||||
dct16_forward(r, x);
|
||||
|
||||
/* --- Shape 3 candidate ---
|
||||
* Project to or below pyramid N = 16, K = 6,
|
||||
* then add unit pulses until you reach K = 6, over N = 16 */
|
||||
|
||||
float xm[16];
|
||||
float xm_sum = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
xm[i] = fabsf(x[i]);
|
||||
xm_sum += xm[i];
|
||||
}
|
||||
|
||||
float proj_factor = (6 - 1) / fmaxf(xm_sum, 1e-31);
|
||||
float corr = 0, energy = 0;
|
||||
int npulses = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
c[3][i] = floorf(xm[i] * proj_factor);
|
||||
npulses += c[3][i];
|
||||
corr += c[3][i] * xm[i];
|
||||
energy += c[3][i] * c[3][i];
|
||||
}
|
||||
|
||||
add_pulse(xm, c[3], 16, npulses, 6, &corr, &energy);
|
||||
npulses = 6;
|
||||
|
||||
/* --- Shape 2 candidate ---
|
||||
* Add unit pulses until you reach K = 8 on shape 3 */
|
||||
|
||||
memcpy(c[2], c[3], sizeof(c[2]));
|
||||
|
||||
add_pulse(xm, c[2], 16, npulses, 8, &corr, &energy);
|
||||
npulses = 8;
|
||||
|
||||
/* --- Shape 1 candidate ---
|
||||
* Remove any unit pulses from shape 2 that are not part of 0 to 9
|
||||
* Update energy and correlation terms accordingly
|
||||
* Add unit pulses until you reach K = 10, over N = 10 */
|
||||
|
||||
memcpy(c[1], c[2], sizeof(c[1]));
|
||||
|
||||
for (int i = 10; i < 16; i++) {
|
||||
c[1][i] = 0;
|
||||
npulses -= c[2][i];
|
||||
corr -= c[2][i] * xm[i];
|
||||
energy -= c[2][i] * c[2][i];
|
||||
}
|
||||
|
||||
add_pulse(xm, c[1], 10, npulses, 10, &corr, &energy);
|
||||
npulses = 10;
|
||||
|
||||
/* --- Shape 0 candidate ---
|
||||
* Add unit pulses until you reach K = 1, on shape 1 */
|
||||
|
||||
memcpy(c[0], c[1], sizeof(c[0]));
|
||||
|
||||
add_pulse(xm + 10, c[0] + 10, 6, 0, 1, &corr, &energy);
|
||||
|
||||
/* --- Add sign and unit energy normalize --- */
|
||||
|
||||
for (int j = 0; j < 16; j++)
|
||||
for (int i = 0; i < 4; i++)
|
||||
c[i][j] = x[j] < 0 ? -c[i][j] : c[i][j];
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
normalize(c[i], cn[i]);
|
||||
|
||||
/* --- Determe shape & gain index ---
|
||||
* Search the Mean Square Error, within (shape, gain) combinations */
|
||||
|
||||
float mse_min = INFINITY;
|
||||
*shape_idx = *gain_idx = 0;
|
||||
|
||||
for (int ic = 0; ic < 4; ic++) {
|
||||
const struct lc3_sns_vq_gains *cgains = lc3_sns_vq_gains + ic;
|
||||
float cmse_min = INFINITY;
|
||||
int cgain_idx = 0;
|
||||
|
||||
for (int ig = 0; ig < cgains->count; ig++) {
|
||||
float g = cgains->v[ig];
|
||||
|
||||
float mse = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
mse += (x[i] - g * cn[ic][i]) * (x[i] - g * cn[ic][i]);
|
||||
|
||||
if (mse < cmse_min) {
|
||||
cgain_idx = ig,
|
||||
cmse_min = mse;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmse_min < mse_min) {
|
||||
*shape_idx = ic, *gain_idx = cgain_idx;
|
||||
mse_min = cmse_min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unquantization of codebooks residual
|
||||
* lf/hfcb_idx Low and high frequency codebooks index
|
||||
* c Table of normalized pulse configuration
|
||||
* shape/gain Selected shape/gain indexes
|
||||
* scf Return unquantized scale factors
|
||||
*/
|
||||
static void unquantize(int lfcb_idx, int hfcb_idx,
|
||||
const float *c, int shape, int gain, float *scf)
|
||||
{
|
||||
const float *lfcb = lc3_sns_lfcb[lfcb_idx];
|
||||
const float *hfcb = lc3_sns_hfcb[hfcb_idx];
|
||||
float g = lc3_sns_vq_gains[shape].v[gain];
|
||||
|
||||
dct16_inverse(c, scf);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
scf[i] = lfcb[i] + g * scf[i];
|
||||
|
||||
for (int i = 8; i < 16; i++)
|
||||
scf[i] = hfcb[i-8] + g * scf[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-procedure of `sns_enumerate()`, enumeration of a vector
|
||||
* c, n Table of pulse configuration, and length
|
||||
* idx, ls Return enumeration set
|
||||
*/
|
||||
static void enum_mvpq(const int *c, int n, int *idx, bool *ls)
|
||||
{
|
||||
int ci, i, j;
|
||||
|
||||
/* --- Scan for 1st significant coeff --- */
|
||||
|
||||
for (i = 0, c += n; (ci = *(--c)) == 0 ; i++);
|
||||
|
||||
*idx = 0;
|
||||
*ls = ci < 0;
|
||||
|
||||
/* --- Scan remaining coefficients --- */
|
||||
|
||||
for (i++, j = LC3_ABS(ci); i < n; i++, j += LC3_ABS(ci)) {
|
||||
|
||||
if ((ci = *(--c)) != 0) {
|
||||
*idx = (*idx << 1) | *ls;
|
||||
*ls = ci < 0;
|
||||
}
|
||||
|
||||
*idx += lc3_sns_mpvq_offsets[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-procedure of `sns_deenumerate()`, deenumeration of a vector
|
||||
* idx, ls Enumeration set
|
||||
* npulses Number of pulses in the set
|
||||
* c, n Table of pulses configuration, and length
|
||||
*/
|
||||
static void deenum_mvpq(int idx, bool ls, int npulses, int *c, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* --- Scan for coefficients --- */
|
||||
|
||||
for (i = n-1; i >= 0 && idx; i--) {
|
||||
|
||||
int ci = 0;
|
||||
|
||||
for (ci = 0; idx < lc3_sns_mpvq_offsets[i][npulses - ci]; ci++);
|
||||
idx -= lc3_sns_mpvq_offsets[i][npulses - ci];
|
||||
|
||||
*(c++) = ls ? -ci : ci;
|
||||
npulses -= ci;
|
||||
if (ci > 0) {
|
||||
ls = idx & 1;
|
||||
idx >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Set last significant --- */
|
||||
|
||||
int ci = npulses;
|
||||
|
||||
if (i-- >= 0)
|
||||
*(c++) = ls ? -ci : ci;
|
||||
|
||||
while (i-- >= 0)
|
||||
*(c++) = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* SNS Enumeration of PVQ configuration
|
||||
* shape Selected shape index
|
||||
* c Selected pulse configuration
|
||||
* idx_a, ls_a Return enumeration set A
|
||||
* idx_b, ls_b Return enumeration set B (shape = 0)
|
||||
*/
|
||||
static void enumerate(int shape, const int *c,
|
||||
int *idx_a, bool *ls_a, int *idx_b, bool *ls_b)
|
||||
{
|
||||
enum_mvpq(c, shape < 2 ? 10 : 16, idx_a, ls_a);
|
||||
|
||||
if (shape == 0)
|
||||
enum_mvpq(c + 10, 6, idx_b, ls_b);
|
||||
}
|
||||
|
||||
/**
|
||||
* SNS Deenumeration of PVQ configuration
|
||||
* shape Selected shape index
|
||||
* idx_a, ls_a enumeration set A
|
||||
* idx_b, ls_b enumeration set B (shape = 0)
|
||||
* c Return pulse configuration
|
||||
*/
|
||||
static void deenumerate(int shape,
|
||||
int idx_a, bool ls_a, int idx_b, bool ls_b, int *c)
|
||||
{
|
||||
int npulses_a = (const int []){ 10, 10, 8, 6 }[shape];
|
||||
|
||||
deenum_mvpq(idx_a, ls_a, npulses_a, c, shape < 2 ? 10 : 16);
|
||||
|
||||
if (shape == 0)
|
||||
deenum_mvpq(idx_b, ls_b, 1, c + 10, 6);
|
||||
else if (shape == 1)
|
||||
memset(c + 10, 0, 6 * sizeof(*c));
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Filtering
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Spectral shaping
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* scf_q Quantized scale factors
|
||||
* inv True on inverse shaping, False otherwise
|
||||
* x Spectral coefficients
|
||||
* y Return shapped coefficients
|
||||
*
|
||||
* `x` and `y` can be the same buffer
|
||||
*/
|
||||
static void spectral_shaping(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const float *scf_q, bool inv, const float *x, float *y)
|
||||
{
|
||||
/* --- Interpolate scale factors --- */
|
||||
|
||||
float scf[LC3_NUM_BANDS];
|
||||
float s0, s1 = inv ? -scf_q[0] : scf_q[0];
|
||||
|
||||
scf[0] = scf[1] = s1;
|
||||
for (int i = 0; i < 15; i++) {
|
||||
s0 = s1, s1 = inv ? -scf_q[i+1] : scf_q[i+1];
|
||||
scf[4*i+2] = s0 + 0.125 * (s1 - s0);
|
||||
scf[4*i+3] = s0 + 0.375 * (s1 - s0);
|
||||
scf[4*i+4] = s0 + 0.625 * (s1 - s0);
|
||||
scf[4*i+5] = s0 + 0.875 * (s1 - s0);
|
||||
}
|
||||
scf[62] = s1 + 0.125 * (s1 - s0);
|
||||
scf[63] = s1 + 0.375 * (s1 - s0);
|
||||
|
||||
int nb = LC3_MIN(lc3_band_lim[dt][sr][LC3_NUM_BANDS], LC3_NUM_BANDS);
|
||||
int n2 = LC3_NUM_BANDS - nb;
|
||||
|
||||
for (int i2 = 0; i2 < n2; i2++)
|
||||
scf[i2] = 0.5 * (scf[2*i2] + scf[2*i2+1]);
|
||||
|
||||
if (n2 > 0)
|
||||
memmove(scf + n2, scf + 2*n2, (nb - n2) * sizeof(float));
|
||||
|
||||
/* --- Spectral shaping --- */
|
||||
|
||||
const int *lim = lc3_band_lim[dt][sr];
|
||||
|
||||
for (int i = 0, ib = 0; ib < nb; ib++) {
|
||||
float g_sns = powf(2, -scf[ib]);
|
||||
|
||||
for ( ; i < lim[ib+1]; i++)
|
||||
y[i] = x[i] * g_sns;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Interface
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* SNS analysis
|
||||
*/
|
||||
void lc3_sns_analyze(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const float *eb, bool att, struct lc3_sns_data *data,
|
||||
const float *x, float *y)
|
||||
{
|
||||
/* Processing steps :
|
||||
* - Determine 16 scale factors from bands energy estimation
|
||||
* - Get codebooks indexes that match thoses scale factors
|
||||
* - Quantize the residual with the selected codebook
|
||||
* - The pulse configuration `c[]` is enumerated
|
||||
* - Finally shape the spectrum coefficients accordingly */
|
||||
|
||||
float scf[16], cn[4][16];
|
||||
int c[4][16];
|
||||
|
||||
compute_scale_factors(dt, sr, eb, att, scf);
|
||||
|
||||
resolve_codebooks(scf, &data->lfcb, &data->hfcb);
|
||||
|
||||
quantize(scf, data->lfcb, data->hfcb,
|
||||
c, cn, &data->shape, &data->gain);
|
||||
|
||||
unquantize(data->lfcb, data->hfcb,
|
||||
cn[data->shape], data->shape, data->gain, scf);
|
||||
|
||||
enumerate(data->shape, c[data->shape],
|
||||
&data->idx_a, &data->ls_a, &data->idx_b, &data->ls_b);
|
||||
|
||||
spectral_shaping(dt, sr, scf, false, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* SNS synthesis
|
||||
*/
|
||||
void lc3_sns_synthesize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const lc3_sns_data_t *data, const float *x, float *y)
|
||||
{
|
||||
float scf[16], cn[16];
|
||||
int c[16];
|
||||
|
||||
deenumerate(data->shape,
|
||||
data->idx_a, data->ls_a, data->idx_b, data->ls_b, c);
|
||||
|
||||
normalize(c, cn);
|
||||
|
||||
unquantize(data->lfcb, data->hfcb, cn, data->shape, data->gain, scf);
|
||||
|
||||
spectral_shaping(dt, sr, scf, true, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of bits coding the bitstream data
|
||||
*/
|
||||
int lc3_sns_get_nbits(void)
|
||||
{
|
||||
return 38;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put bitstream data
|
||||
*/
|
||||
void lc3_sns_put_data(lc3_bits_t *bits, const struct lc3_sns_data *data)
|
||||
{
|
||||
/* --- Codebooks --- */
|
||||
|
||||
lc3_put_bits(bits, data->lfcb, 5);
|
||||
lc3_put_bits(bits, data->hfcb, 5);
|
||||
|
||||
/* --- Shape, gain and vectors --- *
|
||||
* Write MSB bit of shape index, next LSB bits of shape and gain,
|
||||
* and MVPQ vectors indexes are muxed */
|
||||
|
||||
int shape_msb = data->shape >> 1;
|
||||
lc3_put_bit(bits, shape_msb);
|
||||
|
||||
if (shape_msb == 0) {
|
||||
const int size_a = 2390004;
|
||||
int submode = data->shape & 1;
|
||||
|
||||
int mux_high = submode == 0 ?
|
||||
2 * (data->idx_b + 1) + data->ls_b : data->gain & 1;
|
||||
int mux_code = mux_high * size_a + data->idx_a;
|
||||
|
||||
lc3_put_bits(bits, data->gain >> submode, 1);
|
||||
lc3_put_bits(bits, data->ls_a, 1);
|
||||
lc3_put_bits(bits, mux_code, 25);
|
||||
|
||||
} else {
|
||||
const int size_a = 15158272;
|
||||
int submode = data->shape & 1;
|
||||
|
||||
int mux_code = submode == 0 ?
|
||||
data->idx_a : size_a + 2 * data->idx_a + (data->gain & 1);
|
||||
|
||||
lc3_put_bits(bits, data->gain >> submode, 2);
|
||||
lc3_put_bits(bits, data->ls_a, 1);
|
||||
lc3_put_bits(bits, mux_code, 24);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bitstream data
|
||||
*/
|
||||
int lc3_sns_get_data(lc3_bits_t *bits, struct lc3_sns_data *data)
|
||||
{
|
||||
/* --- Codebooks --- */
|
||||
|
||||
*data = (struct lc3_sns_data){
|
||||
.lfcb = lc3_get_bits(bits, 5),
|
||||
.hfcb = lc3_get_bits(bits, 5)
|
||||
};
|
||||
|
||||
/* --- Shape, gain and vectors --- */
|
||||
|
||||
int shape_msb = lc3_get_bit(bits);
|
||||
data->gain = lc3_get_bits(bits, 1 + shape_msb);
|
||||
data->ls_a = lc3_get_bit(bits);
|
||||
|
||||
int mux_code = lc3_get_bits(bits, 25 - shape_msb);
|
||||
|
||||
if (shape_msb == 0) {
|
||||
const int size_a = 2390004;
|
||||
|
||||
if (mux_code >= size_a * 14)
|
||||
return -1;
|
||||
|
||||
data->idx_a = mux_code % size_a;
|
||||
mux_code = mux_code / size_a;
|
||||
|
||||
data->shape = (mux_code < 2);
|
||||
|
||||
if (data->shape == 0) {
|
||||
data->idx_b = (mux_code - 2) / 2;
|
||||
data->ls_b = (mux_code - 2) % 2;
|
||||
} else {
|
||||
data->gain = (data->gain << 1) + (mux_code % 2);
|
||||
}
|
||||
|
||||
} else {
|
||||
const int size_a = 15158272;
|
||||
|
||||
if (mux_code >= size_a + 1549824)
|
||||
return -1;
|
||||
|
||||
data->shape = 2 + (mux_code >= size_a);
|
||||
if (data->shape == 2) {
|
||||
data->idx_a = mux_code;
|
||||
} else {
|
||||
mux_code -= size_a;
|
||||
data->idx_a = mux_code / 2;
|
||||
data->gain = (data->gain << 1) + (mux_code % 2);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
103
src/sns.h
Normal file
103
src/sns.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Spectral Noise Shaping
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_SNS_H
|
||||
#define __LC3_SNS_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bits.h"
|
||||
|
||||
|
||||
/**
|
||||
* Bitstream data
|
||||
*/
|
||||
|
||||
typedef struct lc3_sns_data {
|
||||
int lfcb, hfcb;
|
||||
int shape, gain;
|
||||
int idx_a, idx_b;
|
||||
bool ls_a, ls_b;
|
||||
} lc3_sns_data_t;
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Encoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* SNS analysis
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* eb Energy estimation per bands, and count of bands
|
||||
* att 1: Attack detected 0: Otherwise
|
||||
* data Return bitstream data
|
||||
* x Spectral coefficients
|
||||
* y Return shapped coefficients
|
||||
*
|
||||
* `x` and `y` can be the same buffer
|
||||
*/
|
||||
void lc3_sns_analyze(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const float *eb, bool att, lc3_sns_data_t *data,
|
||||
const float *x, float *y);
|
||||
|
||||
/**
|
||||
* Return number of bits coding the bitstream data
|
||||
* return Bit consumption
|
||||
*/
|
||||
int lc3_sns_get_nbits(void);
|
||||
|
||||
/**
|
||||
* Put bitstream data
|
||||
* bits Bitstream context
|
||||
* data Bitstream data
|
||||
*/
|
||||
void lc3_sns_put_data(lc3_bits_t *bits, const lc3_sns_data_t *data);
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Decoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get bitstream data
|
||||
* bits Bitstream context
|
||||
* data Return SNS data
|
||||
* return 0: Ok -1: Invalid SNS data
|
||||
*/
|
||||
int lc3_sns_get_data(lc3_bits_t *bits, lc3_sns_data_t *data);
|
||||
|
||||
/**
|
||||
* SNS synthesis
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* data Bitstream data
|
||||
* x Spectral coefficients
|
||||
* y Return shapped coefficients
|
||||
*
|
||||
* `x` and `y` can be the same buffer
|
||||
*/
|
||||
void lc3_sns_synthesize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
const lc3_sns_data_t *data, const float *x, float *y);
|
||||
|
||||
|
||||
#endif /* __LC3_SNS_H */
|
||||
877
src/spec.c
Normal file
877
src/spec.c
Normal file
@@ -0,0 +1,877 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "spec.h"
|
||||
#include "bits.h"
|
||||
#include "tables.h"
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Global Gain / Quantization
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Resolve quantized gain index offset
|
||||
* sr, nbytes Samplerate and size of the frame
|
||||
* return Gain index offset
|
||||
*/
|
||||
static int resolve_gain_offset(enum lc3_srate sr, int nbytes)
|
||||
{
|
||||
int g_off = (nbytes * 8) / (10 * (1 + sr));
|
||||
return 105 + 5*(1 + sr) + LC3_MIN(g_off, 115);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Gain Estimation
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* x Spectral coefficients
|
||||
* nbits_budget Number of bits available coding the spectrum
|
||||
* nbits_off Offset on the available bits, temporarily smoothed
|
||||
* g_off Gain index offset
|
||||
* reset_off Return True when the nbits_off must be reset
|
||||
* return The quantized gain value
|
||||
*/
|
||||
static int estimate_gain(
|
||||
enum lc3_dt dt, enum lc3_srate sr, const float *x,
|
||||
int nbits_budget, float nbits_off, int g_off, bool *reset_off)
|
||||
{
|
||||
int ne = LC3_NE(dt, sr) >> 2;
|
||||
float e[ne];
|
||||
|
||||
/* --- Energy (dB) by 4 NDCT blocks ---
|
||||
* For the next steps, add energy offset 28/20 dB,
|
||||
* and compute the maximum magnitude */
|
||||
|
||||
float x_max = 0;
|
||||
|
||||
for (int i = 0; i < ne; i++, x += 4) {
|
||||
float x0 = fabsf(x[0]), x1 = fabsf(x[1]);
|
||||
float x2 = fabsf(x[2]), x3 = fabsf(x[3]);
|
||||
|
||||
x_max = fmaxf(x_max, x0);
|
||||
x_max = fmaxf(x_max, x1);
|
||||
x_max = fmaxf(x_max, x2);
|
||||
x_max = fmaxf(x_max, x3);
|
||||
|
||||
float s2 = x0*x0 + x1*x1 + x2*x2 + x3*x3;
|
||||
e[i] = 28.f/20 * 10 * (s2 > 0 ? log10f(s2) : -10);
|
||||
}
|
||||
|
||||
/* --- Determine gain index --- */
|
||||
|
||||
int nbits = nbits_budget + nbits_off + 0.5f;
|
||||
int g_int = 255 - g_off;
|
||||
|
||||
for (int i = 128; i > 0; i >>= 1) {
|
||||
const float *e_ptr = e + ne-1;
|
||||
float v = 0;
|
||||
|
||||
g_int -= i;
|
||||
|
||||
for ( ; e_ptr >= e && *e_ptr < g_int; e_ptr--);
|
||||
|
||||
while (e_ptr >= e) {
|
||||
float e_diff = *(e_ptr--) - g_int;
|
||||
|
||||
if (e_diff < 0) {
|
||||
v += 2.7f * 28.f/20;
|
||||
} else {
|
||||
v += e_diff + 7 * 28.f/20;
|
||||
if (e_diff > 43 * 28.f/20)
|
||||
v += e_diff - 43 * 28.f/20;
|
||||
}
|
||||
}
|
||||
|
||||
if (v > nbits * 1.4 * 28./20)
|
||||
g_int += i;
|
||||
}
|
||||
|
||||
/* --- Limit gain index --- */
|
||||
|
||||
int g_min = x_max == 0 ? -g_off :
|
||||
ceilf(28 * log10f(x_max / (32768 - 0.375f)));
|
||||
|
||||
*reset_off = g_int < g_min || x_max == 0;
|
||||
if (*reset_off)
|
||||
g_int = g_min;
|
||||
|
||||
return g_int;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Gain Adjustment
|
||||
* sr Samplerate of the frame
|
||||
* g_idx The estimated quantized gain index
|
||||
* nbits Computed number of bits coding the spectrum
|
||||
* nbits_budget Number of bits available for coding the spectrum
|
||||
* return Gain adjust value (-1 to 2)
|
||||
*/
|
||||
static int adjust_gain(enum lc3_srate sr,
|
||||
int g_idx, int nbits, int nbits_budget)
|
||||
{
|
||||
/* --- Compute delta threshold --- */
|
||||
|
||||
const int *t = (const int [LC3_NUM_SRATE][3]){
|
||||
{ 80, 500, 850 }, { 230, 1025, 1700 }, { 380, 1550, 2550 },
|
||||
{ 530, 2075, 3400 }, { 680, 2600, 4250 }
|
||||
}[sr];
|
||||
|
||||
int delta, den = 48;
|
||||
|
||||
if (nbits < t[0]) {
|
||||
delta = 3*(nbits + 48);
|
||||
|
||||
} else if (nbits < t[1]) {
|
||||
int n0 = 3*(t[0] + 48), range = t[1] - t[0];
|
||||
delta = n0 * range + (nbits - t[0]) * (t[1] - n0);
|
||||
den *= range;
|
||||
|
||||
} else {
|
||||
delta = LC3_MIN(nbits, t[2]);
|
||||
}
|
||||
|
||||
delta = (delta + den/2) / den;
|
||||
|
||||
/* --- Adjust gain --- */
|
||||
|
||||
if (nbits < nbits_budget - (delta + 2))
|
||||
return -(g_idx > 0);
|
||||
|
||||
if (nbits > nbits_budget)
|
||||
return (g_idx < 255) + (g_idx < 254 && nbits >= nbits_budget + delta);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unquantize gain
|
||||
* g_int Quantization gain value
|
||||
* return Unquantized gain value
|
||||
*/
|
||||
static float unquantize_gain(int g_int)
|
||||
{
|
||||
/* Unquantization gain table :
|
||||
* G[i] = 10 ^ (i / 28) , i = [0..64] */
|
||||
|
||||
static const float iq_table[] = {
|
||||
1.00000000e+00, 1.08571112e+00, 1.17876863e+00, 1.27980221e+00,
|
||||
1.38949549e+00, 1.50859071e+00, 1.63789371e+00, 1.77827941e+00,
|
||||
1.93069773e+00, 2.09617999e+00, 2.27584593e+00, 2.47091123e+00,
|
||||
2.68269580e+00, 2.91263265e+00, 3.16227766e+00, 3.43332002e+00,
|
||||
3.72759372e+00, 4.04708995e+00, 4.39397056e+00, 4.77058270e+00,
|
||||
5.17947468e+00, 5.62341325e+00, 6.10540230e+00, 6.62870316e+00,
|
||||
7.19685673e+00, 7.81370738e+00, 8.48342898e+00, 9.21055318e+00,
|
||||
1.00000000e+01, 1.08571112e+01, 1.17876863e+01, 1.27980221e+01,
|
||||
1.38949549e+01, 1.50859071e+01, 1.63789371e+01, 1.77827941e+01,
|
||||
1.93069773e+01, 2.09617999e+01, 2.27584593e+01, 2.47091123e+01,
|
||||
2.68269580e+01, 2.91263265e+01, 3.16227766e+01, 3.43332002e+01,
|
||||
3.72759372e+01, 4.04708995e+01, 4.39397056e+01, 4.77058270e+01,
|
||||
5.17947468e+01, 5.62341325e+01, 6.10540230e+01, 6.62870316e+01,
|
||||
7.19685673e+01, 7.81370738e+01, 8.48342898e+01, 9.21055318e+01,
|
||||
1.00000000e+02, 1.08571112e+02, 1.17876863e+02, 1.27980221e+02,
|
||||
1.38949549e+02, 1.50859071e+02, 1.63789371e+02, 1.77827941e+02,
|
||||
1.93069773e+02
|
||||
};
|
||||
|
||||
float g = iq_table[LC3_ABS(g_int) & 0x3f];
|
||||
for(int n64 = LC3_ABS(g_int) >> 6; n64--; )
|
||||
g *= iq_table[64];
|
||||
|
||||
return g_int >= 0 ? g : 1 / g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spectrum quantization
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* g_int Quantization gain value
|
||||
* x Spectral coefficients, scaled as output
|
||||
* xq, nq Output spectral quantized coefficients, and count
|
||||
*/
|
||||
static void quantize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int g_int, float *x, int16_t *xq, int *nq)
|
||||
{
|
||||
float g_inv = 1 / unquantize_gain(g_int);
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
*nq = ne;
|
||||
|
||||
for (int i = 0; i < ne; i += 2) {
|
||||
int16_t x0, x1;
|
||||
|
||||
x[i+0] *= g_inv;
|
||||
x0 = fminf(floorf(fabsf(x[i+0]) + 6.f/16), INT16_MAX);
|
||||
xq[i+0] = x[i+0] < 0 ? -x0 : x0;
|
||||
|
||||
x[i+1] *= g_inv;
|
||||
x1 = fminf(floorf(fabsf(x[i+1]) + 6.f/16), INT16_MAX);
|
||||
xq[i+1] = x[i+1] < 0 ? -x1 : x1;
|
||||
|
||||
*nq = x0 || x1 ? ne : *nq - 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spectrum quantization inverse
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* g_int Quantization gain value
|
||||
* x, nq Spectral quantized, and count of significants
|
||||
* return Unquantized gain value
|
||||
*/
|
||||
static float unquantize(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int g_int, float *x, int nq)
|
||||
{
|
||||
float g = unquantize_gain(g_int);
|
||||
int i, ne = LC3_NE(dt, sr);
|
||||
|
||||
for (i = 0; i < nq; i++)
|
||||
x[i] = x[i] * g;
|
||||
|
||||
for ( ; i < ne; i++)
|
||||
x[i] = 0;
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Spectrum coding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Resolve High-bitrate mode according size of the frame
|
||||
* sr, nbytes Samplerate and size of the frame
|
||||
* return True when High-Rate mode enabled
|
||||
*/
|
||||
static int resolve_high_rate(enum lc3_srate sr, int nbytes)
|
||||
{
|
||||
return nbytes > 20 * (1 + (int)sr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit consumption
|
||||
* dt, sr, nbytes Duration, samplerate and size of the frame
|
||||
* x Spectral quantized coefficients
|
||||
* n Count of significant coefficients, updated on truncation
|
||||
* nbits_budget Truncate to stay in budget, when not zero
|
||||
* p_lsb_mode Return True when LSB's are not AC coded, or NULL
|
||||
* return The number of bits coding the spectrum
|
||||
*/
|
||||
static int compute_nbits(
|
||||
enum lc3_dt dt, enum lc3_srate sr, int nbytes,
|
||||
const int16_t *x, int *n, int nbits_budget, bool *p_lsb_mode)
|
||||
{
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
/* --- Mode and rate --- */
|
||||
|
||||
bool lsb_mode = nbytes >= 20 * (3 + (int)sr);
|
||||
bool high_rate = resolve_high_rate(sr, nbytes);
|
||||
|
||||
/* --- Loop on quantized coefficients --- */
|
||||
|
||||
int nbits = 0, nbits_lsb = 0;
|
||||
uint8_t state = 0;
|
||||
|
||||
int nbits_end = 0;
|
||||
int n_end = 0;
|
||||
|
||||
nbits_budget = nbits_budget ? nbits_budget * 2048 : INT_MAX;
|
||||
|
||||
for (int i = 0, h = 0; h < 2; h++) {
|
||||
const uint8_t (*lut_coeff)[4] = lc3_spectrum_lookup[high_rate][h];
|
||||
|
||||
for ( ; i < LC3_MIN(*n, (ne + 2) >> (1 - h))
|
||||
&& nbits <= nbits_budget; i += 2) {
|
||||
|
||||
const uint8_t *lut = lut_coeff[state];
|
||||
int a = LC3_ABS(x[i]), b = LC3_ABS(x[i+1]);
|
||||
|
||||
/* --- Sign values --- */
|
||||
|
||||
int s = (a != 0) + (b != 0);
|
||||
nbits += s * 2048;
|
||||
|
||||
/* --- LSB values Reduce to 2*2 bits MSB values ---
|
||||
* Reduce to 2x2 bits MSB values. The LSB's pair are arithmetic
|
||||
* coded with an escape code followed by 1 bit for each values.
|
||||
* The LSB mode does not arthmetic code the first LSB,
|
||||
* add the sign of the LSB when one of pair was at value 1 */
|
||||
|
||||
int k = 0;
|
||||
int m = (a | b) >> 2;
|
||||
|
||||
if (m) {
|
||||
if (lsb_mode) {
|
||||
nbits += lc3_spectrum_bits[lut[k++]][16] - 2*2048;
|
||||
nbits_lsb += 2 + (a == 1) + (b == 1);
|
||||
}
|
||||
|
||||
for (m >>= lsb_mode; m; m >>= 1, k++)
|
||||
nbits += lc3_spectrum_bits[lut[LC3_MIN(k, 3)]][16];
|
||||
|
||||
nbits += k * 2*2048;
|
||||
a >>= k;
|
||||
b >>= k;
|
||||
|
||||
k = LC3_MIN(k, 3);
|
||||
}
|
||||
|
||||
/* --- MSB values --- */
|
||||
|
||||
nbits += lc3_spectrum_bits[lut[k]][a + 4*b];
|
||||
|
||||
/* --- Update state --- */
|
||||
|
||||
if (s && nbits <= nbits_budget) {
|
||||
n_end = i + 2;
|
||||
nbits_end = nbits;
|
||||
}
|
||||
|
||||
state = (state << 4) + (k > 1 ? 12 + k : 1 + (a + b) * (k + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Return --- */
|
||||
|
||||
*n = n_end;
|
||||
|
||||
if (p_lsb_mode)
|
||||
*p_lsb_mode = lsb_mode &&
|
||||
nbits_end + nbits_lsb * 2048 > nbits_budget;
|
||||
|
||||
if (nbits_budget >= INT_MAX)
|
||||
nbits_end += nbits_lsb * 2048;
|
||||
|
||||
return (nbits_end + 2047) / 2048;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put quantized spectrum
|
||||
* bits Bitstream context
|
||||
* dt, sr, nbytes Duration, samplerate and size of the frame
|
||||
* x Spectral quantized
|
||||
* nq, lsb_mode Count of significants, and LSB discard indication
|
||||
*/
|
||||
static void put_quantized(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, int nbytes,
|
||||
const int16_t *x, int nq, bool lsb_mode)
|
||||
{
|
||||
int ne = LC3_NE(dt, sr);
|
||||
bool high_rate = resolve_high_rate(sr, nbytes);
|
||||
|
||||
/* --- Loop on quantized coefficients --- */
|
||||
|
||||
uint8_t state = 0;
|
||||
|
||||
for (int i = 0, h = 0; h < 2; h++) {
|
||||
const uint8_t (*lut_coeff)[4] = lc3_spectrum_lookup[high_rate][h];
|
||||
|
||||
for ( ; i < LC3_MIN(nq, (ne + 2) >> (1 - h)); i += 2) {
|
||||
|
||||
const uint8_t *lut = lut_coeff[state];
|
||||
bool a_neg = x[i] < 0, b_neg = x[i+1] < 0;
|
||||
int a = LC3_ABS(x[i]), b = LC3_ABS(x[i+1]);
|
||||
|
||||
/* --- LSB values Reduce to 2*2 bits MSB values ---
|
||||
* Reduce to 2x2 bits MSB values. The LSB's pair are arithmetic
|
||||
* coded with an escape code and 1 bits for each values.
|
||||
* The LSB mode discard the first LSB (at this step) */
|
||||
|
||||
int m = (a | b) >> 2;
|
||||
int k = 0, shr = 0;
|
||||
|
||||
if (m) {
|
||||
|
||||
if (lsb_mode)
|
||||
lc3_put_symbol(bits,
|
||||
lc3_spectrum_models + lut[k++], 16);
|
||||
|
||||
for (m >>= lsb_mode; m; m >>= 1, k++) {
|
||||
lc3_put_bit(bits, (a >> k) & 1);
|
||||
lc3_put_bit(bits, (b >> k) & 1);
|
||||
lc3_put_symbol(bits,
|
||||
lc3_spectrum_models + lut[LC3_MIN(k, 3)], 16);
|
||||
}
|
||||
|
||||
a >>= lsb_mode;
|
||||
b >>= lsb_mode;
|
||||
|
||||
shr = k - lsb_mode;
|
||||
k = LC3_MIN(k, 3);
|
||||
}
|
||||
|
||||
/* --- Sign values --- */
|
||||
|
||||
if (a) lc3_put_bit(bits, a_neg);
|
||||
if (b) lc3_put_bit(bits, b_neg);
|
||||
|
||||
/* --- MSB values --- */
|
||||
|
||||
a >>= shr;
|
||||
b >>= shr;
|
||||
|
||||
lc3_put_symbol(bits, lc3_spectrum_models + lut[k], a + 4*b);
|
||||
|
||||
/* --- Update state --- */
|
||||
|
||||
state = (state << 4) + (k > 1 ? 12 + k : 1 + (a + b) * (k + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quantized spectrum
|
||||
* bits Bitstream context
|
||||
* dt, sr, nbytes Duration, samplerate and size of the frame
|
||||
* nq, lsb_mode Count of significants, and LSB discard indication
|
||||
* xq Return `nq` spectral quantized coefficients
|
||||
* nf_seed Return the noise factor seed associated
|
||||
* return 0: Ok -1: Invalid bitstream data
|
||||
*/
|
||||
static int get_quantized(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, int nbytes,
|
||||
int nq, bool lsb_mode, float *xq, uint16_t *nf_seed)
|
||||
{
|
||||
int ne = LC3_NE(dt, sr);
|
||||
bool high_rate = resolve_high_rate(sr, nbytes);
|
||||
|
||||
*nf_seed = 0;
|
||||
|
||||
/* --- Loop on quantized coefficients --- */
|
||||
|
||||
uint8_t state = 0;
|
||||
|
||||
for (int i = 0, h = 0; h < 2; h++) {
|
||||
const uint8_t (*lut_coeff)[4] = lc3_spectrum_lookup[high_rate][h];
|
||||
|
||||
for ( ; i < LC3_MIN(nq, (ne + 2) >> (1 - h)); i += 2) {
|
||||
|
||||
const uint8_t *lut = lut_coeff[state];
|
||||
|
||||
/* --- LSB values ---
|
||||
* Until the symbol read indicates the escape value 16,
|
||||
* read an LSB bit for each values.
|
||||
* The LSB mode discard the first LSB (at this step) */
|
||||
|
||||
int u = 0, v = 0;
|
||||
int k = 0, shl = 0;
|
||||
|
||||
unsigned s = lc3_get_symbol(bits, lc3_spectrum_models + lut[k]);
|
||||
|
||||
if (lsb_mode && s >= 16) {
|
||||
s = lc3_get_symbol(bits, lc3_spectrum_models + lut[++k]);
|
||||
shl++;
|
||||
}
|
||||
|
||||
for ( ; s >= 16 && shl < 14; shl++) {
|
||||
u |= lc3_get_bit(bits) << shl;
|
||||
v |= lc3_get_bit(bits) << shl;
|
||||
|
||||
k += (k < 3);
|
||||
s = lc3_get_symbol(bits, lc3_spectrum_models + lut[k]);
|
||||
}
|
||||
|
||||
if (s >= 16)
|
||||
return -1;
|
||||
|
||||
/* --- MSB & sign values --- */
|
||||
|
||||
int a = s % 4;
|
||||
int b = s / 4;
|
||||
|
||||
u |= a << shl;
|
||||
v |= b << shl;
|
||||
|
||||
xq[i ] = u && lc3_get_bit(bits) ? -u : u;
|
||||
xq[i+1] = v && lc3_get_bit(bits) ? -v : v;
|
||||
|
||||
*nf_seed = (*nf_seed + u * i + v * (i+1)) & 0xffff;
|
||||
|
||||
/* --- Update state --- */
|
||||
|
||||
state = (state << 4) + (k > 1 ? 12 + k : 1 + (a + b) * (k + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put residual bits of quantization
|
||||
* bits Bitstream context
|
||||
* nbits Maximum number of bits to output
|
||||
* xq, n Spectral quantized, and count of significants
|
||||
* xf Scaled spectral coefficients
|
||||
*/
|
||||
static void put_residual(lc3_bits_t *bits, int nbits,
|
||||
const int16_t *xq, int n, const float *xf)
|
||||
{
|
||||
for (int i = 0; i < n && nbits > 0; i++) {
|
||||
|
||||
if (xq[i] == 0)
|
||||
continue;
|
||||
|
||||
lc3_put_bit(bits, xf[i] >= xq[i]);
|
||||
nbits--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get residual bits of quantization
|
||||
* bits Bitstream context
|
||||
* nbits Maximum number of bits to output
|
||||
* x, nq Spectral quantized, and count of significants
|
||||
*/
|
||||
static void get_residual(lc3_bits_t *bits, int nbits, float *x, int nq)
|
||||
{
|
||||
for (int i = 0; i < nq && nbits > 0; i++) {
|
||||
|
||||
if (x[i] == 0)
|
||||
continue;
|
||||
|
||||
if (lc3_get_bit(bits) == 0)
|
||||
x[i] -= x[i] < 0 ? 5.f/16 : 3.f/16;
|
||||
else
|
||||
x[i] += x[i] > 0 ? 5.f/16 : 3.f/16;
|
||||
|
||||
nbits--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put LSB values of quantized spectrum values
|
||||
* bits Bitstream context
|
||||
* nbits Maximum number of bits to output
|
||||
* x, n Spectral quantized, and count of significants
|
||||
*/
|
||||
static void put_lsb(lc3_bits_t *bits, int nbits, const int16_t *x, int n)
|
||||
{
|
||||
for (int i = 0; i < n && nbits > 0; i += 2) {
|
||||
|
||||
bool a_neg = x[i] < 0, b_neg = x[i+1] < 0;
|
||||
int a = LC3_ABS(x[i]), b = LC3_ABS(x[i+1]);
|
||||
|
||||
if ((a | b) >> 2 == 0)
|
||||
continue;
|
||||
|
||||
if (nbits-- > 0)
|
||||
lc3_put_bit(bits, a & 1);
|
||||
|
||||
if (a == 1 && nbits-- > 0)
|
||||
lc3_put_bit(bits, a_neg);
|
||||
|
||||
if (nbits-- > 0)
|
||||
lc3_put_bit(bits, b & 1);
|
||||
|
||||
if (b == 1 && nbits-- > 0)
|
||||
lc3_put_bit(bits, b_neg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get LSB values of quantized spectrum values
|
||||
* bits Bitstream context
|
||||
* nbits Maximum number of bits to output
|
||||
* x, nq Spectral quantized, and count of significants
|
||||
* nf_seed Update the noise factor seed according
|
||||
*/
|
||||
static void get_lsb(lc3_bits_t *bits,
|
||||
int nbits, float *x, int nq, uint16_t *nf_seed)
|
||||
{
|
||||
for (int i = 0; i < nq && nbits > 0; i += 2) {
|
||||
|
||||
float a = fabsf(x[i]), b = fabsf(x[i+1]);
|
||||
|
||||
if (fmaxf(a, b) < 4)
|
||||
continue;
|
||||
|
||||
if (nbits-- > 0 && lc3_get_bit(bits)) {
|
||||
if (a) {
|
||||
x[i] += x[i] < 0 ? -1 : 1;
|
||||
*nf_seed = (*nf_seed + i) & 0xffff;
|
||||
} else if (nbits-- > 0) {
|
||||
x[i] = lc3_get_bit(bits) ? -1 : 1;
|
||||
*nf_seed = (*nf_seed + i) & 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
if (nbits-- > 0 && lc3_get_bit(bits)) {
|
||||
if (b) {
|
||||
x[i+1] += x[i+1] < 0 ? -1 : 1;
|
||||
*nf_seed = (*nf_seed + i+1) & 0xffff;
|
||||
} else if (nbits-- > 0) {
|
||||
x[i+1] = lc3_get_bit(bits) ? -1 : 1;
|
||||
*nf_seed = (*nf_seed + i+1) & 0xffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Noise coding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Estimate noise level
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* xq, nq Quantized spectral coefficients
|
||||
* x Quantization scaled spectrum coefficients
|
||||
* return Noise factor (0 to 7)
|
||||
*/
|
||||
static int estimate_noise(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
const int16_t *xq, int nq, const float *x)
|
||||
{
|
||||
int bw_stop = (dt == LC3_DT_7M5 ? 60 : 80) * (1 + bw);
|
||||
int w = 2 + dt;
|
||||
|
||||
float sum = 0;
|
||||
int i, n = 0, z = 0;
|
||||
|
||||
for (i = 6*(3 + dt) - w; i < LC3_MIN(nq, bw_stop); i++) {
|
||||
z = xq[i] ? 0 : z + 1;
|
||||
if (z > 2*w)
|
||||
sum += fabs(x[i - w]), n++;
|
||||
}
|
||||
|
||||
for ( ; i < bw_stop + w; i++)
|
||||
if (++z > 2*w)
|
||||
sum += fabs(x[i - w]), n++;
|
||||
|
||||
int nf = n ? 8 - (int)((16 * sum) / n + 0.5f) : 0;
|
||||
|
||||
return LC3_CLIP(nf, 0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Noise filling
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* nf, nf_seed The noise factor and pseudo-random seed
|
||||
* g Quantization gain
|
||||
* x, nq Spectral quantized, and count of significants
|
||||
*/
|
||||
static void fill_noise(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
int nf, uint16_t nf_seed, float g, float *x, int nq)
|
||||
{
|
||||
int bw_stop = (dt == LC3_DT_7M5 ? 60 : 80) * (1 + bw);
|
||||
int w = 2 + dt;
|
||||
|
||||
float s = g * (float)(8 - nf) / 16;
|
||||
int i, z = 0;
|
||||
|
||||
for (i = 6*(3 + dt) - w; i < LC3_MIN(nq, bw_stop); i++) {
|
||||
z = x[i] ? 0 : z + 1;
|
||||
if (z > 2*w) {
|
||||
nf_seed = (13849 + nf_seed*31821) & 0xffff;
|
||||
x[i - w] = nf_seed & 0x8000 ? -s : s;
|
||||
}
|
||||
}
|
||||
|
||||
for ( ; i < bw_stop + w; i++)
|
||||
if (++z > 2*w) {
|
||||
nf_seed = (13849 + nf_seed*31821) & 0xffff;
|
||||
x[i - w] = nf_seed & 0x8000 ? -s : s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put noise factor
|
||||
* bits Bitstream context
|
||||
* nf Noise factor (0 to 7)
|
||||
*/
|
||||
static void put_noise_factor(lc3_bits_t *bits, int nf)
|
||||
{
|
||||
lc3_put_bits(bits, nf, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get noise factor
|
||||
* bits Bitstream context
|
||||
* return Noise factor (0 to 7)
|
||||
*/
|
||||
static int get_noise_factor(lc3_bits_t *bits)
|
||||
{
|
||||
return lc3_get_bits(bits, 3);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Encoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Bit consumption of the number of coded coefficients
|
||||
* dt, sr Duration, samplerate of the frame
|
||||
* return Bit consumpution of the number of coded coefficients
|
||||
*/
|
||||
static int get_nbits_nq(enum lc3_dt dt, enum lc3_srate sr)
|
||||
{
|
||||
int ne = LC3_NE(dt, sr);
|
||||
return 4 + (ne > 32) + (ne > 64) + (ne > 128) + (ne > 256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit consumption of the arithmetic coder
|
||||
* dt, sr, nbytes Duration, samplerate and size of the frame
|
||||
* return Bit consumption of bitstream data
|
||||
*/
|
||||
static int get_nbits_ac(enum lc3_dt dt, enum lc3_srate sr, int nbytes)
|
||||
{
|
||||
return get_nbits_nq(dt, sr) + 3 + LC3_MIN((nbytes-1) / 160, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spectrum analysis
|
||||
*/
|
||||
void lc3_spec_analyze(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int nbytes, bool pitch, const lc3_tns_data_t *tns,
|
||||
struct lc3_spec_analysis *spec, float *x,
|
||||
int16_t *xq, struct lc3_spec_side *side)
|
||||
{
|
||||
bool reset_off;
|
||||
|
||||
/* --- Bit budget --- */
|
||||
|
||||
const int nbits_gain = 8;
|
||||
const int nbits_nf = 3;
|
||||
|
||||
int nbits_budget = 8*nbytes - get_nbits_ac(dt, sr, nbytes) -
|
||||
lc3_bwdet_get_nbits(sr) - lc3_ltpf_get_nbits(pitch) -
|
||||
lc3_sns_get_nbits() - lc3_tns_get_nbits(tns) - nbits_gain - nbits_nf;
|
||||
|
||||
/* --- Global gain --- */
|
||||
|
||||
float nbits_off = spec->nbits_off + spec->nbits_spare;
|
||||
nbits_off = fminf(fmaxf(nbits_off, -40), 40);
|
||||
nbits_off = 0.8 * spec->nbits_off + 0.2 * nbits_off;
|
||||
|
||||
int g_off = resolve_gain_offset(sr, nbytes);
|
||||
|
||||
int g_int = estimate_gain(dt, sr,
|
||||
x, nbits_budget, nbits_off, g_off, &reset_off);
|
||||
|
||||
/* --- Quantization --- */
|
||||
|
||||
quantize(dt, sr, g_int, x, xq, &side->nq);
|
||||
|
||||
int nbits = compute_nbits(dt, sr, nbytes, xq, &side->nq, 0, NULL);
|
||||
|
||||
spec->nbits_off = reset_off ? 0 : nbits_off;
|
||||
spec->nbits_spare = reset_off ? 0 : nbits_budget - nbits;
|
||||
|
||||
/* --- Adjust gain and requantize --- */
|
||||
|
||||
int g_adj = adjust_gain(sr, g_int + g_off, nbits, nbits_budget);
|
||||
|
||||
if (g_adj)
|
||||
quantize(dt, sr, g_adj, x, xq, &side->nq);
|
||||
|
||||
side->g_idx = g_int + g_adj + g_off;
|
||||
nbits = compute_nbits(dt, sr, nbytes,
|
||||
xq, &side->nq, nbits_budget, &side->lsb_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put spectral quantization side data
|
||||
*/
|
||||
void lc3_spec_put_side(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, const struct lc3_spec_side *side)
|
||||
{
|
||||
int nbits_nq = get_nbits_nq(dt, sr);
|
||||
|
||||
lc3_put_bits(bits, LC3_MAX(side->nq >> 1, 1) - 1, nbits_nq);
|
||||
lc3_put_bits(bits, side->lsb_mode, 1);
|
||||
lc3_put_bits(bits, side->g_idx, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode spectral coefficients
|
||||
*/
|
||||
void lc3_spec_encode(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, enum lc3_bandwidth bw, int nbytes,
|
||||
const int16_t *xq, const lc3_spec_side_t *side, const float *x)
|
||||
{
|
||||
bool lsb_mode = side->lsb_mode;
|
||||
int nq = side->nq;
|
||||
|
||||
put_noise_factor(bits, estimate_noise(dt, bw, xq, nq, x));
|
||||
|
||||
put_quantized(bits, dt, sr, nbytes, xq, nq, lsb_mode);
|
||||
|
||||
int nbits_left = lc3_get_bits_left(bits);
|
||||
|
||||
if (lsb_mode)
|
||||
put_lsb(bits, nbits_left, xq, nq);
|
||||
else
|
||||
put_residual(bits, nbits_left, xq, nq, x);
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Decoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get spectral quantization side data
|
||||
*/
|
||||
int lc3_spec_get_side(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, struct lc3_spec_side *side)
|
||||
{
|
||||
int nbits_nq = get_nbits_nq(dt, sr);
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
side->nq = (lc3_get_bits(bits, nbits_nq) + 1) << 1;
|
||||
side->lsb_mode = lc3_get_bit(bits);
|
||||
side->g_idx = lc3_get_bits(bits, 8);
|
||||
|
||||
return side->nq > ne ? (side->nq = ne), -1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode spectral coefficients
|
||||
*/
|
||||
int lc3_spec_decode(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, enum lc3_bandwidth bw,
|
||||
int nbytes, const lc3_spec_side_t *side, float *x)
|
||||
{
|
||||
bool lsb_mode = side->lsb_mode;
|
||||
int nq = side->nq;
|
||||
int ret = 0;
|
||||
|
||||
int nf = get_noise_factor(bits);
|
||||
uint16_t nf_seed;
|
||||
|
||||
if ((ret = get_quantized(bits, dt, sr, nbytes,
|
||||
nq, lsb_mode, x, &nf_seed)) < 0)
|
||||
return ret;
|
||||
|
||||
int nbits_left = lc3_get_bits_left(bits);
|
||||
|
||||
if (lsb_mode)
|
||||
get_lsb(bits, nbits_left, x, nq, &nf_seed);
|
||||
else
|
||||
get_residual(bits, nbits_left, x, nq);
|
||||
|
||||
int g_int = side->g_idx - resolve_gain_offset(sr, nbytes);
|
||||
float g = unquantize(dt, sr, g_int, x, nq);
|
||||
|
||||
if (nq > 2 || x[0] || x[1] || side->g_idx > 0 || nf < 7)
|
||||
fill_noise(dt, bw, nf, nf_seed, g, x, nq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
111
src/spec.h
Normal file
111
src/spec.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Spectral coefficients encoding/decoding
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_SPEC_H
|
||||
#define __LC3_SPEC_H
|
||||
|
||||
#include "common.h"
|
||||
#include "tables.h"
|
||||
#include "bwdet.h"
|
||||
#include "ltpf.h"
|
||||
#include "tns.h"
|
||||
#include "sns.h"
|
||||
|
||||
|
||||
/**
|
||||
* Spectral quantization side data
|
||||
*/
|
||||
typedef struct lc3_spec_side {
|
||||
int g_idx, nq;
|
||||
bool lsb_mode;
|
||||
} lc3_spec_side_t;
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Encoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Spectrum analysis
|
||||
* dt, sr, nbytes Duration, samplerate and size of the frame
|
||||
* pitch, tns Pitch present indication and TNS bistream data
|
||||
* spec Context of analysis
|
||||
* x Spectral coefficients, scaled as output
|
||||
* xq, side Return quantization data
|
||||
*/
|
||||
void lc3_spec_analyze(enum lc3_dt dt, enum lc3_srate sr,
|
||||
int nbytes, bool pitch, const lc3_tns_data_t *tns,
|
||||
lc3_spec_analysis_t *spec, float *x, int16_t *xq, lc3_spec_side_t *side);
|
||||
|
||||
/**
|
||||
* Put spectral quantization side data
|
||||
* bits Bitstream context
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* side Spectral quantization side data
|
||||
*/
|
||||
void lc3_spec_put_side(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, const lc3_spec_side_t *side);
|
||||
|
||||
/**
|
||||
* Encode spectral coefficients
|
||||
* bits Bitstream context
|
||||
* dt, sr, bw Duration, samplerate, bandwidth
|
||||
* nbytes and size of the frame
|
||||
* xq, side Quantization data
|
||||
* x Scaled spectral coefficients
|
||||
*/
|
||||
void lc3_spec_encode(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, enum lc3_bandwidth bw, int nbytes,
|
||||
const int16_t *xq, const lc3_spec_side_t *side, const float *x);
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Decoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get spectral quantization side data
|
||||
* bits Bitstream context
|
||||
* dt, sr Duration and samplerate of the frame
|
||||
* side Return quantization side data
|
||||
* return 0: Ok -1: Invalid bandwidth indication
|
||||
*/
|
||||
int lc3_spec_get_side(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_srate sr, lc3_spec_side_t *side);
|
||||
|
||||
/**
|
||||
* Decode spectral coefficients
|
||||
* bits Bitstream context
|
||||
* dt, sr, bw Duration, samplerate, bandwidth
|
||||
* nbytes and size of the frame
|
||||
* side Quantization side data
|
||||
* x Spectral coefficients
|
||||
* return 0: Ok -1: Invalid bitstream data
|
||||
*/
|
||||
int lc3_spec_decode(lc3_bits_t *bits, enum lc3_dt dt, enum lc3_srate sr,
|
||||
enum lc3_bandwidth bw, int nbytes, const lc3_spec_side_t *side, float *x);
|
||||
|
||||
|
||||
#endif /* __LC3_SPEC_H */
|
||||
3526
src/tables.c
Normal file
3526
src/tables.c
Normal file
File diff suppressed because it is too large
Load Diff
96
src/tables.h
Normal file
96
src/tables.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __LC3_TABLES_H
|
||||
#define __LC3_TABLES_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bits.h"
|
||||
|
||||
|
||||
/**
|
||||
* MDCT Twiddles and window coefficients
|
||||
*/
|
||||
|
||||
struct lc3_fft_bf3_twiddles { int n3; const struct lc3_complex (*t)[2]; };
|
||||
struct lc3_fft_bf2_twiddles { int n2; const struct lc3_complex *t; };
|
||||
struct lc3_mdct_rot_def { int n4; const struct lc3_complex *w; };
|
||||
|
||||
extern const struct lc3_fft_bf3_twiddles *lc3_fft_twiddles_bf3[];
|
||||
extern const struct lc3_fft_bf2_twiddles *lc3_fft_twiddles_bf2[][3];
|
||||
extern const struct lc3_mdct_rot_def *lc3_mdct_rot[LC3_NUM_DT][LC3_NUM_SRATE];
|
||||
|
||||
extern const float *lc3_mdct_win[LC3_NUM_DT][LC3_NUM_SRATE];
|
||||
|
||||
|
||||
/**
|
||||
* Limits of bands
|
||||
*/
|
||||
|
||||
#define LC3_NUM_BANDS 64
|
||||
|
||||
extern const int lc3_band_lim[LC3_NUM_DT][LC3_NUM_SRATE][LC3_NUM_BANDS+1];
|
||||
|
||||
|
||||
/**
|
||||
* SNS Quantization
|
||||
*/
|
||||
|
||||
extern const float lc3_sns_lfcb[32][8];
|
||||
extern const float lc3_sns_hfcb[32][8];
|
||||
|
||||
struct lc3_sns_vq_gains {
|
||||
int count; const float *v;
|
||||
};
|
||||
|
||||
extern const struct lc3_sns_vq_gains lc3_sns_vq_gains[4];
|
||||
|
||||
extern const int32_t lc3_sns_mpvq_offsets[][11];
|
||||
|
||||
|
||||
/**
|
||||
* TNS Arithmetic Coding
|
||||
*/
|
||||
|
||||
extern const struct lc3_ac_model lc3_tns_order_models[];
|
||||
extern const uint16_t lc3_tns_order_bits[][8];
|
||||
|
||||
extern const struct lc3_ac_model lc3_tns_coeffs_models[];
|
||||
extern const uint16_t lc3_tns_coeffs_bits[][17];
|
||||
|
||||
|
||||
/**
|
||||
* Long Term Postfilter
|
||||
*/
|
||||
|
||||
extern const float lc3_ltpf_h12k8[240];
|
||||
|
||||
extern const float *lc3_ltpf_cnum[LC3_NUM_SRATE][4];
|
||||
extern const float *lc3_ltpf_cden[LC3_NUM_SRATE][4];
|
||||
|
||||
|
||||
/**
|
||||
* Spectral Data Arithmetic Coding
|
||||
*/
|
||||
|
||||
extern const uint8_t lc3_spectrum_lookup[2][2][256][4];
|
||||
extern const struct lc3_ac_model lc3_spectrum_models[];
|
||||
extern const uint16_t lc3_spectrum_bits[][17];
|
||||
|
||||
|
||||
#endif /* __LC3_TABLES_H */
|
||||
454
src/tns.c
Normal file
454
src/tns.c
Normal file
@@ -0,0 +1,454 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "tns.h"
|
||||
#include "tables.h"
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Filter Coefficients
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Resolve LPC Weighting indication according bitrate
|
||||
* dt, nbytes Duration and size of the frame
|
||||
* return True when LPC Weighting enabled
|
||||
*/
|
||||
static bool resolve_lpc_weighting(enum lc3_dt dt, int nbytes)
|
||||
{
|
||||
return nbytes < (dt == LC3_DT_7M5 ? 360/8 : 480/8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return dot product of 2 vectors
|
||||
* a, b, n The 2 vectors of size `n`
|
||||
* return sum( a[i] * b[i] ), i = [0..n-1]
|
||||
*/
|
||||
static inline float dot(const float *a, const float *b, int n)
|
||||
{
|
||||
float v = 0;
|
||||
|
||||
while (n--)
|
||||
v += *(a++) * *(b++);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* LPC Coefficients
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* x Spectral coefficients
|
||||
* gain, a Output the prediction gains and LPC coefficients
|
||||
*/
|
||||
static void compute_lpc_coeffs(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
const float *x, float *gain, float (*a)[9])
|
||||
{
|
||||
static const int sub_7m5_nb[] = { 9, 26, 43, 60 };
|
||||
static const int sub_7m5_wb[] = { 9, 46, 83, 120 };
|
||||
static const int sub_7m5_sswb[] = { 9, 66, 123, 180 };
|
||||
static const int sub_7m5_swb[] = { 9, 46, 82, 120, 159, 200, 240 };
|
||||
static const int sub_7m5_fb[] = { 9, 56, 103, 150, 200, 250, 300 };
|
||||
|
||||
static const int sub_10m_nb[] = { 12, 34, 57, 80 };
|
||||
static const int sub_10m_wb[] = { 12, 61, 110, 160 };
|
||||
static const int sub_10m_sswb[] = { 12, 88, 164, 240 };
|
||||
static const int sub_10m_swb[] = { 12, 61, 110, 160, 213, 266, 320 };
|
||||
static const int sub_10m_fb[] = { 12, 74, 137, 200, 266, 333, 400 };
|
||||
|
||||
/* --- Normalized autocorrelation --- */
|
||||
|
||||
static const float lag_window[] = {
|
||||
1.00000000e+00, 9.98028026e-01, 9.92135406e-01, 9.82391584e-01,
|
||||
9.68910791e-01, 9.51849807e-01, 9.31404933e-01, 9.07808230e-01,
|
||||
8.81323137e-01
|
||||
};
|
||||
|
||||
const int *sub = (const int * const [LC3_NUM_DT][LC3_NUM_SRATE]){
|
||||
{ sub_7m5_nb, sub_7m5_wb, sub_7m5_sswb, sub_7m5_swb, sub_7m5_fb },
|
||||
{ sub_10m_nb, sub_10m_wb, sub_10m_sswb, sub_10m_swb, sub_10m_fb },
|
||||
}[dt][bw];
|
||||
|
||||
int nfilters = 1 + (bw >= LC3_BANDWIDTH_SWB);
|
||||
|
||||
const float *xs, *xe = x + *sub;
|
||||
float r[2][9];
|
||||
|
||||
for (int f = 0; f < nfilters; f++) {
|
||||
float c[9][3];
|
||||
|
||||
for (int s = 0; s < 3; s++) {
|
||||
xs = xe, xe = x + *(++sub);
|
||||
|
||||
for (int k = 0; k < 9; k++)
|
||||
c[k][s] = dot(xs, xs + k, (xe - xs) - k);
|
||||
}
|
||||
|
||||
float e0 = c[0][0], e1 = c[0][1], e2 = c[0][2];
|
||||
|
||||
r[f][0] = 3;
|
||||
for (int k = 1; k < 9; k++)
|
||||
r[f][k] = e0 == 0 || e1 == 0 || e2 == 0 ? 0 :
|
||||
(c[k][0]/e0 + c[k][1]/e1 + c[k][2]/e2) * lag_window[k];
|
||||
}
|
||||
|
||||
/* --- Levinson-Durbin recursion --- */
|
||||
|
||||
for (int f = 0; f < nfilters; f++) {
|
||||
float *a0 = a[f], a1[9];
|
||||
float err = r[f][0], rc;
|
||||
|
||||
gain[f] = err;
|
||||
|
||||
a0[0] = 1;
|
||||
for (int k = 1; k < 9; ) {
|
||||
|
||||
rc = -r[f][k];
|
||||
for (int i = 1; i < k; i++)
|
||||
rc -= a0[i] * r[f][k-i];
|
||||
|
||||
rc /= err;
|
||||
err *= 1 - rc * rc;
|
||||
|
||||
for (int i = 1; i < k; i++)
|
||||
a1[i] = a0[i] + rc * a0[k-i];
|
||||
a1[k++] = rc;
|
||||
|
||||
rc = -r[f][k];
|
||||
for (int i = 1; i < k; i++)
|
||||
rc -= a1[i] * r[f][k-i];
|
||||
|
||||
rc /= err;
|
||||
err *= 1 - rc * rc;
|
||||
|
||||
for (int i = 1; i < k; i++)
|
||||
a0[i] = a1[i] + rc * a1[k-i];
|
||||
a0[k++] = rc;
|
||||
}
|
||||
|
||||
gain[f] /= err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LPC Weighting
|
||||
* gain, a Prediction gain and LPC coefficients, weighted as output
|
||||
*/
|
||||
static void lpc_weighting(float pred_gain, float *a)
|
||||
{
|
||||
float gamma = 1. - (1. - 0.85) * (2. - pred_gain) / (2. - 1.5), g = 1;
|
||||
for (int i = 1; i < 9; i++)
|
||||
a[i] *= (g *= gamma);
|
||||
}
|
||||
|
||||
/**
|
||||
* LPC reflection
|
||||
* a LPC coefficients
|
||||
* rc Output refelection coefficients
|
||||
*/
|
||||
static void lpc_reflection(const float *a, float *rc)
|
||||
{
|
||||
float e, b[2][7], *b0, *b1;
|
||||
|
||||
rc[7] = a[1+7];
|
||||
e = 1 - rc[7] * rc[7];
|
||||
|
||||
b1 = b[1];
|
||||
for (int i = 0; i < 7; i++)
|
||||
b1[i] = (a[1+i] - rc[7] * a[7-i]) / e;
|
||||
|
||||
for (int k = 6; k > 0; k--) {
|
||||
b0 = b1, b1 = b[k & 1];
|
||||
|
||||
rc[k] = b0[k];
|
||||
e = 1 - rc[k] * rc[k];
|
||||
|
||||
for (int i = 0; i < k; i++)
|
||||
b1[i] = (b0[i] - rc[k] * b0[k-1-i]) / e;
|
||||
}
|
||||
|
||||
rc[0] = b1[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Quantization of RC coefficients
|
||||
* rc Refelection coefficients
|
||||
* rc_order Return order of coefficients
|
||||
* rc_i Return quantized coefficients
|
||||
*/
|
||||
static void quantize_rc(const float *rc, int *rc_order, int *rc_q)
|
||||
{
|
||||
/* Quantization table, sin(delta * (i + 0.5)), delta = Pi / 17 */
|
||||
|
||||
static float q_thr[] = {
|
||||
9.22683595e-02, 2.73662990e-01, 4.45738356e-01, 6.02634636e-01,
|
||||
7.39008917e-01, 8.50217136e-01, 9.32472229e-01, 9.82973100e-01
|
||||
};
|
||||
|
||||
*rc_order = 8;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
float rc_m = fabsf(rc[i]);
|
||||
|
||||
rc_q[i] = 4 * (rc_m >= q_thr[4]);
|
||||
for (int j = 0; j < 4 && rc_m >= q_thr[rc_q[i]]; j++, rc_q[i]++);
|
||||
|
||||
if (rc[i] < 0)
|
||||
rc_q[i] = -rc_q[i];
|
||||
|
||||
*rc_order = rc_q[i] != 0 ? 8 : *rc_order - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unquantization of RC coefficients
|
||||
* rc_q Quantized coefficients
|
||||
* rc_order Order of coefficients
|
||||
* rc Return refelection coefficients
|
||||
*/
|
||||
static void unquantize_rc(const int *rc_q, int rc_order, float rc[8])
|
||||
{
|
||||
/* Quantization table, sin(delta * i), delta = Pi / 17 */
|
||||
|
||||
static float q_inv[] = {
|
||||
0.00000000e+00, 1.83749517e-01, 3.61241664e-01, 5.26432173e-01,
|
||||
6.73695641e-01, 7.98017215e-01, 8.95163302e-01, 9.61825645e-01,
|
||||
9.95734176e-01
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < rc_order; i++) {
|
||||
float rc_m = q_inv[LC3_ABS(rc_q[i])];
|
||||
rc[i] = rc_q[i] < 0 ? -rc_m : rc_m;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Filtering
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Forward filtering
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* rc_order, rc Order of coefficients, and coefficients
|
||||
* x Spectral coefficients, filtered as output
|
||||
*/
|
||||
static void forward_filtering(
|
||||
enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
const int rc_order[2], const float rc[2][8], float *x)
|
||||
{
|
||||
int nfilters = 1 + (bw >= LC3_BANDWIDTH_SWB);
|
||||
int nf = LC3_NE(dt, bw) >> (nfilters - 1);
|
||||
int i0, ie = 3*(3 + dt);
|
||||
|
||||
float s[8] = { 0 };
|
||||
|
||||
for (int f = 0; f < nfilters; f++) {
|
||||
|
||||
i0 = ie;
|
||||
ie = nf * (1 + f);
|
||||
|
||||
if (!rc_order[f])
|
||||
continue;
|
||||
|
||||
for (int i = i0; i < ie; i++) {
|
||||
float xi = x[i];
|
||||
float s0, s1 = xi;
|
||||
|
||||
for (int k = 0; k < rc_order[f]; k++) {
|
||||
s0 = s[k];
|
||||
s[k] = s1;
|
||||
|
||||
s1 = rc[f][k] * xi + s0;
|
||||
xi += rc[f][k] * s0;
|
||||
}
|
||||
|
||||
x[i] = xi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse filtering
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* rc_order, rc Order of coefficients, and unquantized coefficients
|
||||
* x Spectral coefficients, filtered as output
|
||||
*/
|
||||
static void inverse_filtering(
|
||||
enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
const int rc_order[2], const float rc[2][8], float *x)
|
||||
{
|
||||
int nfilters = 1 + (bw >= LC3_BANDWIDTH_SWB);
|
||||
int nf = LC3_NE(dt, bw) >> (nfilters - 1);
|
||||
int i0, ie = 3*(3 + dt);
|
||||
|
||||
float s[8] = { 0 };
|
||||
|
||||
for (int f = 0; f < nfilters; f++) {
|
||||
|
||||
i0 = ie;
|
||||
ie = nf * (1 + f);
|
||||
|
||||
if (!rc_order[f])
|
||||
continue;
|
||||
|
||||
for (int i = i0; i < ie; i++) {
|
||||
float xi = x[i];
|
||||
|
||||
xi -= s[7] * rc[f][7];
|
||||
for (int k = 6; k >= 0; k--) {
|
||||
xi -= s[k] * rc[f][k];
|
||||
s[k+1] = s[k] + rc[f][k] * xi;
|
||||
}
|
||||
s[0] = xi;
|
||||
x[i] = xi;
|
||||
}
|
||||
|
||||
for (int k = 7; k >= rc_order[f]; k--)
|
||||
s[k] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Interface
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* TNS analysis
|
||||
*/
|
||||
void lc3_tns_analyze(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
bool nn_flag, int nbytes, struct lc3_tns_data *data, float *x)
|
||||
{
|
||||
/* Processing steps :
|
||||
* - Determine the LPC (Linear Predictive Coding) Coefficients
|
||||
* - Check is the filtering is disabled
|
||||
* - The coefficients are weighted on low bitrates and predicition gain
|
||||
* - Convert to reflection coefficients and quantize
|
||||
* - Finally filter the spectral coefficients */
|
||||
|
||||
float pred_gain[2], a[2][9];
|
||||
float rc[2][8];
|
||||
|
||||
data->nfilters = 1 + (bw >= LC3_BANDWIDTH_SWB);
|
||||
data->lpc_weighting = resolve_lpc_weighting(dt, nbytes);
|
||||
|
||||
compute_lpc_coeffs(dt, bw, x, pred_gain, a);
|
||||
|
||||
for (int f = 0; f < data->nfilters; f++) {
|
||||
|
||||
data->rc_order[f] = 0;
|
||||
if (nn_flag || pred_gain[f] <= 1.5)
|
||||
continue;
|
||||
|
||||
if (data->lpc_weighting && pred_gain[f] < 2)
|
||||
lpc_weighting(pred_gain[f], a[f]);
|
||||
|
||||
lpc_reflection(a[f], rc[f]);
|
||||
|
||||
quantize_rc(rc[f], &data->rc_order[f], data->rc[f]);
|
||||
unquantize_rc(data->rc[f], data->rc_order[f], rc[f]);
|
||||
}
|
||||
|
||||
forward_filtering(dt, bw, data->rc_order, rc, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* TNS synthesis
|
||||
*/
|
||||
void lc3_tns_synthesize(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
const struct lc3_tns_data *data, float *x)
|
||||
{
|
||||
float rc[2][8] = { };
|
||||
|
||||
for (int f = 0; f < data->nfilters; f++)
|
||||
if (data->rc_order[f])
|
||||
unquantize_rc(data->rc[f], data->rc_order[f], rc[f]);
|
||||
|
||||
inverse_filtering(dt, bw, data->rc_order, rc, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit consumption of bitstream data
|
||||
*/
|
||||
int lc3_tns_get_nbits(const struct lc3_tns_data *data)
|
||||
{
|
||||
int nbits = 0;
|
||||
|
||||
for (int f = 0; f < data->nfilters; f++) {
|
||||
|
||||
int nbits_2048 = 2048;
|
||||
int rc_order = data->rc_order[f];
|
||||
|
||||
nbits_2048 += rc_order > 0 ? lc3_tns_order_bits
|
||||
[data->lpc_weighting][rc_order-1] : 0;
|
||||
|
||||
for (int i = 0; i < rc_order; i++)
|
||||
nbits_2048 += lc3_tns_coeffs_bits[i][8 + data->rc[f][i]];
|
||||
|
||||
nbits += (nbits_2048 + (1 << 11) - 1) >> 11;
|
||||
}
|
||||
|
||||
return nbits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put bitstream data
|
||||
*/
|
||||
void lc3_tns_put_data(lc3_bits_t *bits, const struct lc3_tns_data *data)
|
||||
{
|
||||
for (int f = 0; f < data->nfilters; f++) {
|
||||
int rc_order = data->rc_order[f];
|
||||
|
||||
lc3_put_bits(bits, rc_order > 0, 1);
|
||||
if (rc_order <= 0)
|
||||
continue;
|
||||
|
||||
lc3_put_symbol(bits,
|
||||
lc3_tns_order_models + data->lpc_weighting, rc_order-1);
|
||||
|
||||
for (int i = 0; i < rc_order; i++)
|
||||
lc3_put_symbol(bits,
|
||||
lc3_tns_coeffs_models + i, 8 + data->rc[f][i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bitstream data
|
||||
*/
|
||||
void lc3_tns_get_data(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_bandwidth bw, int nbytes, lc3_tns_data_t *data)
|
||||
{
|
||||
data->nfilters = 1 + (bw >= LC3_BANDWIDTH_SWB);
|
||||
data->lpc_weighting = resolve_lpc_weighting(dt, nbytes);
|
||||
|
||||
for (int f = 0; f < data->nfilters; f++) {
|
||||
|
||||
data->rc_order[f] = lc3_get_bit(bits);
|
||||
if (!data->rc_order[f])
|
||||
continue;
|
||||
|
||||
data->rc_order[f] += lc3_get_symbol(bits,
|
||||
lc3_tns_order_models + data->lpc_weighting);
|
||||
|
||||
for (int i = 0; i < data->rc_order[f]; i++)
|
||||
data->rc[f][i] = (int)lc3_get_symbol(bits,
|
||||
lc3_tns_coeffs_models + i) - 8;
|
||||
}
|
||||
}
|
||||
99
src/tns.h
Normal file
99
src/tns.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* LC3 - Temporal Noise Shaping
|
||||
*
|
||||
* Reference : Low Complexity Communication Codec (LC3)
|
||||
* Bluetooth Specification v1.0
|
||||
*/
|
||||
|
||||
#ifndef __LC3_TNS_H
|
||||
#define __LC3_TNS_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bits.h"
|
||||
|
||||
|
||||
/**
|
||||
* Bitstream data
|
||||
*/
|
||||
|
||||
typedef struct lc3_tns_data {
|
||||
int nfilters;
|
||||
bool lpc_weighting;
|
||||
int rc_order[2];
|
||||
int rc[2][8];
|
||||
} lc3_tns_data_t;
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Encoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* TNS analysis
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* nn_flag True when high energy detected near Nyquist frequency
|
||||
* nbytes Size in bytes of the frame
|
||||
* data Return bitstream data
|
||||
* x Spectral coefficients, filtered as output
|
||||
*/
|
||||
void lc3_tns_analyze(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
bool nn_flag, int nbytes, lc3_tns_data_t *data, float *x);
|
||||
|
||||
/**
|
||||
* Return number of bits coding the data
|
||||
* data Bitstream data
|
||||
* return Bit consumption
|
||||
*/
|
||||
int lc3_tns_get_nbits(const lc3_tns_data_t *data);
|
||||
|
||||
/**
|
||||
* Put bitstream data
|
||||
* bits Bitstream context
|
||||
* data Bitstream data
|
||||
*/
|
||||
void lc3_tns_put_data(lc3_bits_t *bits, const lc3_tns_data_t *data);
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Decoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get bitstream data
|
||||
* bits Bitstream context
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* nbytes Size in bytes of the frame
|
||||
* data Bitstream data
|
||||
*/
|
||||
void lc3_tns_get_data(lc3_bits_t *bits,
|
||||
enum lc3_dt dt, enum lc3_bandwidth bw, int nbytes, lc3_tns_data_t *data);
|
||||
|
||||
/**
|
||||
* TNS synthesis
|
||||
* dt, bw Duration and bandwidth of the frame
|
||||
* data Bitstream data
|
||||
* x Spectral coefficients, filtered as output
|
||||
*/
|
||||
void lc3_tns_synthesize(enum lc3_dt dt, enum lc3_bandwidth bw,
|
||||
const lc3_tns_data_t *data, float *x);
|
||||
|
||||
|
||||
#endif /* __LC3_TNS_H */
|
||||
118
tables/mktables.py
Executable file
118
tables/mktables.py
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
|
||||
|
||||
def print_table(t, m=4):
|
||||
|
||||
for (i, v) in enumerate(t):
|
||||
print('{:14.8e},'.format(v), end = '\n' if i%m == m-1 else ' ')
|
||||
|
||||
if len(t) % 4:
|
||||
print('')
|
||||
|
||||
|
||||
def mdct_fft_twiddles():
|
||||
|
||||
for n in (10, 20, 30, 40, 60, 80, 90, 120, 160, 180, 240):
|
||||
|
||||
print('\n--- fft bf2 twiddles {:3d} ---'.format(n))
|
||||
|
||||
kv = -2 * np.pi * np.arange(n // 2) / n
|
||||
for (i, k) in enumerate(kv):
|
||||
print('{{ {:14.7e}, {:14.7e} }},'.format(np.cos(k), np.sin(k)),
|
||||
end = '\n' if i%2 == 1 else ' ')
|
||||
|
||||
for n in (15, 45):
|
||||
|
||||
print('\n--- fft bf3 twiddles {:3d} ---'.format(n))
|
||||
|
||||
kv = -2 * np.pi * np.arange(n) / n
|
||||
for k in kv:
|
||||
print(('{{ {{ {:14.7e}, {:14.7e} }},' +
|
||||
' {{ {:14.7e}, {:14.7e} }} }},').format(
|
||||
np.cos(k), np.sin(k), np.cos(2*k), np.sin(2*k)))
|
||||
|
||||
|
||||
def mdct_rot_twiddles():
|
||||
|
||||
for n in (120, 160, 240, 320, 360, 480, 640, 720, 960):
|
||||
|
||||
print('\n--- mdct rot twiddles {:3d} ---'.format(n))
|
||||
|
||||
kv = 2 * np.pi * (np.arange(n // 4) + 1/8) / n
|
||||
for (i, k) in enumerate(kv):
|
||||
print('{{ {:14.7e}, {:14.7e} }},'.format(np.cos(k), np.sin(k)),
|
||||
end = '\n' if i%2 == 1 else ' ')
|
||||
|
||||
|
||||
def mdct_scaling():
|
||||
|
||||
print('\n--- mdct scaling ---')
|
||||
ns = np.array([ [ 60, 120, 180, 240, 360], [ 80, 160, 240, 320, 480] ])
|
||||
print_table(np.sqrt(2 / ns[0]))
|
||||
print_table(np.sqrt(2 / ns[1]))
|
||||
|
||||
|
||||
def tns_lag_window():
|
||||
|
||||
print('\n--- tns lag window ---')
|
||||
print_table(np.exp(-0.5 * (0.02 * np.pi * np.arange(9)) ** 2))
|
||||
|
||||
|
||||
def tns_quantization_table():
|
||||
|
||||
print('\n--- tns quantization table ---')
|
||||
print_table(np.sin((np.arange(8) + 0.5) * (np.pi / 17)))
|
||||
print_table(np.sin((np.arange(8)) * (np.pi / 17)))
|
||||
|
||||
|
||||
def quant_iq_table():
|
||||
|
||||
print('\n--- quantization iq table ---')
|
||||
print_table(10 ** (np.arange(65) / 28))
|
||||
|
||||
|
||||
def sns_ge_table():
|
||||
|
||||
g_tilt_table = [ 14, 18, 22, 26, 30 ]
|
||||
|
||||
for (sr, g_tilt) in enumerate(g_tilt_table):
|
||||
print('\n--- sns ge table, sr:{} ---'.format(sr))
|
||||
print_table(10 ** ((np.arange(64) * g_tilt) / 630))
|
||||
|
||||
|
||||
def inv_table():
|
||||
|
||||
print('\n--- inv table ---')
|
||||
print_table(np.append(np.zeros(1), 1 / np.arange(1, 28)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
mdct_fft_twiddles()
|
||||
mdct_rot_twiddles()
|
||||
mdct_scaling()
|
||||
|
||||
inv_table()
|
||||
sns_ge_table()
|
||||
tns_lag_window()
|
||||
tns_quantization_table()
|
||||
quant_iq_table()
|
||||
|
||||
print('')
|
||||
4083
test/appendix_c.py
Normal file
4083
test/appendix_c.py
Normal file
File diff suppressed because it is too large
Load Diff
185
test/attdet.py
Normal file
185
test/attdet.py
Normal file
@@ -0,0 +1,185 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class AttackDetector:
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.dt = dt
|
||||
self.sr = sr
|
||||
self.ms = T.DT_MS[dt]
|
||||
|
||||
self.xn1 = 0
|
||||
self.xn2 = 0
|
||||
self.en1 = 0
|
||||
self.an1 = 0
|
||||
self.p_att = 0
|
||||
|
||||
def is_enabled(self, nbytes):
|
||||
|
||||
c1 = self.dt == T.DT_10M and \
|
||||
self.sr == T.SRATE_32K and nbytes > 80
|
||||
|
||||
c2 = self.dt == T.DT_10M and \
|
||||
self.sr >= T.SRATE_48K and nbytes >= 100
|
||||
|
||||
c3 = self.dt == T.DT_7M5 and \
|
||||
self.sr == T.SRATE_32K and nbytes >= 61 and nbytes < 150
|
||||
|
||||
c4 = self.dt == T.DT_7M5 and \
|
||||
self.sr >= T.SRATE_48K and nbytes >= 75 and nbytes < 150
|
||||
|
||||
return c1 or c2 or c3 or c4
|
||||
|
||||
def run(self, nbytes, x):
|
||||
|
||||
### 3.3.6.2 Downsampling and filtering input
|
||||
|
||||
mf = int(16 * self.ms)
|
||||
|
||||
r = len(x) // mf
|
||||
x_att = np.array([ np.sum(x[i*r:(i+1)*r]) for i in range(mf) ])
|
||||
|
||||
x_hp = np.empty(mf)
|
||||
x_hp[0 ] = 0.375 * x_att[0 ] - 0.5 * self.xn1 + 0.125 * self.xn2
|
||||
x_hp[1 ] = 0.375 * x_att[1 ] - 0.5 * x_att[0 ] + 0.125 * self.xn1
|
||||
x_hp[2:] = 0.375 * x_att[2:] - 0.5 * x_att[1:-1] + 0.125 * x_att[0:-2]
|
||||
self.xn2 = x_att[-2]
|
||||
self.xn1 = x_att[-1]
|
||||
|
||||
### 3.3.6.3 Energy calculation
|
||||
|
||||
nb = int(self.ms / 2.5)
|
||||
|
||||
e_att = np.array([ np.sum(np.square(x_hp[40*i:40*(i+1)]))
|
||||
for i in range(nb) ])
|
||||
|
||||
a_att = np.empty(nb)
|
||||
a_att[0] = np.maximum(0.25 * self.an1, self.en1)
|
||||
for i in range(1,nb):
|
||||
a_att[i] = np.maximum(0.25 * a_att[i-1], e_att[i-1])
|
||||
self.en1 = e_att[-1]
|
||||
self.an1 = a_att[-1]
|
||||
|
||||
### 3.3.6.4 Attack Detection
|
||||
|
||||
p_att = -1
|
||||
flags = [ (e_att[i] > 8.5 * a_att[i]) for i in range(nb) ]
|
||||
|
||||
for (i, f) in enumerate(flags):
|
||||
if f: p_att = i
|
||||
|
||||
f_att = p_att >= 0 or self.p_att - 1 >= nb // 2
|
||||
self.p_att = 1 + p_att
|
||||
|
||||
return self.is_enabled(nbytes) and f_att
|
||||
|
||||
|
||||
def initial_state():
|
||||
return { 'en1': 0.0, 'an1': 0.0, 'p_att': 0 }
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_enabling(rng, dt):
|
||||
|
||||
ok = True
|
||||
|
||||
for sr in range(T.SRATE_16K, T.NUM_SRATE):
|
||||
|
||||
attdet = AttackDetector(dt, sr)
|
||||
|
||||
for nbytes in [ 61, 61-1, 75-1, 75, 80, 80+1, 100-1, 100, 150-1, 150 ]:
|
||||
|
||||
f_att = lc3.attdet_run(dt, sr, nbytes,
|
||||
initial_state(), 2 * rng.random(T.NS[dt][sr]+6) - 1)
|
||||
|
||||
ok = ok and f_att == attdet.is_enabled(nbytes)
|
||||
|
||||
return ok
|
||||
|
||||
def check_unit(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
ok = True
|
||||
|
||||
attdet = AttackDetector(dt, sr)
|
||||
|
||||
state_c = initial_state()
|
||||
x_c = np.zeros(ns+6)
|
||||
|
||||
for run in range(100):
|
||||
|
||||
### Generate noise, and an attack at random point
|
||||
|
||||
x = (2 * rng.random(ns)) - 1
|
||||
x[(ns * rng.random()).astype(int)] *= 100
|
||||
|
||||
### Check Implementation
|
||||
|
||||
f_att = attdet.run(100, x)
|
||||
|
||||
x_c = np.append(x_c[-6:], x)
|
||||
f_att_c = lc3.attdet_run(dt, sr, 100, state_c, x_c)
|
||||
|
||||
ok = ok and f_att_c == f_att
|
||||
ok = ok and np.amax(np.abs(1 - state_c['en1']/attdet.en1)) < 1e-6
|
||||
ok = ok and np.amax(np.abs(1 - state_c['an1']/attdet.an1)) < 1e-6
|
||||
ok = ok and state_c['p_att'] == attdet.p_att
|
||||
|
||||
return ok
|
||||
|
||||
def check_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_48K
|
||||
|
||||
state = initial_state()
|
||||
|
||||
x = np.append(np.zeros(6), C.X_PCM_ATT[dt][0])
|
||||
f_att = lc3.attdet_run(dt, sr, C.NBYTES_ATT[dt], state, x)
|
||||
ok = f_att == C.F_ATT[dt][0]
|
||||
|
||||
x = np.append(x[-6:], C.X_PCM_ATT[dt][1])
|
||||
f_att = lc3.attdet_run(dt, sr, C.NBYTES_ATT[dt], state, x)
|
||||
ok = f_att == C.F_ATT[dt][1]
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok and check_enabling(rng, dt)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.SRATE_32K, T.NUM_SRATE):
|
||||
ok = ok and check_unit(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
62
test/attdet_py.c
Normal file
62
test/attdet_py.c
Normal file
@@ -0,0 +1,62 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <attdet.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *attdet_run_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
unsigned dt, sr, nbytes;
|
||||
PyObject *attdet_obj, *x_obj;
|
||||
struct lc3_attdet_analysis attdet = { 0 };
|
||||
float *x;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIIOO",
|
||||
&dt, &sr, &nbytes, &attdet_obj, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK(NULL, attdet_obj = to_attdet_analysis(attdet_obj, &attdet));
|
||||
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ns+6, &x));
|
||||
|
||||
int att = lc3_attdet_run(dt, sr, nbytes, &attdet, x+6);
|
||||
|
||||
from_attdet_analysis(attdet_obj, &attdet);
|
||||
return Py_BuildValue("i", att);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "attdet_run", attdet_run_py, METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_attdet_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
240
test/bitstream.py
Normal file
240
test/bitstream.py
Normal file
@@ -0,0 +1,240 @@
|
||||
#
|
||||
# Copyright 2022 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 math
|
||||
|
||||
class Bitstream:
|
||||
|
||||
def __init__(self, data):
|
||||
|
||||
self.bytes = data
|
||||
|
||||
self.bp_bw = len(data) - 1
|
||||
self.mask_bw = 1
|
||||
|
||||
self.bp = 0
|
||||
self.low = 0
|
||||
self.range = 0xffffff
|
||||
|
||||
def dump(self):
|
||||
|
||||
b = self.bytes
|
||||
|
||||
for i in range(0, len(b), 20):
|
||||
print(''.join('{:02x} '.format(x)
|
||||
for x in b[i:min(i+20, len(b))] ))
|
||||
|
||||
class BitstreamReader(Bitstream):
|
||||
|
||||
def __init__(self, data):
|
||||
|
||||
super().__init__(data)
|
||||
|
||||
self.low = ( (self.bytes[0] << 16) |
|
||||
(self.bytes[1] << 8) |
|
||||
(self.bytes[2] ) )
|
||||
self.bp = 3
|
||||
|
||||
def read_bit(self):
|
||||
|
||||
bit = bool(self.bytes[self.bp_bw] & self.mask_bw)
|
||||
|
||||
self.mask_bw <<= 1
|
||||
if self.mask_bw == 0x100:
|
||||
self.mask_bw = 1
|
||||
self.bp_bw -= 1
|
||||
|
||||
return bit
|
||||
|
||||
def read_uint(self, nbits):
|
||||
|
||||
val = 0
|
||||
for k in range(nbits):
|
||||
val |= self.read_bit() << k
|
||||
|
||||
return val
|
||||
|
||||
def ac_decode(self, cum_freqs, sym_freqs):
|
||||
|
||||
r = self.range >> 10
|
||||
if self.low >= r << 10:
|
||||
raise ValueError('Invalid ac bitstream')
|
||||
|
||||
val = len(cum_freqs) - 1
|
||||
while self.low < r * cum_freqs[val]:
|
||||
val -= 1
|
||||
|
||||
self.low -= r * cum_freqs[val]
|
||||
self.range = r * sym_freqs[val]
|
||||
while self.range < 0x10000:
|
||||
self.range <<= 8
|
||||
|
||||
self.low <<= 8
|
||||
self.low &= 0xffffff
|
||||
self.low += self.bytes[self.bp]
|
||||
self.bp += 1
|
||||
|
||||
return val
|
||||
|
||||
def get_bits_left(self):
|
||||
|
||||
nbits = 8 * len(self.bytes)
|
||||
|
||||
nbits_bw = nbits - \
|
||||
(8*self.bp_bw + 8 - int(math.log2(self.mask_bw)))
|
||||
|
||||
nbits_ac = 8 * (self.bp - 3) + \
|
||||
(25 - int(math.floor(math.log2(self.range))))
|
||||
|
||||
return nbits - (nbits_bw + nbits_ac)
|
||||
|
||||
class BitstreamWriter(Bitstream):
|
||||
|
||||
def __init__(self, nbytes):
|
||||
|
||||
super().__init__(bytearray(nbytes))
|
||||
|
||||
self.cache = -1
|
||||
self.carry = 0
|
||||
self.carry_count = 0
|
||||
|
||||
def write_bit(self, bit):
|
||||
|
||||
mask = self.mask_bw
|
||||
bp = self.bp_bw
|
||||
|
||||
if bit == 0:
|
||||
self.bytes[bp] &= ~mask
|
||||
else:
|
||||
self.bytes[bp] |= mask
|
||||
|
||||
self.mask_bw <<= 1
|
||||
if self.mask_bw == 0x100:
|
||||
self.mask_bw = 1
|
||||
self.bp_bw -= 1
|
||||
|
||||
def write_uint(self, val, nbits):
|
||||
|
||||
for k in range(nbits):
|
||||
self.write_bit(val & 1)
|
||||
val >>= 1
|
||||
|
||||
def ac_shift(self):
|
||||
|
||||
if self.low < 0xff0000 or self.carry == 1:
|
||||
|
||||
if self.cache >= 0:
|
||||
self.bytes[self.bp] = self.cache + self.carry
|
||||
self.bp += 1
|
||||
|
||||
while self.carry_count > 0:
|
||||
self.bytes[self.bp] = (self.carry + 0xff) & 0xff
|
||||
self.bp += 1
|
||||
self.carry_count -= 1
|
||||
|
||||
self.cache = self.low >> 16
|
||||
self.carry = 0
|
||||
|
||||
else:
|
||||
self.carry_count += 1
|
||||
|
||||
self.low <<= 8
|
||||
self.low &= 0xffffff
|
||||
|
||||
def ac_encode(self, cum_freq, sym_freq):
|
||||
|
||||
r = self.range >> 10
|
||||
self.low += r * cum_freq
|
||||
if (self.low >> 24) != 0:
|
||||
self.carry = 1
|
||||
|
||||
self.low &= 0xffffff
|
||||
self.range = r * sym_freq
|
||||
while self.range < 0x10000:
|
||||
self.range <<= 8;
|
||||
self.ac_shift()
|
||||
|
||||
def get_bits_left(self):
|
||||
|
||||
nbits = 8 * len(self.bytes)
|
||||
|
||||
nbits_bw = nbits - \
|
||||
(8*self.bp_bw + 8 - int(math.log2(self.mask_bw)))
|
||||
|
||||
nbits_ac = 8 * self.bp + (25 - int(math.floor(math.log2(self.range))))
|
||||
if self.cache >= 0:
|
||||
nbits_ac += 8
|
||||
if self.carry_count > 0:
|
||||
nbits_ac += 8 * self.carry_count
|
||||
|
||||
return nbits - (nbits_bw + nbits_ac)
|
||||
|
||||
def terminate(self):
|
||||
|
||||
bits = 1
|
||||
while self.range >> (24 - bits) == 0:
|
||||
bits += 1
|
||||
|
||||
mask = 0xffffff >> bits;
|
||||
val = self.low + mask;
|
||||
|
||||
over1 = val >> 24
|
||||
val &= 0x00ffffff
|
||||
high = self.low + self.range
|
||||
over2 = high >> 24
|
||||
high &= 0x00ffffff
|
||||
val = val & ~mask
|
||||
|
||||
if over1 == over2:
|
||||
|
||||
if val + mask >= high:
|
||||
bits += 1
|
||||
mask >>= 1
|
||||
val = ((self.low + mask) & 0x00ffffff) & ~mask
|
||||
|
||||
if val < self.low:
|
||||
self.carry = 1
|
||||
|
||||
self.low = val
|
||||
while bits > 0:
|
||||
self.ac_shift()
|
||||
bits -= 8
|
||||
bits += 8;
|
||||
|
||||
val = self.cache
|
||||
|
||||
if self.carry_count > 0:
|
||||
self.bytes[self.bp] = self.cache
|
||||
self.bp += 1
|
||||
|
||||
while self.carry_count > 1:
|
||||
self.bytes[self.bp] = 0xff
|
||||
self.bp += 1
|
||||
self.carry_count -= 1
|
||||
|
||||
val = 0xff >> (8 - bits)
|
||||
|
||||
mask = 0x80;
|
||||
for k in range(bits):
|
||||
|
||||
if val & mask == 0:
|
||||
self.bytes[self.bp] &= ~mask
|
||||
else:
|
||||
self.bytes[self.bp] |= mask
|
||||
|
||||
mask >>= 1
|
||||
|
||||
return self.bytes
|
||||
162
test/bwdet.py
Normal file
162
test/bwdet.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
|
||||
BW_START = [
|
||||
[ [], [ 51 ], [ 45, 58 ], [ 42, 53, 60 ], [ 40, 51, 57, 61 ] ],
|
||||
[ [], [ 53 ], [ 47, 59 ], [ 44, 54, 60 ], [ 41, 51, 57, 61 ] ]
|
||||
]
|
||||
|
||||
BW_STOP = [
|
||||
[ [], [ 63 ], [ 55, 63 ], [ 51, 58, 63 ], [ 48, 55, 60, 63 ] ],
|
||||
[ [], [ 63 ], [ 56, 63 ], [ 52, 59, 63 ], [ 49, 55, 60, 63 ] ]
|
||||
]
|
||||
|
||||
TQ = [ 20, 10, 10, 10 ]
|
||||
|
||||
TC = [ 15, 23, 20, 20 ]
|
||||
L = [ [ 4, 4, 3, 2 ], [ 4, 4, 3, 1 ] ]
|
||||
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class BandwidthDetector:
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.dt = dt
|
||||
self.sr = sr
|
||||
|
||||
def run(self, e):
|
||||
|
||||
dt = self.dt
|
||||
sr = self.sr
|
||||
|
||||
### Stage 1, determine bw0 candidate
|
||||
|
||||
bw0 = 0
|
||||
|
||||
for bw in range(sr):
|
||||
i0 = BW_START[dt][sr][bw]
|
||||
i1 = BW_STOP[dt][sr][bw]
|
||||
if np.mean(e[i0:i1+1]) >= TQ[bw]:
|
||||
bw0 = bw + 1
|
||||
|
||||
### Stage 2, Cut-off random coefficients at each steps
|
||||
|
||||
bw = bw0
|
||||
|
||||
if bw0 < sr:
|
||||
l = L[dt][bw0]
|
||||
i0 = BW_START[dt][sr][bw0] - l
|
||||
i1 = BW_START[dt][sr][bw0]
|
||||
|
||||
c = 10 * np.log10(1e-31 + e[i0-l+1:i1-l+2] / e[i0+1:i1+2])
|
||||
if np.amax(c) <= TC[bw0]:
|
||||
bw = sr
|
||||
|
||||
self.bw = bw
|
||||
return self.bw
|
||||
|
||||
def get_nbits(self):
|
||||
|
||||
return 0 if self.sr == 0 else \
|
||||
1 + np.log2(self.sr).astype(int)
|
||||
|
||||
def store_bw(self, b):
|
||||
|
||||
b.write_uint(self.bw, self.get_nbits())
|
||||
|
||||
def get_bw(self, b):
|
||||
|
||||
return b.read_uint(self.get_nbits())
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_unit(rng, dt, sr):
|
||||
|
||||
ok = True
|
||||
|
||||
bwdet = BandwidthDetector(dt, sr)
|
||||
|
||||
for bw0 in range(sr+1):
|
||||
for drop in range(10):
|
||||
|
||||
### Generate random 'high' energy and
|
||||
### scale relevant bands to select 'bw0'
|
||||
|
||||
e = 20 + 100 * rng.random(64)
|
||||
|
||||
for i in range(sr):
|
||||
if i+1 != bw0:
|
||||
i0 = BW_START[dt][sr][i]
|
||||
i1 = BW_STOP[dt][sr][i]
|
||||
e[i0:i1+1] /= (np.mean(e[i0:i1+1]) / TQ[i] + 1e-3)
|
||||
|
||||
### Stage 2 Condition,
|
||||
### cut-off random coefficients at each steps
|
||||
|
||||
if bw0 < sr:
|
||||
l = L[dt][bw0]
|
||||
i0 = BW_START[dt][sr][bw0] - l
|
||||
i1 = BW_START[dt][sr][bw0]
|
||||
|
||||
e[i0-l+1:i1+2] /= np.power(10, np.arange(2*l+1) / (1 + drop))
|
||||
|
||||
### Check with implementation
|
||||
|
||||
bw_c = lc3.bwdet_run(dt, sr, e)
|
||||
|
||||
ok = ok and bw_c == bwdet.run(e)
|
||||
|
||||
return ok
|
||||
|
||||
def check_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
E_B = C.E_B[dt]
|
||||
P_BW = C.P_BW[dt]
|
||||
|
||||
bw = lc3.bwdet_run(dt, sr, E_B[0])
|
||||
ok = ok and bw == P_BW[0]
|
||||
|
||||
bw = lc3.bwdet_run(dt, sr, E_B[1])
|
||||
ok = ok and bw == P_BW[1]
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
|
||||
ok = True
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_unit(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
55
test/bwdet_py.c
Normal file
55
test/bwdet_py.c
Normal file
@@ -0,0 +1,55 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <bwdet.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *bwdet_run_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
unsigned dt, sr;
|
||||
PyObject *e_obj;
|
||||
float *e;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIO", &dt, &sr, &e_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK("e", to_1d_ptr(e_obj, NPY_FLOAT, LC3_NUM_BANDS, &e));
|
||||
|
||||
int bw = lc3_bwdet_run(dt, sr, e);
|
||||
|
||||
return Py_BuildValue("i", bw);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "bwdet_run", bwdet_run_py, METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_bwdet_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
869
test/ctypes.h
Normal file
869
test/ctypes.h
Normal file
@@ -0,0 +1,869 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __CTYPES_H
|
||||
#define __CTYPES_H
|
||||
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
#define CTYPES_CHECK(exc, t) \
|
||||
do { \
|
||||
if (!(t)) return (exc) ? PyErr_Format(PyExc_TypeError, exc) : NULL; \
|
||||
} while(0)
|
||||
|
||||
|
||||
/**
|
||||
* From C types to Numpy Array types
|
||||
*/
|
||||
|
||||
#define to_scalar(obj, t, ptr) \
|
||||
__to_scalar(obj, t, (void *)(ptr))
|
||||
|
||||
#define to_1d_ptr(obj, t, n, ptr) \
|
||||
__to_1d_ptr(obj, t, n, (void **)(ptr))
|
||||
|
||||
#define to_2d_ptr(obj, t, n1, n2, ptr) \
|
||||
__to_2d_ptr(obj, t, n1, n2, (void **)(ptr))
|
||||
|
||||
#define to_1d_copy(obj, t, ptr, n) \
|
||||
__to_1d_copy(obj, t, ptr, n)
|
||||
|
||||
#define to_2d_copy(obj, t, ptr, n1, n2) \
|
||||
__to_2d_copy(obj, t, ptr, n1, n2)
|
||||
|
||||
|
||||
/**
|
||||
* From Numpy Array types to C types
|
||||
*/
|
||||
|
||||
#define new_scalar(obj, ptr) \
|
||||
__new_scalar(obj, ptr)
|
||||
|
||||
#define new_1d_ptr(t, n, ptr) \
|
||||
__new_1d_ptr(t, n, (void **)(ptr))
|
||||
|
||||
#define new_2d_ptr(t, n1, n2, ptr) \
|
||||
__new_2d_ptr(t, n1, n2, (void **)(ptr))
|
||||
|
||||
#define new_1d_copy(t, n, src) \
|
||||
__new_1d_copy(t, n, src)
|
||||
|
||||
#define new_2d_copy(t, n1, n2, src) \
|
||||
__new_2d_copy(t, n1, n2, src)
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__to_scalar(PyObject *obj, int t, void *ptr)
|
||||
{
|
||||
obj = obj ? PyArray_FROMANY(obj, t, 0, 0, NPY_ARRAY_FORCECAST) : obj;
|
||||
if (!obj)
|
||||
return NULL;
|
||||
|
||||
memcpy(ptr, PyArray_DATA((PyArrayObject *)obj),
|
||||
PyArray_NBYTES((PyArrayObject *)obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__to_1d_ptr(PyObject *obj, int t, int n, void **ptr)
|
||||
{
|
||||
obj = obj ? PyArray_FROMANY(obj,
|
||||
t, 1, 1, NPY_ARRAY_FORCECAST|NPY_ARRAY_CARRAY) : obj;
|
||||
if (!obj || (n && PyArray_SIZE((PyArrayObject *)obj) != n))
|
||||
return NULL;
|
||||
|
||||
*ptr = PyArray_DATA((PyArrayObject *)obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__to_2d_ptr(PyObject *obj, int t, int n1, int n2, void **ptr)
|
||||
{
|
||||
obj = obj ? PyArray_FROMANY(obj,
|
||||
t, 2, 2, NPY_ARRAY_FORCECAST|NPY_ARRAY_CARRAY) : obj;
|
||||
if (!obj || (n1 && PyArray_DIMS((PyArrayObject *)obj)[0] != n1)
|
||||
|| (n2 && PyArray_DIMS((PyArrayObject *)obj)[1] != n2))
|
||||
return NULL;
|
||||
|
||||
*ptr = PyArray_DATA((PyArrayObject *)obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__to_1d_copy(PyObject *obj, int t, void *v, int n)
|
||||
{
|
||||
void *src;
|
||||
|
||||
if ((obj = to_1d_ptr(obj, t, n, &src)))
|
||||
memcpy(v, src, PyArray_NBYTES((PyArrayObject *)obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__to_2d_copy(PyObject *obj, int t, void *v, int n1, int n2)
|
||||
{
|
||||
void *src;
|
||||
|
||||
if ((obj = to_2d_ptr(obj, t, n1, n2, &src)))
|
||||
memcpy(v, src, PyArray_NBYTES((PyArrayObject *)obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__new_scalar(int t, const void *ptr)
|
||||
{
|
||||
PyObject *obj = PyArray_SimpleNew(0, NULL, t);
|
||||
|
||||
memcpy(PyArray_DATA((PyArrayObject *)obj), ptr,
|
||||
PyArray_NBYTES((PyArrayObject *)obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__new_1d_ptr(int t, int n, void **ptr)
|
||||
{
|
||||
PyObject *obj = PyArray_SimpleNew(1, (const npy_intp []){ n }, t);
|
||||
|
||||
*ptr = PyArray_DATA((PyArrayObject *)obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__new_2d_ptr(int t, int n1, int n2, void **ptr)
|
||||
{
|
||||
PyObject *obj;
|
||||
|
||||
obj = PyArray_SimpleNew(2, ((const npy_intp []){ n1, n2 }), t);
|
||||
|
||||
*ptr = PyArray_DATA((PyArrayObject *)obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__new_1d_copy(int t, int n, const void *src)
|
||||
{
|
||||
PyObject *obj;
|
||||
void *dst;
|
||||
|
||||
if ((obj = new_1d_ptr(t, n, &dst)))
|
||||
memcpy(dst, src, PyArray_NBYTES((PyArrayObject *)obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *__new_2d_copy(int t, int n1, int n2, const void *src)
|
||||
{
|
||||
PyObject *obj;
|
||||
void *dst;
|
||||
|
||||
if ((obj = new_2d_ptr(t, n1, n2, &dst)))
|
||||
memcpy(dst, src, PyArray_NBYTES((PyArrayObject *)obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <lc3.h>
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_attdet_analysis(
|
||||
PyObject *obj, struct lc3_attdet_analysis *attdet)
|
||||
{
|
||||
CTYPES_CHECK("attdet", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("attdet.en1", to_scalar(
|
||||
PyDict_GetItemString(obj, "en1"), NPY_FLOAT, &attdet->en1));
|
||||
|
||||
CTYPES_CHECK("attdet.an1", to_scalar(
|
||||
PyDict_GetItemString(obj, "an1"), NPY_FLOAT, &attdet->an1));
|
||||
|
||||
CTYPES_CHECK("attdet.p_att", to_scalar(
|
||||
PyDict_GetItemString(obj, "p_att"), NPY_INT, &attdet->p_att));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_attdet_analysis(
|
||||
PyObject *obj, const struct lc3_attdet_analysis *attdet)
|
||||
{
|
||||
if (!obj) obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "en1",
|
||||
new_scalar(NPY_FLOAT, &attdet->en1));
|
||||
|
||||
PyDict_SetItemString(obj, "an1",
|
||||
new_scalar(NPY_FLOAT, &attdet->an1));
|
||||
|
||||
PyDict_SetItemString(obj, "p_att",
|
||||
new_scalar(NPY_INT, &attdet->p_att));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <ltpf.h>
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_ltpf_hp50_state(
|
||||
PyObject *obj, struct lc3_ltpf_hp50_state *hp50)
|
||||
{
|
||||
CTYPES_CHECK("hp50", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("hp50.s1", to_scalar(
|
||||
PyDict_GetItemString(obj, "s1"), NPY_FLOAT, &hp50->s1));
|
||||
|
||||
CTYPES_CHECK("hp50.s2", to_scalar(
|
||||
PyDict_GetItemString(obj, "s2"), NPY_FLOAT, &hp50->s2));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_ltpf_hp50_state(
|
||||
PyObject *obj, const struct lc3_ltpf_hp50_state *hp50)
|
||||
{
|
||||
PyDict_SetItemString(obj, "s1",
|
||||
new_scalar(NPY_FLOAT, &hp50->s1));
|
||||
|
||||
PyDict_SetItemString(obj, "s2",
|
||||
new_scalar(NPY_FLOAT, &hp50->s2));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_ltpf_analysis(
|
||||
PyObject *obj, struct lc3_ltpf_analysis *ltpf)
|
||||
{
|
||||
PyObject *nc_obj, *x_12k8_obj, *x_6k4_obj;
|
||||
const int n_12k8 = sizeof(ltpf->x_12k8) / sizeof(float);
|
||||
const int n_6k4 = sizeof(ltpf->x_6k4) / sizeof(float);
|
||||
|
||||
CTYPES_CHECK("ltpf", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("ltpf.active", to_scalar(
|
||||
PyDict_GetItemString(obj, "active"), NPY_BOOL, <pf->active));
|
||||
|
||||
CTYPES_CHECK("ltpf.pitch", to_scalar(
|
||||
PyDict_GetItemString(obj, "pitch"), NPY_INT, <pf->pitch));
|
||||
|
||||
CTYPES_CHECK("ltpf.nc", nc_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "nc"), NPY_FLOAT, ltpf->nc, 2));
|
||||
PyDict_SetItemString(obj, "nc", nc_obj);
|
||||
|
||||
CTYPES_CHECK(NULL, to_ltpf_hp50_state(
|
||||
PyDict_GetItemString(obj, "hp50"), <pf->hp50));
|
||||
|
||||
CTYPES_CHECK("ltpf.x_12k8", x_12k8_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "x_12k8"), NPY_FLOAT, ltpf->x_12k8, n_12k8));
|
||||
PyDict_SetItemString(obj, "x_12k8", x_12k8_obj);
|
||||
|
||||
CTYPES_CHECK("ltpf.x_6k4", x_6k4_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "x_6k4"), NPY_FLOAT, ltpf->x_6k4, n_6k4));
|
||||
PyDict_SetItemString(obj, "x_6k4", x_6k4_obj);
|
||||
|
||||
CTYPES_CHECK("ltpf.tc", to_scalar(
|
||||
PyDict_GetItemString(obj, "tc"), NPY_INT, <pf->tc));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_ltpf_analysis(
|
||||
PyObject *obj, const struct lc3_ltpf_analysis *ltpf)
|
||||
{
|
||||
const int n_12k8 = sizeof(ltpf->x_12k8) / sizeof(float);
|
||||
const int n_6k4 = sizeof(ltpf->x_6k4) / sizeof(float);
|
||||
|
||||
if (!obj) obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "active",
|
||||
new_scalar(NPY_BOOL, <pf->active));
|
||||
|
||||
PyDict_SetItemString(obj, "pitch",
|
||||
new_scalar(NPY_INT, <pf->pitch));
|
||||
|
||||
PyDict_SetItemString(obj, "nc",
|
||||
new_1d_copy(NPY_FLOAT, 2, <pf->nc));
|
||||
|
||||
PyDict_SetItemString(obj, "hp50",
|
||||
from_ltpf_hp50_state(PyDict_New(), <pf->hp50));
|
||||
|
||||
PyDict_SetItemString(obj, "x_12k8",
|
||||
new_1d_copy(NPY_FLOAT, n_12k8, <pf->x_12k8));
|
||||
|
||||
PyDict_SetItemString(obj, "x_6k4",
|
||||
new_1d_copy(NPY_FLOAT, n_6k4, <pf->x_6k4));
|
||||
|
||||
PyDict_SetItemString(obj, "tc",
|
||||
new_scalar(NPY_INT, <pf->tc));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_ltpf_synthesis(
|
||||
PyObject *obj, struct lc3_ltpf_synthesis *ltpf)
|
||||
{
|
||||
PyObject *c_obj, *x_obj;
|
||||
|
||||
CTYPES_CHECK("ltpf", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("ltpf.active", to_scalar(
|
||||
PyDict_GetItemString(obj, "active"), NPY_BOOL, <pf->active));
|
||||
|
||||
CTYPES_CHECK("ltpf.pitch", to_scalar(
|
||||
PyDict_GetItemString(obj, "pitch"), NPY_INT, <pf->pitch));
|
||||
|
||||
CTYPES_CHECK("ltpf.c", c_obj = to_2d_copy(
|
||||
PyDict_GetItemString(obj, "c"), NPY_FLOAT, ltpf->c, 12, 2));
|
||||
PyDict_SetItemString(obj, "c", c_obj);
|
||||
|
||||
CTYPES_CHECK("ltpf.x", x_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "x"), NPY_FLOAT, ltpf->x, 12));
|
||||
PyDict_SetItemString(obj, "x", x_obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_ltpf_synthesis(
|
||||
PyObject *obj, const struct lc3_ltpf_synthesis *ltpf)
|
||||
{
|
||||
if (!obj) obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "active",
|
||||
new_scalar(NPY_BOOL, <pf->active));
|
||||
|
||||
PyDict_SetItemString(obj, "pitch",
|
||||
new_scalar(NPY_INT, <pf->pitch));
|
||||
|
||||
PyDict_SetItemString(obj, "c",
|
||||
new_2d_copy(NPY_FLOAT, 12, 2, <pf->c));
|
||||
|
||||
PyDict_SetItemString(obj, "x",
|
||||
new_1d_copy(NPY_FLOAT, 12, <pf->x));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *new_ltpf_data(const struct lc3_ltpf_data *data)
|
||||
{
|
||||
PyObject *obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "active",
|
||||
new_scalar(NPY_BOOL, &data->active));
|
||||
|
||||
PyDict_SetItemString(obj, "pitch_index",
|
||||
new_scalar(NPY_INT, &data->pitch_index));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_ltpf_data(
|
||||
PyObject *obj, const struct lc3_ltpf_data *data)
|
||||
{
|
||||
PyObject *item;
|
||||
|
||||
CTYPES_CHECK("ltpf", obj && PyDict_Check(obj));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "active")))
|
||||
CTYPES_CHECK("ltpf.active",
|
||||
to_scalar(item, NPY_BOOL, &data->active));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "pitch_index")))
|
||||
CTYPES_CHECK("ltpf.pitch_index",
|
||||
to_scalar(item, NPY_INT, &data->pitch_index));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <sns.h>
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *new_sns_data(const struct lc3_sns_data *data)
|
||||
{
|
||||
PyObject *obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "lfcb",
|
||||
new_scalar(NPY_INT, &data->lfcb));
|
||||
|
||||
PyDict_SetItemString(obj, "hfcb",
|
||||
new_scalar(NPY_INT, &data->hfcb));
|
||||
|
||||
PyDict_SetItemString(obj, "shape",
|
||||
new_scalar(NPY_INT, &data->shape));
|
||||
|
||||
PyDict_SetItemString(obj, "gain",
|
||||
new_scalar(NPY_INT, &data->gain));
|
||||
|
||||
PyDict_SetItemString(obj, "idx_a",
|
||||
new_scalar(NPY_INT, &data->idx_a));
|
||||
|
||||
PyDict_SetItemString(obj, "ls_a",
|
||||
new_scalar(NPY_BOOL, &data->ls_a));
|
||||
|
||||
PyDict_SetItemString(obj, "idx_b",
|
||||
new_scalar(NPY_INT, &data->idx_b));
|
||||
|
||||
PyDict_SetItemString(obj, "ls_b",
|
||||
new_scalar(NPY_BOOL, &data->ls_b));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_sns_data(PyObject *obj, struct lc3_sns_data *data)
|
||||
{
|
||||
PyObject *item;
|
||||
|
||||
CTYPES_CHECK("sns", obj && PyDict_Check(obj));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "lfcb")))
|
||||
CTYPES_CHECK("sns.lfcb", to_scalar(item, NPY_INT, &data->lfcb));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "hfcb")))
|
||||
CTYPES_CHECK("sns.hfcb", to_scalar(item, NPY_INT, &data->hfcb));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "shape")))
|
||||
CTYPES_CHECK("sns.shape", to_scalar(item, NPY_INT, &data->shape));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "gain")))
|
||||
CTYPES_CHECK("sns.gain", to_scalar(item, NPY_INT, &data->gain));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "idx_a")))
|
||||
CTYPES_CHECK("sns.idx_a", to_scalar(item, NPY_INT, &data->idx_a));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "ls_a")))
|
||||
CTYPES_CHECK("sns.ls_a", to_scalar(item, NPY_INT, &data->ls_a));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "idx_b")))
|
||||
CTYPES_CHECK("sns.idx_b", to_scalar(item, NPY_INT, &data->idx_b));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "ls_b")))
|
||||
CTYPES_CHECK("sns.ls_b", to_scalar(item, NPY_INT, &data->ls_b));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <tns.h>
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *new_tns_data(const struct lc3_tns_data *side)
|
||||
{
|
||||
PyObject *obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "nfilters",
|
||||
new_scalar(NPY_INT, &side->nfilters));
|
||||
|
||||
PyDict_SetItemString(obj, "lpc_weighting",
|
||||
new_scalar(NPY_BOOL, &side->lpc_weighting));
|
||||
|
||||
PyDict_SetItemString(obj, "rc_order",
|
||||
new_1d_copy(NPY_INT, 2, side->rc_order));
|
||||
|
||||
PyDict_SetItemString(obj, "rc",
|
||||
new_2d_copy(NPY_INT, 2, 8, side->rc));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_tns_data(PyObject *obj, struct lc3_tns_data *side)
|
||||
{
|
||||
PyObject *item;
|
||||
|
||||
CTYPES_CHECK("tns", obj && PyDict_Check(obj));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "nfilters")))
|
||||
CTYPES_CHECK("tns.nfilters",
|
||||
to_scalar(item, NPY_INT, &side->nfilters));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "lpc_weighting"))) {
|
||||
CTYPES_CHECK("tns.lpc_weighting",
|
||||
to_scalar(item, NPY_BOOL, &side->lpc_weighting));
|
||||
}
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "rc_order"))) {
|
||||
CTYPES_CHECK("tns.rc_order",
|
||||
item = to_1d_copy(item, NPY_INT, side->rc_order, 2));
|
||||
PyDict_SetItemString(obj, "rc_order", item);
|
||||
}
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "rc"))) {
|
||||
CTYPES_CHECK("tns.rc",
|
||||
item = to_2d_copy(item, NPY_INT, side->rc, 2, 8));
|
||||
PyDict_SetItemString(obj, "rc", item);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#include <spec.h>
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_spec_analysis(
|
||||
PyObject *obj, const struct lc3_spec_analysis *spec)
|
||||
{
|
||||
if (!obj) obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "nbits_off",
|
||||
new_scalar(NPY_FLOAT, &spec->nbits_off));
|
||||
|
||||
PyDict_SetItemString(obj, "nbits_spare",
|
||||
new_scalar(NPY_INT, &spec->nbits_spare));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_spec_analysis(
|
||||
PyObject *obj, struct lc3_spec_analysis *spec)
|
||||
{
|
||||
CTYPES_CHECK("spec", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("spec.nbits_off",
|
||||
to_scalar(PyDict_GetItemString(obj, "nbits_off"),
|
||||
NPY_FLOAT, &spec->nbits_off));
|
||||
|
||||
CTYPES_CHECK("spec.nbits_spare",
|
||||
to_scalar(PyDict_GetItemString(obj, "nbits_spare"),
|
||||
NPY_INT, &spec->nbits_spare));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *new_spec_side(const struct lc3_spec_side *side)
|
||||
{
|
||||
PyObject *obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "g_idx",
|
||||
new_scalar(NPY_INT, &side->g_idx));
|
||||
|
||||
PyDict_SetItemString(obj, "nq",
|
||||
new_scalar(NPY_INT, &side->nq));
|
||||
|
||||
PyDict_SetItemString(obj, "lsb_mode",
|
||||
new_scalar(NPY_BOOL, &side->lsb_mode));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_spec_data(
|
||||
PyObject *obj, struct lc3_spec_side *side)
|
||||
{
|
||||
PyObject *item;
|
||||
|
||||
CTYPES_CHECK("side", obj && PyDict_Check(obj));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "g_idx")))
|
||||
CTYPES_CHECK("side.g_idx",
|
||||
to_scalar(item, NPY_INT, &side->g_idx));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "nq")))
|
||||
CTYPES_CHECK("side.nq",
|
||||
to_scalar(item, NPY_INT, &side->nq));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "lsb_mode")))
|
||||
CTYPES_CHECK("side.lsb_mode",
|
||||
to_scalar(item, NPY_BOOL, &side->lsb_mode));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#ifdef __CTYPES_LC3_C
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *new_side_data(const struct side_data *side)
|
||||
{
|
||||
PyObject *obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "bw",
|
||||
new_scalar(NPY_INT, &(int){ side->bw }));
|
||||
|
||||
PyDict_SetItemString(obj, "ltpf",
|
||||
new_ltpf_data(&side->ltpf));
|
||||
|
||||
PyDict_SetItemString(obj, "sns",
|
||||
new_sns_data(&side->sns));
|
||||
|
||||
PyDict_SetItemString(obj, "tns",
|
||||
new_tns_data(&side->tns));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_side_data(PyObject *obj, struct side_data *side)
|
||||
{
|
||||
PyObject *item;
|
||||
|
||||
CTYPES_CHECK("frame", obj && PyDict_Check(obj));
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "bw"))) {
|
||||
int bw;
|
||||
CTYPES_CHECK("frame.bw", to_scalar(item, NPY_INT, &bw));
|
||||
side->bw = bw;
|
||||
}
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "ltpf")))
|
||||
to_ltpf_data(item, &side->ltpf);
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "sns")))
|
||||
to_sns_data(item, &side->sns);
|
||||
|
||||
if ((item = PyDict_GetItemString(obj, "tns")))
|
||||
to_tns_data(item, &side->tns);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *new_plc_state(const struct lc3_plc_state *plc)
|
||||
{
|
||||
PyObject *obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "seed",
|
||||
new_scalar(NPY_UINT16, &plc->seed));
|
||||
|
||||
PyDict_SetItemString(obj, "count",
|
||||
new_scalar(NPY_INT, &plc->count));
|
||||
|
||||
PyDict_SetItemString(obj, "alpha",
|
||||
new_scalar(NPY_FLOAT, &plc->alpha));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_plc_state(
|
||||
PyObject *obj, struct lc3_plc_state *plc)
|
||||
{
|
||||
CTYPES_CHECK("plc", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("plc.seed", to_scalar(
|
||||
PyDict_GetItemString(obj, "seed"), NPY_UINT16, &plc->seed));
|
||||
|
||||
CTYPES_CHECK("plc.count", to_scalar(
|
||||
PyDict_GetItemString(obj, "count"), NPY_INT, &plc->count));
|
||||
|
||||
CTYPES_CHECK("plc.alpha", to_scalar(
|
||||
PyDict_GetItemString(obj, "alpha"), NPY_FLOAT, &plc->alpha));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_encoder(PyObject *obj, const struct lc3_encoder *enc)
|
||||
{
|
||||
unsigned dt = enc->dt, sr = enc->sr;
|
||||
unsigned sr_pcm = enc->sr_pcm;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
int nd = LC3_ND(dt, sr);
|
||||
|
||||
if (!obj) obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "dt",
|
||||
new_scalar(NPY_INT, &dt));
|
||||
|
||||
PyDict_SetItemString(obj, "sr",
|
||||
new_scalar(NPY_INT, &sr));
|
||||
|
||||
PyDict_SetItemString(obj, "sr_pcm",
|
||||
new_scalar(NPY_INT, &sr_pcm));
|
||||
|
||||
PyDict_SetItemString(obj, "attdet",
|
||||
from_attdet_analysis(NULL, &enc->attdet));
|
||||
|
||||
PyDict_SetItemString(obj, "ltpf",
|
||||
from_ltpf_analysis(NULL, &enc->ltpf));
|
||||
|
||||
PyDict_SetItemString(obj, "quant",
|
||||
from_spec_analysis(NULL, &enc->spec));
|
||||
|
||||
PyDict_SetItemString(obj, "xs",
|
||||
new_1d_copy(NPY_FLOAT, ns+nd, enc->xs-nd));
|
||||
|
||||
PyDict_SetItemString(obj, "xf",
|
||||
new_1d_copy(NPY_FLOAT, ns, enc->xf));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_encoder(PyObject *obj, struct lc3_encoder *enc)
|
||||
{
|
||||
unsigned dt, sr, sr_pcm;
|
||||
PyObject *xs_obj, *xf_obj;
|
||||
|
||||
CTYPES_CHECK("encoder", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("encoder.dt", to_scalar(
|
||||
PyDict_GetItemString(obj, "dt"), NPY_INT, &dt));
|
||||
CTYPES_CHECK("encoder.dt", (unsigned)(enc->dt = dt) < LC3_NUM_DT);
|
||||
|
||||
CTYPES_CHECK("encoder.sr", to_scalar(
|
||||
PyDict_GetItemString(obj, "sr"), NPY_INT, &sr));
|
||||
CTYPES_CHECK("encoder.sr", (unsigned)(enc->sr = sr) < LC3_NUM_SRATE);
|
||||
|
||||
CTYPES_CHECK("encoder.sr_pcm", to_scalar(
|
||||
PyDict_GetItemString(obj, "sr_pcm"), NPY_INT, &sr_pcm));
|
||||
CTYPES_CHECK("encoder.s_pcmr",
|
||||
(unsigned)(enc->sr_pcm = sr_pcm) < LC3_NUM_SRATE);
|
||||
|
||||
int ns = LC3_NS(dt, sr);
|
||||
int nd = LC3_ND(dt, sr);
|
||||
|
||||
CTYPES_CHECK(NULL, to_attdet_analysis(
|
||||
PyDict_GetItemString(obj, "attdet"), &enc->attdet));
|
||||
|
||||
CTYPES_CHECK(NULL, to_ltpf_analysis(
|
||||
PyDict_GetItemString(obj, "ltpf"), &enc->ltpf));
|
||||
|
||||
CTYPES_CHECK(NULL, to_spec_analysis(
|
||||
PyDict_GetItemString(obj, "quant"), &enc->spec));
|
||||
|
||||
CTYPES_CHECK("encoder.xs", xs_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "xs"), NPY_FLOAT, enc->xs-nd, ns+nd));
|
||||
PyDict_SetItemString(obj, "xs", xs_obj);
|
||||
|
||||
CTYPES_CHECK("encoder.xf", xf_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "xf"), NPY_FLOAT, enc->xf, ns));
|
||||
PyDict_SetItemString(obj, "xf", xf_obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *from_decoder(PyObject *obj, const struct lc3_decoder *dec)
|
||||
{
|
||||
unsigned dt = dec->dt, sr = dec->sr;
|
||||
unsigned sr_pcm = dec->sr_pcm;
|
||||
int ns = LC3_NS(dt, sr);
|
||||
int nd = LC3_ND(dt, sr);
|
||||
int nh = LC3_NH(sr);
|
||||
|
||||
if (!obj) obj = PyDict_New();
|
||||
|
||||
PyDict_SetItemString(obj, "dt",
|
||||
new_scalar(NPY_INT, &dt));
|
||||
|
||||
PyDict_SetItemString(obj, "sr",
|
||||
new_scalar(NPY_INT, &sr));
|
||||
|
||||
PyDict_SetItemString(obj, "sr_pcm",
|
||||
new_scalar(NPY_INT, &sr_pcm));
|
||||
|
||||
PyDict_SetItemString(obj, "ltpf",
|
||||
from_ltpf_synthesis(NULL, &dec->ltpf));
|
||||
|
||||
PyDict_SetItemString(obj, "plc",
|
||||
new_plc_state(&dec->plc));
|
||||
|
||||
PyDict_SetItemString(obj, "xs",
|
||||
new_1d_copy(NPY_FLOAT, nh+ns, dec->xs-nh));
|
||||
|
||||
PyDict_SetItemString(obj, "xd",
|
||||
new_1d_copy(NPY_FLOAT, nd, dec->xd));
|
||||
|
||||
PyDict_SetItemString(obj, "xg",
|
||||
new_1d_copy(NPY_FLOAT, ns, dec->xg));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static PyObject *to_decoder(PyObject *obj, struct lc3_decoder *dec)
|
||||
{
|
||||
unsigned dt, sr, sr_pcm;
|
||||
PyObject *xs_obj, *xd_obj, *xg_obj;
|
||||
|
||||
CTYPES_CHECK("decoder", obj && PyDict_Check(obj));
|
||||
|
||||
CTYPES_CHECK("decoder.dt", to_scalar(
|
||||
PyDict_GetItemString(obj, "dt"), NPY_INT, &dt));
|
||||
CTYPES_CHECK("decoder.dt", (unsigned)(dec->dt = dt) < LC3_NUM_DT);
|
||||
|
||||
CTYPES_CHECK("decoder.sr", to_scalar(
|
||||
PyDict_GetItemString(obj, "sr"), NPY_INT, &sr));
|
||||
CTYPES_CHECK("decoder.sr", (unsigned)(dec->sr = sr) < LC3_NUM_SRATE);
|
||||
|
||||
CTYPES_CHECK("decoder.sr_pcm", to_scalar(
|
||||
PyDict_GetItemString(obj, "sr_pcm"), NPY_INT, &sr_pcm));
|
||||
CTYPES_CHECK("decoder.sr_pcm",
|
||||
(unsigned)(dec->sr_pcm = sr_pcm) < LC3_NUM_SRATE);
|
||||
|
||||
int ns = LC3_NS(dt, sr);
|
||||
int nd = LC3_ND(dt, sr);
|
||||
int nh = LC3_NH(sr);
|
||||
|
||||
CTYPES_CHECK(NULL, to_ltpf_synthesis(
|
||||
PyDict_GetItemString(obj, "ltpf"), &dec->ltpf));
|
||||
|
||||
CTYPES_CHECK(NULL, to_plc_state(
|
||||
PyDict_GetItemString(obj, "plc"), &dec->plc));
|
||||
|
||||
CTYPES_CHECK("decoder.xs", xs_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "xs"), NPY_FLOAT, dec->xs-nh, nh+ns));
|
||||
PyDict_SetItemString(obj, "xs", xs_obj);
|
||||
|
||||
CTYPES_CHECK("decoder.xd", xd_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "xd"), NPY_FLOAT, dec->xd, nd));
|
||||
PyDict_SetItemString(obj, "xd", xd_obj);
|
||||
|
||||
CTYPES_CHECK("decoder.xg", xg_obj = to_1d_copy(
|
||||
PyDict_GetItemString(obj, "xg"), NPY_FLOAT, dec->xg, ns));
|
||||
PyDict_SetItemString(obj, "xg", xg_obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#endif /* __CTYPES_LC3_C */
|
||||
|
||||
#endif /* __CTYPES */
|
||||
200
test/decoder.py
Executable file
200
test/decoder.py
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
import scipy.signal as signal
|
||||
import scipy.io.wavfile as wavfile
|
||||
import struct
|
||||
import argparse
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
import mdct, energy, bwdet, sns, tns, spec, ltpf
|
||||
import bitstream
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Decoder:
|
||||
|
||||
def __init__(self, dt_ms, sr_hz):
|
||||
|
||||
dt = { 7.5: T.DT_7M5, 10: T.DT_10M }[dt_ms]
|
||||
|
||||
sr = { 8000: T.SRATE_8K , 16000: T.SRATE_16K, 24000: T.SRATE_24K,
|
||||
32000: T.SRATE_32K, 48000: T.SRATE_48K }[sr_hz]
|
||||
|
||||
self.sr = sr
|
||||
self.ne = T.NE[dt][sr]
|
||||
self.ns = T.NS[dt][sr]
|
||||
|
||||
self.mdct = mdct.MdctInverse(dt, sr)
|
||||
|
||||
self.bwdet = bwdet.BandwidthDetector(dt, sr)
|
||||
self.spec = spec.SpectrumSynthesis(dt, sr)
|
||||
self.tns = tns.TnsSynthesis(dt)
|
||||
self.sns = sns.SnsSynthesis(dt, sr)
|
||||
self.ltpf = ltpf.LtpfSynthesis(dt, sr)
|
||||
|
||||
def decode(self, data):
|
||||
|
||||
b = bitstream.BitstreamReader(data)
|
||||
|
||||
bw = self.bwdet.get_bw(b)
|
||||
if bw > self.sr:
|
||||
raise ValueError('Invalid bandwidth indication')
|
||||
|
||||
self.spec.load(b)
|
||||
|
||||
self.tns.load(b, bw, len(data))
|
||||
|
||||
pitch = b.read_bit()
|
||||
|
||||
self.sns.load(b)
|
||||
|
||||
if pitch:
|
||||
self.ltpf.load(b)
|
||||
else:
|
||||
self.ltpf.disable()
|
||||
|
||||
x = self.spec.decode(b, bw, len(data))
|
||||
|
||||
return (x, bw, pitch)
|
||||
|
||||
def synthesize(self, x, bw, pitch, nbytes):
|
||||
|
||||
x = self.tns.run(x, bw)
|
||||
|
||||
x = self.sns.run(x)
|
||||
|
||||
x = np.append(x, np.zeros(self.ns - self.ne))
|
||||
x = self.mdct.run(x)
|
||||
|
||||
x = self.ltpf.run(x, len(data))
|
||||
|
||||
return x
|
||||
|
||||
def run(self, data):
|
||||
|
||||
(x, bw, pitch) = self.decode(data)
|
||||
|
||||
x = self.synthesize(x, bw, pitch, len(data))
|
||||
|
||||
return x
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_appendix_c(dt):
|
||||
|
||||
ok = True
|
||||
|
||||
dec_c = lc3.setup_decoder(int(T.DT_MS[dt] * 1000), 16000)
|
||||
|
||||
for i in range(len(C.BYTES_AC[dt])):
|
||||
|
||||
pcm = lc3.decode(dec_c, bytes(C.BYTES_AC[dt][i]))
|
||||
ok = ok and np.max(np.abs(pcm - C.X_HAT_CLIP[dt][i])) < 1
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='LC3 Decoder Test Framework')
|
||||
parser.add_argument('lc3_file',
|
||||
help='Input bitstream file', type=argparse.FileType('r'))
|
||||
parser.add_argument('--pyout',
|
||||
help='Python output file', type=argparse.FileType('w'))
|
||||
parser.add_argument('--cout',
|
||||
help='C output file', type=argparse.FileType('w'))
|
||||
args = parser.parse_args()
|
||||
|
||||
### File Header ###
|
||||
|
||||
f_lc3 = open(args.lc3_file.name, 'rb')
|
||||
|
||||
header = struct.unpack('=HHHHHHHI', f_lc3.read(18))
|
||||
|
||||
if header[0] != 0xcc1c:
|
||||
raise ValueError('Invalid bitstream file')
|
||||
|
||||
if header[4] != 1:
|
||||
raise ValueError('Unsupported number of channels')
|
||||
|
||||
sr_hz = header[2] * 100
|
||||
bitrate = header[3] * 100
|
||||
nchannels = header[4]
|
||||
dt_ms = header[5] / 100
|
||||
|
||||
f_lc3.seek(header[1])
|
||||
|
||||
### Setup ###
|
||||
|
||||
dec = Decoder(dt_ms, sr_hz)
|
||||
dec_c = lc3.setup_decoder(int(dt_ms * 1000), sr_hz)
|
||||
|
||||
pcm_c = np.empty(0).astype(np.int16)
|
||||
pcm_py = np.empty(0).astype(np.int16)
|
||||
|
||||
### Decoding loop ###
|
||||
|
||||
nframes = 0
|
||||
|
||||
while True:
|
||||
|
||||
data = f_lc3.read(2)
|
||||
if len(data) != 2:
|
||||
break
|
||||
|
||||
if nframes >= 1000:
|
||||
break
|
||||
|
||||
(frame_nbytes,) = struct.unpack('=H', data)
|
||||
|
||||
print('Decoding frame %d' % nframes, end='\r')
|
||||
|
||||
data = f_lc3.read(frame_nbytes)
|
||||
|
||||
x = dec.run(data)
|
||||
pcm_py = np.append(pcm_py,
|
||||
np.clip(np.round(x), -32768, 32767).astype(np.int16))
|
||||
|
||||
x_c = lc3.decode(dec_c, data)
|
||||
pcm_c = np.append(pcm_c, x_c)
|
||||
|
||||
nframes += 1
|
||||
|
||||
print('done ! %16s' % '')
|
||||
|
||||
### Terminate ###
|
||||
|
||||
if args.pyout:
|
||||
wavfile.write(args.pyout.name, sr_hz, pcm_py)
|
||||
if args.cout:
|
||||
wavfile.write(args.cout.name, sr_hz, pcm_c)
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
213
test/encoder.py
Executable file
213
test/encoder.py
Executable file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
import scipy.signal as signal
|
||||
import scipy.io.wavfile as wavfile
|
||||
import struct
|
||||
import argparse
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
import attdet, ltpf
|
||||
import mdct, energy, bwdet, sns, tns, spec
|
||||
import bitstream
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Encoder:
|
||||
|
||||
def __init__(self, dt_ms, sr_hz):
|
||||
|
||||
dt = { 7.5: T.DT_7M5, 10: T.DT_10M }[dt_ms]
|
||||
|
||||
sr = { 8000: T.SRATE_8K , 16000: T.SRATE_16K, 24000: T.SRATE_24K,
|
||||
32000: T.SRATE_32K, 48000: T.SRATE_48K }[sr_hz]
|
||||
|
||||
self.ne = T.NE[dt][sr]
|
||||
|
||||
self.attdet = attdet.AttackDetector(dt, sr)
|
||||
self.ltpf = ltpf.Ltpf(dt, sr)
|
||||
|
||||
self.mdct = mdct.Mdct(dt, sr)
|
||||
self.energy = e_energy.EnergyBand(dt, sr)
|
||||
self.bwdet = bwdet.BandwidthDetector(dt, sr)
|
||||
self.sns = sns.SnsAnalysis(dt, sr)
|
||||
self.tns = tns.TnsAnalysis(dt)
|
||||
self.spec = spec.SpectrumEncoder(dt, sr)
|
||||
|
||||
def analyse(self, x, nbytes):
|
||||
|
||||
att = self.attdet.run(nbytes, x)
|
||||
|
||||
pitch_present = self.ltpf.run(x)
|
||||
|
||||
x = self.mdct.forward(x)[:self.ne]
|
||||
|
||||
(e, nn_flag) = self.energy.compute(x)
|
||||
if nn_flag:
|
||||
self.ltpf.disable()
|
||||
|
||||
bw = self.bwdet.run(e)
|
||||
|
||||
x = self.sns.run(e, att, x)
|
||||
|
||||
x = self.tns.run(x, bw, nn_flag, nbytes)
|
||||
|
||||
(xq, lastnz, x) = self.spec.quantize(bw, nbytes,
|
||||
self.bwdet.get_nbits(), self.ltpf.get_nbits(),
|
||||
self.sns.get_nbits(), self.tns.get_nbits(), x)
|
||||
|
||||
return pitch_present
|
||||
|
||||
def encode(self, pitch_present, nbytes):
|
||||
|
||||
b = bitstream.BitstreamWriter(nbytes)
|
||||
|
||||
self.bwdet.store(b)
|
||||
|
||||
self.spec.store(b)
|
||||
|
||||
self.tns.store(b)
|
||||
|
||||
b.write_bit(pitch_present)
|
||||
|
||||
self.sns.store(b)
|
||||
|
||||
if pitch_present:
|
||||
self.ltpf.store_data(b)
|
||||
|
||||
self.spec.encode(b)
|
||||
|
||||
return b.terminate()
|
||||
|
||||
def run(self, x, nbytes):
|
||||
|
||||
pitch_present = self.analyse(x, nbytes)
|
||||
|
||||
data = self.encode(pitch_present, nbytes)
|
||||
|
||||
return data
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_appendix_c(dt):
|
||||
|
||||
ok = True
|
||||
|
||||
enc_c = lc3.setup_encoder(int(T.DT_MS[dt] * 1000), 16000)
|
||||
|
||||
for i in range(len(C.X_PCM[dt])):
|
||||
|
||||
data = lc3.encode(enc_c, C.X_PCM[dt][i], C.NBYTES[dt])
|
||||
ok = ok and data == C.BYTES_AC[dt][i]
|
||||
if not ok:
|
||||
dump(data)
|
||||
dump(C.BYTES_AC[dt][i])
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def dump(data):
|
||||
for i in range(0, len(data), 20):
|
||||
print(''.join('{:02x} '.format(x)
|
||||
for x in data[i:min(i+20, len(data))] ))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='LC3 Encoder Test Framework')
|
||||
parser.add_argument('wav_file',
|
||||
help='Input wave file', type=argparse.FileType('r'))
|
||||
parser.add_argument('--bitrate',
|
||||
help='Bitrate in bps', type=int, required=True)
|
||||
parser.add_argument('--dt',
|
||||
help='Frame duration in ms', type=float, default=10)
|
||||
parser.add_argument('--pyout',
|
||||
help='Python output file', type=argparse.FileType('w'))
|
||||
parser.add_argument('--cout',
|
||||
help='C output file', type=argparse.FileType('w'))
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.bitrate < 16000 or args.bitrate > 320000:
|
||||
raise ValueError('Invalid bitate %d bps' % args.bitrate)
|
||||
|
||||
if args.dt not in (7.5, 10):
|
||||
raise ValueError('Invalid frame duration %.1f ms' % args.dt)
|
||||
|
||||
(sr_hz, pcm) = wavfile.read(args.wav_file.name)
|
||||
if sr_hz not in (8000, 16000, 24000, 320000, 48000):
|
||||
raise ValueError('Unsupported input samplerate: %d' % sr_hz)
|
||||
|
||||
### Setup ###
|
||||
|
||||
enc = Encoder(args.dt, sr_hz)
|
||||
enc_c = lc3.setup_encoder(int(args.dt * 1000), sr_hz)
|
||||
|
||||
frame_samples = int((args.dt * sr_hz) / 1000)
|
||||
frame_nbytes = int((args.bitrate * args.dt) / (1000 * 8))
|
||||
|
||||
### File Header ###
|
||||
|
||||
f_py = open(args.pyout.name, 'wb') if args.pyout else None
|
||||
f_c = open(args.cout.name , 'wb') if args.cout else None
|
||||
|
||||
header = struct.pack('=HHHHHHHI', 0xcc1c, 18,
|
||||
sr_hz // 100, args.bitrate // 100, 1, int(args.dt * 100), 0, len(pcm))
|
||||
|
||||
for f in (f_py, f_c):
|
||||
if f: f.write(header)
|
||||
|
||||
### Encoding loop ###
|
||||
|
||||
if len(pcm) % frame_samples > 0:
|
||||
pcm = np.append(pcm, np.zeros(frame_samples - (len(pcm) % frame_samples)))
|
||||
|
||||
for i in range(0, len(pcm), frame_samples):
|
||||
|
||||
print('Encoding frame %d' % (i // frame_samples), end='\r')
|
||||
|
||||
frame_pcm = pcm[i:i+frame_samples]
|
||||
|
||||
data = enc.run(frame_pcm, frame_nbytes)
|
||||
data_c = lc3.encode(enc_c, frame_pcm, frame_nbytes)
|
||||
|
||||
for f in (f_py, f_c):
|
||||
if f: f.write(struct.pack('=H', frame_nbytes))
|
||||
|
||||
if f_py: f_py.write(data)
|
||||
if f_c: f_c.write(data_c)
|
||||
|
||||
print('done ! %16s' % '')
|
||||
|
||||
### Terminate ###
|
||||
|
||||
for f in (f_py, f_c):
|
||||
if f: f.close()
|
||||
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
92
test/energy.py
Normal file
92
test/energy.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class EnergyBand:
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.dt = dt
|
||||
self.I = T.I[dt][sr]
|
||||
|
||||
def compute(self, x):
|
||||
|
||||
e = [ np.mean(np.square(x[self.I[i]:self.I[i+1]]))
|
||||
for i in range(len(self.I)-1) ]
|
||||
|
||||
e_lo = np.sum(e[:len(e) - [4, 2][self.dt]])
|
||||
e_hi = np.sum(e[len(e) - [4, 2][self.dt]:])
|
||||
|
||||
return np.append(e, np.zeros(64-len(e))), (e_hi > 30*e_lo)
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_unit(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
ok = True
|
||||
|
||||
nrg = EnergyBand(dt, sr)
|
||||
|
||||
x = (2 * rng.random(T.NS[dt][sr])) - 1
|
||||
|
||||
(e , nn ) = nrg.compute(x)
|
||||
(e_c, nn_c) = lc3.energy_compute(dt, sr, x)
|
||||
ok = ok and np.amax(np.abs(e_c - e)) < 1e-5 and nn_c == nn
|
||||
|
||||
x[15*ns//16:] *= 1e2;
|
||||
|
||||
(e , nn ) = nrg.compute(x)
|
||||
(e_c, nn_c) = lc3.energy_compute(dt, sr, x)
|
||||
ok = ok and np.amax(np.abs(e_c - e)) < 1e-3 and nn_c == nn
|
||||
|
||||
return ok
|
||||
|
||||
def check_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
e = lc3.energy_compute(dt, sr, C.X[dt][0])[0]
|
||||
ok = ok and np.amax(np.abs(1 - e/C.E_B[dt][0])) < 1e-6
|
||||
|
||||
e = lc3.energy_compute(dt, sr, C.X[dt][1])[0]
|
||||
ok = ok and np.amax(np.abs(1 - e/C.E_B[dt][1])) < 1e-6
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_unit(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
62
test/energy_py.c
Normal file
62
test/energy_py.c
Normal file
@@ -0,0 +1,62 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "lc3.h"
|
||||
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <energy.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *energy_compute_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
unsigned dt, sr;
|
||||
PyObject *x_obj, *e_obj;
|
||||
float *x, *e;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIO", &dt, &sr, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ns = LC3_NS(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, ns, &x));
|
||||
e_obj = new_1d_ptr(NPY_FLOAT, LC3_NUM_BANDS, &e);
|
||||
|
||||
int nn_flag = lc3_energy_compute(dt, sr, x, e);
|
||||
|
||||
return Py_BuildValue("Ni", e_obj, nn_flag);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "energy_compute", energy_compute_py, METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_energy_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
142
test/lc3_py.c
Normal file
142
test/lc3_py.c
Normal file
@@ -0,0 +1,142 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "lc3.h"
|
||||
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <lc3.c>
|
||||
|
||||
#define __CTYPES_LC3_C
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *setup_encoder_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
int dt_us, sr_hz;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ii", &dt_us, &sr_hz))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt_us", LC3_CHECK_DT_US(dt_us));
|
||||
CTYPES_CHECK("sr_hz", LC3_CHECK_SR_HZ(sr_hz));
|
||||
|
||||
lc3_encoder_t encoder = lc3_setup_encoder(dt_us, sr_hz, 0,
|
||||
malloc(lc3_encoder_size(dt_us, sr_hz)));
|
||||
|
||||
PyObject *encoder_obj = from_encoder(NULL, encoder);
|
||||
|
||||
free(encoder);
|
||||
|
||||
return Py_BuildValue("N", encoder_obj);
|
||||
}
|
||||
|
||||
static PyObject *encode_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *encoder_obj, *pcm_obj;
|
||||
int nbytes;
|
||||
int16_t *pcm;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "OOi", &encoder_obj, &pcm_obj, &nbytes))
|
||||
return NULL;
|
||||
|
||||
lc3_encoder_t encoder =
|
||||
lc3_setup_encoder(10000, 48000, 0, &(lc3_encoder_mem_48k_t){ });
|
||||
|
||||
CTYPES_CHECK(NULL, encoder_obj = to_encoder(encoder_obj, encoder));
|
||||
|
||||
int ns = LC3_NS(encoder->dt, encoder->sr);
|
||||
|
||||
CTYPES_CHECK("x", pcm_obj = to_1d_ptr(pcm_obj, NPY_INT16, ns, &pcm));
|
||||
CTYPES_CHECK("nbytes", nbytes >= 20 && nbytes <= 400);
|
||||
|
||||
uint8_t out[nbytes];
|
||||
|
||||
lc3_encode(encoder, LC3_PCM_FORMAT_S16, pcm, 1, nbytes, out);
|
||||
|
||||
from_encoder(encoder_obj, encoder);
|
||||
|
||||
return Py_BuildValue("N",
|
||||
PyBytes_FromStringAndSize((const char *)out, nbytes));
|
||||
}
|
||||
|
||||
static PyObject *setup_decoder_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
int dt_us, sr_hz;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ii", &dt_us, &sr_hz))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt_us", LC3_CHECK_DT_US(dt_us));
|
||||
CTYPES_CHECK("sr_hz", LC3_CHECK_SR_HZ(sr_hz));
|
||||
|
||||
lc3_decoder_t decoder = lc3_setup_decoder(dt_us, sr_hz, 0,
|
||||
malloc(lc3_decoder_size(dt_us, sr_hz)));
|
||||
|
||||
PyObject *decoder_obj = from_decoder(NULL, decoder);
|
||||
|
||||
free(decoder);
|
||||
|
||||
return Py_BuildValue("N", decoder_obj);
|
||||
}
|
||||
|
||||
static PyObject *decode_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *decoder_obj, *pcm_obj, *in_obj;
|
||||
int16_t *pcm;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "OO", &decoder_obj, &in_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("in", in_obj == Py_None || PyBytes_Check(in_obj));
|
||||
|
||||
char *in = in_obj == Py_None ? NULL : PyBytes_AsString(in_obj);
|
||||
int nbytes = in_obj == Py_None ? 0 : PyBytes_Size(in_obj);
|
||||
|
||||
lc3_decoder_t decoder =
|
||||
lc3_setup_decoder(10000, 48000, 0, &(lc3_decoder_mem_48k_t){ });
|
||||
|
||||
CTYPES_CHECK(NULL, decoder_obj = to_decoder(decoder_obj, decoder));
|
||||
|
||||
int ns = LC3_NS(decoder->dt, decoder->sr);
|
||||
pcm_obj = new_1d_ptr(NPY_INT16, ns, &pcm);
|
||||
|
||||
lc3_decode(decoder, in, nbytes, LC3_PCM_FORMAT_S16, pcm, 1);
|
||||
|
||||
from_decoder(decoder_obj, decoder);
|
||||
|
||||
return Py_BuildValue("N", pcm_obj);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "setup_encoder" , setup_encoder_py , METH_VARARGS },
|
||||
{ "encode" , encode_py , METH_VARARGS },
|
||||
{ "setup_decoder" , setup_decoder_py , METH_VARARGS },
|
||||
{ "decode" , decode_py , METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_interface_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
660
test/ltpf.py
Normal file
660
test/ltpf.py
Normal file
@@ -0,0 +1,660 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
import scipy.signal as signal
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Resampler_12k8:
|
||||
|
||||
def __init__(self, dt, sr, history = 0):
|
||||
|
||||
self.sr = sr
|
||||
self.p = 192 // T.SRATE_KHZ[sr]
|
||||
self.w = 240 // self.p
|
||||
|
||||
self.n = ((T.DT_MS[dt] * 128) / 10).astype(int)
|
||||
self.d = [ 44, 24 ][dt]
|
||||
|
||||
self.x = np.zeros(self.w + T.NS[dt][sr])
|
||||
self.u = np.zeros(self.n + 2)
|
||||
self.y = np.zeros(self.n + self.d + history)
|
||||
|
||||
def resample(self, x):
|
||||
|
||||
p = self.p
|
||||
w = self.w
|
||||
d = self.d
|
||||
n = self.n
|
||||
|
||||
### Sliding window
|
||||
|
||||
self.x[:w] = self.x[-w:]
|
||||
self.x[w:] = x
|
||||
self.u[:2] = self.u[-2:]
|
||||
|
||||
if len(self.y) > 2*n + d:
|
||||
self.y[n+d:-n] = self.y[d+2*n:]
|
||||
if len(self.y) > n + d:
|
||||
self.y[-n:] = self.y[:n]
|
||||
self.y[:d] = self.y[n:d+n]
|
||||
|
||||
x = self.x
|
||||
u = self.u
|
||||
|
||||
### 3.3.9.3 Resampling
|
||||
|
||||
h = np.zeros(240 + p)
|
||||
h[-119:] = T.LTPF_H12K8[:119]
|
||||
h[ :120] = T.LTPF_H12K8[119:]
|
||||
|
||||
for i in range(n):
|
||||
e = (15 * i) // p
|
||||
f = (15 * i) % p
|
||||
k = np.arange(-120, 120 + p, p) - f
|
||||
u[2+i] = p * np.dot( x[e:e+w+1], np.take(h, k) )
|
||||
|
||||
if self.sr == T.SRATE_8K:
|
||||
u = 0.5 * u
|
||||
|
||||
### 3.3.9.4 High-pass filtering
|
||||
|
||||
b = [ 0.9827947082978771, -1.9655894165957540, 0.9827947082978771 ]
|
||||
a = [ 1 , -1.9652933726226904, 0.9658854605688177 ]
|
||||
|
||||
self.y[d:d+n] = b[0] * u[2:] + b[1] * u[1:-1] + b[2] * u[:-2]
|
||||
for i in range(n):
|
||||
self.y[d+i] -= a[1] * self.y[d+i-1] + a[2] * self.y[d+i-2]
|
||||
|
||||
return self.y
|
||||
|
||||
|
||||
class Resampler_6k4:
|
||||
|
||||
def __init__(self, n, history = 0):
|
||||
|
||||
self.x = np.zeros(n + 5)
|
||||
self.n = n // 2
|
||||
|
||||
self.y = np.zeros(self.n + history)
|
||||
|
||||
def resample(self, x):
|
||||
|
||||
n = self.n
|
||||
|
||||
### Sliding window
|
||||
|
||||
self.x[:3] = self.x[-5:-2]
|
||||
self.x[3:] = x[:2*n+2]
|
||||
x = self.x
|
||||
|
||||
if len(self.y) > 2*n:
|
||||
self.y[n:-n] = self.y[2*n:]
|
||||
if len(self.y) > n:
|
||||
self.y[-n:] = self.y[:n]
|
||||
|
||||
### 3.3.9.5 Downsampling to 6.4 KHz
|
||||
|
||||
h = [ 0.1236796411180537, 0.2353512128364889, 0.2819382920909148,
|
||||
0.2353512128364889, 0.1236796411180537 ]
|
||||
|
||||
self.y[:n] = [ np.dot(x[2*i:2*i+5], h) for i in range(self.n) ]
|
||||
return self.y
|
||||
|
||||
|
||||
def initial_hp50_state():
|
||||
return { 's1': 0.0, 's2': 0.0 }
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Ltpf:
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.dt = dt
|
||||
self.sr = sr
|
||||
|
||||
(self.pitch_present, self.pitch_index) = (None, None)
|
||||
|
||||
def get_data(self):
|
||||
|
||||
return { 'active' : self.active,
|
||||
'pitch_index' : self.pitch_index }
|
||||
|
||||
def get_nbits(self):
|
||||
|
||||
return 1 + 10 * int(self.pitch_present)
|
||||
|
||||
|
||||
class LtpfAnalysis(Ltpf):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
self.resampler_12k8 = Resampler_12k8(
|
||||
dt, sr, history = 232)
|
||||
|
||||
self.resampler_6k4 = Resampler_6k4(
|
||||
self.resampler_12k8.n, history = 114)
|
||||
|
||||
self.active = False
|
||||
self.tc = 0
|
||||
self.pitch = 0
|
||||
self.nc = np.zeros(2)
|
||||
|
||||
def correlate(self, x, n, k0, k1):
|
||||
|
||||
return [ np.dot(x[:n], np.take(x, np.arange(n) - k)) \
|
||||
for k in range(k0, 1+k1) ]
|
||||
|
||||
def norm_corr(self, x, n, k):
|
||||
|
||||
u = x[:n]
|
||||
v = np.take(x, np.arange(n) - k)
|
||||
uv = np.dot(u, v)
|
||||
return uv / np.sqrt(np.dot(u, u) * np.dot(v, v)) if uv > 0 else 0
|
||||
|
||||
def run(self, x):
|
||||
|
||||
### 3.3.9.3-4 Resampling
|
||||
|
||||
x_12k8 = self.resampler_12k8.resample(x)
|
||||
|
||||
### 3.3.9.5-6 Pitch detection algorithm
|
||||
|
||||
x = self.resampler_6k4.resample(x_12k8)
|
||||
n = self.resampler_6k4.n
|
||||
|
||||
r = self.correlate(x, n, 17, 114)
|
||||
rw = r * (1 - 0.5 * np.arange(len(r)) / (len(r) - 1))
|
||||
|
||||
tc = self.tc
|
||||
k0 = max(0, tc-4)
|
||||
k1 = min(len(r)-1, tc+4)
|
||||
t = [ 17 + np.argmax(rw), 17 + k0 + np.argmax(r[k0:1+k1]) ]
|
||||
|
||||
nc = [ self.norm_corr(x, n, t[i]) for i in range(2) ]
|
||||
ti = int(nc[1] > 0.85 * nc[0])
|
||||
self.tc = t[ti] - 17
|
||||
|
||||
self.pitch_present = bool(nc[ti] > 0.6)
|
||||
|
||||
### 3.3.9.7 Pitch-lag parameter
|
||||
|
||||
if self.pitch_present:
|
||||
tc = self.tc + 17
|
||||
|
||||
x = x_12k8
|
||||
n = self.resampler_12k8.n
|
||||
|
||||
k0 = max( 32, 2*tc-4)
|
||||
k1 = min(228, 2*tc+4)
|
||||
r = self.correlate(x, n, k0-4, k1+4)
|
||||
e = k0 + np.argmax(r[4:-4])
|
||||
|
||||
h = np.zeros(42)
|
||||
h[-15:] = T.LTPF_H4[:15]
|
||||
h[ :16] = T.LTPF_H4[15:]
|
||||
|
||||
m = np.arange(-4, 5)
|
||||
s = [ np.dot( np.take(r, e-k0+4 + m), np.take(h, 4*m-d) ) \
|
||||
for d in range(-3, 4) ]
|
||||
|
||||
f = np.argmax(s[3:]) if e <= 32 else \
|
||||
-3 + np.argmax(s) if e < 127 else \
|
||||
-2 + 2*np.argmax(s[1:-1:2]) if e < 157 else 0
|
||||
|
||||
e -= (f < 0)
|
||||
f += 4*(f < 0)
|
||||
|
||||
self.pitch_index = 4*e + f - 128 if e < 127 else \
|
||||
2*e + f//2 + 126 if e < 157 else e + 283
|
||||
|
||||
else:
|
||||
e = f = 0
|
||||
self.pitch_index = 0
|
||||
|
||||
### 3.3.9.8 Activation bit
|
||||
|
||||
h = np.zeros(24)
|
||||
h[-7:] = T.LTPF_HI[:7]
|
||||
h[ :8] = T.LTPF_HI[7:]
|
||||
|
||||
k = np.arange(-2, 3)
|
||||
u = [ np.dot( np.take(x, i-k), np.take(h, 4*k) ) \
|
||||
for i in range(n) ]
|
||||
v = [ np.dot( np.take(x, i-k), np.take(h, 4*k-f) ) \
|
||||
for i in range(-e, n-e) ]
|
||||
|
||||
nc = max(0, np.dot(u, v)) / np.sqrt(np.dot(u, u) * np.dot(v, v)) \
|
||||
if self.pitch_present else 0
|
||||
|
||||
pitch = e + f/4
|
||||
|
||||
if not self.active:
|
||||
active = (self.dt == T.DT_10M or self.nc[1] > 0.94) \
|
||||
and self.nc[0] > 0.94 and nc > 0.94
|
||||
|
||||
else:
|
||||
dp = abs(pitch - self.pitch)
|
||||
dc = nc - self.nc[0]
|
||||
active = nc > 0.9 or (dp < 2 and dc > -0.1 and nc > 0.84)
|
||||
|
||||
if not self.pitch_present:
|
||||
active = False
|
||||
pitch = 0
|
||||
nc = 0
|
||||
|
||||
self.active = active
|
||||
self.pitch = pitch
|
||||
self.nc[1] = self.nc[0]
|
||||
self.nc[0] = nc
|
||||
|
||||
return self.pitch_present
|
||||
|
||||
def disable(self):
|
||||
|
||||
self.active = False
|
||||
|
||||
def store(self, b):
|
||||
|
||||
b.write_uint(self.active, 1)
|
||||
b.write_uint(self.pitch_index, 9)
|
||||
|
||||
|
||||
class LtpfSynthesis(Ltpf):
|
||||
|
||||
C_N = [ T.LTPF_N_8K , T.LTPF_N_16K,
|
||||
T.LTPF_N_24K, T.LTPF_N_32K, T.LTPF_N_48K ]
|
||||
|
||||
C_D = [ T.LTPF_D_8K , T.LTPF_D_16K,
|
||||
T.LTPF_D_24K, T.LTPF_D_32K, T.LTPF_D_48K ]
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
self.C_N = LtpfSynthesis.C_N[sr]
|
||||
self.C_D = LtpfSynthesis.C_D[sr]
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
|
||||
self.active = [ False, False ]
|
||||
self.pitch_index = 0
|
||||
|
||||
max_pitch_12k8 = 228
|
||||
max_pitch = max_pitch_12k8 * T.SRATE_KHZ[self.sr] / 12.8
|
||||
max_pitch = np.ceil(max_pitch).astype(np.int)
|
||||
|
||||
self.x = np.zeros(ns)
|
||||
self.y = np.zeros(max_pitch + len(self.C_D[0]))
|
||||
|
||||
self.p_e = [ 0, 0 ]
|
||||
self.p_f = [ 0, 0 ]
|
||||
self.c_n = [ None, None ]
|
||||
self.c_d = [ None, None ]
|
||||
|
||||
def load(self, b):
|
||||
|
||||
self.active[0] = bool(b.read_uint(1))
|
||||
self.pitch_index = b.read_uint(9)
|
||||
|
||||
def disable(self):
|
||||
|
||||
self.active[0] = False
|
||||
self.pitch_index = 0
|
||||
|
||||
def run(self, x, nbytes):
|
||||
|
||||
sr = self.sr
|
||||
dt = self.dt
|
||||
|
||||
### 3.4.9.4 Filter parameters
|
||||
|
||||
pitch_index = self.pitch_index
|
||||
|
||||
if pitch_index >= 440:
|
||||
p_e = pitch_index - 283
|
||||
p_f = 0
|
||||
elif pitch_index >= 380:
|
||||
p_e = pitch_index // 2 - 63
|
||||
p_f = 2*(pitch_index - 2*(p_e + 63))
|
||||
else:
|
||||
p_e = pitch_index // 4 + 32
|
||||
p_f = pitch_index - 4*(p_e - 32)
|
||||
|
||||
p = (p_e + p_f / 4) * T.SRATE_KHZ[self.sr] / 12.8
|
||||
|
||||
self.p_e[0] = int(p * 4 + 0.5) // 4
|
||||
self.p_f[0] = int(p * 4 + 0.5) - 4*self.p_e[0]
|
||||
|
||||
nbits = round(nbytes*80 / T.DT_MS[dt])
|
||||
g_idx = max(nbits // 80, 3+sr) - (3+sr)
|
||||
|
||||
g = [ 0.4, 0.35, 0.3, 0.25 ][g_idx] if g_idx < 4 else 0
|
||||
g_idx = min(g_idx, 3)
|
||||
|
||||
self.c_n[0] = 0.85 * g * LtpfSynthesis.C_N[sr][g_idx]
|
||||
self.c_d[0] = g * LtpfSynthesis.C_D[sr][self.p_f[0]]
|
||||
|
||||
### 3.4.9.2 Transition handling
|
||||
|
||||
n0 = (T.SRATE_KHZ[sr] * 1000) // 400
|
||||
ns = T.NS[dt][sr]
|
||||
|
||||
x = np.append(x, self.x)
|
||||
y = np.append(np.zeros(ns), self.y)
|
||||
yc = y.copy()
|
||||
|
||||
c_n = self.c_n
|
||||
c_d = self.c_d
|
||||
|
||||
l_n = len(c_n[0])
|
||||
l_d = len(c_d[0])
|
||||
|
||||
d = [ self.p_e[0] - (l_d - 1) // 2,
|
||||
self.p_e[1] - (l_d - 1) // 2 ]
|
||||
|
||||
for k in range(n0):
|
||||
|
||||
if not self.active[0] and not self.active[1]:
|
||||
y[k] = x[k]
|
||||
|
||||
elif self.active[0] and not self.active[1]:
|
||||
u = np.dot(c_n[0], np.take(x, k - np.arange(l_n))) - \
|
||||
np.dot(c_d[0], np.take(y, k - d[0] - np.arange(l_d)))
|
||||
y[k] = x[k] - (k/n0) * u
|
||||
|
||||
elif not self.active[0] and self.active[1]:
|
||||
u = np.dot(c_n[1], np.take(x, k - np.arange(l_n))) - \
|
||||
np.dot(c_d[1], np.take(y, k - d[1] - np.arange(l_d)))
|
||||
y[k] = x[k] - (1 - k/n0) * u
|
||||
|
||||
elif self.p_e[0] == self.p_e[1] and self.p_f[0] == self.p_f[1]:
|
||||
u = np.dot(c_n[0], np.take(x, k - np.arange(l_n))) - \
|
||||
np.dot(c_d[0], np.take(y, k - d[0] - np.arange(l_d)))
|
||||
y[k] = x[k] - u
|
||||
|
||||
else:
|
||||
u = np.dot(c_n[1], np.take(x, k - np.arange(l_n))) - \
|
||||
np.dot(c_d[1], np.take(y, k - d[1] - np.arange(l_d)))
|
||||
yc[k] = x[k] - (1 - k/n0) * u
|
||||
|
||||
u = np.dot(c_n[0], np.take(yc, k - np.arange(l_n))) - \
|
||||
np.dot(c_d[0], np.take(y , k - d[0] - np.arange(l_d)))
|
||||
y[k] = yc[k] - (k/n0) * u
|
||||
|
||||
|
||||
### 3.4.9.3 Remainder of the frame
|
||||
|
||||
for k in range(n0, ns):
|
||||
|
||||
if not self.active[0]:
|
||||
y[k] = x[k]
|
||||
|
||||
else:
|
||||
u = np.dot(c_n[0], np.take(x, k - np.arange(l_n))) - \
|
||||
np.dot(c_d[0], np.take(y, k - d[0] - np.arange(l_d)))
|
||||
y[k] = x[k] - u
|
||||
|
||||
### Sliding window
|
||||
|
||||
self.active[1] = self.active[0]
|
||||
self.p_e[1] = self.p_e[0]
|
||||
self.p_f[1] = self.p_f[0]
|
||||
self.c_n[1] = self.c_n[0]
|
||||
self.c_d[1] = self.c_d[0]
|
||||
|
||||
self.x = x[:ns]
|
||||
self.y = np.append(self.y[ns:], y[:ns])
|
||||
|
||||
return y[:ns]
|
||||
|
||||
def initial_state():
|
||||
return { 'active' : False, 'pitch': 0, 'nc': np.zeros(2),
|
||||
'hp50' : initial_hp50_state(),
|
||||
'x_12k8' : np.zeros(384), 'x_6k4' : np.zeros(178), 'tc' : 0 }
|
||||
|
||||
def initial_sstate():
|
||||
return { 'active': False, 'pitch': 0,
|
||||
'c': np.zeros((12,2)), 'x': np.zeros(12) }
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_resampler(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
nd = T.ND[dt][sr]
|
||||
ok = True
|
||||
|
||||
r = Resampler_12k8(dt, sr)
|
||||
|
||||
hp50_c = initial_hp50_state()
|
||||
x_c = np.zeros(nd)
|
||||
y_c = np.zeros(384)
|
||||
|
||||
for run in range(10):
|
||||
|
||||
x = (2 * rng.random(ns)) - 1
|
||||
y = r.resample(x)
|
||||
|
||||
x_c = np.append(x_c[-nd:], x)
|
||||
y_c[:-r.n] = y_c[r.n:]
|
||||
y_c = lc3.ltpf_resample(dt, sr, hp50_c, x_c, y_c)
|
||||
|
||||
ok = ok and np.amax(np.abs(y_c[-r.d-r.n:] - y[:r.d+r.n])) < 1e-4
|
||||
|
||||
return ok
|
||||
|
||||
def check_resampler_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
nd = T.ND[dt][sr]
|
||||
n = [ 96, 128 ][dt]
|
||||
k = [ 44, 24 ][dt] + n
|
||||
|
||||
state = initial_hp50_state()
|
||||
|
||||
x = np.append(np.zeros(nd), C.X_PCM[dt][0])
|
||||
y = np.zeros(384)
|
||||
y = lc3.ltpf_resample(dt, sr, state, x, y)
|
||||
u = y[-k:len(C.X_TILDE_12K8D[dt][0])-k]
|
||||
|
||||
ok = np.amax(np.abs(u - C.X_TILDE_12K8D[dt][0])) < 1e0
|
||||
|
||||
x = np.append(x[-nd:], C.X_PCM[dt][1])
|
||||
y[:-n] = y[n:]
|
||||
y = lc3.ltpf_resample(dt, sr, state, x, y)
|
||||
u = y[-k:len(C.X_TILDE_12K8D[dt][1])-k]
|
||||
|
||||
ok = ok and np.amax(np.abs(u - C.X_TILDE_12K8D[dt][1])) < 1e0
|
||||
|
||||
return ok
|
||||
|
||||
def check_analysis(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
nd = T.ND[dt][sr]
|
||||
ok = True
|
||||
|
||||
state_c = initial_state()
|
||||
x_c = np.zeros(ns+nd)
|
||||
|
||||
ltpf = LtpfAnalysis(dt, sr)
|
||||
|
||||
t = np.arange(100 * ns) / (T.SRATE_KHZ[sr] * 1000)
|
||||
s = signal.chirp(t, f0=50, f1=3e3, t1=t[-1], method='logarithmic')
|
||||
|
||||
for i in range(20):
|
||||
|
||||
x = s[i*ns:(i+1)*ns]
|
||||
|
||||
pitch_present = ltpf.run(x)
|
||||
data = ltpf.get_data()
|
||||
|
||||
x_c = np.append(x_c[-nd:], x)
|
||||
(pitch_present_c, data_c) = lc3.ltpf_analyse(dt, sr, state_c, x_c)
|
||||
|
||||
ok = ok and state_c['tc'] == ltpf.tc
|
||||
ok = ok and np.amax(np.abs(state_c['nc'][0] - ltpf.nc[0])) < 1e-4
|
||||
ok = ok and pitch_present_c == pitch_present
|
||||
ok = ok and data_c['active'] == data['active']
|
||||
ok = ok and data_c['pitch_index'] == data['pitch_index']
|
||||
ok = ok and lc3.ltpf_get_nbits(pitch_present) == ltpf.get_nbits()
|
||||
|
||||
return ok
|
||||
|
||||
def check_synthesis(rng, dt, sr):
|
||||
|
||||
ok = True
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
nd = 18 * T.SRATE_KHZ[sr]
|
||||
|
||||
synthesis = LtpfSynthesis(dt, sr)
|
||||
|
||||
state_c = initial_sstate()
|
||||
x_c = np.zeros(nd+ns)
|
||||
|
||||
for i in range(50):
|
||||
pitch_present = bool(rng.integers(0, 10) >= 1)
|
||||
if not pitch_present:
|
||||
synthesis.disable()
|
||||
else:
|
||||
synthesis.active[0] = bool(rng.integers(0, 5) >= 1)
|
||||
synthesis.pitch_index = rng.integers(0, 512)
|
||||
|
||||
data_c = None if not pitch_present else \
|
||||
{ 'active' : synthesis.active[0],
|
||||
'pitch_index' : synthesis.pitch_index }
|
||||
|
||||
x = rng.random(ns) * 1e4
|
||||
nbytes = rng.integers(10*(2+sr), 10*(6+sr))
|
||||
|
||||
x_c[:nd] = x_c[ns:]
|
||||
x_c[nd:] = x
|
||||
|
||||
y = synthesis.run(x, nbytes)
|
||||
x_c = lc3.ltpf_synthesize(dt, sr, nbytes, state_c, data_c, x_c)
|
||||
|
||||
ok = ok and np.amax(np.abs(x_c[nd:] - y)) < 1e-2
|
||||
|
||||
return ok
|
||||
|
||||
def check_analysis_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
nd = T.ND[dt][sr]
|
||||
ok = True
|
||||
|
||||
state = initial_state()
|
||||
|
||||
x = np.append(np.zeros(nd), C.X_PCM[dt][0])
|
||||
(pitch_present, data) = lc3.ltpf_analyse(dt, sr, state, x)
|
||||
|
||||
ok = ok and C.T_CURR[dt][0] - state['tc'] == 17
|
||||
ok = ok and np.amax(np.abs(state['nc'][0] - C.NC_LTPF[dt][0])) < 1e-5
|
||||
ok = ok and pitch_present == C.PITCH_PRESENT[dt][0]
|
||||
ok = ok and data['pitch_index'] == C.PITCH_INDEX[dt][0]
|
||||
ok = ok and data['active'] == C.LTPF_ACTIVE[dt][0]
|
||||
|
||||
x = np.append(x[-nd:], C.X_PCM[dt][1])
|
||||
(pitch_present, data) = lc3.ltpf_analyse(dt, sr, state, x)
|
||||
|
||||
ok = ok and C.T_CURR[dt][1] - state['tc'] == 17
|
||||
ok = ok and np.amax(np.abs(state['nc'][0] - C.NC_LTPF[dt][1])) < 1e-5
|
||||
ok = ok and pitch_present == C.PITCH_PRESENT[dt][1]
|
||||
ok = ok and data['pitch_index'] == C.PITCH_INDEX[dt][1]
|
||||
ok = ok and data['active'] == C.LTPF_ACTIVE[dt][1]
|
||||
|
||||
return ok
|
||||
|
||||
def check_synthesis_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
if dt != T.DT_10M:
|
||||
return ok
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
nd = 18 * T.SRATE_KHZ[sr]
|
||||
|
||||
NBYTES = [ C.LTPF_C2_NBITS // 8, C.LTPF_C3_NBITS // 8,
|
||||
C.LTPF_C4_NBITS // 8, C.LTPF_C5_NBITS // 8 ]
|
||||
|
||||
ACTIVE = [ C.LTPF_C2_ACTIVE, C.LTPF_C3_ACTIVE,
|
||||
C.LTPF_C4_ACTIVE, C.LTPF_C5_ACTIVE ]
|
||||
|
||||
PITCH_INDEX = [ C.LTPF_C2_PITCH_INDEX, C.LTPF_C3_PITCH_INDEX,
|
||||
C.LTPF_C4_PITCH_INDEX, C.LTPF_C5_PITCH_INDEX ]
|
||||
|
||||
X = [ C.LTPF_C2_X, C.LTPF_C3_X,
|
||||
C.LTPF_C4_X, C.LTPF_C5_X ]
|
||||
|
||||
PREV = [ C.LTPF_C2_PREV, C.LTPF_C3_PREV,
|
||||
C.LTPF_C4_PREV, C.LTPF_C5_PREV ]
|
||||
|
||||
TRANS = [ C.LTPF_C2_TRANS, C.LTPF_C3_TRANS,
|
||||
C.LTPF_C4_TRANS, C.LTPF_C5_TRANS ]
|
||||
|
||||
for i in range(4):
|
||||
|
||||
state = initial_sstate()
|
||||
nbytes = NBYTES[i]
|
||||
|
||||
data = { 'active' : ACTIVE[i][0], 'pitch_index' : PITCH_INDEX[i][0] }
|
||||
x = np.append(np.zeros(nd), X[i][0])
|
||||
|
||||
lc3.ltpf_synthesize(dt, sr, nbytes, state, data, x)
|
||||
|
||||
data = { 'active' : ACTIVE[i][1], 'pitch_index' : PITCH_INDEX[i][1] }
|
||||
x[ :nd-ns] = PREV[i][0][-nd+ns:]
|
||||
x[nd-ns:nd] = PREV[i][1]
|
||||
x[nd:nd+ns] = X[i][1]
|
||||
|
||||
y = lc3.ltpf_synthesize(dt, sr, nbytes, state, data, x)[nd:]
|
||||
|
||||
ok = ok and np.amax(np.abs(y - TRANS[i])) < 1e-3
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_resampler(rng, dt, sr)
|
||||
ok = ok and check_analysis(rng, dt, sr)
|
||||
ok = ok and check_synthesis(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_resampler_appendix_c(dt)
|
||||
ok = ok and check_analysis_appendix_c(dt)
|
||||
ok = ok and check_synthesis_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
138
test/ltpf_py.c
Normal file
138
test/ltpf_py.c
Normal file
@@ -0,0 +1,138 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <ltpf.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *resample_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
unsigned dt, sr;
|
||||
PyObject *hp50_obj, *x_obj, *y_obj;
|
||||
struct lc3_ltpf_hp50_state hp50;
|
||||
float *x, *y;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOOO", &dt, &sr, &hp50_obj, &x_obj, &y_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK(NULL, hp50_obj = to_ltpf_hp50_state(hp50_obj, &hp50));
|
||||
|
||||
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
|
||||
int ny = sizeof((struct lc3_ltpf_analysis){ }.x_12k8) / sizeof(float);
|
||||
int n = dt == LC3_DT_7M5 ? 96 : 128;
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ns+nd, &x));
|
||||
CTYPES_CHECK("y", y_obj = to_1d_ptr(y_obj, NPY_FLOAT, ny, &y));
|
||||
|
||||
resample_12k8[sr](&hp50, x + nd, y + (ny - n), n);
|
||||
|
||||
from_ltpf_hp50_state(hp50_obj, &hp50);
|
||||
return Py_BuildValue("O", y_obj);
|
||||
}
|
||||
|
||||
static PyObject *analyse_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *ltpf_obj, *x_obj;
|
||||
unsigned dt, sr;
|
||||
struct lc3_ltpf_analysis ltpf;
|
||||
struct lc3_ltpf_data data = { 0 };
|
||||
float *x;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOO", &dt, &sr, <pf_obj, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK(NULL, ltpf_obj = to_ltpf_analysis(ltpf_obj, <pf));
|
||||
|
||||
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ns+nd, &x));
|
||||
|
||||
int pitch_present =
|
||||
lc3_ltpf_analyse(dt, sr, <pf, x + nd, &data);
|
||||
|
||||
from_ltpf_analysis(ltpf_obj, <pf);
|
||||
return Py_BuildValue("iN", pitch_present, new_ltpf_data(&data));
|
||||
}
|
||||
|
||||
static PyObject *synthesize_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *ltpf_obj, *data_obj, *x_obj;
|
||||
struct lc3_ltpf_synthesis ltpf;
|
||||
struct lc3_ltpf_data data;
|
||||
bool pitch_present;
|
||||
unsigned dt, sr;
|
||||
int nbytes;
|
||||
float *x;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIiOOO",
|
||||
&dt, &sr, &nbytes, <pf_obj, &data_obj, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK("nbytes", nbytes >= 20 && nbytes <= 400);
|
||||
CTYPES_CHECK(NULL, ltpf_obj = to_ltpf_synthesis(ltpf_obj, <pf));
|
||||
|
||||
if ((pitch_present = (data_obj != Py_None)))
|
||||
CTYPES_CHECK(NULL, data_obj = to_ltpf_data(data_obj, &data));
|
||||
|
||||
int ns = LC3_NS(dt,sr), nd = 18 * LC3_SRATE_KHZ(sr);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, nd+ns, &x));
|
||||
|
||||
lc3_ltpf_synthesize(dt, sr, nbytes,
|
||||
<pf, pitch_present ? &data : NULL, x+nd);
|
||||
|
||||
from_ltpf_synthesis(ltpf_obj, <pf);
|
||||
return Py_BuildValue("O", x_obj);
|
||||
}
|
||||
|
||||
static PyObject *get_nbits_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
int pitch_present;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i", &pitch_present))
|
||||
return NULL;
|
||||
|
||||
int nbits = lc3_ltpf_get_nbits(pitch_present);
|
||||
|
||||
return Py_BuildValue("i", nbits);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "ltpf_resample" , resample_py , METH_VARARGS },
|
||||
{ "ltpf_analyse" , analyse_py , METH_VARARGS },
|
||||
{ "ltpf_synthesize", synthesize_py, METH_VARARGS },
|
||||
{ "ltpf_get_nbits" , get_nbits_py , METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_ltpf_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
27
test/makefile.mk
Normal file
27
test/makefile.mk
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
TEST_DIR := test
|
||||
|
||||
.PHONY: test test-clean
|
||||
|
||||
test:
|
||||
$(V)cd $(TEST_DIR) && python3 setup.py && python3 run.py
|
||||
|
||||
test-clean:
|
||||
$(V)cd $(TEST_DIR) && python3 setup.py clean > /tmp/zero
|
||||
|
||||
clean-all: test-clean
|
||||
196
test/mdct.py
Normal file
196
test/mdct.py
Normal file
@@ -0,0 +1,196 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
import scipy.fft
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Mdct:
|
||||
|
||||
W = [ [ T.W_7M5_60, T.W_7M5_120, T.W_7M5_180, T.W_7M5_240, T.W_7M5_360 ],
|
||||
[ T.W_10M_80, T.W_10M_160, T.W_10M_240, T.W_10M_320, T.W_10M_480 ] ]
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.ns = T.NS[dt][sr]
|
||||
self.nd = T.ND[dt][sr]
|
||||
|
||||
self.t = np.zeros(2*self.ns)
|
||||
self.w = Mdct.W[dt][sr]
|
||||
|
||||
|
||||
class MdctForward(Mdct):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
def run(self, x):
|
||||
|
||||
ns = self.ns
|
||||
nd = self.nd
|
||||
|
||||
self.t[nd:nd+ns] = x
|
||||
t = self.t * self.w
|
||||
self.t[0:nd] = x[ns-nd:]
|
||||
|
||||
n = len(t)
|
||||
n2 = n // 2
|
||||
|
||||
z = t * np.exp(-2j * np.pi * np.arange(n) / (2*n))
|
||||
z = scipy.fft.fft(z)[:n2]
|
||||
z = z * np.exp(-2j * np.pi *
|
||||
(n2/2 + 0.5) * (np.arange(n2) + 0.5) / (2 * n2))
|
||||
return np.real(z) * np.sqrt(2/n2)
|
||||
|
||||
|
||||
class MdctInverse(Mdct):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
def run(self, x):
|
||||
|
||||
ns = self.ns
|
||||
nd = self.nd
|
||||
|
||||
n = len(x)
|
||||
|
||||
x = np.append(x, -x[::-1])
|
||||
z = x * np.exp(2j * np.pi * (n/2 + 0.5) * np.arange(2*n) / (2*n))
|
||||
z = scipy.fft.ifft(z) * n
|
||||
z = z * np.exp(2j * np.pi * (np.arange(2*n) + (n/2 + 0.5)) / (4*n))
|
||||
t = np.real(z) * np.sqrt(2/n)
|
||||
|
||||
t = t * self.w[::-1]
|
||||
|
||||
y = np.empty(ns)
|
||||
y[:nd] = t[ns-nd:ns] + self.t[2*ns-nd:]
|
||||
y[nd:] = t[ns:2*ns-nd]
|
||||
self.t = t
|
||||
|
||||
return y
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_forward_unit(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
nd = T.ND[dt][sr]
|
||||
ok = True
|
||||
|
||||
x = (2 * rng.random(ns)) - 1
|
||||
|
||||
mdct = MdctForward(dt, sr)
|
||||
y = [ mdct.run(x), mdct.run(x) ]
|
||||
|
||||
y_c = [ lc3.mdct_forward(dt, sr, np.append(np.zeros(nd), x)),
|
||||
lc3.mdct_forward(dt, sr, np.append(x[-nd:], x)) ]
|
||||
|
||||
ok = ok and np.amax(np.abs(y[0] - y_c[0])) < 1e-5
|
||||
ok = ok and np.amax(np.abs(y[1] - y_c[1])) < 1e-5
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def check_forward_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ns = T.NS[dt][sr]
|
||||
nd = T.ND[dt][sr]
|
||||
ok = True
|
||||
|
||||
y = lc3.mdct_forward(dt, sr,
|
||||
np.append(np.zeros(nd), C.X_PCM[dt][0]))
|
||||
ok = ok and np.amax(np.abs(y - C.X[dt][0])) < 1e-1
|
||||
|
||||
y = lc3.mdct_forward(dt, sr,
|
||||
np.append(C.X_PCM[dt][0][-nd:], C.X_PCM[dt][1]))
|
||||
ok = ok and np.amax(np.abs(y - C.X[dt][1])) < 1e-1
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def check_inverse_unit(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
nd = [ (23 * ns) // 30, (5 * ns) // 8 ][dt]
|
||||
ok = True
|
||||
|
||||
x = (2 * rng.random(ns)) - 1
|
||||
|
||||
y = [ None ] * 2
|
||||
y_c = [ None ] * 2
|
||||
|
||||
mdct = MdctInverse(dt, sr)
|
||||
y[0] = mdct.run(x)
|
||||
y[1] = mdct.run(x)
|
||||
|
||||
(y_c[0], d_c) = lc3.mdct_inverse(dt, sr, x, np.zeros(nd))
|
||||
y_c[1] = lc3.mdct_inverse(dt, sr, x, d_c)[0]
|
||||
|
||||
ok = ok and np.amax(np.abs(y[0] - y_c[0])) < 1e-5
|
||||
ok = ok and np.amax(np.abs(y[1] - y_c[1])) < 1e-5
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def check_inverse_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ns = T.NS[dt][sr]
|
||||
nd = [ (23 * ns) // 30, (5 * ns) // 8 ][dt]
|
||||
ok = True
|
||||
|
||||
(y, d0) = lc3.mdct_inverse(dt, sr, C.X_HAT_SNS[dt][0], np.zeros(nd))
|
||||
yr = C.T_HAT_MDCT[dt][0][ns-nd:2*ns-nd]
|
||||
dr = C.T_HAT_MDCT[dt][0][2*ns-nd:]
|
||||
|
||||
ok = ok and np.amax(np.abs(yr - y )) < 1e-1
|
||||
ok = ok and np.amax(np.abs(dr - d0)) < 1e-1
|
||||
|
||||
(y, d1) = lc3.mdct_inverse(dt, sr, C.X_HAT_SNS[dt][1], d0)
|
||||
yr[ :nd] = C.T_HAT_MDCT[dt][1][ns-nd:ns] + d0
|
||||
yr[nd:ns] = C.T_HAT_MDCT[dt][1][ns:2*ns-nd]
|
||||
dr = C.T_HAT_MDCT[dt][1][2*ns-nd:]
|
||||
|
||||
ok = ok and np.amax(np.abs(yr - y )) < 1e-1
|
||||
ok = ok and np.amax(np.abs(dr - d1)) < 1e-1
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_forward_unit(rng, dt, sr)
|
||||
ok = ok and check_inverse_unit(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_forward_appendix_c(dt)
|
||||
ok = ok and check_inverse_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
89
test/mdct_py.c
Normal file
89
test/mdct_py.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <mdct.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
|
||||
static PyObject *mdct_forward_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj, *y_obj;
|
||||
enum lc3_dt dt;
|
||||
enum lc3_srate sr;
|
||||
float *x, *y;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iiO", &dt, &sr, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, nd+ns, &x));
|
||||
y_obj = new_1d_ptr(NPY_FLOAT, ns, &y);
|
||||
|
||||
lc3_mdct_forward(dt, sr, sr, x+nd, y);
|
||||
|
||||
return Py_BuildValue("N", y_obj);
|
||||
}
|
||||
|
||||
static PyObject *mdct_inverse_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj, *xd_obj, *d_obj, *y_obj;
|
||||
enum lc3_dt dt;
|
||||
enum lc3_srate sr;
|
||||
float *x, *xd, *d, *y;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iiOO", &dt, &sr, &x_obj, &xd_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ns = LC3_NS(dt, sr), nd = LC3_ND(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, ns, &x));
|
||||
CTYPES_CHECK("xd", to_1d_ptr(xd_obj, NPY_FLOAT, nd, &xd));
|
||||
d_obj = new_1d_ptr(NPY_FLOAT, nd, &d);
|
||||
y_obj = new_1d_ptr(NPY_FLOAT, ns, &y);
|
||||
|
||||
memcpy(d, xd, nd * sizeof(float));
|
||||
|
||||
lc3_mdct_inverse(dt, sr, sr, x, d, y);
|
||||
|
||||
return Py_BuildValue("NN", y_obj, d_obj);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "mdct_forward", mdct_forward_py, METH_VARARGS },
|
||||
{ "mdct_inverse", mdct_inverse_py, METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_mdct_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
53
test/module_py.c
Normal file
53
test/module_py.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
static struct PyModuleDef module_def = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "LC3",
|
||||
.m_doc = "LC3 Test Python Module",
|
||||
.m_size = -1,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_mdct_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_energy_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_attdet_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_bwdet_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_ltpf_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_sns_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_tns_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_spec_py_init(PyObject *);
|
||||
PyMODINIT_FUNC lc3_interface_py_init(PyObject *);
|
||||
|
||||
PyMODINIT_FUNC PyInit_lc3(void)
|
||||
{
|
||||
PyObject *m = PyModule_Create(&module_def);
|
||||
|
||||
if (m) m = lc3_mdct_py_init(m);
|
||||
if (m) m = lc3_energy_py_init(m);
|
||||
if (m) m = lc3_attdet_py_init(m);
|
||||
if (m) m = lc3_bwdet_py_init(m);
|
||||
if (m) m = lc3_ltpf_py_init(m);
|
||||
if (m) m = lc3_sns_py_init(m);
|
||||
if (m) m = lc3_tns_py_init(m);
|
||||
if (m) m = lc3_spec_py_init(m);
|
||||
if (m) m = lc3_interface_py_init(m);
|
||||
|
||||
return m;
|
||||
}
|
||||
40
test/run.py
Executable file
40
test/run.py
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2022 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 mdct, energy, bwdet, attdet
|
||||
import ltpf, sns, tns, spec, encoder, decoder
|
||||
|
||||
ok = True
|
||||
|
||||
for m in [ ( mdct , "MDCT" ),
|
||||
( energy , "Energy Band" ),
|
||||
( bwdet , "Bandwidth Detector" ),
|
||||
( attdet , "Attack Detector" ),
|
||||
( ltpf , "Long Term Postfilter" ),
|
||||
( sns , "Spectral Noise Shaping" ),
|
||||
( tns , "Temporal Noise Shaping" ),
|
||||
( spec , "Spectral Quantization" ),
|
||||
( encoder , "Encoder" ),
|
||||
( decoder , "Decoder" ) ]:
|
||||
|
||||
print('[{:^6}] {:}'.format('...', m[1]), end='\r', flush=True)
|
||||
ret = m[0].check()
|
||||
print('[{:^6}] {:}'.format('OK' if ret else 'FAILED', m[1]))
|
||||
|
||||
ok = ok and ret
|
||||
|
||||
exit(0 if ok else 1);
|
||||
50
test/setup.py
Executable file
50
test/setup.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
from distutils.core import setup, Extension
|
||||
import os, sys, glob
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
sys.argv = sys.argv + [
|
||||
'build', '--build-base', os.getcwd() + os.sep + 'build',
|
||||
'install', '--install-lib', os.getcwd() + os.sep + 'build' ]
|
||||
|
||||
INC_DIR = '..' + os.sep + 'include'
|
||||
SRC_DIR = '..' + os.sep + 'src'
|
||||
|
||||
sources = glob.glob('*_py.c') + \
|
||||
[ SRC_DIR + os.sep + 'tables.c',
|
||||
SRC_DIR + os.sep + 'bits.c',
|
||||
SRC_DIR + os.sep + 'plc.c' ]
|
||||
|
||||
depends = [ 'ctypes.h' ] + \
|
||||
glob.glob(INC_DIR + os.sep + '*.h') + \
|
||||
glob.glob(SRC_DIR + os.sep + '*.[c,h]')
|
||||
|
||||
includes = [ SRC_DIR, INC_DIR ]
|
||||
|
||||
ctiming = Extension('lc3',
|
||||
extra_compile_args = ['-std=c11'],
|
||||
define_macros = [ ('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION') ],
|
||||
sources = sources,
|
||||
depends = depends,
|
||||
include_dirs = includes)
|
||||
|
||||
setup(name = 'LC3',
|
||||
version = '1.0',
|
||||
description = 'LC3 Test Python Module',
|
||||
ext_modules = [ctiming])
|
||||
594
test/sns.py
Normal file
594
test/sns.py
Normal file
@@ -0,0 +1,594 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
import scipy.fftpack as fftpack
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Sns:
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.dt = dt
|
||||
self.sr = sr
|
||||
|
||||
(self.ind_lf, self.ind_hf, self.shape, self.gain) = \
|
||||
(None, None, None, None)
|
||||
|
||||
(self.idx_a, self.ls_a, self.idx_b, self.ls_b) = \
|
||||
(None, None, None, None)
|
||||
|
||||
def get_data(self):
|
||||
|
||||
data = { 'lfcb' : self.ind_lf, 'hfcb' : self.ind_hf,
|
||||
'shape' : self.shape, 'gain' : self.gain,
|
||||
'idx_a' : self.idx_a, 'ls_a' : self.ls_a }
|
||||
|
||||
if self.idx_b is not None:
|
||||
data.update({ 'idx_b' : self.idx_b, 'ls_b' : self.ls_b })
|
||||
|
||||
return data
|
||||
|
||||
def get_nbits(self):
|
||||
|
||||
return 38
|
||||
|
||||
def spectral_shaping(self, scf, inv, x):
|
||||
|
||||
## 3.3.7.4 Scale factors interpolation
|
||||
|
||||
scf_i = np.empty(4*len(scf))
|
||||
scf_i[0 ] = scf[0]
|
||||
scf_i[1 ] = scf[0]
|
||||
scf_i[2:62:4] = scf[:15] + 1/8 * (scf[1:] - scf[:15])
|
||||
scf_i[3:63:4] = scf[:15] + 3/8 * (scf[1:] - scf[:15])
|
||||
scf_i[4:64:4] = scf[:15] + 5/8 * (scf[1:] - scf[:15])
|
||||
scf_i[5:64:4] = scf[:15] + 7/8 * (scf[1:] - scf[:15])
|
||||
scf_i[62 ] = scf[15 ] + 1/8 * (scf[15] - scf[14 ])
|
||||
scf_i[63 ] = scf[15 ] + 3/8 * (scf[15] - scf[14 ])
|
||||
|
||||
n2 = 64 - min(len(x), 64)
|
||||
|
||||
for i in range(n2):
|
||||
scf_i[i] = 0.5 * (scf_i[2*i] + scf_i[2*i+1])
|
||||
scf_i = np.append(scf_i[:n2], scf_i[2*n2:])
|
||||
|
||||
g_sns = np.power(2, [ -scf_i, scf_i ][inv])
|
||||
|
||||
## 3.3.7.4 Spectral shaping
|
||||
|
||||
y = np.empty(len(x))
|
||||
I = T.I[self.dt][self.sr]
|
||||
|
||||
for b in range(len(g_sns)):
|
||||
y[I[b]:I[b+1]] = x[I[b]:I[b+1]] * g_sns[b]
|
||||
|
||||
return y
|
||||
|
||||
|
||||
class SnsAnalysis(Sns):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
def compute_scale_factors(self, e, att):
|
||||
|
||||
dt = self.dt
|
||||
|
||||
## 3.3.7.2.1 Padding
|
||||
|
||||
n2 = 64 - len(e)
|
||||
|
||||
e = np.append(np.empty(n2), e)
|
||||
for i in range(n2):
|
||||
e[2*i+0] = e[2*i+1] = e[n2+i]
|
||||
|
||||
## 3.3.7.2.2 Smoothing
|
||||
|
||||
e_s = np.zeros(len(e))
|
||||
e_s[0 ] = 0.75 * e[0 ] + 0.25 * e[1 ]
|
||||
e_s[1:63] = 0.25 * e[0:62] + 0.5 * e[1:63] + 0.25 * e[2:64]
|
||||
e_s[ 63] = 0.25 * e[ 62] + 0.75 * e[ 63]
|
||||
|
||||
## 3.3.7.2.3 Pre-emphasis
|
||||
|
||||
g_tilt = [ 14, 18, 22, 26, 30 ][self.sr]
|
||||
e_p = e_s * (10 ** ((np.arange(64) * g_tilt) / 630))
|
||||
|
||||
## 3.3.7.2.4 Noise floor
|
||||
|
||||
noise_floor = max(np.average(e_p) * (10 ** (-40/10)), 2 ** -32)
|
||||
e_p = np.fmax(e_p, noise_floor * np.ones(len(e)))
|
||||
|
||||
## 3.3.7.2.5 Logarithm
|
||||
|
||||
e_l = np.log2(10 ** -31 + e_p) / 2
|
||||
|
||||
## 3.3.7.2.6 Band energy grouping
|
||||
|
||||
w = [ 1/12, 2/12, 3/12, 3/12, 2/12, 1/12 ]
|
||||
|
||||
e_4 = np.zeros(len(e_l) // 4)
|
||||
e_4[0 ] = w[0] * e_l[0] + np.sum(w[1:] * e_l[:5])
|
||||
e_4[1:15] = [ np.sum(w * e_l[4*i-1:4*i+5]) for i in range(1, 15) ]
|
||||
e_4[ 15] = np.sum(w[:5] * e_l[59:64]) + w[5] * e_l[63]
|
||||
|
||||
## 3.3.7.2.7 Mean removal and scaling, attack handling
|
||||
|
||||
scf = 0.85 * (e_4 - np.average(e_4))
|
||||
|
||||
scf_a = np.zeros(len(scf))
|
||||
scf_a[0 ] = np.average(scf[:3])
|
||||
scf_a[1 ] = np.average(scf[:4])
|
||||
scf_a[2:14] = [ np.average(scf[i:i+5]) for i in range(12) ]
|
||||
scf_a[ 14] = np.average(scf[12:])
|
||||
scf_a[ 15] = np.average(scf[13:])
|
||||
|
||||
scf_a = (0.5 if self.dt == T.DT_10M else 0.3) * \
|
||||
(scf_a - np.average(scf_a))
|
||||
|
||||
return scf_a if att else scf
|
||||
|
||||
def enum_mpvq(self, v):
|
||||
|
||||
sign = None
|
||||
index = 0
|
||||
x = 0
|
||||
|
||||
for (n, vn) in enumerate(v[::-1]):
|
||||
|
||||
if sign is not None and vn != 0:
|
||||
index = 2*index + sign
|
||||
if vn != 0:
|
||||
sign = 1 if vn < 0 else 0
|
||||
|
||||
index += T.SNS_MPVQ_OFFSETS[n][x]
|
||||
x += abs(vn)
|
||||
|
||||
return (index, bool(sign))
|
||||
|
||||
def quantize(self, scf):
|
||||
|
||||
## 3.3.7.3.2 Stage 1
|
||||
|
||||
dmse_lf = [ np.sum((scf[:8] - T.SNS_LFCB[i]) ** 2) for i in range(32) ]
|
||||
dmse_hf = [ np.sum((scf[8:] - T.SNS_HFCB[i]) ** 2) for i in range(32) ]
|
||||
|
||||
self.ind_lf = np.argmin(dmse_lf)
|
||||
self.ind_hf = np.argmin(dmse_hf)
|
||||
|
||||
st1 = np.append(T.SNS_LFCB[self.ind_lf], T.SNS_HFCB[self.ind_hf])
|
||||
r1 = scf - st1
|
||||
|
||||
## 3.3.7.3.3 Stage 2
|
||||
|
||||
t2_rot = fftpack.dct(r1, norm = 'ortho')
|
||||
x = np.abs(t2_rot)
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 1
|
||||
|
||||
K = 6
|
||||
|
||||
proj_fac = (K - 1) / sum(np.abs(t2_rot))
|
||||
y3 = np.floor(x * proj_fac).astype(int)
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 2
|
||||
|
||||
corr_xy = np.sum(y3 * x)
|
||||
energy_y = np.sum(y3 * y3)
|
||||
|
||||
k0 = sum(y3)
|
||||
for k in range(k0, K):
|
||||
q_pvq = ((corr_xy + x) ** 2) / (energy_y + 2*y3 + 1)
|
||||
n_best = np.argmax(q_pvq)
|
||||
|
||||
corr_xy += x[n_best]
|
||||
energy_y += 2*y3[n_best] + 1
|
||||
y3[n_best] += 1
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 3
|
||||
|
||||
K = 8
|
||||
|
||||
y2 = y3.copy()
|
||||
|
||||
for k in range(sum(y2), K):
|
||||
q_pvq = ((corr_xy + x) ** 2) / (energy_y + 2*y2 + 1)
|
||||
n_best = np.argmax(q_pvq)
|
||||
|
||||
corr_xy += x[n_best]
|
||||
energy_y += 2*y2[n_best] + 1
|
||||
y2[n_best] += 1
|
||||
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 4
|
||||
|
||||
y1 = np.append(y2[:10], [0] * 6)
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 5
|
||||
|
||||
corr_xy -= sum(y2[10:] * x[10:])
|
||||
energy_y -= sum(y2[10:] * y2[10:])
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 6
|
||||
|
||||
K = 10
|
||||
|
||||
for k in range(sum(y1), K):
|
||||
q_pvq = ((corr_xy + x[:10]) ** 2) / (energy_y + 2*y1[:10] + 1)
|
||||
n_best = np.argmax(q_pvq)
|
||||
|
||||
corr_xy += x[n_best]
|
||||
energy_y += 2*y1[n_best] + 1
|
||||
y1[n_best] += 1
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 7
|
||||
|
||||
y0 = np.append(y1[:10], [ 0 ] * 6)
|
||||
|
||||
q_pvq = ((corr_xy + x[10:]) ** 2) / (energy_y + 2*y0[10:] + 1)
|
||||
n_best = 10 + np.argmax(q_pvq)
|
||||
|
||||
y0[n_best] += 1
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 8
|
||||
|
||||
y0 *= np.sign(t2_rot).astype(int)
|
||||
y1 *= np.sign(t2_rot).astype(int)
|
||||
y2 *= np.sign(t2_rot).astype(int)
|
||||
y3 *= np.sign(t2_rot).astype(int)
|
||||
|
||||
## 3.3.7.3.3 Stage 2 Shape search, step 9
|
||||
|
||||
xq = [ y / np.sqrt(sum(y ** 2)) for y in (y0, y1, y2, y3) ]
|
||||
|
||||
## 3.3.7.3.3 Shape and gain combination determination
|
||||
|
||||
G = [ T.SNS_VQ_REG_ADJ_GAINS, T.SNS_VQ_REG_LF_ADJ_GAINS,
|
||||
T.SNS_VQ_NEAR_ADJ_GAINS, T.SNS_VQ_FAR_ADJ_GAINS ]
|
||||
|
||||
dMSE = [ [ sum((t2_rot - G[j][i] * xq[j]) ** 2)
|
||||
for i in range(len(G[j])) ] for j in range(4) ]
|
||||
|
||||
self.shape = np.argmin([ np.min(dMSE[j]) for j in range(4) ])
|
||||
self.gain = np.argmin(dMSE[self.shape])
|
||||
|
||||
gain = G[self.shape][self.gain]
|
||||
|
||||
## 3.3.7.3.3 Enumeration of the selected PVQ pulse configurations
|
||||
|
||||
if self.shape == 0:
|
||||
(self.idx_a, self.ls_a) = self.enum_mpvq(y0[:10])
|
||||
(self.idx_b, self.ls_b) = self.enum_mpvq(y0[10:])
|
||||
elif self.shape == 1:
|
||||
(self.idx_a, self.ls_a) = self.enum_mpvq(y1[:10])
|
||||
(self.idx_b, self.ls_b) = (None, None)
|
||||
elif self.shape == 2:
|
||||
(self.idx_a, self.ls_a) = self.enum_mpvq(y2)
|
||||
(self.idx_b, self.ls_b) = (None, None)
|
||||
elif self.shape == 3:
|
||||
(self.idx_a, self.ls_a) = self.enum_mpvq(y3)
|
||||
(self.idx_b, self.ls_b) = (None, None)
|
||||
|
||||
## 3.3.7.3.4 Synthesis of the Quantized scale factor
|
||||
|
||||
scf_q = st1 + gain * fftpack.idct(xq[self.shape], norm = 'ortho')
|
||||
|
||||
return scf_q
|
||||
|
||||
def run(self, eb, att, x):
|
||||
|
||||
scf = self.compute_scale_factors(eb, att)
|
||||
scf_q = self.quantize(scf)
|
||||
y = self.spectral_shaping(scf_q, False, x)
|
||||
|
||||
return y
|
||||
|
||||
def store(self, b):
|
||||
|
||||
shape = self.shape
|
||||
gain_msb_bits = np.array([ 1, 1, 2, 2 ])[shape]
|
||||
gain_lsb_bits = np.array([ 0, 1, 0, 1 ])[shape]
|
||||
|
||||
b.write_uint(self.ind_lf, 5)
|
||||
b.write_uint(self.ind_hf, 5)
|
||||
|
||||
b.write_bit(shape >> 1)
|
||||
|
||||
b.write_uint(self.gain >> gain_lsb_bits, gain_msb_bits)
|
||||
|
||||
b.write_bit(self.ls_a)
|
||||
|
||||
if self.shape == 0:
|
||||
sz_shape_a = 2390004
|
||||
index_joint = self.idx_a + \
|
||||
(2 * self.idx_b + self.ls_b + 2) * sz_shape_a
|
||||
|
||||
elif self.shape == 1:
|
||||
sz_shape_a = 2390004
|
||||
index_joint = self.idx_a + (self.gain & 1) * sz_shape_a
|
||||
|
||||
elif self.shape == 2:
|
||||
index_joint = self.idx_a
|
||||
|
||||
elif self.shape == 3:
|
||||
sz_shape_a = 15158272
|
||||
index_joint = sz_shape_a + (self.gain & 1) + 2 * self.idx_a
|
||||
|
||||
b.write_uint(index_joint, 14 - gain_msb_bits)
|
||||
b.write_uint(index_joint >> (14 - gain_msb_bits), 12)
|
||||
|
||||
|
||||
class SnsSynthesis(Sns):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
def deenum_mpvq(self, index, ls, npulses, n):
|
||||
|
||||
y = np.zeros(n, dtype=np.int)
|
||||
pos = 0
|
||||
|
||||
for i in range(len(y)-1, -1, -1):
|
||||
|
||||
if index > 0:
|
||||
yi = 0
|
||||
while index < T.SNS_MPVQ_OFFSETS[i][npulses - yi]: yi += 1
|
||||
index -= T.SNS_MPVQ_OFFSETS[i][npulses - yi]
|
||||
else:
|
||||
yi = npulses
|
||||
|
||||
y[pos] = [ yi, -yi ][int(ls)]
|
||||
pos += 1
|
||||
|
||||
npulses -= yi
|
||||
if npulses <= 0:
|
||||
break
|
||||
|
||||
if yi > 0:
|
||||
ls = index & 1
|
||||
index >>= 1
|
||||
|
||||
return y
|
||||
|
||||
def unquantize(self):
|
||||
|
||||
## 3.7.4.2.1-2 SNS VQ Decoding
|
||||
|
||||
y = np.empty(16, dtype=np.int)
|
||||
|
||||
if self.shape == 0:
|
||||
y[:10] = self.deenum_mpvq(self.idx_a, self.ls_a, 10, 10)
|
||||
y[10:] = self.deenum_mpvq(self.idx_b, self.ls_b, 1, 6)
|
||||
elif self.shape == 1:
|
||||
y[:10] = self.deenum_mpvq(self.idx_a, self.ls_a, 10, 10)
|
||||
y[10:] = np.zeros(6, dtype=np.int)
|
||||
elif self.shape == 2:
|
||||
y = self.deenum_mpvq(self.idx_a, self.ls_a, 8, 16)
|
||||
elif self.shape == 3:
|
||||
y = self.deenum_mpvq(self.idx_a, self.ls_a, 6, 16)
|
||||
|
||||
## 3.7.4.2.3 Unit energy normalization
|
||||
|
||||
y = y / np.sqrt(sum(y ** 2))
|
||||
|
||||
## 3.7.4.2.4 Reconstruction of the quantized scale factors
|
||||
|
||||
G = [ T.SNS_VQ_REG_ADJ_GAINS, T.SNS_VQ_REG_LF_ADJ_GAINS,
|
||||
T.SNS_VQ_NEAR_ADJ_GAINS, T.SNS_VQ_FAR_ADJ_GAINS ]
|
||||
|
||||
gain = G[self.shape][self.gain]
|
||||
|
||||
scf = np.append(T.SNS_LFCB[self.ind_lf], T.SNS_HFCB[self.ind_hf]) \
|
||||
+ gain * fftpack.idct(y, norm = 'ortho')
|
||||
|
||||
return scf
|
||||
|
||||
def load(self, b):
|
||||
|
||||
self.ind_lf = b.read_uint(5)
|
||||
self.ind_hf = b.read_uint(5)
|
||||
|
||||
shape_msb = b.read_bit()
|
||||
|
||||
gain_msb_bits = 1 + shape_msb
|
||||
self.gain = b.read_uint(gain_msb_bits)
|
||||
|
||||
self.ls_a = b.read_bit()
|
||||
|
||||
index_joint = b.read_uint(14 - gain_msb_bits)
|
||||
index_joint |= b.read_uint(12) << (14 - gain_msb_bits)
|
||||
|
||||
if shape_msb == 0:
|
||||
sz_shape_a = 2390004
|
||||
|
||||
if index_joint >= sz_shape_a * 14:
|
||||
raise ValueError('Invalide SNS joint index')
|
||||
|
||||
self.idx_a = index_joint % sz_shape_a
|
||||
index_joint = index_joint // sz_shape_a
|
||||
if index_joint >= 2:
|
||||
self.shape = 0
|
||||
self.idx_b = (index_joint - 2) // 2
|
||||
self.ls_b = (index_joint - 2) % 2
|
||||
else:
|
||||
self.shape = 1
|
||||
self.gain = (self.gain << 1) + (index_joint & 1)
|
||||
|
||||
else:
|
||||
sz_shape_a = 15158272
|
||||
if index_joint >= sz_shape_a + 1549824:
|
||||
raise ValueError('Invalide SNS joint index')
|
||||
|
||||
if index_joint < sz_shape_a:
|
||||
self.shape = 2
|
||||
self.idx_a = index_joint
|
||||
else:
|
||||
self.shape = 3
|
||||
index_joint -= sz_shape_a
|
||||
self.gain = (self.gain << 1) + (index_joint % 2)
|
||||
self.idx_a = index_joint // 2
|
||||
|
||||
def run(self, x):
|
||||
|
||||
scf = self.unquantize()
|
||||
y = self.spectral_shaping(scf, True, x)
|
||||
|
||||
return y
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_analysis(rng, dt, sr):
|
||||
|
||||
ok = True
|
||||
|
||||
analysis = SnsAnalysis(dt, sr)
|
||||
|
||||
for i in range(10):
|
||||
x = rng.random(T.NE[dt][sr]) * 1e4
|
||||
e = rng.random(min(len(x), 64)) * 1e10
|
||||
|
||||
for att in (0, 1):
|
||||
y = analysis.run(e, att, x)
|
||||
data = analysis.get_data()
|
||||
|
||||
(y_c, data_c) = lc3.sns_analyze(dt, sr, e, att, x)
|
||||
|
||||
for k in data.keys():
|
||||
ok = ok and data_c[k] == data[k]
|
||||
|
||||
ok = ok and lc3.sns_get_nbits() == analysis.get_nbits()
|
||||
ok = ok and np.amax(np.abs(y - y_c)) < 1e-2
|
||||
|
||||
return ok
|
||||
|
||||
def check_synthesis(rng, dt, sr):
|
||||
|
||||
ok = True
|
||||
|
||||
synthesis = SnsSynthesis(dt, sr)
|
||||
|
||||
for i in range(100):
|
||||
|
||||
synthesis.ind_lf = rng.integers(0, 32)
|
||||
synthesis.ind_hf = rng.integers(0, 32)
|
||||
|
||||
shape = rng.integers(0, 4)
|
||||
sz_shape_a = [ 2390004, 2390004, 15158272, 774912 ][shape]
|
||||
sz_shape_b = [ 6, 1, 0, 0 ][shape]
|
||||
synthesis.shape = shape
|
||||
synthesis.gain = rng.integers(0, [ 2, 4, 4, 8 ][shape])
|
||||
synthesis.idx_a = rng.integers(0, sz_shape_a, endpoint=True)
|
||||
synthesis.ls_a = bool(rng.integers(0, 1, endpoint=True))
|
||||
synthesis.idx_b = rng.integers(0, sz_shape_b, endpoint=True)
|
||||
synthesis.ls_b = bool(rng.integers(0, 1, endpoint=True))
|
||||
|
||||
x = rng.random(T.NE[dt][sr]) * 1e4
|
||||
|
||||
y = synthesis.run(x)
|
||||
y_c = lc3.sns_synthesize(dt, sr, synthesis.get_data(), x)
|
||||
ok = ok and np.amax(np.abs(y - y_c)) < 1e0
|
||||
|
||||
return ok
|
||||
|
||||
def check_analysis_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
for i in range(len(C.E_B[dt])):
|
||||
|
||||
scf = lc3.sns_compute_scale_factors(dt, sr, C.E_B[dt][i], False)
|
||||
ok = ok and np.amax(np.abs(scf - C.SCF[dt][i])) < 1e-5
|
||||
|
||||
(lf, hf) = lc3.sns_resolve_codebooks(scf)
|
||||
ok = ok and lf == C.IND_LF[dt][i] and hf == C.IND_HF[dt][i]
|
||||
|
||||
(y, yn, shape, gain) = lc3.sns_quantize(scf, lf, hf)
|
||||
ok = ok and np.any(y[0][:16] - C.SNS_Y0[dt][i] == 0)
|
||||
ok = ok and np.any(y[1][:10] - C.SNS_Y1[dt][i] == 0)
|
||||
ok = ok and np.any(y[2][:16] - C.SNS_Y2[dt][i] == 0)
|
||||
ok = ok and np.any(y[3][:16] - C.SNS_Y3[dt][i] == 0)
|
||||
ok = ok and shape == 2*C.SUBMODE_MSB[dt][i] + C.SUBMODE_LSB[dt][i]
|
||||
ok = ok and gain == C.G_IND[dt][i]
|
||||
|
||||
scf_q = lc3.sns_unquantize(lf, hf, yn[shape], shape, gain)
|
||||
ok = ok and np.amax(np.abs(scf_q - C.SCF_Q[dt][i])) < 1e-5
|
||||
|
||||
x = lc3.sns_spectral_shaping(dt, sr, C.SCF_Q[dt][i], False, C.X[dt][i])
|
||||
ok = ok and np.amax(np.abs(1 - x/C.X_S[dt][i])) < 1e-6
|
||||
|
||||
(x, data) = lc3.sns_analyze(dt, sr, C.E_B[dt][i], False, C.X[dt][i])
|
||||
ok = ok and data['lfcb'] == C.IND_LF[dt][i]
|
||||
ok = ok and data['hfcb'] == C.IND_HF[dt][i]
|
||||
ok = ok and data['shape'] == \
|
||||
2*C.SUBMODE_MSB[dt][i] + C.SUBMODE_LSB[dt][i]
|
||||
ok = ok and data['gain'] == C.G_IND[dt][i]
|
||||
ok = ok and data['idx_a'] == C.IDX_A[dt][i]
|
||||
ok = ok and data['ls_a'] == C.LS_IND_A[dt][i]
|
||||
ok = ok and (C.IDX_B[dt][i] is None or
|
||||
data['idx_b'] == C.IDX_B[dt][i])
|
||||
ok = ok and (C.LS_IND_B[dt][i] is None or
|
||||
data['ls_b'] == C.LS_IND_B[dt][i])
|
||||
ok = ok and np.amax(np.abs(1 - x/C.X_S[dt][i])) < 1e-6
|
||||
|
||||
return ok
|
||||
|
||||
def check_synthesis_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
for i in range(len(C.X_HAT_TNS[dt])):
|
||||
|
||||
data = {
|
||||
'lfcb' : C.IND_LF[dt][i], 'hfcb' : C.IND_HF[dt][i],
|
||||
'shape' : 2*C.SUBMODE_MSB[dt][i] + C.SUBMODE_LSB[dt][i],
|
||||
'gain' : C.G_IND[dt][i],
|
||||
'idx_a' : C.IDX_A[dt][i],
|
||||
'ls_a' : C.LS_IND_A[dt][i],
|
||||
'idx_b' : C.IDX_B[dt][i] if C.IDX_B[dt][i] is not None else 0,
|
||||
'ls_b' : C.LS_IND_B[dt][i] if C.LS_IND_B[dt][i] is not None else 0,
|
||||
}
|
||||
|
||||
x = lc3.sns_synthesize(dt, sr, data, C.X_HAT_TNS[dt][i])
|
||||
ok = ok and np.amax(np.abs(x - C.X_HAT_SNS[dt][i])) < 1e0
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_analysis(rng, dt, sr)
|
||||
ok = ok and check_synthesis(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_analysis_appendix_c(dt)
|
||||
ok = ok and check_synthesis_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
215
test/sns_py.c
Normal file
215
test/sns_py.c
Normal file
@@ -0,0 +1,215 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "lc3.h"
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <sns.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *compute_scale_factors_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
unsigned dt, sr;
|
||||
PyObject *eb_obj, *scf_obj;
|
||||
float *eb, *scf;
|
||||
int att;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOp", &dt, &sr, &eb_obj, &att))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int nb = LC3_MIN(lc3_band_lim[dt][sr][LC3_NUM_BANDS], LC3_NUM_BANDS);
|
||||
|
||||
CTYPES_CHECK("eb", to_1d_ptr(eb_obj, NPY_FLOAT, nb, &eb));
|
||||
scf_obj = new_1d_ptr(NPY_FLOAT, 16, &scf);
|
||||
|
||||
compute_scale_factors(dt, sr, eb, att, scf);
|
||||
|
||||
return Py_BuildValue("N", scf_obj);
|
||||
}
|
||||
|
||||
static PyObject *resolve_codebooks_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *scf_obj;
|
||||
float *scf;
|
||||
int lfcb_idx, hfcb_idx;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &scf_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("eb", to_1d_ptr(scf_obj, NPY_FLOAT, 16, &scf));
|
||||
|
||||
resolve_codebooks(scf, &lfcb_idx, &hfcb_idx);
|
||||
|
||||
return Py_BuildValue("ii", lfcb_idx, hfcb_idx);
|
||||
}
|
||||
|
||||
static PyObject *quantize_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *scf_obj, *y_obj, *yn_obj;
|
||||
float *scf;
|
||||
int lfcb_idx, hfcb_idx;
|
||||
int shape_idx, gain_idx;
|
||||
float (*yn)[16];
|
||||
int (*y)[16];
|
||||
|
||||
if (!PyArg_ParseTuple(args, "Oii", &scf_obj, &lfcb_idx, &hfcb_idx))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("scf", to_1d_ptr(scf_obj, NPY_FLOAT, 16, &scf));
|
||||
CTYPES_CHECK("lfcb_idx", (unsigned)lfcb_idx < 32);
|
||||
CTYPES_CHECK("hfcb_idx", (unsigned)hfcb_idx < 32);
|
||||
|
||||
y_obj = new_2d_ptr(NPY_INT, 4, 16, &y);
|
||||
yn_obj = new_2d_ptr(NPY_FLOAT, 4, 16, &yn);
|
||||
|
||||
quantize(scf, lfcb_idx, hfcb_idx,
|
||||
y, yn, &shape_idx, &gain_idx);
|
||||
|
||||
return Py_BuildValue("NNii", y_obj, yn_obj, shape_idx, gain_idx);
|
||||
}
|
||||
|
||||
static PyObject *unquantize_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *y_obj, *scf_obj;
|
||||
int lfcb_idx, hfcb_idx;
|
||||
int shape, gain;
|
||||
float *y, *scf;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iiOii",
|
||||
&lfcb_idx, &hfcb_idx, &y_obj, &shape, &gain))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("lfcb_idx", (unsigned)lfcb_idx < 32);
|
||||
CTYPES_CHECK("hfcb_idx", (unsigned)hfcb_idx < 32);
|
||||
CTYPES_CHECK("y", to_1d_ptr(y_obj, NPY_FLOAT, 16, &y));
|
||||
CTYPES_CHECK("shape", (unsigned)shape < 4);
|
||||
CTYPES_CHECK("gain",
|
||||
(unsigned)gain < (unsigned)lc3_sns_vq_gains[shape].count);
|
||||
|
||||
scf_obj = new_1d_ptr(NPY_FLOAT, 16, &scf);
|
||||
|
||||
unquantize(lfcb_idx, hfcb_idx, y, shape, gain, scf);
|
||||
|
||||
return Py_BuildValue("N", scf_obj);
|
||||
}
|
||||
|
||||
static PyObject *spectral_shaping_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *scf_q_obj, *x_obj;
|
||||
unsigned dt, sr;
|
||||
float *scf_q, *x;
|
||||
int inv;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOpO", &dt, &sr, &scf_q_obj, &inv, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
CTYPES_CHECK("scf_q", to_1d_ptr(scf_q_obj, NPY_FLOAT, 16, &scf_q));
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
spectral_shaping(dt, sr, scf_q, inv, x, x);
|
||||
|
||||
return Py_BuildValue("O", x_obj);
|
||||
}
|
||||
|
||||
static PyObject *analyze_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *eb_obj, *x_obj;
|
||||
struct lc3_sns_data data = { 0 };
|
||||
unsigned dt, sr;
|
||||
float *eb, *x;
|
||||
int att;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOpO", &dt, &sr, &eb_obj, &att, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
int nb = LC3_MIN(ne, LC3_NUM_BANDS);
|
||||
|
||||
CTYPES_CHECK("eb", to_1d_ptr(eb_obj, NPY_FLOAT, nb, &eb));
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
lc3_sns_analyze(dt, sr, eb, att, &data, x, x);
|
||||
|
||||
return Py_BuildValue("ON", x_obj, new_sns_data(&data));
|
||||
}
|
||||
|
||||
static PyObject *synthesize_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *data_obj, *x_obj;
|
||||
struct lc3_sns_data data;
|
||||
unsigned dt, sr;
|
||||
float *x;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOO", &dt, &sr, &data_obj, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK(NULL, data_obj = to_sns_data(data_obj, &data));
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
lc3_sns_synthesize(dt, sr, &data, x, x);
|
||||
|
||||
return Py_BuildValue("O", x_obj);
|
||||
}
|
||||
|
||||
static PyObject *get_nbits_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
if (!PyArg_ParseTuple(args, ""))
|
||||
return NULL;
|
||||
|
||||
int nbits = lc3_sns_get_nbits();
|
||||
|
||||
return Py_BuildValue("i", nbits);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "sns_compute_scale_factors", compute_scale_factors_py, METH_VARARGS },
|
||||
{ "sns_resolve_codebooks" , resolve_codebooks_py , METH_VARARGS },
|
||||
{ "sns_quantize" , quantize_py , METH_VARARGS },
|
||||
{ "sns_unquantize" , unquantize_py , METH_VARARGS },
|
||||
{ "sns_spectral_shaping" , spectral_shaping_py , METH_VARARGS },
|
||||
{ "sns_analyze" , analyze_py , METH_VARARGS },
|
||||
{ "sns_synthesize" , synthesize_py , METH_VARARGS },
|
||||
{ "sns_get_nbits" , get_nbits_py , METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_sns_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
812
test/spec.py
Normal file
812
test/spec.py
Normal file
@@ -0,0 +1,812 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
import bwdet as m_bwdet
|
||||
import ltpf as m_ltpf
|
||||
import sns as m_sns
|
||||
import tns as m_tns
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class SpectrumQuantization:
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
self.dt = dt
|
||||
self.sr = sr
|
||||
|
||||
def get_gain_offset(self, nbytes):
|
||||
|
||||
g_off = (nbytes * 8) // (10 * (1 + self.sr))
|
||||
g_off = -min(115, g_off) - (105 + 5*(1 + self.sr))
|
||||
|
||||
return g_off
|
||||
|
||||
def get_noise_indices(self, bw, xq, lastnz):
|
||||
|
||||
nf_start = [ 18, 24 ][self.dt]
|
||||
nf_width = [ 2, 3 ][self.dt]
|
||||
|
||||
bw_stop = int([ 80, 160, 240, 320, 400 ][bw] * (T.DT_MS[self.dt] / 10))
|
||||
|
||||
xq = np.append(xq[:lastnz], np.zeros(len(xq) - lastnz))
|
||||
|
||||
i_nf = [ np.all(xq[k-nf_width:min(bw_stop, k+nf_width+1)] == 0)
|
||||
for k in range(nf_start, bw_stop) ]
|
||||
|
||||
return (i_nf, nf_start, bw_stop)
|
||||
|
||||
|
||||
class SpectrumAnalysis(SpectrumQuantization):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
self.reset_off = 0
|
||||
self.nbits_off = 0
|
||||
self.nbits_spec = 0
|
||||
self.nbits_est = 0
|
||||
|
||||
self.g_idx = None
|
||||
|
||||
def estimate_gain(self, x, nbits_spec, nbits_off, g_off):
|
||||
|
||||
nbits = int(nbits_spec + nbits_off + 0.5)
|
||||
|
||||
### Energy (dB) by 4 MDCT coefficients
|
||||
|
||||
e = [ np.sum(x[4*k:4*(k+1)] ** 2) for k in range(len(x) // 4) ]
|
||||
e = 10 * np.log10(2**-31 + np.array(e))
|
||||
|
||||
### Compute gain index
|
||||
|
||||
g_idx = 255
|
||||
|
||||
for i in range(8):
|
||||
factor = 1 << (7 - i)
|
||||
g_idx -= factor
|
||||
tmp = 0
|
||||
iszero = 1
|
||||
|
||||
for ei in e[-1::-1]:
|
||||
|
||||
if ei * 28/20 < g_idx + g_off:
|
||||
if iszero == 0:
|
||||
tmp += 2.7*28/20
|
||||
else:
|
||||
if g_idx + g_off < (ei - 43) * 28/20:
|
||||
tmp += 2*ei*28/20 - 2*(g_idx + g_off) - 36*28/20
|
||||
else:
|
||||
tmp += ei*28/20 - (g_idx + g_off) + 7*28/20
|
||||
iszero = 0
|
||||
|
||||
if tmp > nbits * 1.4 * 28/20 and iszero == 0:
|
||||
g_idx += factor
|
||||
|
||||
### Limit gain index
|
||||
|
||||
x_max = np.amax(np.abs(x))
|
||||
if x_max > 0:
|
||||
g_min = 28 * np.log10(x_max / (32768 - 0.375))
|
||||
g_min = np.ceil(g_min).astype(int) - g_off
|
||||
reset_off = g_idx < g_min
|
||||
else:
|
||||
g_min = 0
|
||||
reset_off = True
|
||||
|
||||
if reset_off:
|
||||
g_idx = g_min
|
||||
|
||||
return (g_idx + g_off, reset_off)
|
||||
|
||||
def quantize(self, g_int, x):
|
||||
|
||||
xg = x / 10 ** (g_int / 28)
|
||||
|
||||
xq = np.where(xg < 0, np.ceil(xg - 0.375), np.floor(xg + 0.375))
|
||||
xq = xq.astype(int)
|
||||
xq = np.fmin(np.fmax(xq, -32768), 32767)
|
||||
|
||||
nz_pairs = np.any([ xq[::2] != 0, xq[1::2] != 0 ], axis=0)
|
||||
lastnz = len(xq) - 2 * np.argmax(nz_pairs[-1::-1])
|
||||
if not np.any(nz_pairs):
|
||||
lastnz = 0
|
||||
|
||||
return (xg, xq, lastnz)
|
||||
|
||||
def compute_nbits(self, nbytes, x, lastnz, nbits_spec):
|
||||
|
||||
mode = 1 if nbytes >= 20 * (3 + self.sr) else 0
|
||||
rate = 512 if nbytes > 20 * (1 + self.sr) else 0
|
||||
|
||||
nbits_est = 0
|
||||
nbits_trunc = 0
|
||||
nbits_lsb = 0
|
||||
lastnz_trunc = 2
|
||||
c = 0
|
||||
|
||||
for n in range(0, lastnz, 2):
|
||||
t = c + rate
|
||||
if n > len(x) // 2:
|
||||
t += 256
|
||||
|
||||
a = abs(x[n ])
|
||||
b = abs(x[n+1])
|
||||
lev = 0
|
||||
while max(a, b) >= 4:
|
||||
nbits_est += \
|
||||
T.AC_SPEC_BITS[T.AC_SPEC_LOOKUP[t + lev*1024]][16];
|
||||
if lev == 0 and mode == 1:
|
||||
nbits_lsb += 2
|
||||
else:
|
||||
nbits_est += 2 * 2048
|
||||
|
||||
a >>= 1
|
||||
b >>= 1
|
||||
lev = min(lev + 1, 3)
|
||||
|
||||
nbits_est += \
|
||||
T.AC_SPEC_BITS[T.AC_SPEC_LOOKUP[t + lev*1024]][a + 4*b]
|
||||
|
||||
a_lsb = abs(x[n ])
|
||||
b_lsb = abs(x[n+1])
|
||||
nbits_est += (min(a_lsb, 1) + min(b_lsb, 1)) * 2048
|
||||
if lev > 0 and mode == 1:
|
||||
a_lsb >>= 1;
|
||||
b_lsb >>= 1;
|
||||
nbits_lsb += int(a_lsb == 0 and x[n ] != 0)
|
||||
nbits_lsb += int(b_lsb == 0 and x[n+1] != 0)
|
||||
|
||||
if (x[n] != 0 or x[n+1] != 0) and \
|
||||
(nbits_est <= nbits_spec * 2048):
|
||||
lastnz_trunc = n + 2;
|
||||
nbits_trunc = nbits_est
|
||||
|
||||
t = 1 + (a + b) * (lev + 1) if lev <= 1 else 12 + lev;
|
||||
c = (c & 15) * 16 + t;
|
||||
|
||||
nbits_est = (nbits_est + 2047) // 2048 + nbits_lsb;
|
||||
nbits_trunc = (nbits_trunc + 2047) // 2048
|
||||
|
||||
self.rate = rate
|
||||
self.lsb_mode = mode == 1 and nbits_est > nbits_spec
|
||||
|
||||
return (nbits_est, nbits_trunc, lastnz_trunc, self.lsb_mode)
|
||||
|
||||
def adjust_gain(self, g_idx, nbits, nbits_spec):
|
||||
|
||||
T1 = [ 80, 230, 380, 530, 680 ]
|
||||
T2 = [ 500, 1025, 1550, 2075, 2600 ]
|
||||
T3 = [ 850, 1700, 2550, 3400, 4250 ]
|
||||
|
||||
sr = self.sr
|
||||
|
||||
if nbits < T1[sr]:
|
||||
delta = (nbits + 48) / 16
|
||||
|
||||
elif nbits < T2[sr]:
|
||||
a = T1[sr] / 16 + 3
|
||||
b = T2[sr] / 48
|
||||
delta = a + (nbits - T1[sr]) * (b - a) / (T2[sr] - T1[sr])
|
||||
|
||||
elif nbits < T3[sr]:
|
||||
delta = nbits / 48
|
||||
|
||||
else:
|
||||
delta = T3[sr] / 48;
|
||||
|
||||
delta = np.fix(delta + 0.5).astype(int)
|
||||
|
||||
if (g_idx < 255 and nbits > nbits_spec) or \
|
||||
(g_idx > 0 and nbits < nbits_spec - (delta + 2)):
|
||||
|
||||
if nbits < nbits_spec - (delta + 2):
|
||||
return - 1
|
||||
|
||||
if g_idx == 254 or nbits < nbits_spec + delta:
|
||||
return 1
|
||||
|
||||
else:
|
||||
return 2
|
||||
|
||||
return 0
|
||||
|
||||
def estimate_noise(self, bw, xq, lastnz, x):
|
||||
|
||||
(i_nf, nf_start, nf_stop) = self.get_noise_indices(bw, xq, lastnz)
|
||||
|
||||
nf = 8 - 16 * sum(abs(x[nf_start:nf_stop] * i_nf)) / sum(i_nf) \
|
||||
if sum(i_nf) > 0 else 0
|
||||
|
||||
return min(max(np.rint(nf).astype(int), 0), 7)
|
||||
|
||||
def run(self,
|
||||
bw, nbytes, nbits_bw, nbits_ltpf, nbits_sns, nbits_tns, x):
|
||||
|
||||
sr = self.sr
|
||||
|
||||
### Bit budget
|
||||
|
||||
nbits_gain = 8
|
||||
nbits_nf = 3
|
||||
|
||||
nbits_ari = np.ceil(np.log2(len(x) / 2)).astype(int)
|
||||
nbits_ari += 3 + min((8*nbytes - 1) // 1280, 2)
|
||||
|
||||
nbits_spec = 8*nbytes - \
|
||||
nbits_bw - nbits_ltpf - nbits_sns - nbits_tns - \
|
||||
nbits_gain - nbits_nf - nbits_ari
|
||||
|
||||
### Global gain estimation
|
||||
|
||||
nbits_off = self.nbits_off + self.nbits_spec - self.nbits_est
|
||||
nbits_off = min(40, max(-40, nbits_off))
|
||||
|
||||
nbits_off = 0 if self.reset_off else \
|
||||
0.8 * self.nbits_off + 0.2 * nbits_off
|
||||
|
||||
g_off = self.get_gain_offset(nbytes)
|
||||
|
||||
(g_int, self.reset_off) = \
|
||||
self.estimate_gain(x, nbits_spec, nbits_off, g_off)
|
||||
self.nbits_off = nbits_off
|
||||
self.nbits_spec = nbits_spec
|
||||
|
||||
### Quantization
|
||||
|
||||
(xg, xq, lastnz) = self.quantize(g_int, x)
|
||||
|
||||
(nbits_est, nbits_trunc, lastnz_trunc, _) = \
|
||||
self.compute_nbits(nbytes, xq, lastnz, nbits_spec)
|
||||
|
||||
self.nbits_est = nbits_est
|
||||
|
||||
### Adjust gain and requantize
|
||||
|
||||
g_adj = self.adjust_gain(g_int - g_off, nbits_est, nbits_spec)
|
||||
|
||||
(xg, xq, lastnz) = self.quantize(g_adj, xg)
|
||||
|
||||
(nbits_est, nbits_trunc, lastnz_trunc, lsb_mode) = \
|
||||
self.compute_nbits(nbytes, xq, lastnz, nbits_spec)
|
||||
|
||||
self.g_idx = g_int + g_adj - g_off
|
||||
self.xq = xq
|
||||
self.lastnz = lastnz_trunc
|
||||
|
||||
self.nbits_residual_max = nbits_spec - nbits_trunc + 4
|
||||
self.xg = xg
|
||||
|
||||
### Noise factor
|
||||
|
||||
self.noise_factor = self.estimate_noise(bw, xq, lastnz, x)
|
||||
|
||||
return (self.xq, self.lastnz, self.xg)
|
||||
|
||||
def store(self, b):
|
||||
|
||||
ne = T.NE[self.dt][self.sr]
|
||||
nbits_lastnz = np.ceil(np.log2(ne/2)).astype(int)
|
||||
|
||||
b.write_uint((self.lastnz >> 1) - 1, nbits_lastnz)
|
||||
b.write_uint(self.lsb_mode, 1)
|
||||
|
||||
def encode(self, bits):
|
||||
|
||||
### Noise factor
|
||||
|
||||
bits.write_uint(self.noise_factor, 3)
|
||||
|
||||
### Quantized data
|
||||
|
||||
lsbs = []
|
||||
|
||||
x = self.xq
|
||||
c = 0
|
||||
|
||||
for n in range(0, self.lastnz, 2):
|
||||
t = c + self.rate
|
||||
if n > len(x) // 2:
|
||||
t += 256
|
||||
|
||||
a = abs(x[n ])
|
||||
b = abs(x[n+1])
|
||||
lev = 0
|
||||
while max(a, b) >= 4:
|
||||
|
||||
bits.ac_encode(
|
||||
T.AC_SPEC_CUMFREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][16],
|
||||
T.AC_SPEC_FREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][16])
|
||||
|
||||
if lev == 0 and self.lsb_mode:
|
||||
lsb_0 = a & 1
|
||||
lsb_1 = b & 1
|
||||
else:
|
||||
bits.write_bit(a & 1)
|
||||
bits.write_bit(b & 1)
|
||||
|
||||
a >>= 1
|
||||
b >>= 1
|
||||
lev = min(lev + 1, 3)
|
||||
|
||||
bits.ac_encode(
|
||||
T.AC_SPEC_CUMFREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][a + 4*b],
|
||||
T.AC_SPEC_FREQ[T.AC_SPEC_LOOKUP[t + lev*1024]][a + 4*b])
|
||||
|
||||
a_lsb = abs(x[n ])
|
||||
b_lsb = abs(x[n+1])
|
||||
if lev > 0 and self.lsb_mode:
|
||||
a_lsb >>= 1
|
||||
b_lsb >>= 1
|
||||
|
||||
lsbs.append(lsb_0)
|
||||
if a_lsb == 0 and x[n+0] != 0:
|
||||
lsbs.append(int(x[n+0] < 0))
|
||||
|
||||
lsbs.append(lsb_1)
|
||||
if b_lsb == 0 and x[n+1] != 0:
|
||||
lsbs.append(int(x[n+1] < 0))
|
||||
|
||||
if a_lsb > 0:
|
||||
bits.write_bit(int(x[n+0] < 0))
|
||||
|
||||
if b_lsb > 0:
|
||||
bits.write_bit(int(x[n+1] < 0))
|
||||
|
||||
t = 1 + (a + b) * (lev + 1) if lev <= 1 else 12 + lev;
|
||||
c = (c & 15) * 16 + t;
|
||||
|
||||
### Residual data
|
||||
|
||||
if self.lsb_mode == 0:
|
||||
nbits_residual = min(bits.get_bits_left(), self.nbits_residual_max)
|
||||
|
||||
for i in range(len(self.xg)):
|
||||
|
||||
if self.xq[i] == 0:
|
||||
continue
|
||||
|
||||
bits.write_bit(self.xg[i] >= self.xq[i])
|
||||
nbits_residual -= 1
|
||||
if nbits_residual <= 0:
|
||||
break
|
||||
|
||||
else:
|
||||
nbits_residual = min(bits.get_bits_left(), len(lsbs))
|
||||
for lsb in lsbs[:nbits_residual]:
|
||||
bits.write_bit(lsb)
|
||||
|
||||
|
||||
class SpectrumSynthesis(SpectrumQuantization):
|
||||
|
||||
def __init__(self, dt, sr):
|
||||
|
||||
super().__init__(dt, sr)
|
||||
|
||||
def fill_noise(self, bw, x, lastnz, f_nf, nf_seed):
|
||||
|
||||
(i_nf, nf_start, nf_stop) = self.get_noise_indices(bw, x, lastnz)
|
||||
|
||||
k_nf = nf_start + np.argwhere(i_nf)
|
||||
l_nf = (8 - f_nf)/16
|
||||
|
||||
for k in k_nf:
|
||||
nf_seed = (13849 + nf_seed * 31821) & 0xffff
|
||||
x[k] = [ -l_nf, l_nf ][nf_seed < 0x8000]
|
||||
|
||||
return x
|
||||
|
||||
def load(self, b):
|
||||
|
||||
ne = T.NE[self.dt][self.sr]
|
||||
nbits_lastnz = np.ceil(np.log2(ne/2)).astype(int)
|
||||
|
||||
self.lastnz = (b.read_uint(nbits_lastnz) + 1) << 1
|
||||
self.lsb_mode = b.read_uint(1)
|
||||
self.g_idx = b.read_uint(8)
|
||||
|
||||
if self.lastnz > ne:
|
||||
raise ValueError('Invalid count of coded samples')
|
||||
|
||||
def decode(self, bits, bw, nbytes):
|
||||
|
||||
### Noise factor
|
||||
|
||||
f_nf = bits.read_uint(3)
|
||||
|
||||
### Quantized data
|
||||
|
||||
x = np.zeros(T.NE[self.dt][self.sr])
|
||||
rate = 512 if nbytes > 20 * (1 + self.sr) else 0
|
||||
|
||||
levs = np.zeros(len(x), dtype=np.int)
|
||||
c = 0
|
||||
|
||||
for n in range(0, self.lastnz, 2):
|
||||
t = c + rate
|
||||
if n > len(x) // 2:
|
||||
t += 256
|
||||
|
||||
for lev in range(14):
|
||||
|
||||
s = t + min(lev, 3) * 1024
|
||||
|
||||
sym = bits.ac_decode(
|
||||
T.AC_SPEC_CUMFREQ[T.AC_SPEC_LOOKUP[s]],
|
||||
T.AC_SPEC_FREQ[T.AC_SPEC_LOOKUP[s]])
|
||||
|
||||
if sym < 16:
|
||||
break
|
||||
|
||||
if self.lsb_mode == 0 or lev > 0:
|
||||
x[n ] += bits.read_bit() << lev
|
||||
x[n+1] += bits.read_bit() << lev
|
||||
|
||||
if lev >= 14:
|
||||
raise ValueError('Out of range value')
|
||||
|
||||
a = sym % 4
|
||||
b = sym // 4
|
||||
|
||||
levs[n ] = lev
|
||||
levs[n+1] = lev
|
||||
|
||||
x[n ] += a << lev
|
||||
x[n+1] += b << lev
|
||||
|
||||
if x[n] and bits.read_bit():
|
||||
x[n] = -x[n]
|
||||
|
||||
if x[n+1] and bits.read_bit():
|
||||
x[n+1] = -x[n+1]
|
||||
|
||||
lev = min(lev, 3)
|
||||
t = 1 + (a + b) * (lev + 1) if lev <= 1 else 12 + lev;
|
||||
c = (c & 15) * 16 + t;
|
||||
|
||||
### Residual data
|
||||
|
||||
nbits_residual = bits.get_bits_left()
|
||||
if nbits_residual < 0:
|
||||
raise ValueError('Out of bitstream')
|
||||
|
||||
if self.lsb_mode == 0:
|
||||
|
||||
xr = np.zeros(len(x), dtype=np.bool)
|
||||
|
||||
for i in range(len(x)):
|
||||
|
||||
if nbits_residual <= 0:
|
||||
xr.resize(i)
|
||||
break
|
||||
|
||||
if x[i] == 0:
|
||||
continue
|
||||
|
||||
xr[i] = bits.read_bit()
|
||||
nbits_residual -= 1
|
||||
|
||||
else:
|
||||
|
||||
for i in range(len(levs)):
|
||||
|
||||
if nbits_residual <= 0:
|
||||
break
|
||||
|
||||
if levs[i] <= 0:
|
||||
continue
|
||||
|
||||
lsb = bits.read_bit()
|
||||
nbits_residual -= 1
|
||||
if not lsb:
|
||||
continue
|
||||
|
||||
sign = int(x[i] < 0)
|
||||
|
||||
if x[i] == 0:
|
||||
|
||||
if nbits_residual <= 0:
|
||||
break
|
||||
|
||||
sign = bits.read_bit()
|
||||
nbits_residual -= 1
|
||||
|
||||
x[i] += [ 1, -1 ][sign]
|
||||
|
||||
### Set residual and noise
|
||||
|
||||
nf_seed = sum(abs(x.astype(np.int)) * range(len(x)))
|
||||
|
||||
zero_frame = (self.lastnz <= 2 and x[0] == 0 and x[1] == 0
|
||||
and self.g_idx <= 0 and nf >= 7)
|
||||
|
||||
if self.lsb_mode == 0:
|
||||
|
||||
for i in range(len(xr)):
|
||||
|
||||
if x[i] and xr[i] == 0:
|
||||
x[i] += [ -0.1875, -0.3125 ][x[i] < 0]
|
||||
elif x[i]:
|
||||
x[i] += [ 0.1875, 0.3125 ][x[i] > 0]
|
||||
|
||||
if not zero_frame:
|
||||
x = self.fill_noise(bw, x, self.lastnz, f_nf, nf_seed)
|
||||
|
||||
### Rescale coefficients
|
||||
|
||||
g_int = self.get_gain_offset(nbytes) + self.g_idx
|
||||
x *= 10 ** (g_int / 28)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def initial_state():
|
||||
return { 'nbits_off' : 0.0, 'nbits_spare' : 0 }
|
||||
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_estimate_gain(rng, dt, sr):
|
||||
|
||||
ne = T.I[dt][sr][-1]
|
||||
ok = True
|
||||
|
||||
analysis = SpectrumAnalysis(dt, sr)
|
||||
|
||||
for i in range(10):
|
||||
x = rng.random(ne) * i * 1e2
|
||||
|
||||
nbytes = 20 + int(rng.random() * 100)
|
||||
nbits_budget = 8 * nbytes - int(rng.random() * 100)
|
||||
nbits_off = rng.random() * 10
|
||||
g_off = 10 - int(rng.random() * 20)
|
||||
|
||||
(g_int, reset_off) = \
|
||||
analysis.estimate_gain(x, nbits_budget, nbits_off, g_off)
|
||||
|
||||
(g_int_c, reset_off_c) = lc3.spec_estimate_gain(
|
||||
dt, sr, x, nbits_budget, nbits_off, -g_off)
|
||||
|
||||
ok = ok and g_int_c == g_int
|
||||
ok = ok and reset_off_c == reset_off
|
||||
|
||||
return ok
|
||||
|
||||
def check_quantization(rng, dt, sr):
|
||||
|
||||
ne = T.I[dt][sr][-1]
|
||||
ok = True
|
||||
|
||||
analysis = SpectrumAnalysis(dt, sr)
|
||||
|
||||
for g_int in range(-128, 128):
|
||||
|
||||
x = rng.random(ne) * 1e2
|
||||
nbytes = 20 + int(rng.random() * 30)
|
||||
|
||||
(xg, xq, nq) = analysis.quantize(g_int, x)
|
||||
(xg_c, xq_c, nq_c) = lc3.spec_quantize(dt, sr, g_int, x)
|
||||
|
||||
ok = ok and np.amax(np.abs(1 - xg_c/xg)) < 1e-6
|
||||
ok = ok and np.any(abs(xq_c - xq) < 1)
|
||||
ok = ok and nq_c == nq
|
||||
|
||||
return ok
|
||||
|
||||
def check_compute_nbits(rng, dt, sr):
|
||||
|
||||
ne = T.I[dt][sr][-1]
|
||||
ok = True
|
||||
|
||||
analysis = SpectrumAnalysis(dt, sr)
|
||||
|
||||
for nbytes in range(20, 150):
|
||||
|
||||
nbits_budget = nbytes * 8 - int(rng.random() * 100)
|
||||
xq = (rng.random(ne) * 8).astype(int)
|
||||
nq = ne // 2 + int(rng.random() * ne // 2)
|
||||
|
||||
nq = nq - nq % 2
|
||||
if xq[nq-2] == 0 and xq[nq-1] == 0:
|
||||
xq[nq-2] = 1
|
||||
|
||||
(nbits, nbits_trunc, nq_trunc, lsb_mode) = \
|
||||
analysis.compute_nbits(nbytes, xq, nq, nbits_budget)
|
||||
|
||||
(nbits_c, nq_c, _) = \
|
||||
lc3.spec_compute_nbits(dt, sr, nbytes, xq, nq, 0)
|
||||
|
||||
(nbits_trunc_c, nq_trunc_c, lsb_mode_c) = \
|
||||
lc3.spec_compute_nbits(dt, sr, nbytes, xq, nq, nbits_budget)
|
||||
|
||||
ok = ok and nbits_c == nbits
|
||||
ok = ok and nbits_trunc_c == nbits_trunc
|
||||
ok = ok and nq_trunc_c == nq_trunc
|
||||
ok = ok and lsb_mode_c == lsb_mode
|
||||
|
||||
return ok
|
||||
|
||||
def check_adjust_gain(rng, dt, sr):
|
||||
|
||||
ne = T.I[dt][sr][-1]
|
||||
ok = True
|
||||
|
||||
analysis = SpectrumAnalysis(dt, sr)
|
||||
|
||||
for g_idx in (0, 128, 254, 255):
|
||||
for nbits in range(50, 5000, 5):
|
||||
nbits_budget = int(nbits * (0.95 + (rng.random() * 0.1)))
|
||||
|
||||
g_adj = analysis.adjust_gain(g_idx, nbits, nbits_budget)
|
||||
|
||||
g_adj_c = lc3.spec_adjust_gain(sr, g_idx, nbits, nbits_budget)
|
||||
|
||||
ok = ok and g_adj_c == g_adj
|
||||
|
||||
return ok
|
||||
|
||||
def check_unit(rng, dt, sr):
|
||||
|
||||
ns = T.NS[dt][sr]
|
||||
ne = T.I[dt][sr][-1]
|
||||
ok = True
|
||||
|
||||
state_c = initial_state()
|
||||
|
||||
bwdet = m_bwdet.BandwidthDetector(dt, sr)
|
||||
ltpf = m_ltpf.LtpfAnalysis(dt, sr)
|
||||
tns = m_tns.TnsAnalysis(dt)
|
||||
sns = m_sns.SnsAnalysis(dt, sr)
|
||||
analysis = SpectrumAnalysis(dt, sr)
|
||||
|
||||
nbytes = 100
|
||||
|
||||
for i in range(10):
|
||||
|
||||
x = rng.random(ns) * 1e4
|
||||
e = rng.random(min(len(x), 64)) * 1e10
|
||||
|
||||
bwdet.run(e)
|
||||
pitch_present = ltpf.run(x)
|
||||
tns.run(x[:ne], sr, False, nbytes)
|
||||
sns.run(e, False, x)
|
||||
|
||||
(xq, nq, _) = analysis.run(sr, nbytes, bwdet.get_nbits(),
|
||||
ltpf.get_nbits(), sns.get_nbits(), tns.get_nbits(), x[:ne])
|
||||
|
||||
(_, xq_c, side_c) = lc3.spec_analyze(
|
||||
dt, sr, nbytes, pitch_present, tns.get_data(), state_c, x[:ne])
|
||||
|
||||
ok = ok and side_c['g_idx'] == analysis.g_idx
|
||||
ok = ok and side_c['nq'] == nq
|
||||
ok = ok and np.any(abs(xq_c - xq) < 1)
|
||||
|
||||
return ok
|
||||
|
||||
def check_noise(rng, dt, bw):
|
||||
|
||||
ne = T.NE[dt][bw]
|
||||
ok = True
|
||||
|
||||
analysis = SpectrumAnalysis(dt, bw)
|
||||
|
||||
for i in range(10):
|
||||
|
||||
xq = ((rng.random(ne) - 0.5) * 10 ** (0.5)).astype(int)
|
||||
nq = ne - int(rng.random() * 5)
|
||||
x = rng.random(ne) * i * 1e-1
|
||||
|
||||
nf = analysis.estimate_noise(bw, xq, nq, x)
|
||||
nf_c = lc3.spec_estimate_noise(dt, bw, xq, nq, x)
|
||||
|
||||
ok = ok and nf_c == nf
|
||||
|
||||
return ok
|
||||
|
||||
def check_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ne = T.NE[dt][sr]
|
||||
ok = True
|
||||
|
||||
state_c = initial_state()
|
||||
|
||||
for i in range(len(C.X_F[dt])):
|
||||
|
||||
g_int = lc3.spec_estimate_gain(dt, sr, C.X_F[dt][i],
|
||||
C.NBITS_SPEC[dt][i], C.NBITS_OFFSET[dt][i], -C.GG_OFF[dt][i])[0]
|
||||
ok = ok and g_int == C.GG_IND[dt][i] + C.GG_OFF[dt][i]
|
||||
|
||||
(_, xq, nq) = lc3.spec_quantize(dt, sr,
|
||||
C.GG_IND[dt][i] + C.GG_OFF[dt][i], C.X_F[dt][i])
|
||||
ok = ok and np.any((xq - C.X_Q[dt][i]) == 0)
|
||||
ok = ok and nq == C.LASTNZ[dt][i]
|
||||
|
||||
nbits = lc3.spec_compute_nbits(dt, sr,
|
||||
C.NBYTES[dt], C.X_Q[dt][i], C.LASTNZ[dt][i], 0)[0]
|
||||
ok = ok and nbits == C.NBITS_EST[dt][i]
|
||||
|
||||
g_adj = lc3.spec_adjust_gain(sr,
|
||||
C.GG_IND[dt][i], C.NBITS_EST[dt][i], C.NBITS_SPEC[dt][i])
|
||||
ok = ok and g_adj == C.GG_IND_ADJ[dt][i] - C.GG_IND[dt][i]
|
||||
|
||||
if C.GG_IND_ADJ[dt][i] != C.GG_IND[dt][i]:
|
||||
|
||||
(_, xq, nq) = lc3.spec_quantize(dt, sr,
|
||||
C.GG_IND_ADJ[dt][i] + C.GG_OFF[dt][i], C.X_F[dt][i])
|
||||
lastnz = C.LASTNZ_REQ[dt][i]
|
||||
ok = ok and np.any(((xq - C.X_Q_REQ[dt][i])[:lastnz]) == 0)
|
||||
|
||||
tns_data = {
|
||||
'nfilters' : C.NUM_TNS_FILTERS[dt][i],
|
||||
'lpc_weighting' : [ True, True ],
|
||||
'rc_order' : [ C.RC_ORDER[dt][i][0], 0 ],
|
||||
'rc' : [ C.RC_I_1[dt][i] - 8, np.zeros(8, dtype = np.int) ]
|
||||
}
|
||||
|
||||
(x, xq, side) = lc3.spec_analyze(dt, sr, C.NBYTES[dt],
|
||||
C.PITCH_PRESENT[dt][i], tns_data, state_c, C.X_F[dt][i])
|
||||
|
||||
ok = ok and np.abs(state_c['nbits_off'] - C.NBITS_OFFSET[dt][i]) < 1e-5
|
||||
if C.GG_IND_ADJ[dt][i] != C.GG_IND[dt][i]:
|
||||
xq = C.X_Q_REQ[dt][i]
|
||||
nq = C.LASTNZ_REQ[dt][i]
|
||||
ok = ok and side['g_idx'] == C.GG_IND_ADJ[dt][i]
|
||||
ok = ok and side['nq'] == nq
|
||||
ok = ok and np.any(((xq[:nq] - xq[:nq])) == 0)
|
||||
else:
|
||||
xq = C.X_Q[dt][i]
|
||||
nq = C.LASTNZ[dt][i]
|
||||
ok = ok and side['g_idx'] == C.GG_IND[dt][i]
|
||||
ok = ok and side['nq'] == nq
|
||||
ok = ok and np.any((xq[:nq] - C.X_Q[dt][i][:nq]) == 0)
|
||||
ok = ok and side['lsb_mode'] == C.LSB_MODE[dt][i]
|
||||
|
||||
gg = C.GG[dt][i] if C.GG_IND_ADJ[dt][i] == C.GG_IND[dt][i] \
|
||||
else C.GG_ADJ[dt][i]
|
||||
|
||||
nf = lc3.spec_estimate_noise(dt, C.P_BW[dt][i],
|
||||
xq, nq, C.X_F[dt][i] / gg)
|
||||
ok = ok and nf == C.F_NF[dt][i]
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_estimate_gain(rng, dt, sr)
|
||||
ok = ok and check_quantization(rng, dt, sr)
|
||||
ok = ok and check_compute_nbits(rng, dt, sr)
|
||||
ok = ok and check_adjust_gain(rng, dt, sr)
|
||||
ok = ok and check_unit(rng, dt, sr)
|
||||
ok = ok and check_noise(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
192
test/spec_py.c
Normal file
192
test/spec_py.c
Normal file
@@ -0,0 +1,192 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "lc3.h"
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <spec.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *estimate_gain_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj;
|
||||
unsigned dt, sr;
|
||||
float *x;
|
||||
int nbits_budget;
|
||||
float nbits_off;
|
||||
int g_off;
|
||||
bool reset_off;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOifi", &dt, &sr,
|
||||
&x_obj, &nbits_budget, &nbits_off, &g_off))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
int g_int = estimate_gain(dt, sr,
|
||||
x, nbits_budget, nbits_off, g_off, &reset_off);
|
||||
|
||||
return Py_BuildValue("ii", g_int, reset_off);
|
||||
}
|
||||
|
||||
static PyObject *adjust_gain_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
unsigned sr;
|
||||
int g_idx, nbits, nbits_budget;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "Iiii", &sr, &g_idx, &nbits, &nbits_budget))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK("g_idx", g_idx >= 0 && g_idx <= 255);
|
||||
|
||||
g_idx = adjust_gain(sr, g_idx, nbits, nbits_budget);
|
||||
|
||||
return Py_BuildValue("i", g_idx);
|
||||
}
|
||||
|
||||
static PyObject *quantize_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj, *xq_obj;
|
||||
unsigned dt, sr;
|
||||
float *x;
|
||||
int16_t *xq;
|
||||
int g_int, nq;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIiO", &dt, &sr, &g_int, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
CTYPES_CHECK("g_int", g_int >= -255 && g_int <= 255);
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
xq_obj = new_1d_ptr(NPY_INT16, ne, &xq);
|
||||
|
||||
quantize(dt, sr, g_int, x, xq, &nq);
|
||||
|
||||
return Py_BuildValue("ONi", x_obj, xq_obj, nq);
|
||||
}
|
||||
|
||||
static PyObject *compute_nbits_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *xq_obj;
|
||||
unsigned dt, sr, nbytes;
|
||||
int16_t *xq;
|
||||
int nq, nbits_budget;
|
||||
bool lsb_mode;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIIOii", &dt, &sr,
|
||||
&nbytes, &xq_obj, &nq, &nbits_budget))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
CTYPES_CHECK("xq", xq_obj = to_1d_ptr(xq_obj, NPY_INT16, ne, &xq));
|
||||
|
||||
int nbits = compute_nbits(
|
||||
dt, sr, nbytes, xq, &nq, nbits_budget, &lsb_mode);
|
||||
|
||||
return Py_BuildValue("iii", nbits, nq, lsb_mode);
|
||||
}
|
||||
|
||||
static PyObject *analyze_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *tns_obj, *spec_obj, *x_obj, *xq_obj;
|
||||
struct lc3_tns_data tns = { 0 };
|
||||
struct lc3_spec_analysis spec = { 0 };
|
||||
struct lc3_spec_side side = { 0 };
|
||||
unsigned dt, sr, nbytes;
|
||||
int pitch;
|
||||
float *x;
|
||||
int16_t *xq;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIIpOOO", &dt, &sr, &nbytes,
|
||||
&pitch, &tns_obj, &spec_obj, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)sr < LC3_NUM_SRATE);
|
||||
|
||||
int ne = LC3_NE(dt, sr);
|
||||
|
||||
CTYPES_CHECK(NULL, tns_obj = to_tns_data(tns_obj, &tns));
|
||||
CTYPES_CHECK(NULL, spec_obj = to_spec_analysis(spec_obj, &spec));
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
xq_obj = new_1d_ptr(NPY_INT16, ne, &xq);
|
||||
|
||||
lc3_spec_analyze(dt, sr, nbytes, pitch, &tns, &spec, x, xq, &side);
|
||||
|
||||
from_spec_analysis(spec_obj, &spec);
|
||||
return Py_BuildValue("ONN", x_obj, xq_obj, new_spec_side(&side));
|
||||
}
|
||||
|
||||
static PyObject *estimate_noise_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj, *xq_obj;
|
||||
unsigned dt, bw;
|
||||
int16_t *xq;
|
||||
float *x;
|
||||
int nq;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOIO", &dt, &bw, &xq_obj, &nq, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("bw", (unsigned)bw < LC3_NUM_BANDWIDTH);
|
||||
|
||||
int ne = LC3_NE(dt, bw);
|
||||
|
||||
CTYPES_CHECK("xq", xq_obj = to_1d_ptr(xq_obj, NPY_INT16, ne, &xq));
|
||||
CTYPES_CHECK("x" , x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x ));
|
||||
|
||||
int noise_factor = estimate_noise(dt, bw, xq, nq, x);
|
||||
|
||||
return Py_BuildValue("i", noise_factor);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "spec_estimate_gain" , estimate_gain_py , METH_VARARGS },
|
||||
{ "spec_adjust_gain" , adjust_gain_py , METH_VARARGS },
|
||||
{ "spec_quantize" , quantize_py , METH_VARARGS },
|
||||
{ "spec_compute_nbits" , compute_nbits_py , METH_VARARGS },
|
||||
{ "spec_analyze" , analyze_py , METH_VARARGS },
|
||||
{ "spec_estimate_noise", estimate_noise_py, METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_spec_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
2709
test/tables.py
Normal file
2709
test/tables.py
Normal file
File diff suppressed because it is too large
Load Diff
440
test/tns.py
Normal file
440
test/tns.py
Normal file
@@ -0,0 +1,440 @@
|
||||
#
|
||||
# Copyright 2022 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 numpy as np
|
||||
|
||||
import build.lc3 as lc3
|
||||
import tables as T, appendix_c as C
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
class Tns:
|
||||
|
||||
SUB_LIM_10M_NB = [ [ 12, 34, 57, 80 ] ]
|
||||
SUB_LIM_10M_WB = [ [ 12, 61, 110, 160 ] ]
|
||||
SUB_LIM_10M_SSWB = [ [ 12, 88, 164, 240 ] ]
|
||||
SUB_LIM_10M_SWB = [ [ 12, 61, 110, 160 ], [ 160, 213, 266, 320 ] ]
|
||||
SUB_LIM_10M_FB = [ [ 12, 74, 137, 200 ], [ 200, 266, 333, 400 ] ]
|
||||
|
||||
SUB_LIM_10M = [ SUB_LIM_10M_NB, SUB_LIM_10M_WB,
|
||||
SUB_LIM_10M_SSWB, SUB_LIM_10M_SWB, SUB_LIM_10M_FB ]
|
||||
|
||||
SUB_LIM_7M5_NB = [ [ 9, 26, 43, 60 ] ]
|
||||
SUB_LIM_7M5_WB = [ [ 9, 46, 83, 120 ] ]
|
||||
SUB_LIM_7M5_SSWB = [ [ 9, 66, 123, 180 ] ]
|
||||
SUB_LIM_7M5_SWB = [ [ 9, 46, 82, 120 ], [ 120, 159, 200, 240 ] ]
|
||||
SUB_LIM_7M5_FB = [ [ 9, 56, 103, 150 ], [ 150, 200, 250, 300 ] ]
|
||||
|
||||
SUB_LIM_7M5 = [ SUB_LIM_7M5_NB, SUB_LIM_7M5_WB,
|
||||
SUB_LIM_7M5_SSWB, SUB_LIM_7M5_SWB, SUB_LIM_7M5_FB ]
|
||||
|
||||
SUB_LIM = [ SUB_LIM_7M5, SUB_LIM_10M ]
|
||||
|
||||
FREQ_LIM_10M_NB = [ 12, 80 ]
|
||||
FREQ_LIM_10M_WB = [ 12, 160 ]
|
||||
FREQ_LIM_10M_SSWB = [ 12, 240 ]
|
||||
FREQ_LIM_10M_SWB = [ 12, 160, 320 ]
|
||||
FREQ_LIM_10M_FB = [ 12, 200, 400 ]
|
||||
|
||||
FREQ_LIM_10M = [ FREQ_LIM_10M_NB, FREQ_LIM_10M_WB,
|
||||
FREQ_LIM_10M_SSWB, FREQ_LIM_10M_SWB, FREQ_LIM_10M_FB ]
|
||||
|
||||
FREQ_LIM_7M5_NB = [ 9, 60 ]
|
||||
FREQ_LIM_7M5_WB = [ 9, 120 ]
|
||||
FREQ_LIM_7M5_SSWB = [ 9, 180 ]
|
||||
FREQ_LIM_7M5_SWB = [ 9, 120, 240 ]
|
||||
FREQ_LIM_7M5_FB = [ 9, 150, 300 ]
|
||||
|
||||
FREQ_LIM_7M5 = [ FREQ_LIM_7M5_NB, FREQ_LIM_7M5_WB,
|
||||
FREQ_LIM_7M5_SSWB, FREQ_LIM_7M5_SWB, FREQ_LIM_7M5_FB ]
|
||||
|
||||
FREQ_LIM = [ FREQ_LIM_7M5, FREQ_LIM_10M ]
|
||||
|
||||
def __init__(self, dt):
|
||||
|
||||
self.dt = dt
|
||||
|
||||
(self.nfilters, self.lpc_weighting, self.rc_order, self.rc) = \
|
||||
(None, None, None, None)
|
||||
|
||||
def get_data(self):
|
||||
|
||||
return { 'nfilters' : self.nfilters,
|
||||
'lpc_weighting' : self.lpc_weighting,
|
||||
'rc_order' : self.rc_order, 'rc' : self.rc - 8 }
|
||||
|
||||
def get_nbits(self):
|
||||
|
||||
lpc_weighting = self.lpc_weighting
|
||||
nbits = 0
|
||||
|
||||
for f in range(self.nfilters):
|
||||
rc_order = self.rc_order[f]
|
||||
rc = self.rc[f]
|
||||
|
||||
nbits_order = T.TNS_ORDER_BITS[int(lpc_weighting)][rc_order]
|
||||
nbits_coef = sum([ T.TNS_COEF_BITS[k][rc[k]]
|
||||
for k in range(rc_order) ])
|
||||
|
||||
nbits += ((2048 + nbits_order + nbits_coef) + 2047) >> 11
|
||||
|
||||
return nbits
|
||||
|
||||
|
||||
class TnsAnalysis(Tns):
|
||||
|
||||
def __init__(self, dt):
|
||||
|
||||
super().__init__(dt)
|
||||
|
||||
def compute_lpc_coeffs(self, bw, f, x):
|
||||
|
||||
### Normalized autocorrelation function
|
||||
|
||||
S = Tns.SUB_LIM[self.dt][bw][f]
|
||||
|
||||
r = np.append([ 3 ], np.zeros(8))
|
||||
e = [ sum(x[S[s]:S[s+1]] ** 2) for s in range(3) ]
|
||||
|
||||
for k in range(len(r) if sum(e) > 0 else 0):
|
||||
c = [ np.dot(x[S[s]:S[s+1]-k], x[S[s]+k:S[s+1]])
|
||||
for s in range(3) ]
|
||||
|
||||
r[k] = np.sum( np.array(c) / np.array(e) )
|
||||
|
||||
r *= np.exp(-0.5 * (0.02 * np.pi * np.arange(9)) ** 2)
|
||||
|
||||
### Levinson-Durbin recursion
|
||||
|
||||
err = r[0]
|
||||
a = np.ones(len(r))
|
||||
|
||||
for k in range(1, len(a)):
|
||||
|
||||
rc = -sum(a[:k] * r[k:0:-1]) / err
|
||||
|
||||
a[1:k] += rc * a[k-1:0:-1]
|
||||
a[k] = rc
|
||||
|
||||
err *= 1 - rc ** 2
|
||||
|
||||
return (r[0] / err, a)
|
||||
|
||||
def lpc_weighting(self, pred_gain, a):
|
||||
|
||||
gamma = 1 - (1 - 0.85) * (2 - pred_gain) / (2 - 1.5)
|
||||
return a * np.power(gamma, np.arange(len(a)))
|
||||
|
||||
def coeffs_reflexion(self, a):
|
||||
|
||||
rc = np.zeros(8)
|
||||
b = a.copy()
|
||||
|
||||
for k in range(8, 0, -1):
|
||||
rc[k-1] = b[k]
|
||||
e = 1 - rc[k-1] ** 2
|
||||
b[1:k] = (b[1:k] - rc[k-1] * b[k-1:0:-1]) / e
|
||||
|
||||
return rc
|
||||
|
||||
def quantization(self, rc, lpc_weighting):
|
||||
|
||||
delta = np.pi / 17
|
||||
rc_i = np.rint(np.arcsin(rc) / delta).astype(int) + 8
|
||||
rc_q = np.sin(delta * (rc_i - 8))
|
||||
|
||||
rc_order = len(rc_i) - np.argmin(rc_i[::-1] == 8)
|
||||
|
||||
return (rc_order, rc_q, rc_i)
|
||||
|
||||
def filtering(self, st, x, rc_order, rc):
|
||||
|
||||
y = np.empty(len(x))
|
||||
|
||||
for i in range(len(x)):
|
||||
|
||||
xi = x[i]
|
||||
s1 = xi
|
||||
|
||||
for k in range(rc_order):
|
||||
s0 = st[k]
|
||||
st[k] = s1
|
||||
|
||||
s1 = rc[k] * xi + s0
|
||||
xi += rc[k] * s0
|
||||
|
||||
y[i] = xi
|
||||
|
||||
return y
|
||||
|
||||
def run(self, x, bw, nn_flag, nbytes):
|
||||
|
||||
fstate = np.zeros(8)
|
||||
y = x.copy()
|
||||
|
||||
self.nfilters = len(Tns.SUB_LIM[self.dt][bw])
|
||||
self.lpc_weighting = nbytes * 8 < 48 * T.DT_MS[self.dt]
|
||||
self.rc_order = np.zeros(2, dtype=np.int)
|
||||
self.rc = np.zeros((2, 8), dtype=np.int)
|
||||
|
||||
for f in range(self.nfilters):
|
||||
|
||||
(pred_gain, a) = self.compute_lpc_coeffs(bw, f, x)
|
||||
|
||||
tns_off = pred_gain <= 1.5 or nn_flag
|
||||
if tns_off:
|
||||
continue
|
||||
|
||||
if self.lpc_weighting and pred_gain < 2:
|
||||
a = self.lpc_weighting(pred_gain, a)
|
||||
|
||||
rc = self.coeffs_reflexion(a)
|
||||
|
||||
(rc_order, rc_q, rc_i) = \
|
||||
self.quantization(rc, self.lpc_weighting)
|
||||
|
||||
self.rc_order[f] = rc_order
|
||||
self.rc[f] = rc_i
|
||||
|
||||
if rc_order > 0:
|
||||
i0 = Tns.FREQ_LIM[self.dt][bw][f]
|
||||
i1 = Tns.FREQ_LIM[self.dt][bw][f+1]
|
||||
|
||||
y[i0:i1] = self.filtering(
|
||||
fstate, x[i0:i1], rc_order, rc_q)
|
||||
|
||||
return y
|
||||
|
||||
def store(self, b):
|
||||
|
||||
for f in range(self.nfilters):
|
||||
lpc_weighting = self.lpc_weighting[f]
|
||||
rc_order = self.rc_order[f]
|
||||
rc = self.rc[f]
|
||||
|
||||
b.write_bit(min(rc_order, 1))
|
||||
|
||||
if rc_order > 0:
|
||||
b.ac_encode(
|
||||
T.TNS_ORDER_CUMFREQ[int(lpc_weighting)][rc_order-1],
|
||||
T.TNS_ORDER_FREQ[int(lpc_weighting)][rc_order-1] )
|
||||
|
||||
for k in range(rc_order):
|
||||
b.ac_encode(T.TNS_COEF_CUMFREQ[k][rc[k]],
|
||||
T.TNS_COEF_FREQ[k][rc[k]] )
|
||||
|
||||
|
||||
class TnsSynthesis(Tns):
|
||||
|
||||
def filtering(self, st, x, rc_order, rc):
|
||||
|
||||
y = x.copy()
|
||||
|
||||
for i in range(len(x)):
|
||||
|
||||
xi = x[i] - rc[rc_order-1] * st[rc_order-1]
|
||||
for k in range(rc_order-2, -1, -1):
|
||||
xi -= rc[k] * st[k]
|
||||
st[k+1] = xi * rc[k] + st[k];
|
||||
st[0] = xi;
|
||||
|
||||
y[i] = xi
|
||||
|
||||
return y
|
||||
|
||||
def load(self, b, bw, nbytes):
|
||||
|
||||
self.nfilters = len(Tns.SUB_LIM[self.dt][bw])
|
||||
self.lpc_weighting = nbytes * 8 < 48 * T.DT_MS[self.dt]
|
||||
self.rc_order = np.zeros(2, dtype=np.int)
|
||||
self.rc = 8 * np.ones((2, 8), dtype=np.int)
|
||||
|
||||
for f in range(self.nfilters):
|
||||
|
||||
if not b.read_bit():
|
||||
continue
|
||||
|
||||
rc_order = 1 + b.ac_decode(
|
||||
T.TNS_ORDER_CUMFREQ[int(self.lpc_weighting)],
|
||||
T.TNS_ORDER_FREQ[int(self.lpc_weighting)])
|
||||
|
||||
self.rc_order[f] = rc_order
|
||||
|
||||
for k in range(rc_order):
|
||||
rc = b.ac_decode(T.TNS_COEF_CUMFREQ[k], T.TNS_COEF_FREQ[k])
|
||||
self.rc[f][k] = rc
|
||||
|
||||
def run(self, x, bw):
|
||||
|
||||
fstate = np.zeros(8)
|
||||
y = x.copy()
|
||||
|
||||
for f in range(self.nfilters):
|
||||
|
||||
rc_order = self.rc_order[f]
|
||||
rc = np.sin((np.pi / 17) * (self.rc[f] - 8))
|
||||
|
||||
if rc_order > 0:
|
||||
i0 = Tns.FREQ_LIM[self.dt][bw][f]
|
||||
i1 = Tns.FREQ_LIM[self.dt][bw][f+1]
|
||||
|
||||
y[i0:i1] = self.filtering(
|
||||
fstate, x[i0:i1], rc_order, rc)
|
||||
|
||||
return y
|
||||
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
|
||||
def check_analysis(rng, dt, bw):
|
||||
|
||||
ok = True
|
||||
|
||||
analysis = TnsAnalysis(dt)
|
||||
nbytes_lim = int((48 * T.DT_MS[dt]) // 8)
|
||||
|
||||
for i in range(10):
|
||||
x = rng.random(T.NE[dt][bw]) * 1e2
|
||||
x = pow(x, .5 + i/5)
|
||||
|
||||
for nn_flag in (True, False):
|
||||
for nbytes in (nbytes_lim, nbytes_lim + 1):
|
||||
|
||||
y = analysis.run(x, bw, nn_flag, nbytes)
|
||||
(y_c, data_c) = lc3.tns_analyze(dt, bw, nn_flag, nbytes, x)
|
||||
|
||||
ok = ok and data_c['nfilters'] == analysis.nfilters
|
||||
ok = ok and data_c['lpc_weighting'] == analysis.lpc_weighting
|
||||
for f in range(analysis.nfilters):
|
||||
rc_order = analysis.rc_order[f]
|
||||
rc_order_c = data_c['rc_order'][f]
|
||||
rc_c = 8 + data_c['rc'][f]
|
||||
ok = ok and rc_order_c == rc_order
|
||||
ok = ok and not np.any((rc_c - analysis.rc[f])[:rc_order])
|
||||
|
||||
ok = ok and lc3.tns_get_nbits(data_c) == analysis.get_nbits()
|
||||
ok = ok and np.amax(np.abs(y_c - y)) < 1e-2
|
||||
|
||||
return ok
|
||||
|
||||
def check_synthesis(rng, dt, bw):
|
||||
|
||||
ok = True
|
||||
synthesis = TnsSynthesis(dt)
|
||||
|
||||
for i in range(100):
|
||||
|
||||
x = rng.random(T.NE[dt][bw]) * 1e2
|
||||
|
||||
synthesis.nfilters = 1 + int(bw >= T.SRATE_32K)
|
||||
synthesis.rc_order = rng.integers(0, 9, 2)
|
||||
synthesis.rc = rng.integers(0, 17, 16).reshape(2, 8)
|
||||
|
||||
y = synthesis.run(x, bw)
|
||||
y_c = lc3.tns_synthesize(dt, bw, synthesis.get_data(), x)
|
||||
|
||||
ok = ok and np.amax(np.abs(y_c - y) < 1e-6)
|
||||
|
||||
return ok
|
||||
|
||||
def check_analysis_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
fs = Tns.FREQ_LIM[dt][sr][0]
|
||||
fe = Tns.FREQ_LIM[dt][sr][1]
|
||||
st = np.zeros(8)
|
||||
|
||||
for i in range(len(C.X_S[dt])):
|
||||
|
||||
(_, a) = lc3.tns_compute_lpc_coeffs(dt, sr, C.X_S[dt][i])
|
||||
ok = ok and np.amax(np.abs(a[0] - C.TNS_LEV_A[dt][i])) < 1e-5
|
||||
|
||||
rc = lc3.tns_lpc_reflection(a[0])
|
||||
ok = ok and np.amax(np.abs(rc - C.TNS_LEV_RC[dt][i])) < 1e-5
|
||||
|
||||
(rc_order, rc_i) = lc3.tns_quantize_rc(C.TNS_LEV_RC[dt][i])
|
||||
ok = ok and rc_order == C.RC_ORDER[dt][i][0]
|
||||
ok = ok and np.any((rc_i + 8) - C.RC_I_1[dt][i] == 0)
|
||||
|
||||
rc_q = lc3.tns_unquantize_rc(rc_i, rc_order)
|
||||
ok = ok and np.amax(np.abs(rc_q - C.RC_Q_1[dt][i])) < 1e-6
|
||||
|
||||
(x, side) = lc3.tns_analyze(dt, sr, False, C.NBYTES[dt], C.X_S[dt][i])
|
||||
ok = ok and side['nfilters'] == 1
|
||||
ok = ok and side['rc_order'][0] == C.RC_ORDER[dt][i][0]
|
||||
ok = ok and not np.any((side['rc'][0] + 8) - C.RC_I_1[dt][i])
|
||||
ok = ok and lc3.tns_get_nbits(side) == C.NBITS_TNS[dt][i]
|
||||
ok = ok and np.amax(np.abs(x - C.X_F[dt][i])) < 1e-3
|
||||
|
||||
return ok
|
||||
|
||||
def check_synthesis_appendix_c(dt):
|
||||
|
||||
sr = T.SRATE_16K
|
||||
ok = True
|
||||
|
||||
for i in range(len(C.X_HAT_Q[dt])):
|
||||
|
||||
side = {
|
||||
'nfilters' : 1,
|
||||
'lpc_weighting' : C.NBYTES[dt] * 8 < 48 * T.DT_MS[dt],
|
||||
'rc_order': C.RC_ORDER[dt][i],
|
||||
'rc': [ C.RC_I_1[dt][i] - 8, C.RC_I_2[dt][i] - 8 ]
|
||||
}
|
||||
|
||||
g_int = C.GG_IND_ADJ[dt][i] + C.GG_OFF[dt][i]
|
||||
x = C.X_HAT_Q[dt][i] * (10 ** (g_int / 28))
|
||||
|
||||
x = lc3.tns_synthesize(dt, sr, side, x)
|
||||
ok = ok and np.amax(np.abs(x - C.X_HAT_TNS[dt][i])) < 1e-3
|
||||
|
||||
if dt != T.DT_10M:
|
||||
return ok
|
||||
|
||||
sr = T.SRATE_48K
|
||||
|
||||
side = {
|
||||
'nfilters' : 2,
|
||||
'lpc_weighting' : False,
|
||||
'rc_order': C.RC_ORDER_48K_10M,
|
||||
'rc': [ C.RC_I_1_48K_10M - 8, C.RC_I_2_48K_10M - 8 ]
|
||||
}
|
||||
|
||||
x = C.X_HAT_F_48K_10M
|
||||
x = lc3.tns_synthesize(dt, sr, side, x)
|
||||
ok = ok and np.amax(np.abs(x - C.X_HAT_TNS_48K_10M)) < 1e-3
|
||||
|
||||
return ok
|
||||
|
||||
def check():
|
||||
|
||||
rng = np.random.default_rng(1234)
|
||||
ok = True
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
for sr in range(T.NUM_SRATE):
|
||||
ok = ok and check_analysis(rng, dt, sr)
|
||||
ok = ok and check_synthesis(rng, dt, sr)
|
||||
|
||||
for dt in range(T.NUM_DT):
|
||||
ok = ok and check_analysis_appendix_c(dt)
|
||||
ok = ok and check_synthesis_appendix_c(dt)
|
||||
|
||||
return ok
|
||||
|
||||
### ------------------------------------------------------------------------ ###
|
||||
183
test/tns_py.c
Normal file
183
test/tns_py.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "lc3.h"
|
||||
#include <Python.h>
|
||||
#include <numpy/ndarrayobject.h>
|
||||
|
||||
#include <tns.c>
|
||||
#include "ctypes.h"
|
||||
|
||||
static PyObject *compute_lpc_coeffs_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj, *a_obj, *g_obj;
|
||||
unsigned dt, bw;
|
||||
float *x, *g, (*a)[9];
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIO", &dt, &bw, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("sr", (unsigned)bw < LC3_NUM_BANDWIDTH);
|
||||
|
||||
int ne = LC3_NE(dt, bw);
|
||||
|
||||
CTYPES_CHECK("x", to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
g_obj = new_1d_ptr(NPY_FLOAT, 2, &g);
|
||||
a_obj = new_2d_ptr(NPY_FLOAT, 2, 9, &a);
|
||||
|
||||
compute_lpc_coeffs(dt, bw, x, g, a);
|
||||
|
||||
return Py_BuildValue("NN", g_obj, a_obj);
|
||||
}
|
||||
|
||||
static PyObject *lpc_reflection_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *a_obj, *rc_obj;
|
||||
float *a, *rc;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &a_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("a", to_1d_ptr(a_obj, NPY_FLOAT, 9, &a));
|
||||
rc_obj = new_1d_ptr(NPY_FLOAT, 8, &rc);
|
||||
|
||||
lpc_reflection(a, rc);
|
||||
|
||||
return Py_BuildValue("N", rc_obj);
|
||||
}
|
||||
|
||||
static PyObject *quantize_rc_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *rc_obj, *rc_q_obj;
|
||||
float *rc;
|
||||
int rc_order, *rc_q;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &rc_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("rc", to_1d_ptr(rc_obj, NPY_FLOAT, 8, &rc));
|
||||
|
||||
rc_q_obj = new_1d_ptr(NPY_INT, 8, &rc_q);
|
||||
|
||||
quantize_rc(rc, &rc_order, rc_q);
|
||||
|
||||
return Py_BuildValue("iN", rc_order, rc_q_obj);
|
||||
}
|
||||
|
||||
static PyObject *unquantize_rc_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *rc_q_obj, *rc_obj;
|
||||
int rc_order, *rc_q;
|
||||
float *rc;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "OI", &rc_q_obj, &rc_order))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("rc_q", to_1d_ptr(rc_q_obj, NPY_INT, 8, &rc_q));
|
||||
CTYPES_CHECK("rc_order", (unsigned)rc_order <= 8);
|
||||
|
||||
rc_obj = new_1d_ptr(NPY_FLOAT, 8, &rc);
|
||||
|
||||
unquantize_rc(rc_q, rc_order, rc);
|
||||
|
||||
return Py_BuildValue("N", rc_obj);
|
||||
}
|
||||
|
||||
static PyObject *analyze_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *x_obj;
|
||||
struct lc3_tns_data data = { 0 };
|
||||
unsigned dt, bw;
|
||||
int nn_flag;
|
||||
unsigned nbytes;
|
||||
float *x;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIpIO", &dt, &bw, &nn_flag, &nbytes, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("bw", (unsigned)bw < LC3_NUM_BANDWIDTH);
|
||||
|
||||
int ne = LC3_NE(dt, bw);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
lc3_tns_analyze(dt, bw, nn_flag, nbytes, &data, x);
|
||||
|
||||
return Py_BuildValue("ON", x_obj, new_tns_data(&data));
|
||||
}
|
||||
|
||||
static PyObject *synthesize_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *data_obj, *x_obj;
|
||||
unsigned dt, bw;
|
||||
struct lc3_tns_data data;
|
||||
float *x;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "IIOO", &dt, &bw, &data_obj, &x_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("dt", (unsigned)dt < LC3_NUM_DT);
|
||||
CTYPES_CHECK("bw", (unsigned)bw < LC3_NUM_BANDWIDTH);
|
||||
CTYPES_CHECK(NULL, data_obj = to_tns_data(data_obj, &data));
|
||||
|
||||
int ne = LC3_NE(dt, bw);
|
||||
|
||||
CTYPES_CHECK("x", x_obj = to_1d_ptr(x_obj, NPY_FLOAT, ne, &x));
|
||||
|
||||
lc3_tns_synthesize(dt, bw, &data, x);
|
||||
|
||||
return Py_BuildValue("O", x_obj);
|
||||
}
|
||||
|
||||
static PyObject *get_nbits_py(PyObject *m, PyObject *args)
|
||||
{
|
||||
PyObject *data_obj;
|
||||
struct lc3_tns_data data = { 0 };
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &data_obj))
|
||||
return NULL;
|
||||
|
||||
CTYPES_CHECK("data", to_tns_data(data_obj, &data));
|
||||
|
||||
int nbits = lc3_tns_get_nbits(&data);
|
||||
|
||||
return Py_BuildValue("i", nbits);
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{ "tns_compute_lpc_coeffs", compute_lpc_coeffs_py, METH_VARARGS },
|
||||
{ "tns_lpc_reflection" , lpc_reflection_py , METH_VARARGS },
|
||||
{ "tns_quantize_rc" , quantize_rc_py , METH_VARARGS },
|
||||
{ "tns_unquantize_rc" , unquantize_rc_py , METH_VARARGS },
|
||||
{ "tns_analyze" , analyze_py , METH_VARARGS },
|
||||
{ "tns_synthesize" , synthesize_py , METH_VARARGS },
|
||||
{ "tns_get_nbits" , get_nbits_py , METH_VARARGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC lc3_tns_py_init(PyObject *m)
|
||||
{
|
||||
import_array();
|
||||
|
||||
PyModule_AddFunctions(m, methods);
|
||||
|
||||
return m;
|
||||
}
|
||||
257
tools/dlc3.c
Normal file
257
tools/dlc3.c
Normal file
@@ -0,0 +1,257 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <lc3.h>
|
||||
#include "lc3bin.h"
|
||||
#include "wave.h"
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) ( (a) < (b) ? (a) : (b) )
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Error handling
|
||||
*/
|
||||
|
||||
static void error(int status, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
|
||||
fprintf(stderr, status ? ": %s\n" : "\n", strerror(status));
|
||||
exit(status);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parameters
|
||||
*/
|
||||
|
||||
struct parameters {
|
||||
const char *fname_in;
|
||||
const char *fname_out;
|
||||
int bitdepth;
|
||||
int srate_hz;
|
||||
};
|
||||
|
||||
static struct parameters parse_args(int argc, char *argv[])
|
||||
{
|
||||
static const char *usage =
|
||||
"Usage: %s [in_file] [wav_file]\n"
|
||||
"\n"
|
||||
"wav_file\t" "Input wave file, stdin if omitted\n"
|
||||
"out_file\t" "Output bitstream file, stdout if omitted\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"\t-h\t" "Display help\n"
|
||||
"\t-b\t" "Output bitdepth, 16 bits (default) or 24 bits\n"
|
||||
"\t-r\t" "Output samplerate, default is LC3 stream samplerate\n"
|
||||
"\n";
|
||||
|
||||
struct parameters p = { .bitdepth = 16 };
|
||||
|
||||
for (int iarg = 1; iarg < argc; ) {
|
||||
const char *arg = argv[iarg++];
|
||||
|
||||
if (arg[0] == '-') {
|
||||
if (arg[2] != '\0')
|
||||
error(EINVAL, "Option %s", arg);
|
||||
|
||||
char opt = arg[1];
|
||||
const char *optarg;
|
||||
|
||||
switch (opt) {
|
||||
case 'b': case 'r':
|
||||
if (iarg >= argc)
|
||||
error(EINVAL, "Argument %s", arg);
|
||||
optarg = argv[iarg++];
|
||||
}
|
||||
|
||||
switch (opt) {
|
||||
case 'h': fprintf(stderr, usage, argv[0]); exit(0);
|
||||
case 'b': p.bitdepth = atoi(optarg); break;
|
||||
case 'r': p.srate_hz = atoi(optarg); break;
|
||||
default:
|
||||
error(EINVAL, "Option %s", arg);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (!p.fname_in)
|
||||
p.fname_in = arg;
|
||||
else if (!p.fname_out)
|
||||
p.fname_out = arg;
|
||||
else
|
||||
error(EINVAL, "Argument %s", arg);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return time in (us) from unspecified point in the past
|
||||
*/
|
||||
static unsigned clock_us(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
|
||||
return (unsigned)(ts.tv_sec * 1000*1000) + (unsigned)(ts.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* --- Read parameters --- */
|
||||
|
||||
struct parameters p = parse_args(argc, argv);
|
||||
FILE *fp_in = stdin, *fp_out = stdout;
|
||||
|
||||
if (p.fname_in && (fp_in = fopen(p.fname_in, "rb")) == NULL)
|
||||
error(errno, "%s", p.fname_in);
|
||||
|
||||
if (p.fname_out && (fp_out = fopen(p.fname_out, "wb")) == NULL)
|
||||
error(errno, "%s", p.fname_out);
|
||||
|
||||
if (p.srate_hz && !LC3_CHECK_SR_HZ(p.srate_hz))
|
||||
error(EINVAL, "Samplerate %d Hz", p.srate_hz);
|
||||
|
||||
if (p.bitdepth && p.bitdepth != 16 && p.bitdepth != 24)
|
||||
error(EINVAL, "Bitdepth %d", p.bitdepth);
|
||||
|
||||
/* --- Check parameters --- */
|
||||
|
||||
int frame_us, srate_hz, nch, nsamples;
|
||||
|
||||
if (lc3bin_read_header(fp_in, &frame_us, &srate_hz, &nch, &nsamples) < 0)
|
||||
error(EINVAL, "LC3 binary input file");
|
||||
|
||||
if (nch < 1 || nch > 2)
|
||||
error(EINVAL, "Number of channels %d", nch);
|
||||
|
||||
if (!LC3_CHECK_DT_US(frame_us))
|
||||
error(EINVAL, "Frame duration");
|
||||
|
||||
if (!LC3_CHECK_SR_HZ(srate_hz) || (p.srate_hz && p.srate_hz < srate_hz))
|
||||
error(EINVAL, "Samplerate %d Hz", srate_hz);
|
||||
|
||||
int pcm_sbits = p.bitdepth;
|
||||
int pcm_sbytes = 2 + 2*(pcm_sbits > 16);
|
||||
|
||||
int pcm_srate_hz = !p.srate_hz ? srate_hz : p.srate_hz;
|
||||
int pcm_samples = !p.srate_hz ? nsamples :
|
||||
((int64_t)nsamples * pcm_srate_hz) / srate_hz;
|
||||
|
||||
wave_write_header(fp_out,
|
||||
pcm_sbits, pcm_sbytes, pcm_srate_hz, nch, pcm_samples);
|
||||
|
||||
/* --- Setup decoding --- */
|
||||
|
||||
int frame_samples = lc3_frame_samples(frame_us, pcm_srate_hz);
|
||||
int encode_samples = pcm_samples +
|
||||
lc3_delay_samples(frame_us, pcm_srate_hz);
|
||||
|
||||
lc3_decoder_t dec[nch];
|
||||
uint8_t in[nch * LC3_MAX_FRAME_BYTES];
|
||||
int8_t alignas(int32_t) pcm[nch * frame_samples * pcm_sbytes];
|
||||
enum lc3_pcm_format pcm_fmt =
|
||||
pcm_sbits == 24 ? LC3_PCM_FORMAT_S24 : LC3_PCM_FORMAT_S16;
|
||||
|
||||
for (int ich = 0; ich < nch; ich++)
|
||||
dec[ich] = lc3_setup_decoder(frame_us, srate_hz, p.srate_hz,
|
||||
malloc(lc3_decoder_size(frame_us, pcm_srate_hz)));
|
||||
|
||||
/* --- Decoding loop --- */
|
||||
|
||||
static const char *dash_line = "========================================";
|
||||
|
||||
int nsec = 0;
|
||||
unsigned t0 = clock_us();
|
||||
|
||||
for (int i = 0; i * frame_samples < encode_samples; i++) {
|
||||
|
||||
int frame_bytes = lc3bin_read_data(fp_in, nch, in);
|
||||
|
||||
if (floorf(i * frame_us * 1e-6) > nsec) {
|
||||
|
||||
float progress = fminf((float)i * frame_samples / pcm_samples, 1);
|
||||
|
||||
fprintf(stderr, "%02d:%02d [%-40s]\r",
|
||||
nsec / 60, nsec % 60,
|
||||
dash_line + (int)floorf((1 - progress) * 40));
|
||||
|
||||
nsec = rint(i * frame_us * 1e-6);
|
||||
}
|
||||
|
||||
if (frame_bytes <= 0)
|
||||
memset(pcm, 0, nch * frame_samples * pcm_sbytes);
|
||||
else
|
||||
for (int ich = 0; ich < nch; ich++)
|
||||
lc3_decode(dec[ich],
|
||||
in + ich * frame_bytes, frame_bytes,
|
||||
pcm_fmt, pcm + ich * pcm_sbytes, nch);
|
||||
|
||||
int pcm_offset = i > 0 ? 0 : encode_samples - pcm_samples;
|
||||
int pcm_nwrite = MIN(frame_samples - pcm_offset,
|
||||
encode_samples - i*frame_samples);
|
||||
|
||||
wave_write_pcm(fp_out, pcm_sbytes, pcm, nch, pcm_offset, pcm_nwrite);
|
||||
}
|
||||
|
||||
unsigned t = (clock_us() - t0) / 1000;
|
||||
nsec = nsamples / srate_hz;
|
||||
|
||||
fprintf(stderr, "%02d:%02d Decoded in %d.%03d seconds %20s\n",
|
||||
nsec / 60, nsec % 60, t / 1000, t % 1000, "");
|
||||
|
||||
/* --- Cleanup --- */
|
||||
|
||||
for (int ich = 0; ich < nch; ich++)
|
||||
free(dec[ich]);
|
||||
|
||||
if (fp_in != stdin)
|
||||
fclose(fp_in);
|
||||
|
||||
if (fp_out != stdout)
|
||||
fclose(fp_out);
|
||||
}
|
||||
258
tools/elc3.c
Normal file
258
tools/elc3.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <lc3.h>
|
||||
#include "lc3bin.h"
|
||||
#include "wave.h"
|
||||
|
||||
|
||||
/**
|
||||
* Error handling
|
||||
*/
|
||||
|
||||
static void error(int status, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
|
||||
fprintf(stderr, status ? ": %s\n" : "\n", strerror(status));
|
||||
exit(status);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parameters
|
||||
*/
|
||||
|
||||
struct parameters {
|
||||
const char *fname_in;
|
||||
const char *fname_out;
|
||||
float frame_ms;
|
||||
int srate_hz;
|
||||
int bitrate;
|
||||
};
|
||||
|
||||
static struct parameters parse_args(int argc, char *argv[])
|
||||
{
|
||||
static const char *usage =
|
||||
"Usage: %s [options] [wav_file] [out_file]\n"
|
||||
"\n"
|
||||
"wav_file\t" "Input wave file, stdin if omitted\n"
|
||||
"out_file\t" "Output bitstream file, stdout if omitted\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
"\t-h\t" "Display help\n"
|
||||
"\t-b\t" "Bitrate in bps (mandatory)\n"
|
||||
"\t-m\t" "Frame duration in ms (default 10)\n"
|
||||
"\t-r\t" "Encoder samplerate (default is input samplerate)\n"
|
||||
"\n";
|
||||
|
||||
struct parameters p = { .frame_ms = 10 };
|
||||
|
||||
for (int iarg = 1; iarg < argc; ) {
|
||||
const char *arg = argv[iarg++];
|
||||
|
||||
if (arg[0] == '-') {
|
||||
if (arg[2] != '\0')
|
||||
error(EINVAL, "Option %s", arg);
|
||||
|
||||
char opt = arg[1];
|
||||
const char *optarg;
|
||||
|
||||
switch (opt) {
|
||||
case 'b': case 'm': case 'r':
|
||||
if (iarg >= argc)
|
||||
error(EINVAL, "Argument %s", arg);
|
||||
optarg = argv[iarg++];
|
||||
}
|
||||
|
||||
switch (opt) {
|
||||
case 'h': fprintf(stderr, usage, argv[0]); exit(0);
|
||||
case 'b': p.bitrate = atoi(optarg); break;
|
||||
case 'm': p.frame_ms = atof(optarg); break;
|
||||
case 'r': p.srate_hz = atoi(optarg); break;
|
||||
default:
|
||||
error(EINVAL, "Option %s", arg);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (!p.fname_in)
|
||||
p.fname_in = arg;
|
||||
else if (!p.fname_out)
|
||||
p.fname_out = arg;
|
||||
else
|
||||
error(EINVAL, "Argument %s", arg);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return time in (us) from unspecified point in the past
|
||||
*/
|
||||
|
||||
static unsigned clock_us(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
|
||||
return (unsigned)(ts.tv_sec * 1000*1000) + (unsigned)(ts.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* --- Read parameters --- */
|
||||
|
||||
struct parameters p = parse_args(argc, argv);
|
||||
FILE *fp_in = stdin, *fp_out = stdout;
|
||||
|
||||
if (p.fname_in && (fp_in = fopen(p.fname_in, "rb")) == NULL)
|
||||
error(errno, "%s", p.fname_in);
|
||||
|
||||
if (p.fname_out && (fp_out = fopen(p.fname_out, "wb")) == NULL)
|
||||
error(errno, "%s", p.fname_out);
|
||||
|
||||
if (p.srate_hz && !LC3_CHECK_SR_HZ(p.srate_hz))
|
||||
error(EINVAL, "Samplerate %d Hz", p.srate_hz);
|
||||
|
||||
/* --- Check parameters --- */
|
||||
|
||||
int frame_us = p.frame_ms * 1000;
|
||||
int srate_hz, nch, nsamples;
|
||||
int pcm_sbits, pcm_sbytes;
|
||||
|
||||
if (wave_read_header(fp_in,
|
||||
&pcm_sbits, &pcm_sbytes, &srate_hz, &nch, &nsamples) < 0)
|
||||
error(EINVAL, "Bad or unsupported WAVE input file");
|
||||
|
||||
if (p.bitrate <= 0)
|
||||
error(EINVAL, "Bitrate");
|
||||
|
||||
if (!LC3_CHECK_DT_US(frame_us))
|
||||
error(EINVAL, "Frame duration");
|
||||
|
||||
if (!LC3_CHECK_SR_HZ(srate_hz) || (p.srate_hz && p.srate_hz > srate_hz))
|
||||
error(EINVAL, "Samplerate %d Hz", srate_hz);
|
||||
|
||||
if (pcm_sbits != 16 && pcm_sbits != 24)
|
||||
error(EINVAL, "Bitdepth %d", pcm_sbits);
|
||||
|
||||
if (pcm_sbytes != (pcm_sbits == 16 ? 2 : 4))
|
||||
error(EINVAL, "Sample storage on %d bytes", pcm_sbytes);
|
||||
|
||||
if (nch < 1 || nch > 2)
|
||||
error(EINVAL, "Number of channels %d", nch);
|
||||
|
||||
int enc_srate_hz = !p.srate_hz ? srate_hz : p.srate_hz;
|
||||
int enc_samples = !p.srate_hz ? nsamples :
|
||||
((int64_t)nsamples * enc_srate_hz) / srate_hz;
|
||||
|
||||
lc3bin_write_header(fp_out,
|
||||
frame_us, enc_srate_hz, p.bitrate, nch, enc_samples);
|
||||
|
||||
/* --- Setup encoding --- */
|
||||
|
||||
int frame_bytes = lc3_frame_bytes(frame_us, p.bitrate / nch);
|
||||
int frame_samples = lc3_frame_samples(frame_us, srate_hz);
|
||||
int encode_samples = nsamples + lc3_delay_samples(frame_us, srate_hz);
|
||||
|
||||
lc3_encoder_t enc[nch];
|
||||
int8_t alignas(int32_t) pcm[nch * frame_samples * pcm_sbytes];
|
||||
enum lc3_pcm_format pcm_fmt =
|
||||
pcm_sbits == 24 ? LC3_PCM_FORMAT_S24 : LC3_PCM_FORMAT_S16;
|
||||
uint8_t out[nch][frame_bytes];
|
||||
|
||||
for (int ich = 0; ich < nch; ich++)
|
||||
enc[ich] = lc3_setup_encoder(frame_us, enc_srate_hz, srate_hz,
|
||||
malloc(lc3_encoder_size(frame_us, srate_hz)));
|
||||
|
||||
/* --- Encoding loop --- */
|
||||
|
||||
static const char *dash_line = "========================================";
|
||||
|
||||
int nsec = 0;
|
||||
unsigned t0 = clock_us();
|
||||
|
||||
for (int i = 0; i * frame_samples < encode_samples; i++) {
|
||||
|
||||
int nread = wave_read_pcm(fp_in, pcm_sbytes, nch, frame_samples, pcm);
|
||||
|
||||
memset(pcm + nread * nch, 0,
|
||||
nch * (frame_samples - nread) * pcm_sbytes);
|
||||
|
||||
if (floorf(i * frame_us * 1e-6) > nsec) {
|
||||
float progress = fminf(
|
||||
(float)i * frame_samples / encode_samples, 1);
|
||||
|
||||
fprintf(stderr, "%02d:%02d [%-40s]\r",
|
||||
nsec / 60, nsec % 60,
|
||||
dash_line + (int)floorf((1 - progress) * 40));
|
||||
|
||||
nsec = (int)(i * frame_us * 1e-6);
|
||||
}
|
||||
|
||||
for (int ich = 0; ich < nch; ich++)
|
||||
lc3_encode(enc[ich],
|
||||
pcm_fmt, pcm + ich * pcm_sbytes, nch,
|
||||
frame_bytes, out[ich]);
|
||||
|
||||
lc3bin_write_data(fp_out, out, nch, frame_bytes);
|
||||
}
|
||||
|
||||
unsigned t = (clock_us() - t0) / 1000;
|
||||
nsec = encode_samples / srate_hz;
|
||||
|
||||
fprintf(stderr, "%02d:%02d Encoded in %d.%d seconds %20s\n",
|
||||
nsec / 60, nsec % 60, t / 1000, t % 1000, "");
|
||||
|
||||
/* --- Cleanup --- */
|
||||
|
||||
for (int ich = 0; ich < nch; ich++)
|
||||
free(enc[ich]);
|
||||
|
||||
if (fp_in != stdin)
|
||||
fclose(fp_in);
|
||||
|
||||
if (fp_out != stdout)
|
||||
fclose(fp_out);
|
||||
}
|
||||
110
tools/lc3bin.c
Normal file
110
tools/lc3bin.c
Normal file
@@ -0,0 +1,110 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "lc3bin.h"
|
||||
|
||||
|
||||
/**
|
||||
* LC3 binary header
|
||||
*/
|
||||
|
||||
#define LC3_FILE_ID (0x1C | (0xCC << 8))
|
||||
|
||||
struct lc3bin_header {
|
||||
uint16_t file_id;
|
||||
uint16_t header_size;
|
||||
uint16_t srate_100hz;
|
||||
uint16_t bitrate_100bps;
|
||||
uint16_t channels;
|
||||
uint16_t frame_10us;
|
||||
uint16_t rfu;
|
||||
uint16_t nsamples_low;
|
||||
uint16_t nsamples_high;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Read LC3 binary header
|
||||
*/
|
||||
int lc3bin_read_header(FILE *fp,
|
||||
int *frame_us, int *srate_hz, int *nchannels, int *nsamples)
|
||||
{
|
||||
struct lc3bin_header hdr;
|
||||
|
||||
if (fread(&hdr, sizeof(hdr), 1, fp) != 1
|
||||
|| hdr.file_id != LC3_FILE_ID)
|
||||
return -1;
|
||||
|
||||
*nchannels = hdr.channels;
|
||||
*frame_us = hdr.frame_10us * 10;
|
||||
*srate_hz = hdr.srate_100hz * 100;
|
||||
*nsamples = hdr.nsamples_low | (hdr.nsamples_high << 16);
|
||||
|
||||
fseek(fp, SEEK_SET, hdr.header_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read LC3 block of data
|
||||
*/
|
||||
int lc3bin_read_data(FILE *fp, int nchannels, void *buffer)
|
||||
{
|
||||
uint16_t nbytes;
|
||||
|
||||
if (fread(&nbytes, sizeof(nbytes), 1, fp) < 1
|
||||
|| nbytes > nchannels * LC3_MAX_FRAME_BYTES
|
||||
|| nbytes % nchannels
|
||||
|| fread(buffer, nbytes, 1, fp) < 1)
|
||||
return -1;
|
||||
|
||||
return nbytes / nchannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write LC3 binary header
|
||||
*/
|
||||
void lc3bin_write_header(FILE *fp,
|
||||
int frame_us, int srate_hz, int bitrate, int nchannels, int nsamples)
|
||||
{
|
||||
struct lc3bin_header hdr = {
|
||||
.file_id = LC3_FILE_ID,
|
||||
.header_size = sizeof(struct lc3bin_header),
|
||||
.srate_100hz = srate_hz / 100,
|
||||
.bitrate_100bps = bitrate / 100,
|
||||
.channels = nchannels,
|
||||
.frame_10us = frame_us / 10,
|
||||
.nsamples_low = nsamples & 0xffff,
|
||||
.nsamples_high = nsamples >> 16,
|
||||
};
|
||||
|
||||
fwrite(&hdr, sizeof(hdr), 1, fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write LC3 block of data
|
||||
*/
|
||||
void lc3bin_write_data(FILE *fp,
|
||||
const void *data, int nchannels, int frame_bytes)
|
||||
{
|
||||
uint16_t nbytes = nchannels * frame_bytes;
|
||||
fwrite(&nbytes, sizeof(nbytes), 1, fp);
|
||||
|
||||
fwrite(data, 1, nbytes, fp);
|
||||
}
|
||||
71
tools/lc3bin.h
Normal file
71
tools/lc3bin.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __LC3BIN_H
|
||||
#define __LC3BIN_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <lc3.h>
|
||||
|
||||
|
||||
/**
|
||||
* Read LC3 binary header
|
||||
* fp Opened file, moved after header on return
|
||||
* frame_us Return frame duration, in us
|
||||
* srate_hz Return samplerate, in Hz
|
||||
* nchannels Return number of channels
|
||||
* nsamples Return count of source samples by channels
|
||||
* return 0: Ok -1: Bad LC3 File
|
||||
*/
|
||||
int lc3bin_read_header(FILE *fp,
|
||||
int *frame_us, int *srate_hz, int *nchannels, int *nsamples);
|
||||
|
||||
/**
|
||||
* Read LC3 block of data
|
||||
* fp Opened file
|
||||
* nchannels Number of channels
|
||||
* buffer Output buffer of `nchannels * LC3_MAX_FRAME_BYTES`
|
||||
* return Size of each 'nchannels` frames, -1 on error
|
||||
*/
|
||||
int lc3bin_read_data(FILE *fp, int nchannels, void *buffer);
|
||||
|
||||
/**
|
||||
* Write LC3 binary header
|
||||
* fp Opened file, moved after header on return
|
||||
* frame_us Frame duration, in us
|
||||
* srate_hz Samplerate, in Hz
|
||||
* bitrate Bitrate indication of the stream, in bps
|
||||
* nchannels Number of channels
|
||||
* nsamples Count of source samples by channels
|
||||
*/
|
||||
void lc3bin_write_header(FILE *fp,
|
||||
int frame_us, int srate_hz, int bitrate, int nchannels, int nsamples);
|
||||
|
||||
/**
|
||||
* Write LC3 block of data
|
||||
* fp Opened file
|
||||
* data The frames data
|
||||
* nchannels Number of channels
|
||||
* frame_bytes Size of each `nchannels` frames
|
||||
*/
|
||||
void lc3bin_write_data(FILE *fp,
|
||||
const void *data, int nchannels, int frame_bytes);
|
||||
|
||||
|
||||
#endif /* __LC3BIN_H */
|
||||
45
tools/makefile.mk
Normal file
45
tools/makefile.mk
Normal file
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# Copyright 2022 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.
|
||||
#
|
||||
|
||||
TOOLS_DIR = tools
|
||||
|
||||
|
||||
elc3_src += \
|
||||
$(TOOLS_DIR)/elc3.c \
|
||||
$(TOOLS_DIR)/lc3bin.c \
|
||||
$(TOOLS_DIR)/wave.c
|
||||
|
||||
elc3_lib += liblc3
|
||||
elc3_ldlibs += m
|
||||
elc3_ldflags += -flto
|
||||
|
||||
$(eval $(call add-bin,elc3))
|
||||
|
||||
|
||||
dlc3_src += \
|
||||
$(TOOLS_DIR)/dlc3.c \
|
||||
$(TOOLS_DIR)/lc3bin.c \
|
||||
$(TOOLS_DIR)/wave.c
|
||||
|
||||
dlc3_lib += liblc3
|
||||
dlc3_ldlibs += m
|
||||
elc3_ldflags += -flto
|
||||
|
||||
$(eval $(call add-bin,dlc3))
|
||||
|
||||
|
||||
.PHONY: tools
|
||||
tools: elc3 dlc3
|
||||
179
tools/wave.c
Normal file
179
tools/wave.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "wave.h"
|
||||
|
||||
|
||||
/**
|
||||
* Id formatting
|
||||
*/
|
||||
|
||||
#define __WAVE_ID(s) \
|
||||
(uint32_t)( s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24) )
|
||||
|
||||
|
||||
/**
|
||||
* File format statement
|
||||
* | type_id WAVE_FILE_TYPE_ID
|
||||
* | size File size - 8 bytes
|
||||
* | type_id WAVE_FILE_FMT_ID
|
||||
*/
|
||||
|
||||
#define WAVE_FILE_TYPE_ID __WAVE_ID("RIFF")
|
||||
#define WAVE_FILE_FMT_ID __WAVE_ID("WAVE")
|
||||
|
||||
struct wave_file {
|
||||
uint32_t type_id;
|
||||
uint32_t size;
|
||||
uint32_t fmt_id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Audio format statement
|
||||
* | id WAVE_FORMAT_ID
|
||||
* | size Size of the block - 8 bytes (= 16 bytes)
|
||||
* | format WAVE_FORMAT_PCM
|
||||
* | channels Number of channels
|
||||
* | samplerate Sampling rate
|
||||
* | byterate Bytes per secondes = `samplerate * framesize`
|
||||
* | framesize Bytes per sampling time = `channels * bitdepth / 8`
|
||||
* | bitdepth Number of bits per sample
|
||||
*/
|
||||
|
||||
#define WAVE_FORMAT_ID __WAVE_ID("fmt ")
|
||||
#define WAVE_FORMAT_PCM 1
|
||||
|
||||
struct wave_format {
|
||||
uint32_t id;
|
||||
uint32_t size;
|
||||
uint16_t fmt;
|
||||
uint16_t channels;
|
||||
uint32_t samplerate;
|
||||
uint32_t byterate;
|
||||
uint16_t framesize;
|
||||
uint16_t bitdepth;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Audio data statement
|
||||
* | id WAV_DATA_ID
|
||||
* | size Size of the data following
|
||||
*/
|
||||
|
||||
#define WAVE_DATA_ID __WAVE_ID("data")
|
||||
|
||||
struct wave_data {
|
||||
uint32_t id;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Read WAVE file header
|
||||
*/
|
||||
int wave_read_header(FILE *fp, int *bitdepth, int *samplesize,
|
||||
int *samplerate, int *nchannels, int *nframes)
|
||||
{
|
||||
struct wave_file file;
|
||||
struct wave_format format;
|
||||
struct wave_data data;
|
||||
|
||||
if (fread(&file, sizeof(file), 1, fp) != 1
|
||||
|| file.type_id != WAVE_FILE_TYPE_ID
|
||||
|| file.fmt_id != WAVE_FILE_FMT_ID)
|
||||
return -1;
|
||||
|
||||
if (fread(&format, sizeof(format), 1, fp) != 1
|
||||
|| format.id != WAVE_FORMAT_ID
|
||||
|| format.fmt != WAVE_FORMAT_PCM
|
||||
|| format.byterate != format.samplerate * format.framesize)
|
||||
return -1;
|
||||
|
||||
fseek(fp, sizeof(format) - (8 + format.size), SEEK_CUR);
|
||||
|
||||
if (fread(&data, sizeof(data), 1, fp) != 1
|
||||
|| data.id != WAVE_DATA_ID)
|
||||
return -1;
|
||||
|
||||
*bitdepth = format.bitdepth;
|
||||
*samplesize = format.framesize / format.channels;
|
||||
*samplerate = format.samplerate;
|
||||
*nchannels = format.channels;
|
||||
*nframes = data.size / format.framesize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read PCM samples from wave file
|
||||
*/
|
||||
int wave_read_pcm(FILE *fp, int samplesize,
|
||||
int nch, int count, void *buffer)
|
||||
{
|
||||
return fread(buffer, nch * samplesize, count, fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write WAVE file header
|
||||
*/
|
||||
void wave_write_header(FILE *fp, int bitdepth, int samplesize,
|
||||
int samplerate, int nchannels, int nframes)
|
||||
{
|
||||
struct {
|
||||
struct wave_file file;
|
||||
struct wave_format format;
|
||||
struct wave_data data;
|
||||
} header;
|
||||
|
||||
long data_size = nchannels * nframes * samplesize;
|
||||
long file_size = sizeof(header) + data_size;
|
||||
|
||||
header.file = (struct wave_file){
|
||||
WAVE_FILE_TYPE_ID, file_size - 8,
|
||||
.fmt_id = WAVE_FILE_FMT_ID
|
||||
};
|
||||
|
||||
header.format = (struct wave_format){
|
||||
WAVE_FORMAT_ID, sizeof(header.format) - 8,
|
||||
.fmt = WAVE_FORMAT_PCM,
|
||||
.channels = nchannels,
|
||||
.samplerate = samplerate,
|
||||
.byterate = samplerate * nchannels * samplesize,
|
||||
.framesize = nchannels * samplesize,
|
||||
.bitdepth = bitdepth,
|
||||
};
|
||||
|
||||
header.data = (struct wave_data){
|
||||
WAVE_DATA_ID, data_size
|
||||
};
|
||||
|
||||
fwrite(&header, sizeof(header), 1, fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write PCM samples to wave file
|
||||
*/
|
||||
void wave_write_pcm(FILE *fp, int samplesize,
|
||||
const void *_pcm, int nch, int off, int count)
|
||||
{
|
||||
const int8_t *pcm = _pcm;
|
||||
fwrite(pcm + nch * off * samplesize, nch * samplesize, count, fp);
|
||||
}
|
||||
73
tools/wave.h
Normal file
73
tools/wave.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/******************************************************************************
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __WAVE_H
|
||||
#define __WAVE_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/**
|
||||
* Read WAVE file header
|
||||
* fp Opened file, moved after header on return
|
||||
* bitdepth Return bitdepth
|
||||
* samplesize Return size of samples, in bytes
|
||||
* samplerate Return samplerate, in Hz
|
||||
* nchannels Return number of channels
|
||||
* nframes Return count of frames
|
||||
* return 0: Ok -1: Bad or unsupported WAVE File
|
||||
*/
|
||||
int wave_read_header(FILE *fp, int *bitdepth, int *samplesize,
|
||||
int *samplerate, int *nchannels, int *nframes);
|
||||
|
||||
/**
|
||||
* Read PCM samples from wave file
|
||||
* fp Opened file
|
||||
* samplesize Size of samples, in bytes
|
||||
* nch, count Number of channels and count of frames to read
|
||||
* buffer Output buffer of `nchannels * count` interleaved samples
|
||||
* return Number of frames read
|
||||
*/
|
||||
int wave_read_pcm(FILE *fp, int samplesize,
|
||||
int nch, int count, void *_buffer);
|
||||
|
||||
/**
|
||||
* Write WAVE file header
|
||||
* fp Opened file, moved after header on return
|
||||
* bitdepth Bitdepth
|
||||
* samplesize Size of samples
|
||||
* samplerate Samplerate, in Hz
|
||||
* nchannels Number of channels
|
||||
* nframes Count of frames
|
||||
*/
|
||||
void wave_write_header(FILE *fp, int bitdepth, int samplesize,
|
||||
int samplerate, int nchannels, int nframes);
|
||||
|
||||
/**
|
||||
* Write PCM samples to wave file
|
||||
* fp Opened file
|
||||
* samplesize Size of samples, in bytes
|
||||
* pcm, nch PCM frames, as 'nch' interleaved samples
|
||||
* off, count Offset and count of frames
|
||||
*/
|
||||
void wave_write_pcm(FILE *fp, int samplesize,
|
||||
const void *pcm, int nch, int off, int count);
|
||||
|
||||
|
||||
#endif /* __WAVE_H */
|
||||
Reference in New Issue
Block a user