feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
This commit is contained in:
1 parent
89715d1813
commit
4a2d276881
100 files changed
+11319
No files matched your search
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
@@ -0,0 +1,15 @@
|
||||
goamr-nb
|
||||
Copyright 2026 The goamr-nb authors
|
||||
|
||||
This product is a pure-Go port of AMR-NB (3GPP TS 26.071, 26.073, 26.090,
|
||||
26.101, 26.104) derived from the following Apache-2.0 licensed C reference:
|
||||
|
||||
- opencore-amrnb (AMR-NB encoder and decoder)
|
||||
https://sourceforge.net/projects/opencore-amr/
|
||||
Copyright (C) 2009 Martin Storsjo
|
||||
Copyright (C) The Android Open Source Project
|
||||
|
||||
The fixed-point DSP was translated function-by-function from the above
|
||||
source, preserving its algorithms and bit-exact semantics. The original
|
||||
work is licensed under the Apache License, Version 2.0; this port is
|
||||
distributed under the same license (see LICENSE).
|
||||
@@ -0,0 +1,51 @@
|
||||
# vendored: github.com/VoiceBlender/goamr-nb v1.0.0(AMR-NB 编解码器)
|
||||
|
||||
本目录是**第三方源码的 vendored 拷贝**,不是本项目自研代码。合规要务:随源码保留
|
||||
了上游的 `LICENSE`(Apache-2.0)与 `NOTICE`(Apache-2.0 第 4 条要求),**不得删除**。
|
||||
|
||||
## 出处(可复现)
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 仓库 | https://github.com/VoiceBlender/goamr-nb |
|
||||
| 版本 | `v1.0.0`(tag) |
|
||||
| 上游提交 | `635f7966829a0e5d01030355e36aa6792352c57e` |
|
||||
| 上游发布时间 | 2026-06-14T17:25:04Z(模块代理 `.info`) |
|
||||
| 取得方式 | 模块代理 `https://goproxy.cn/github.com/%21voice%21blender/goamr-nb/@v/v1.0.0.zip`(sha256 `663140d606646d62d6660e8ff8930725826680b4373055e6aeeab55f858f6067`) |
|
||||
| 许可证 | **Apache-2.0**(`LICENSE` + `NOTICE` 均逐字保留) |
|
||||
| 上游依赖 | 仅 `golang.org/x/sys`(`simd_amd64.go` 用 `x/sys/cpu` 判 AVX2);本仓库 `go.mod` 已有 `golang.org/x/sys v0.47.0`,**未新增任何模块依赖** |
|
||||
|
||||
## 为什么是「拷进仓库」而不是 `require`
|
||||
|
||||
上游是 2026-06 新建、**1 个提交 / 1 个 tag / 2 star** 的仓库。当外部依赖用等于把
|
||||
**构建**押在一个随时可能消失的仓库上;而 AMR-NB 是**冻结编解码**(1999 定标),
|
||||
拷进来基本不需要维护。因此:`go.mod` **不加** require,源码在本目录内自持。
|
||||
|
||||
## 拷了什么 / 没拷什么
|
||||
|
||||
- **拷了**:上游全部 57 个非测试 `.go` 文件 + `LICENSE` + `NOTICE`,逐字未改。
|
||||
- **没拷**:上游 48 个 `*_test.go`(含 `dsp_diff_test.go` 这类需要外部 C 参考
|
||||
harness 的测试)与 `Makefile` / `README.md`。本项目的正确性证据在
|
||||
`../codec_amr_nb_test.go`(ffmpeg/libopencore_amrnb 黄金向量),不依赖上游测试。
|
||||
- **本地新增**:`vendored_export.go`(导出桥,见其文件头说明)与本文档。
|
||||
除这两个文件外,本目录内容与上游一致。
|
||||
|
||||
## 已知能力边界(上游自述,实测确认;上游 README 的 `bit-exact` 说法**不覆盖**下列)
|
||||
|
||||
`dsp_decoder.go:13` 的注释写明:**`Cb_gain_average`(增益平滑)与自适应相位色散
|
||||
(phase dispersion)尚未移植**;`dsp.go:68` 写明**错误隐藏(bad frame)与 CNG
|
||||
(SID 舒适噪声)尚未移植**。
|
||||
|
||||
实测结论(本项目 2026-10-05,fixture 见 `../testdata/`):
|
||||
坏帧 / SID / no-data 帧**返回 160 样本静音**(不报错、不做隐藏);好帧解码与
|
||||
`opencore-amrnb`(ffmpeg `libopencore_amrnb`)**逐字节不一致**,样本级差值的量级见
|
||||
`../codec_amr_nb_test.go` 里记录的实测基线(SNR 约 16–21 dB,波形相关系数 ≥0.987,
|
||||
长度/帧数/时长**完全一致**)。可听、可播、内容正确,但**不是**参考位精确实现。
|
||||
|
||||
另注:`dsp.go` 顶部的「DSP 目前是 STUBBED」注释已过期(上游未同步)——
|
||||
`dsp_decoder.go` 里的解码 DSP 是完整实现,实测能解出真实语音。
|
||||
|
||||
## 交叉编译
|
||||
|
||||
纯 Go(`CGO_ENABLED=0` 可构建)。`simd_amd64.s` 是唯一非 Go 源文件,
|
||||
`simd_noasm.go` 提供 `!amd64` 的纯 Go 兜底 ⇒ `GOOS=linux/darwin` 交叉编译通过。
|
||||
@@ -0,0 +1,103 @@
|
||||
package amrnb
|
||||
|
||||
import "errors"
|
||||
|
||||
// Encoder encodes 8 kHz mono PCM into AMR-NB RFC 4867 RTP payloads.
|
||||
//
|
||||
// It satisfies the codec.Encoder interface: Encode takes one 20 ms frame
|
||||
// (160 int16 samples) and returns a complete RTP payload (CMR + ToC + speech
|
||||
// bits) in the configured payload format.
|
||||
type Encoder struct {
|
||||
mode Mode
|
||||
octetAligned bool
|
||||
st encoderState
|
||||
}
|
||||
|
||||
// EncoderConfig configures a new AMR-NB encoder.
|
||||
type EncoderConfig struct {
|
||||
Mode Mode // active speech mode (0..7)
|
||||
OctetAligned bool // true: octet-aligned framing; false: bandwidth-efficient
|
||||
}
|
||||
|
||||
// NewEncoder creates an AMR-NB encoder for the given mode and payload format.
|
||||
func NewEncoder(cfg EncoderConfig) (*Encoder, error) {
|
||||
if !cfg.Mode.Valid() {
|
||||
return nil, errors.New("amrnb: invalid encoder mode")
|
||||
}
|
||||
e := &Encoder{mode: cfg.Mode, octetAligned: cfg.OctetAligned}
|
||||
e.st.reset()
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Encode encodes one 20 ms frame (160 samples) into an RTP payload.
|
||||
func (e *Encoder) Encode(samples []int16) ([]byte, error) {
|
||||
if len(samples) != FrameSamples {
|
||||
return nil, errors.New("amrnb: encode expects exactly 160 samples")
|
||||
}
|
||||
bits, err := e.st.encodeFrame(e.mode, samples)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := frame{ft: e.mode.FrameType(), q: true, data: bits}
|
||||
if e.octetAligned {
|
||||
return packPayloadOctet(noCMR, []frame{f}), nil
|
||||
}
|
||||
return packPayloadBandwidthEfficient(noCMR, []frame{f}), nil
|
||||
}
|
||||
|
||||
// SetMode changes the active speech mode (e.g. in response to a peer CMR).
|
||||
func (e *Encoder) SetMode(m Mode) {
|
||||
if m.Valid() {
|
||||
e.mode = m
|
||||
}
|
||||
}
|
||||
|
||||
// Reset clears all encoder state.
|
||||
func (e *Encoder) Reset() { e.st.reset() }
|
||||
|
||||
// Decoder decodes AMR-NB RFC 4867 RTP payloads into 8 kHz mono PCM.
|
||||
type Decoder struct {
|
||||
octetAligned bool
|
||||
st decoderState
|
||||
}
|
||||
|
||||
// DecoderConfig configures a new AMR-NB decoder.
|
||||
type DecoderConfig struct {
|
||||
OctetAligned bool // must match the negotiated payload format
|
||||
}
|
||||
|
||||
// NewDecoder creates an AMR-NB decoder for the given payload format.
|
||||
func NewDecoder(cfg DecoderConfig) *Decoder {
|
||||
d := &Decoder{octetAligned: cfg.OctetAligned}
|
||||
d.st.reset()
|
||||
return d
|
||||
}
|
||||
|
||||
// Decode decodes one RTP payload, returning concatenated PCM for all frames it
|
||||
// carries (one 160-sample frame per speech/SID/no-data ToC entry).
|
||||
func (d *Decoder) Decode(payload []byte) ([]int16, error) {
|
||||
var (
|
||||
frames []frame
|
||||
err error
|
||||
)
|
||||
if d.octetAligned {
|
||||
_, frames, err = unpackPayloadOctet(payload)
|
||||
} else {
|
||||
_, frames, err = unpackPayloadBandwidthEfficient(payload)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]int16, 0, len(frames)*FrameSamples)
|
||||
for _, f := range frames {
|
||||
pcm, derr := d.st.decodeFrame(f)
|
||||
if derr != nil {
|
||||
return nil, derr
|
||||
}
|
||||
out = append(out, pcm...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Reset clears all decoder state.
|
||||
func (d *Decoder) Reset() { d.st.reset() }
|
||||
@@ -0,0 +1,261 @@
|
||||
package amrnb
|
||||
|
||||
// Fixed-point basic operators, ported from the ITU-T / ETSI "basicop2"
|
||||
// reference (the same primitives used by opencore-amrnb).
|
||||
//
|
||||
// Word16 maps to int16, Word32 to int32. All operators saturate to the 16- or
|
||||
// 32-bit range exactly as the reference does; Go has no implicit integer
|
||||
// saturation, so every operation that can overflow clamps explicitly. The
|
||||
// reference's global Overflow/Carry flags are not modelled — no AMR-NB code
|
||||
// path depends on reading them back.
|
||||
|
||||
const (
|
||||
maxInt16 = 32767
|
||||
minInt16 = -32768
|
||||
maxInt32 = 2147483647
|
||||
minInt32 = -2147483648
|
||||
)
|
||||
|
||||
// satWord16 clamps a 32-bit value to the int16 range.
|
||||
func satWord16(v int32) int16 {
|
||||
if v > maxInt16 {
|
||||
return maxInt16
|
||||
}
|
||||
if v < minInt16 {
|
||||
return minInt16
|
||||
}
|
||||
return int16(v)
|
||||
}
|
||||
|
||||
// satWord32 clamps a 64-bit value to the int32 range.
|
||||
func satWord32(v int64) int32 {
|
||||
if v > maxInt32 {
|
||||
return maxInt32
|
||||
}
|
||||
if v < minInt32 {
|
||||
return minInt32
|
||||
}
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
// add returns the saturated 16-bit sum a+b.
|
||||
func add(a, b int16) int16 { return satWord16(int32(a) + int32(b)) }
|
||||
|
||||
// sub returns the saturated 16-bit difference a-b.
|
||||
func sub(a, b int16) int16 { return satWord16(int32(a) - int32(b)) }
|
||||
|
||||
// abs_s returns the saturated 16-bit absolute value (|-32768| -> 32767).
|
||||
func abs_s(a int16) int16 {
|
||||
if a == minInt16 {
|
||||
return maxInt16
|
||||
}
|
||||
if a < 0 {
|
||||
return -a
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// shl returns a shifted left by b bits with saturation; negative b shifts right.
|
||||
func shl(a, b int16) int16 {
|
||||
if b < 0 {
|
||||
return shr(a, -b)
|
||||
}
|
||||
result := int32(a) << uint(b)
|
||||
// Detect overflow by checking the shift is reversible.
|
||||
if b >= 31 {
|
||||
if a == 0 {
|
||||
return 0
|
||||
}
|
||||
if a > 0 {
|
||||
return maxInt16
|
||||
}
|
||||
return minInt16
|
||||
}
|
||||
return satWord16(result)
|
||||
}
|
||||
|
||||
// shr returns a shifted right by b bits (arithmetic); negative b shifts left.
|
||||
func shr(a, b int16) int16 {
|
||||
if b < 0 {
|
||||
return shl(a, -b)
|
||||
}
|
||||
if b >= 15 {
|
||||
if a < 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return int16(int32(a) >> uint(b))
|
||||
}
|
||||
|
||||
// L_shl returns the saturated 32-bit left shift; negative b shifts right.
|
||||
func L_shl(a int32, b int16) int32 {
|
||||
if b <= 0 {
|
||||
if b == 0 {
|
||||
return a
|
||||
}
|
||||
return L_shr(a, -b)
|
||||
}
|
||||
if b >= 31 {
|
||||
if a > 0 {
|
||||
return maxInt32
|
||||
}
|
||||
if a < 0 {
|
||||
return minInt32
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// The reference shifts one bit at a time, saturating as soon as the value
|
||||
// reaches the overflow region. Since |a<<k| grows monotonically, saturation
|
||||
// is decided by the last pre-shift value a<<(b-1): >= 2^30 -> MAX_32,
|
||||
// < -2^30 -> MIN_32; otherwise a<<b fits. Computed in int64 this is O(1) and
|
||||
// bit-identical to the loop.
|
||||
last := int64(a) << uint(b-1)
|
||||
if last >= 0x40000000 {
|
||||
return maxInt32
|
||||
}
|
||||
if last < -0x40000000 {
|
||||
return minInt32
|
||||
}
|
||||
return a << uint(b)
|
||||
}
|
||||
|
||||
// L_shr returns the 32-bit arithmetic right shift; negative b shifts left.
|
||||
func L_shr(a int32, b int16) int32 {
|
||||
if b < 0 {
|
||||
return L_shl(a, -b)
|
||||
}
|
||||
if b >= 31 {
|
||||
if a < 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return a >> uint(b)
|
||||
}
|
||||
|
||||
// mult returns satWord16((a*b)>>15).
|
||||
func mult(a, b int16) int16 {
|
||||
p := (int32(a) * int32(b)) >> 15
|
||||
return satWord16(p)
|
||||
}
|
||||
|
||||
// L_mult returns satWord32(a*b<<1) — the 32-bit product with the fractional
|
||||
// left shift.
|
||||
func L_mult(a, b int16) int32 {
|
||||
p := int32(a) * int32(b)
|
||||
if p != 0x40000000 {
|
||||
return p << 1
|
||||
}
|
||||
return maxInt32
|
||||
}
|
||||
|
||||
// L_add returns the saturated 32-bit sum.
|
||||
func L_add(a, b int32) int32 { return satWord32(int64(a) + int64(b)) }
|
||||
|
||||
// L_sub returns the saturated 32-bit difference.
|
||||
func L_sub(a, b int32) int32 { return satWord32(int64(a) - int64(b)) }
|
||||
|
||||
// L_mac returns acc + (a*b<<1), saturated.
|
||||
func L_mac(acc int32, a, b int16) int32 { return L_add(acc, L_mult(a, b)) }
|
||||
|
||||
// L_msu returns acc - (a*b<<1), saturated.
|
||||
func L_msu(acc int32, a, b int16) int32 { return L_sub(acc, L_mult(a, b)) }
|
||||
|
||||
// negate returns the saturated 16-bit negation.
|
||||
func negate(a int16) int16 {
|
||||
if a == minInt16 {
|
||||
return maxInt16
|
||||
}
|
||||
return -a
|
||||
}
|
||||
|
||||
// L_negate returns the saturated 32-bit negation.
|
||||
func L_negate(a int32) int32 {
|
||||
if a == minInt32 {
|
||||
return maxInt32
|
||||
}
|
||||
return -a
|
||||
}
|
||||
|
||||
// extract_h returns the high 16 bits of a 32-bit value.
|
||||
func extract_h(a int32) int16 { return int16(a >> 16) }
|
||||
|
||||
// extract_l returns the low 16 bits of a 32-bit value.
|
||||
func extract_l(a int32) int16 { return int16(a & 0xFFFF) }
|
||||
|
||||
// round_ returns the rounded high word: extract_h(L_add(a, 0x8000)).
|
||||
func round_(a int32) int16 { return extract_h(L_add(a, 0x8000)) }
|
||||
|
||||
// mult_r returns the rounded multiply: satWord16(((a*b)+0x4000)>>15).
|
||||
func mult_r(a, b int16) int16 {
|
||||
p := (int32(a)*int32(b) + 0x4000) >> 15
|
||||
return satWord16(p)
|
||||
}
|
||||
|
||||
// L_deposit_h places a 16-bit value in the high half of a 32-bit word.
|
||||
func L_deposit_h(a int16) int32 { return int32(a) << 16 }
|
||||
|
||||
// L_deposit_l sign-extends a 16-bit value into a 32-bit word.
|
||||
func L_deposit_l(a int16) int32 { return int32(a) }
|
||||
|
||||
// norm_l returns the number of left shifts needed to normalize a (0 for 0).
|
||||
func norm_l(a int32) int16 {
|
||||
if a == 0 {
|
||||
return 0
|
||||
}
|
||||
if a == -1 {
|
||||
return 31
|
||||
}
|
||||
if a < 0 {
|
||||
a = ^a
|
||||
}
|
||||
var n int16
|
||||
for a < 0x40000000 {
|
||||
a <<= 1
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// norm_s returns the number of left shifts needed to normalize a 16-bit value.
|
||||
func norm_s(a int16) int16 {
|
||||
if a == 0 {
|
||||
return 0
|
||||
}
|
||||
if a == -1 {
|
||||
return 15
|
||||
}
|
||||
v := a
|
||||
if v < 0 {
|
||||
v = ^v
|
||||
}
|
||||
var n int16
|
||||
for v < 0x4000 {
|
||||
v <<= 1
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// div_s returns the fractional division (a/b)<<15 for 0 <= a <= b, b != 0.
|
||||
func div_s(a, b int16) int16 {
|
||||
if a == 0 {
|
||||
return 0
|
||||
}
|
||||
if a == b {
|
||||
return maxInt16
|
||||
}
|
||||
num := int32(a)
|
||||
den := int32(b)
|
||||
var quot int32
|
||||
for i := 0; i < 15; i++ {
|
||||
quot <<= 1
|
||||
num <<= 1
|
||||
if num >= den {
|
||||
num -= den
|
||||
quot++
|
||||
}
|
||||
}
|
||||
return int16(quot)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package amrnb
|
||||
|
||||
// This file holds the AMR-NB signal-processing state and the entry points the
|
||||
// public Encoder/Decoder call per 20 ms frame. The fixed-point DSP itself
|
||||
// (LP analysis/LSF quantization, open/closed-loop pitch, algebraic codebook
|
||||
// search, gain quantization, synthesis and post-filtering, DTX) is a large
|
||||
// bit-exact port of 3GPP TS 26.090 — see opencore-amrnb (Apache-2.0), which
|
||||
// ships both encoder and decoder.
|
||||
//
|
||||
// The structs below define where that ported state lives. The encodeFrame /
|
||||
// decodeFrame methods are the single integration seam where the per-block DSP
|
||||
// functions are orchestrated per 20 ms frame.
|
||||
//
|
||||
// NOTE: the DSP is currently STUBBED — coder()/decoder() are not yet ported, so
|
||||
// encodeFrame emits a zero-filled bitstream of the correct size and decodeFrame
|
||||
// emits silence. The public API, RFC 4867 framing, fixed-point operators and
|
||||
// SIMD kernels are complete and exercised by the pure-Go tests; the bit-exact
|
||||
// DSP lands in a later phase (see the plan).
|
||||
|
||||
// encoderState holds the persistent encoder analysis state across frames.
|
||||
type encoderState struct {
|
||||
started bool
|
||||
st coderState
|
||||
}
|
||||
|
||||
func (s *encoderState) reset() {
|
||||
s.started = true
|
||||
s.st.reset()
|
||||
}
|
||||
|
||||
// encodeFrame analyses one 160-sample frame in the given mode and returns the
|
||||
// RFC 4867 frame payload bytes (the sorted/packed speech bits, without the
|
||||
// CMR/ToC header which the caller adds). Mirrors Speech_Encode_Frame: mask the 3
|
||||
// LSBs (13-bit input), high-pass + downscale (Pre_Process), run the analysis
|
||||
// (cod_amr), then serialize (prm2bits + IETF reorder via packFrame).
|
||||
func (s *encoderState) encodeFrame(mode Mode, samples []int16) ([]byte, error) {
|
||||
if !s.started {
|
||||
s.reset()
|
||||
}
|
||||
in := make([]int16, cL_FRAME)
|
||||
for i := 0; i < cL_FRAME && i < len(samples); i++ {
|
||||
in[i] = samples[i] &^ 7 // delete 3 LSBs (13-bit input)
|
||||
}
|
||||
s.st.pre.process(in, cL_FRAME)
|
||||
|
||||
prm, err := s.st.codAmr(int(mode), in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return packFrame(prm, mode), nil
|
||||
}
|
||||
|
||||
// decoderState holds the persistent decoder synthesis state across frames.
|
||||
type decoderState struct {
|
||||
dsp decoderStateDSP
|
||||
lastMode Mode
|
||||
started bool
|
||||
}
|
||||
|
||||
func (s *decoderState) reset() {
|
||||
s.dsp.reset()
|
||||
s.lastMode = Mode1220
|
||||
s.started = true
|
||||
}
|
||||
|
||||
// decodeFrame decodes one parsed AMR-NB frame into 160 PCM samples. Good speech
|
||||
// frames are fully decoded; bad/SID/no-data frames currently emit silence
|
||||
// (error concealment and CNG are not yet ported).
|
||||
func (s *decoderState) decodeFrame(f frame) ([]int16, error) {
|
||||
if !s.started {
|
||||
s.reset()
|
||||
}
|
||||
synth := make([]int16, cAMR_NB_PCM_FRAME)
|
||||
if f.ft <= FTMode1220 { // speech
|
||||
mode := Mode(f.ft)
|
||||
s.lastMode = mode
|
||||
if f.q {
|
||||
params := mimeUnsort(f.data, mode)
|
||||
s.dsp.decodeSpeechFrame(int(mode), params, synth)
|
||||
}
|
||||
}
|
||||
return synth, nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package amrnb
|
||||
|
||||
// Adaptive-codebook (pitch) reconstruction, ported from opencore-amrnb
|
||||
// pred_lt.cpp, dec_lag3.cpp and dec_lag6.cpp. predLt interpolates the past
|
||||
// excitation at a fractional pitch lag to build this subframe's adaptive
|
||||
// codebook contribution; decLag3/decLag6 turn the received pitch index into an
|
||||
// integer lag T0 and a fractional part. Bit-exact with the reference.
|
||||
|
||||
const (
|
||||
cL_INTER10 = 10 // interpolation FIR half-context (L_INTERPOL-1)
|
||||
cUP_SAMP_MAX = 6 // fractional pitch upsampling factor
|
||||
)
|
||||
|
||||
// predLt computes the interpolated past excitation for the current subframe in
|
||||
// place: exc[pos : pos+Lsubfr] is overwritten using exc[pos-T0 ...] convolved
|
||||
// with the 1/6-resolution FIR selected by frac. For short lags (T0 < Lsubfr)
|
||||
// the loop deliberately reads samples it just wrote, matching the reference.
|
||||
// Mirrors Pred_lt_3or6.
|
||||
func predLt(exc []int16, pos int, T0, frac, Lsubfr, flag3 int16) {
|
||||
var coeff [2 * cL_INTER10]int16
|
||||
|
||||
pX0 := pos - int(T0)
|
||||
frac = -frac
|
||||
if flag3 != 0 {
|
||||
frac <<= 1 // inter_3l[k] = inter_6[2k]
|
||||
}
|
||||
if frac < 0 {
|
||||
frac += cUP_SAMP_MAX
|
||||
pX0--
|
||||
}
|
||||
f := int(frac)
|
||||
|
||||
k := 0
|
||||
ci := 0
|
||||
for i := cL_INTER10 >> 1; i > 0; i-- {
|
||||
coeff[ci] = inter_6_pred_lt[f+k]
|
||||
coeff[ci+1] = inter_6_pred_lt[(cUP_SAMP_MAX-f)+k]
|
||||
ci += 2
|
||||
k += cUP_SAMP_MAX
|
||||
coeff[ci] = inter_6_pred_lt[f+k]
|
||||
coeff[ci+1] = inter_6_pred_lt[(cUP_SAMP_MAX-f)+k]
|
||||
ci += 2
|
||||
k += cUP_SAMP_MAX
|
||||
}
|
||||
|
||||
pExc := pos
|
||||
for j := int(Lsubfr) >> 1; j != 0; j-- {
|
||||
pX0++
|
||||
ix2 := pX0
|
||||
ix3 := pX0
|
||||
pX0++
|
||||
|
||||
ic := 0
|
||||
s1 := int32(0x4000)
|
||||
s2 := int32(0x4000)
|
||||
for i := cL_INTER10 >> 1; i > 0; i-- {
|
||||
s2 += int32(exc[ix3]) * int32(coeff[ic])
|
||||
ix3--
|
||||
s1 += int32(exc[ix3]) * int32(coeff[ic])
|
||||
ic++
|
||||
s1 += int32(exc[ix2]) * int32(coeff[ic])
|
||||
ix2++
|
||||
s2 += int32(exc[ix2]) * int32(coeff[ic])
|
||||
ic++
|
||||
s2 += int32(exc[ix3]) * int32(coeff[ic])
|
||||
ix3--
|
||||
s1 += int32(exc[ix3]) * int32(coeff[ic])
|
||||
ic++
|
||||
s1 += int32(exc[ix2]) * int32(coeff[ic])
|
||||
ix2++
|
||||
s2 += int32(exc[ix2]) * int32(coeff[ic])
|
||||
ic++
|
||||
}
|
||||
exc[pExc] = int16(s1 >> 15)
|
||||
pExc++
|
||||
exc[pExc] = int16(s2 >> 15)
|
||||
pExc++
|
||||
}
|
||||
}
|
||||
|
||||
// decLag3 decodes the 1/3-resolution pitch lag (used by most modes) from the
|
||||
// received index into an integer lag T0 and fractional part T0frac. i_subfr==0
|
||||
// marks the 1st/3rd subframe (absolute coding); otherwise the lag is coded
|
||||
// relative to t0_min (or, with flag4, to T0prev). Mirrors Dec_lag3.
|
||||
func decLag3(index, t0Min, t0Max, iSubfr, T0prev, flag4 int16) (T0, T0frac int16) {
|
||||
var i, tmpLag int16
|
||||
if iSubfr == 0 { // 1st or 3rd subframe
|
||||
if index < 197 {
|
||||
tmpLag = index + 2
|
||||
tmpLag = mult(tmpLag, 10923)
|
||||
i = tmpLag + 19
|
||||
T0 = i
|
||||
i <<= 1
|
||||
i += T0
|
||||
tmpLag = index - i
|
||||
T0frac = tmpLag + 58
|
||||
} else {
|
||||
T0 = index - 112
|
||||
T0frac = 0
|
||||
}
|
||||
} else { // 2nd or 4th subframe
|
||||
if flag4 == 0 {
|
||||
i = index + 2
|
||||
i = int16((int32(i) * 10923) >> 15)
|
||||
i -= 1
|
||||
T0 = i + t0Min
|
||||
i = i + (i << 1)
|
||||
tmpLag = index - 2
|
||||
T0frac = tmpLag - i
|
||||
} else {
|
||||
tmpLag = T0prev
|
||||
i = sub(tmpLag, t0Min)
|
||||
if i > 5 {
|
||||
tmpLag = t0Min + 5
|
||||
}
|
||||
i = t0Max - tmpLag
|
||||
if i > 4 {
|
||||
tmpLag = t0Max - 4
|
||||
}
|
||||
if index < 4 {
|
||||
i = tmpLag - 5
|
||||
T0 = i + index
|
||||
T0frac = 0
|
||||
} else if index < 12 {
|
||||
i = index - 5
|
||||
i = int16((int32(i) * 10923) >> 15)
|
||||
i--
|
||||
T0 = i + tmpLag
|
||||
i = i + (i << 1)
|
||||
tmpLag = index - 9
|
||||
T0frac = tmpLag - i
|
||||
} else {
|
||||
i = index - 12
|
||||
i = i + tmpLag
|
||||
T0 = i + 1
|
||||
T0frac = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return T0, T0frac
|
||||
}
|
||||
|
||||
// decLag6 decodes the 1/6-resolution pitch lag (MR122) from the received index.
|
||||
// T0in supplies the previous subframe's integer lag for the relative (2nd/4th)
|
||||
// subframe path. Mirrors Dec_lag6.
|
||||
func decLag6(index, pitMin, pitMax, iSubfr, T0in int16) (T0, T0frac int16) {
|
||||
var i, T0min, T0max, k int16
|
||||
if iSubfr == 0 { // 1st or 3rd subframe
|
||||
if index < 463 {
|
||||
i = index + 5
|
||||
i = int16((int32(i) * 5462) >> 15)
|
||||
i += 17
|
||||
T0 = i
|
||||
i <<= 1
|
||||
i += T0
|
||||
i <<= 1
|
||||
i = index - i
|
||||
T0frac = i + 105
|
||||
} else {
|
||||
T0 = index - 368
|
||||
T0frac = 0
|
||||
}
|
||||
} else { // 2nd or 4th subframe
|
||||
T0min = T0in - 5
|
||||
if T0min < pitMin {
|
||||
T0min = pitMin
|
||||
}
|
||||
T0max = T0min + 9
|
||||
if T0max > pitMax {
|
||||
T0max = pitMax
|
||||
T0min = T0max - 9
|
||||
}
|
||||
i = index + 5
|
||||
i = int16((int32(i) * 5462) >> 15)
|
||||
i -= 1
|
||||
T0 = i + T0min
|
||||
i = i + (i << 1)
|
||||
i <<= 1
|
||||
k = index - 3
|
||||
T0frac = k - i
|
||||
}
|
||||
return T0, T0frac
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package amrnb
|
||||
|
||||
// Adaptive gain control for the post-filter, ported from opencore-amrnb agc.cpp.
|
||||
// agc scales the post-filtered signal so its energy tracks the synthesis
|
||||
// energy, with a per-sample gain smoothed by agc_fac across the subframe. The
|
||||
// running gain is carried in agcState. Bit-exact with the reference.
|
||||
|
||||
const cAGC_FAC = 29491 // 0.9, the post-filter AGC factor
|
||||
|
||||
// agcState holds the smoothed per-sample gain across subframes.
|
||||
type agcState struct {
|
||||
pastGain int16
|
||||
}
|
||||
|
||||
// energyOld accumulates the energy of a prescaled signal (used when energyNew
|
||||
// would overflow). Mirrors energy_old.
|
||||
func energyOld(in []int16, lTrm int) int32 {
|
||||
var s int32
|
||||
for i := 0; i < lTrm; i++ {
|
||||
temp := in[i] >> 2
|
||||
s = L_mac(s, temp, temp)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// energyNew returns the energy of a signal (>>4 scaled), falling back to
|
||||
// energyOld on saturation. Mirrors energy_new.
|
||||
func energyNew(in []int16, lTrm int) int32 {
|
||||
var s int32
|
||||
for i := 0; i < lTrm; i++ {
|
||||
s = L_mac(s, in[i], in[i])
|
||||
}
|
||||
if s != maxInt32 {
|
||||
return s >> 4
|
||||
}
|
||||
return energyOld(in, lTrm)
|
||||
}
|
||||
|
||||
// agc scales sigOut in place so its energy approaches sigIn's, applying a gain
|
||||
// smoothed across the subframe by agcFac and carried in the state. Mirrors agc.
|
||||
func (st *agcState) agc(sigIn, sigOut []int16, agcFac int16, lTrm int) {
|
||||
s := energyNew(sigOut, lTrm)
|
||||
if s == 0 {
|
||||
st.pastGain = 0
|
||||
return
|
||||
}
|
||||
exp := norm_l(s) - 1
|
||||
gainOut := round_(L_shl(s, exp))
|
||||
|
||||
s = energyNew(sigIn, lTrm)
|
||||
var g0 int16
|
||||
if s == 0 {
|
||||
g0 = 0
|
||||
} else {
|
||||
i := norm_l(s)
|
||||
gainIn := round_(s << i)
|
||||
exp -= i
|
||||
|
||||
temp := div_s(gainOut, gainIn)
|
||||
ss := int32(temp) << 7
|
||||
ss = L_shr(ss, exp)
|
||||
ss = invSqrt(ss)
|
||||
lTemp := ss << 9
|
||||
ii := int16((lTemp + 0x8000) >> 16)
|
||||
temp = 32767 - agcFac
|
||||
g0 = int16((int32(ii) * int32(temp)) >> 15)
|
||||
}
|
||||
|
||||
gain := st.pastGain
|
||||
for i := 0; i < lTrm; i++ {
|
||||
gain = int16((int32(gain) * int32(agcFac)) >> 15)
|
||||
gain += g0
|
||||
lTemp := (int32(sigOut[i]) * int32(gain)) << 1
|
||||
sigOut[i] = int16(lTemp >> 13)
|
||||
}
|
||||
st.pastGain = gain
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package amrnb
|
||||
|
||||
// A(z) -> LSP conversion, ported from opencore-amrnb az_lsp.cpp (Chebps, Az_lsp).
|
||||
// Az_lsp finds the line spectral pairs of an order-10 LP filter by locating the
|
||||
// roots of the symmetric/antisymmetric polynomials F1(z)/F2(z) on the unit
|
||||
// circle, evaluated via the Chebyshev recursion (Chebps) over the cosine grid.
|
||||
// This is the encoder-side inverse of lspAz. Bit-exact with the reference.
|
||||
|
||||
const cNC = cM / 2 // 5
|
||||
|
||||
// chebps evaluates the Chebyshev series of f at x. Mirrors Chebps.
|
||||
func chebps(x int16, f []int16, n int) int16 {
|
||||
pf := 1
|
||||
lTemp := int32(0x01000000)
|
||||
t0 := (int32(x) << 10) + (int32(f[pf]) << 14)
|
||||
pf++
|
||||
b1h := int16(t0 >> 16)
|
||||
b1l := int16((t0 >> 1) - (int32(b1h) << 15))
|
||||
|
||||
for i := 2; i < n; i++ {
|
||||
t0 = int32(b1h) * int32(x)
|
||||
t0 += (int32(b1l) * int32(x)) >> 15
|
||||
t0 <<= 2
|
||||
t0 -= lTemp
|
||||
t0 += int32(f[pf]) << 14
|
||||
pf++
|
||||
lTemp = (int32(b1h) << 16) + (int32(b1l) << 1)
|
||||
b1h = int16(t0 >> 16)
|
||||
b1l = int16((t0 >> 1) - (int32(b1h) << 15))
|
||||
}
|
||||
|
||||
t0 = int32(b1h) * int32(x)
|
||||
t0 += (int32(b1l) * int32(x)) >> 15
|
||||
t0 <<= 1
|
||||
t0 -= lTemp
|
||||
t0 += int32(f[pf]) << 13
|
||||
|
||||
if uint32(t0+33554432) < 67108863 {
|
||||
return int16(t0 >> 10)
|
||||
} else if t0 > 0x01ffffff {
|
||||
return maxInt16
|
||||
}
|
||||
return minInt16
|
||||
}
|
||||
|
||||
// azLsp converts order-10 LP coefficients a[0:M+1] to line spectral pairs
|
||||
// lsp[0:M]. oldLsp is reused if fewer than M roots are found. Mirrors Az_lsp.
|
||||
func azLsp(a, lsp, oldLsp []int16) {
|
||||
var f1, f2 [cNC + 1]int16
|
||||
f1[0] = 1024
|
||||
f2[0] = 1024
|
||||
for i := 0; i < cNC; i++ {
|
||||
lt1 := int32(a[i+1])
|
||||
lt2 := int32(a[cM-i])
|
||||
x := int16((lt1 + lt2) >> 2)
|
||||
y := int16((lt1 - lt2) >> 2)
|
||||
x -= f1[i]
|
||||
f1[i+1] = x
|
||||
y += f2[i]
|
||||
f2[i+1] = y
|
||||
}
|
||||
|
||||
nf := 0
|
||||
ip := 0
|
||||
coef := f1[:]
|
||||
|
||||
xlow := grid[0]
|
||||
ylow := chebps(xlow, coef, cNC)
|
||||
|
||||
j := 0
|
||||
for nf < cM && j < 60 {
|
||||
j++
|
||||
xhigh := xlow
|
||||
yhigh := ylow
|
||||
xlow = grid[j]
|
||||
ylow = chebps(xlow, coef, cNC)
|
||||
|
||||
if int32(ylow)*int32(yhigh) <= 0 {
|
||||
for i := 4; i != 0; i-- {
|
||||
xmid := (xlow >> 1) + (xhigh >> 1)
|
||||
ymid := chebps(xmid, coef, cNC)
|
||||
if int32(ylow)*int32(ymid) <= 0 {
|
||||
yhigh = ymid
|
||||
xhigh = xmid
|
||||
} else {
|
||||
ylow = ymid
|
||||
xlow = xmid
|
||||
}
|
||||
}
|
||||
|
||||
x := xhigh - xlow
|
||||
yv := yhigh - ylow
|
||||
var xint int16
|
||||
if yv == 0 {
|
||||
xint = xlow
|
||||
} else {
|
||||
sign := yv
|
||||
yv = abs_s(yv)
|
||||
exp := norm_s(yv)
|
||||
yv = shl(yv, exp)
|
||||
yv = div_s(16383, yv)
|
||||
yv = int16((int32(x) * int32(yv)) >> uint(19-exp))
|
||||
if sign < 0 {
|
||||
yv = -yv
|
||||
}
|
||||
xint = int16(int32(xlow) - ((int32(ylow) * int32(yv)) >> 10))
|
||||
}
|
||||
|
||||
lsp[nf] = xint
|
||||
xlow = xint
|
||||
nf++
|
||||
|
||||
if ip == 0 {
|
||||
ip = 1
|
||||
coef = f2[:]
|
||||
} else {
|
||||
ip = 0
|
||||
coef = f1[:]
|
||||
}
|
||||
ylow = chebps(xlow, coef, cNC)
|
||||
}
|
||||
}
|
||||
|
||||
if nf < cM {
|
||||
copy(lsp[:cM], oldLsp[:cM])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package amrnb
|
||||
|
||||
// Decoder-side bit de-serialization, ported from opencore-amrnb bits2prm.cpp
|
||||
// (Bin2int/Bits2prm) and the IETF->ETS reordering in wmf_to_ets.cpp (used for
|
||||
// MIME_IETF input). The chain inverts the encoder side: IETF storage bytes ->
|
||||
// ETS serial bits (unpacked MSB-first then inverse-permuted by reorderBits) ->
|
||||
// parameter array (each parameter read MSB-first from its bitno width).
|
||||
|
||||
// bin2int reads nBits from bits[0:nBits], MSB first (bits[0] is the most
|
||||
// significant bit), and returns the assembled value. Mirrors Bin2int.
|
||||
func bin2int(nBits int, bits []int16) int16 {
|
||||
var v int16
|
||||
for i := 0; i < nBits; i++ {
|
||||
v = (v << 1) | (bits[i] & 1)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ietfToEts unpacks the RFC 4867 speech payload bytes (IETF storage order,
|
||||
// MSB-first) and inverse-permutes them via reorderBits[mode] into the ETS
|
||||
// serial bit array. Mirrors the speech branch of wmf_to_ets.
|
||||
func ietfToEts(mode Mode, data []byte) []int16 {
|
||||
nb := modeBits[mode]
|
||||
ro := reorderBits[mode]
|
||||
ets := make([]int16, nb)
|
||||
for i := 0; i < nb; i++ {
|
||||
bit := (data[i>>3] >> uint(7-(i&7))) & 1
|
||||
ets[ro[i]] = int16(bit)
|
||||
}
|
||||
return ets
|
||||
}
|
||||
|
||||
// bits2prm reads the ETS serial bit array back into the mode's parameter array.
|
||||
// Mirrors Bits2prm.
|
||||
func bits2prm(mode Mode, ets []int16) []int16 {
|
||||
bn := bitno[mode]
|
||||
prm := make([]int16, len(bn))
|
||||
pos := 0
|
||||
for i, w := range bn {
|
||||
prm[i] = bin2int(int(w), ets[pos:])
|
||||
pos += int(w)
|
||||
}
|
||||
return prm
|
||||
}
|
||||
|
||||
// mimeUnsort decodes the RFC 4867 speech payload bytes for the given mode into
|
||||
// the decoder parameter array (the inverse of packFrame).
|
||||
func mimeUnsort(data []byte, mode Mode) []int16 {
|
||||
return bits2prm(mode, ietfToEts(mode, data))
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package amrnb
|
||||
|
||||
// Core AMR-NB signal-processing constants, from opencore-amrnb cnst.h and
|
||||
// mode.h. AMR-NB codes 8 kHz speech in 20 ms frames of 160 samples, with an
|
||||
// order-10 LP filter and four 40-sample subframes.
|
||||
|
||||
const (
|
||||
cL_FRAME = 160 // frame size (samples)
|
||||
cL_FRAME_BY2 = 80 // frame size / 2
|
||||
cL_SUBFR = 40 // subframe size
|
||||
cL_CODE = 40 // codevector length
|
||||
cNB_SUBFR = 4 // subframes per frame
|
||||
cL_WINDOW = 240 // LP analysis window size
|
||||
cL_NEXT = 40 // LP analysis look-ahead
|
||||
cL_TOTAL = 320 // total speech analysis buffer
|
||||
cM = 10 // LP filter order
|
||||
cMP1 = 11 // LP filter order + 1
|
||||
cL_INTERPOL = 11 // interpolation filter length (10+1)
|
||||
cPIT_MIN = 20 // minimum pitch lag
|
||||
cPIT_MAX = 143 // maximum pitch lag
|
||||
|
||||
cAMR_NB_PCM_FRAME = 160 // PCM samples produced/consumed per frame
|
||||
cMAX_PRM_SIZE = 57 // max number of serial parameters (MR122)
|
||||
)
|
||||
|
||||
// Internal coding-mode enum, matching opencore-amrnb mode.h (enum Mode). These
|
||||
// are the DSP-side mode indices; the public Mode type shares the speech values
|
||||
// 0..7, with dMRDTX used for SID/comfort-noise handling.
|
||||
const (
|
||||
dMR475 = iota
|
||||
dMR515
|
||||
dMR59
|
||||
dMR67
|
||||
dMR74
|
||||
dMR795
|
||||
dMR102
|
||||
dMR122
|
||||
dMRDTX
|
||||
dNMODES
|
||||
)
|
||||
|
||||
// Receive frame types, matching opencore-amrnb enum RXFrameType. The decoder
|
||||
// driver maps RFC 4867 ToC frame types onto these.
|
||||
const (
|
||||
cRX_SPEECH_GOOD = iota
|
||||
cRX_SPEECH_DEGRADED
|
||||
cRX_ONSET
|
||||
cRX_SPEECH_BAD
|
||||
cRX_SID_FIRST
|
||||
cRX_SID_UPDATE
|
||||
cRX_SID_BAD
|
||||
cRX_NO_DATA
|
||||
cRX_N_FRAMETYPES
|
||||
)
|
||||
@@ -0,0 +1,262 @@
|
||||
package amrnb
|
||||
|
||||
// Per-mode algebraic (fixed) codebook decoders, ported from opencore-amrnb
|
||||
// d2_9pf.cpp, d2_11pf.cpp, d3_14pf.cpp, d4_17pf.cpp, d8_31pf.cpp and
|
||||
// d1035pf.cpp. Each reconstructs the 40-sample innovation vector cod[] from a
|
||||
// mode's pulse-position and sign indices (GSM-style tracks, Gray-coded for the
|
||||
// 4/8/10-pulse modes). Pulse amplitudes are ±8191/∓8192 (or ±4096 for MR122),
|
||||
// matching the reference exactly.
|
||||
|
||||
// pulse amplitude for the ±1.0 codebook entries (MR122 uses ±4096).
|
||||
const cPOS_CODE = 8191
|
||||
|
||||
// decode2i40_9bits decodes the 2-pulse codebook for MR475/MR515. Mirrors
|
||||
// decode_2i40_9bits (d2_9pf.cpp); subNr is the subframe number.
|
||||
func decode2i40_9bits(subNr, sign, index int16, cod []int16) {
|
||||
var pos [2]int16
|
||||
|
||||
j := index & 64
|
||||
j >>= 3
|
||||
i := index & 7
|
||||
k := shl(subNr, 1)
|
||||
k += j
|
||||
pos[0] = i*5 + startPos[k]
|
||||
k++
|
||||
|
||||
index >>= 3
|
||||
i = index & 7
|
||||
pos[1] = i*5 + startPos[k]
|
||||
|
||||
for n := 0; n < cL_SUBFR; n++ {
|
||||
cod[n] = 0
|
||||
}
|
||||
for j := 0; j < 2; j++ {
|
||||
s := sign & 0x1
|
||||
cod[pos[j]] = s*16383 - 8192
|
||||
sign >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// decode2i40_11bits decodes the 2-pulse codebook for MR59. Mirrors
|
||||
// decode_2i40_11bits (d2_11pf.cpp).
|
||||
func decode2i40_11bits(sign, index int16, cod []int16) {
|
||||
var pos [2]int16
|
||||
|
||||
j := index & 0x1
|
||||
index >>= 1
|
||||
i := index & 0x7
|
||||
pos[0] = i*5 + j*2 + 1
|
||||
|
||||
index >>= 3
|
||||
j = index & 0x3
|
||||
index >>= 2
|
||||
i = index & 0x7
|
||||
if j == 3 {
|
||||
pos[1] = i*5 + 4
|
||||
} else {
|
||||
pos[1] = i*5 + j
|
||||
}
|
||||
|
||||
for n := 0; n < cL_SUBFR; n++ {
|
||||
cod[n] = 0
|
||||
}
|
||||
for j := 0; j < 2; j++ {
|
||||
s := sign & 1
|
||||
cod[pos[j]] = s*16383 - 8192
|
||||
sign >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// decode3i40_14bits decodes the 3-pulse codebook for MR67. Mirrors
|
||||
// decode_3i40_14bits (d3_14pf.cpp).
|
||||
func decode3i40_14bits(sign, index int16, cod []int16) {
|
||||
var pos [3]int16
|
||||
|
||||
i := index & 0x7
|
||||
pos[0] = i * 5
|
||||
|
||||
index >>= 3
|
||||
j := index & 0x1
|
||||
index >>= 1
|
||||
i = index & 0x7
|
||||
pos[1] = i*5 + j*2 + 1
|
||||
|
||||
index >>= 3
|
||||
j = index & 0x1
|
||||
index >>= 1
|
||||
i = index & 0x7
|
||||
pos[2] = i*5 + j*2 + 2
|
||||
|
||||
for n := 0; n < cL_SUBFR; n++ {
|
||||
cod[n] = 0
|
||||
}
|
||||
for j := 0; j < 3; j++ {
|
||||
s := sign & 1
|
||||
cod[pos[j]] = s*16383 - 8192
|
||||
sign >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// decode4i40_17bits decodes the 4-pulse Gray-coded codebook for MR74/MR795.
|
||||
// Mirrors decode_4i40_17bits (d4_17pf.cpp).
|
||||
func decode4i40_17bits(sign, index int16, cod []int16) {
|
||||
var pos [4]int16
|
||||
|
||||
i := index & 0x7
|
||||
i = dgray[i]
|
||||
pos[0] = i * 5
|
||||
|
||||
index >>= 3
|
||||
i = index & 0x7
|
||||
i = dgray[i]
|
||||
pos[1] = i*5 + 1
|
||||
|
||||
index >>= 3
|
||||
i = index & 0x7
|
||||
i = dgray[i]
|
||||
pos[2] = i*5 + 2
|
||||
|
||||
index >>= 3
|
||||
j := index & 0x1
|
||||
index >>= 1
|
||||
i = index & 0x7
|
||||
i = dgray[i]
|
||||
pos[3] = i*5 + 3 + j
|
||||
|
||||
for n := 0; n < cL_SUBFR; n++ {
|
||||
cod[n] = 0
|
||||
}
|
||||
for j := 0; j < 4; j++ {
|
||||
s := sign & 0x1
|
||||
cod[pos[j]] = s*16383 - 8192
|
||||
sign >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// decompress10 expands one compressed 10x10x10 track index into three pulse
|
||||
// positions. Mirrors decompress10 (d8_31pf.cpp).
|
||||
func decompress10(MSBs, LSBs, index1, index2, index3 int16, posIndx []int16) {
|
||||
if MSBs > 124 {
|
||||
MSBs = 124
|
||||
}
|
||||
ia := mult(MSBs, 1311)
|
||||
ia = int16(int32(MSBs) - (L_mult(ia, 25) >> 1))
|
||||
ib := mult(ia, 6554)
|
||||
ib = ia - int16(L_mult(ib, 5)>>1)
|
||||
ib = shl(ib, 1)
|
||||
ic := LSBs - ((LSBs >> 2) << 2)
|
||||
posIndx[index1] = ib + (ic & 1)
|
||||
|
||||
ib = mult(ia, 6554)
|
||||
ib = shl(ib, 1)
|
||||
posIndx[index2] = ib + (ic >> 1)
|
||||
|
||||
ib = LSBs >> 2
|
||||
ic = mult(MSBs, 1311)
|
||||
ic = shl(ic, 1)
|
||||
posIndx[index3] = add(ib, ic)
|
||||
}
|
||||
|
||||
// decompressCode expands the eight compressed MR102 pulse indices into linear
|
||||
// signs and codeword positions. Mirrors decompress_code (d8_31pf.cpp).
|
||||
func decompressCode(indx, signIndx, posIndx []int16) {
|
||||
const nbTrack = 4
|
||||
for i := 0; i < nbTrack; i++ {
|
||||
signIndx[i] = indx[i]
|
||||
}
|
||||
|
||||
MSBs := indx[nbTrack] >> 3
|
||||
LSBs := indx[nbTrack] & 0x7
|
||||
decompress10(MSBs, LSBs, 0, 4, 1, posIndx)
|
||||
|
||||
MSBs = indx[nbTrack+1] >> 3
|
||||
LSBs = indx[nbTrack+1] & 0x7
|
||||
decompress10(MSBs, LSBs, 2, 6, 5, posIndx)
|
||||
|
||||
MSBs = indx[nbTrack+2] >> 2
|
||||
LSBs = indx[nbTrack+2] & 0x3
|
||||
ia := int16(L_mult(MSBs, 25) >> 1)
|
||||
ia += 12
|
||||
MSBs0_24 := ia >> 5
|
||||
|
||||
ia = mult(MSBs0_24, 6554)
|
||||
ia &= 1
|
||||
ib := mult(MSBs0_24, 6554)
|
||||
ib = MSBs0_24 - int16(L_mult(ib, 5)>>1)
|
||||
if ia == 1 {
|
||||
ib = 4 - ib
|
||||
}
|
||||
ib = shl(ib, 1)
|
||||
ia = LSBs & 0x1
|
||||
posIndx[3] = add(ib, ia)
|
||||
|
||||
ia = mult(MSBs0_24, 6554)
|
||||
ia = shl(ia, 1)
|
||||
posIndx[7] = ia + (LSBs >> 1)
|
||||
}
|
||||
|
||||
// dec8i40_31bits decodes the 8-pulse codebook for MR102. Mirrors
|
||||
// dec_8i40_31bits (d8_31pf.cpp).
|
||||
func dec8i40_31bits(index, cod []int16) {
|
||||
const nbTrack = 4
|
||||
var linearSigns [nbTrack]int16
|
||||
var linearCodewords [8]int16
|
||||
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
decompressCode(index, linearSigns[:], linearCodewords[:])
|
||||
|
||||
for j := int16(0); j < nbTrack; j++ {
|
||||
pos1 := (linearCodewords[j] << 2) + j
|
||||
var sign int16
|
||||
if linearSigns[j] == 0 {
|
||||
sign = cPOS_CODE // +1.0
|
||||
} else {
|
||||
sign = -cPOS_CODE // -1.0
|
||||
}
|
||||
if pos1 < cL_SUBFR {
|
||||
cod[pos1] = sign
|
||||
}
|
||||
pos2 := (linearCodewords[j+4] << 2) + j
|
||||
if pos2 < pos1 {
|
||||
sign = negate(sign)
|
||||
}
|
||||
if pos2 < cL_SUBFR {
|
||||
cod[pos2] += sign
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dec10i40_35bits decodes the 10-pulse Gray-coded codebook for MR122. Mirrors
|
||||
// dec_10i40_35bits (d1035pf.cpp).
|
||||
func dec10i40_35bits(index, cod []int16) {
|
||||
const nbTrack = 5
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
for j := int16(0); j < nbTrack; j++ {
|
||||
tmp := index[j]
|
||||
i := tmp & 7
|
||||
i = dgray[i]
|
||||
i = i * 5
|
||||
pos1 := i + j
|
||||
|
||||
var sign int16
|
||||
if (tmp>>3)&1 == 0 {
|
||||
sign = 4096 // +1.0
|
||||
} else {
|
||||
sign = -4096 // -1.0
|
||||
}
|
||||
cod[pos1] = sign
|
||||
|
||||
i = index[j+5] & 7
|
||||
i = dgray[i]
|
||||
i = i * 5
|
||||
pos2 := i + j
|
||||
if pos2 < pos1 {
|
||||
sign = negate(sign)
|
||||
}
|
||||
cod[pos2] += sign
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package amrnb
|
||||
|
||||
// Gain dequantization, ported from opencore-amrnb d_gain_p.cpp, d_gain_c.cpp and
|
||||
// dec_gain.cpp. These turn the received gain indices into the per-subframe pitch
|
||||
// gain and fixed-codebook gain, driving the gc_pred energy predictor. dGainPitch
|
||||
// and dGainCode handle MR795/MR122; decGain handles the joint/scalar-VQ modes
|
||||
// (MR475 joint, MR515/MR59 low-rate, MR67/MR74/MR102 high-rate). Bit-exact with
|
||||
// the reference.
|
||||
|
||||
const cMR475_VQ_SIZE = 256
|
||||
|
||||
// dGainPitch returns the quantized pitch gain (Q14) for the given index.
|
||||
// Mirrors d_gain_pitch.
|
||||
func dGainPitch(mode int, index int16) int16 {
|
||||
gain := qua_gain_pitch[index]
|
||||
if mode == dMR122 {
|
||||
gain &^= 3 // clear 2 LSBits
|
||||
}
|
||||
return gain
|
||||
}
|
||||
|
||||
// dGainCode decodes the fixed-codebook gain for MR795/MR122 from the predicted
|
||||
// gain (gc_pred) and the quantized correction factor, then updates the
|
||||
// predictor. Mirrors d_gain_code.
|
||||
func (pred *gcPredState) dGainCode(mode int, index int16, code []int16) (gainCode int16) {
|
||||
r := pred.gcPred(mode, code)
|
||||
exp, frac := r.expGcode0, r.fracGcode0
|
||||
|
||||
index &= 31 // index < 32, avoid buffer overflow
|
||||
tblTmp := index + (index << 1)
|
||||
p := qua_gain_code[tblTmp:]
|
||||
|
||||
if mode == dMR122 {
|
||||
gcode0 := int16(pow2(exp, frac))
|
||||
gcode0 = shl(gcode0, 4)
|
||||
gainCode = shl(mult(gcode0, p[0]), 1)
|
||||
} else {
|
||||
gcode0 := int16(pow2(14, frac))
|
||||
lTmp := L_mult(p[0], gcode0)
|
||||
lTmp = L_shr(lTmp, sub(9, exp))
|
||||
gainCode = int16(lTmp >> 16) // Q1
|
||||
}
|
||||
|
||||
quaEnerMR122 := p[1]
|
||||
quaEner := p[2]
|
||||
pred.update(quaEnerMR122, quaEner)
|
||||
return gainCode
|
||||
}
|
||||
|
||||
// decGain decodes both the pitch gain and fixed-codebook gain for the
|
||||
// scalar/joint-VQ modes (all except MR795/MR122), updating the predictor.
|
||||
// evenSubfr selects the MR475 joint-gain half. Mirrors Dec_gain.
|
||||
func (pred *gcPredState) decGain(mode int, index int16, code []int16, evenSubfr int16) (gainPit, gainCod int16) {
|
||||
index = shl(index, 2)
|
||||
|
||||
var gCode, quaEnerMR122, quaEner int16
|
||||
|
||||
switch {
|
||||
case mode == dMR102 || mode == dMR74 || mode == dMR67:
|
||||
p := table_gain_highrates[index:]
|
||||
gainPit = p[0]
|
||||
gCode = p[1]
|
||||
quaEnerMR122 = p[2]
|
||||
quaEner = p[3]
|
||||
case mode == dMR475:
|
||||
index += (1 ^ evenSubfr) << 1 // evenSubfr is 0 or 1
|
||||
if index > int16(cMR475_VQ_SIZE*4-2) {
|
||||
index = int16(cMR475_VQ_SIZE*4 - 2)
|
||||
}
|
||||
p := table_gain_MR475[index:]
|
||||
gainPit = p[0]
|
||||
gCode = p[1]
|
||||
// Predictor update values are recomputed (not stored in the 4.75 table):
|
||||
// qua_ener_MR122 = log2(g), qua_ener = 20*log10(g)
|
||||
e, f := log2(int32(gCode)) // Log2(x Q12) = log2(x) + 12
|
||||
e -= 12
|
||||
t1 := shr_r(f, 5)
|
||||
t2 := shl(e, 10)
|
||||
quaEnerMR122 = add(t1, t2)
|
||||
lTmp := mpy32_16(e, f, 24660) // 24660 Q12 ~= 20*log10(2)
|
||||
lTmp = L_shl(lTmp, 13)
|
||||
quaEner = round_(lTmp)
|
||||
default: // MR515, MR59
|
||||
p := table_gain_lowrates[index:]
|
||||
gainPit = p[0]
|
||||
gCode = p[1]
|
||||
quaEnerMR122 = p[2]
|
||||
quaEner = p[3]
|
||||
}
|
||||
|
||||
r := pred.gcPred(mode, code)
|
||||
exp, frac := r.expGcode0, r.fracGcode0
|
||||
gcode0 := int16(pow2(14, frac))
|
||||
|
||||
lTmp := L_mult(gCode, gcode0)
|
||||
lTmp = L_shr(lTmp, 10-exp)
|
||||
gainCod = int16(lTmp >> 16)
|
||||
|
||||
pred.update(quaEnerMR122, quaEner)
|
||||
return gainPit, gainCod
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package amrnb
|
||||
|
||||
// LSF dequantization, ported from opencore-amrnb d_plsf.cpp, d_plsf_3.cpp,
|
||||
// d_plsf_5.cpp and reorder.cpp. These reconstruct the line-spectral
|
||||
// frequencies from their transmitted codebook indices through a moving-average
|
||||
// predictor (state carried across frames), enforce a minimum LSF spacing, and
|
||||
// convert to the cosine (LSP) domain via lsfLsp. d_plsf_3 serves all modes
|
||||
// except 12.2 kbit/s; d_plsf_5 serves 12.2 kbit/s (two LSP sets per frame).
|
||||
|
||||
const (
|
||||
cLSF_GAP = 205 // minimum distance between adjacent LSFs (reorder)
|
||||
|
||||
cALPHA_3 = 29491 // 0.9 (d_plsf_3 bad-frame lean toward mean)
|
||||
cONE_ALPHA_3 = 3277 // 0.1
|
||||
cALPHA_5 = 31128 // ~0.95 (d_plsf_5 bad-frame)
|
||||
cONE_ALPHA_5 = 1639 // ~0.05
|
||||
|
||||
cLSP_PRED_FAC_MR122 = 21299 // 0.65 Q15, MR122 LSP prediction factor
|
||||
|
||||
cDICO1_SIZE = 256
|
||||
cDICO2_SIZE = 512
|
||||
cDICO3_SIZE = 512
|
||||
cMR515_3_SIZE = 128
|
||||
cMR795_1_SIZE = 512
|
||||
)
|
||||
|
||||
// dPlsfState holds the LSF predictor memory across frames. Mirrors D_plsfState.
|
||||
type dPlsfState struct {
|
||||
pastRQ [cM]int16 // past quantized prediction error, Q15
|
||||
pastLsfQ [cM]int16 // past dequantized LSFs, Q15
|
||||
}
|
||||
|
||||
// reset clears the LSF predictor: past residual to zero, past LSFs to the
|
||||
// 5-table mean. Mirrors D_plsf_reset.
|
||||
func (st *dPlsfState) reset() {
|
||||
for i := 0; i < cM; i++ {
|
||||
st.pastRQ[i] = 0
|
||||
}
|
||||
copy(st.pastLsfQ[:], mean_lsf_5[:cM])
|
||||
}
|
||||
|
||||
// reorderLsf enforces a minimum distance minDist between successive LSFs.
|
||||
// Mirrors Reorder_lsf (reorder.cpp).
|
||||
func reorderLsf(lsf []int16, minDist, n int16) {
|
||||
lsfMin := minDist
|
||||
for i := int16(0); i < n; i++ {
|
||||
if lsf[i] < lsfMin {
|
||||
lsf[i] = lsfMin
|
||||
lsfMin = lsfMin + minDist
|
||||
} else {
|
||||
lsfMin = lsf[i] + minDist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dPlsf3 decodes the 3-split LSF for the given mode from three indices into
|
||||
// lsp1q (length cM). bfi selects the bad-frame concealment path. Mirrors
|
||||
// D_plsf_3.
|
||||
func (st *dPlsfState) dPlsf3(mode int, bfi int16, indice []int16, lsp1q []int16) {
|
||||
var lsf1r, lsf1q [cM]int16
|
||||
|
||||
if bfi != 0 {
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := mult(st.pastLsfQ[i], cALPHA_3)
|
||||
index := mult(mean_lsf_3[i], cONE_ALPHA_3)
|
||||
lsf1q[i] = add(index, temp)
|
||||
}
|
||||
if mode != dMRDTX {
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := mult(st.pastRQ[i], pred_fac_3[i])
|
||||
temp = add(mean_lsf_3[i], temp)
|
||||
st.pastRQ[i] = sub(lsf1q[i], temp)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := add(mean_lsf_3[i], st.pastRQ[i])
|
||||
st.pastRQ[i] = sub(lsf1q[i], temp)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var pCb1, pCb3 []int16
|
||||
var indexLimit1, indexLimit3 int16
|
||||
pCb2 := dico2_lsf_3
|
||||
indexLimit2 := int16((cDICO2_SIZE - 1) * 3)
|
||||
switch {
|
||||
case mode == dMR475 || mode == dMR515:
|
||||
pCb1, pCb3 = dico1_lsf_3, mr515_3_lsf
|
||||
indexLimit1, indexLimit3 = (cDICO1_SIZE-1)*3, (cMR515_3_SIZE-1)*4
|
||||
case mode == dMR795:
|
||||
pCb1, pCb3 = mr795_1_lsf, dico3_lsf_3
|
||||
indexLimit1, indexLimit3 = (cMR795_1_SIZE-1)*3, (cDICO3_SIZE-1)*4
|
||||
default: // MR59, MR67, MR74, MR102, MRDTX
|
||||
pCb1, pCb3 = dico1_lsf_3, dico3_lsf_3
|
||||
indexLimit1, indexLimit3 = (cDICO1_SIZE-1)*3, (cDICO3_SIZE-1)*4
|
||||
}
|
||||
|
||||
index := indice[0]
|
||||
temp := index + (index << 1)
|
||||
if temp > indexLimit1 {
|
||||
temp = indexLimit1
|
||||
}
|
||||
lsf1r[0] = pCb1[temp]
|
||||
lsf1r[1] = pCb1[temp+1]
|
||||
lsf1r[2] = pCb1[temp+2]
|
||||
|
||||
index = indice[1]
|
||||
if mode == dMR475 || mode == dMR515 {
|
||||
index <<= 1 // these modes use every second entry
|
||||
}
|
||||
temp = index + (index << 1)
|
||||
if temp > indexLimit2 {
|
||||
temp = indexLimit2
|
||||
}
|
||||
lsf1r[3] = pCb2[temp]
|
||||
lsf1r[4] = pCb2[temp+1]
|
||||
lsf1r[5] = pCb2[temp+2]
|
||||
|
||||
index = indice[2]
|
||||
temp = index << 2
|
||||
if temp > indexLimit3 {
|
||||
temp = indexLimit3
|
||||
}
|
||||
lsf1r[6] = pCb3[temp]
|
||||
lsf1r[7] = pCb3[temp+1]
|
||||
lsf1r[8] = pCb3[temp+2]
|
||||
lsf1r[9] = pCb3[temp+3]
|
||||
|
||||
if mode != dMRDTX {
|
||||
for i := 0; i < cM; i++ {
|
||||
temp = mult(st.pastRQ[i], pred_fac_3[i])
|
||||
temp = add(mean_lsf_3[i], temp)
|
||||
lsf1q[i] = add(lsf1r[i], temp)
|
||||
st.pastRQ[i] = lsf1r[i]
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < cM; i++ {
|
||||
temp = add(mean_lsf_3[i], st.pastRQ[i])
|
||||
lsf1q[i] = add(lsf1r[i], temp)
|
||||
st.pastRQ[i] = lsf1r[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reorderLsf(lsf1q[:], cLSF_GAP, cM)
|
||||
copy(st.pastLsfQ[:], lsf1q[:])
|
||||
lsfLsp(lsf1q[:], lsp1q, cM)
|
||||
}
|
||||
|
||||
// dPlsf5 decodes the 5-split LSF (MR122) from five indices into two LSP sets
|
||||
// lsp1q and lsp2q (each length cM). Mirrors D_plsf_5.
|
||||
func (st *dPlsfState) dPlsf5(bfi int16, indice []int16, lsp1q, lsp2q []int16) {
|
||||
var lsf1r, lsf2r, lsf1q, lsf2q [cM]int16
|
||||
|
||||
if bfi != 0 {
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := int16((int32(st.pastLsfQ[i]) * cALPHA_5) >> 15)
|
||||
sign := int16((int32(mean_lsf_5[i]) * cONE_ALPHA_5) >> 15)
|
||||
lsf1q[i] = add(sign, temp)
|
||||
lsf2q[i] = lsf1q[i]
|
||||
temp = int16((int32(st.pastRQ[i]) * cLSP_PRED_FAC_MR122) >> 15)
|
||||
temp = add(mean_lsf_5[i], temp)
|
||||
st.pastRQ[i] = sub(lsf2q[i], temp)
|
||||
}
|
||||
} else {
|
||||
temp := shl(indice[0], 2)
|
||||
lsf1r[0] = dico1_lsf_5[temp]
|
||||
lsf1r[1] = dico1_lsf_5[temp+1]
|
||||
lsf2r[0] = dico1_lsf_5[temp+2]
|
||||
lsf2r[1] = dico1_lsf_5[temp+3]
|
||||
|
||||
temp = shl(indice[1], 2)
|
||||
lsf1r[2] = dico2_lsf_5[temp]
|
||||
lsf1r[3] = dico2_lsf_5[temp+1]
|
||||
lsf2r[2] = dico2_lsf_5[temp+2]
|
||||
lsf2r[3] = dico2_lsf_5[temp+3]
|
||||
|
||||
sign := indice[2] & 1
|
||||
var ii int16
|
||||
if indice[2] < 0 {
|
||||
ii = ^(^indice[2] >> 1)
|
||||
} else {
|
||||
ii = indice[2] >> 1
|
||||
}
|
||||
temp = shl(ii, 2)
|
||||
if sign == 0 {
|
||||
lsf1r[4] = dico3_lsf_5[temp]
|
||||
lsf1r[5] = dico3_lsf_5[temp+1]
|
||||
lsf2r[4] = dico3_lsf_5[temp+2]
|
||||
lsf2r[5] = dico3_lsf_5[temp+3]
|
||||
} else {
|
||||
lsf1r[4] = negate(dico3_lsf_5[temp])
|
||||
lsf1r[5] = negate(dico3_lsf_5[temp+1])
|
||||
lsf2r[4] = negate(dico3_lsf_5[temp+2])
|
||||
lsf2r[5] = negate(dico3_lsf_5[temp+3])
|
||||
}
|
||||
|
||||
temp = shl(indice[3], 2)
|
||||
lsf1r[6] = dico4_lsf_5[temp]
|
||||
lsf1r[7] = dico4_lsf_5[temp+1]
|
||||
lsf2r[6] = dico4_lsf_5[temp+2]
|
||||
lsf2r[7] = dico4_lsf_5[temp+3]
|
||||
|
||||
temp = shl(indice[4], 2)
|
||||
lsf1r[8] = dico5_lsf_5[temp]
|
||||
lsf1r[9] = dico5_lsf_5[temp+1]
|
||||
lsf2r[8] = dico5_lsf_5[temp+2]
|
||||
lsf2r[9] = dico5_lsf_5[temp+3]
|
||||
|
||||
for i := 0; i < cM; i++ {
|
||||
temp = mult(st.pastRQ[i], cLSP_PRED_FAC_MR122)
|
||||
temp = add(mean_lsf_5[i], temp)
|
||||
lsf1q[i] = add(lsf1r[i], temp)
|
||||
lsf2q[i] = add(lsf2r[i], temp)
|
||||
st.pastRQ[i] = lsf2r[i]
|
||||
}
|
||||
}
|
||||
|
||||
reorderLsf(lsf1q[:], cLSF_GAP, cM)
|
||||
reorderLsf(lsf2q[:], cLSF_GAP, cM)
|
||||
copy(st.pastLsfQ[:], lsf2q[:])
|
||||
lsfLsp(lsf1q[:], lsp1q, cM)
|
||||
lsfLsp(lsf2q[:], lsp2q, cM)
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package amrnb
|
||||
|
||||
// AMR-NB decoder frame driver, ported from opencore-amrnb dec_amr.cpp
|
||||
// (Decoder_amr) and sp_dec.cpp (GSMFrameDecode). It orchestrates the
|
||||
// already-ported building blocks for one 20 ms frame: LSF dequantization and
|
||||
// LSP interpolation, then four subframes of pitch-lag decode, adaptive
|
||||
// codebook, fixed codebook with pitch sharpening, gain decode, excitation
|
||||
// assembly and LP synthesis; finally the formant post-filter and output
|
||||
// high-pass.
|
||||
//
|
||||
// This implements the active-speech (good-frame) path. Error concealment
|
||||
// (bad-frame / DTX), background-noise adaptation, the gain-averaging
|
||||
// (Cb_gain_average) and adaptive phase dispersion are not yet ported, so the
|
||||
// excitation is synthesized directly from the total excitation (equivalent to
|
||||
// phase dispersion disabled) and the code gain is used unmixed.
|
||||
|
||||
const (
|
||||
cSHARPMAX = 13017 // max pitch sharpening
|
||||
cExcOff = cPIT_MAX + cL_INTERPOL // 154: excitation history length
|
||||
cPIT_MIN_MR122 = 18
|
||||
)
|
||||
|
||||
// decoderStateDSP holds the persistent decoder synthesis state across frames.
|
||||
type decoderStateDSP struct {
|
||||
oldExc [cL_SUBFR + cPIT_MAX + cL_INTERPOL]int16 // excitation w/ history
|
||||
lspOld [cM]int16 // previous-frame LSPs
|
||||
memSyn [cM]int16 // synthesis filter memory
|
||||
sharp int16 // pitch sharpening (prev gain_pit)
|
||||
oldT0 int16 // previous integer pitch lag
|
||||
lsf dPlsfState // LSF predictor state
|
||||
pred gcPredState // gain predictor state
|
||||
pst postFilterState // formant post-filter state
|
||||
post postProcessState // output HP filter state
|
||||
}
|
||||
|
||||
func (st *decoderStateDSP) reset() {
|
||||
for i := range st.oldExc {
|
||||
st.oldExc[i] = 0
|
||||
}
|
||||
copy(st.lspOld[:], lsp_init_data[:cM])
|
||||
for i := range st.memSyn {
|
||||
st.memSyn[i] = 0
|
||||
}
|
||||
st.sharp = 0
|
||||
st.oldT0 = 40
|
||||
st.lsf.reset()
|
||||
st.pred.reset()
|
||||
st.pst.reset()
|
||||
st.post = postProcessState{}
|
||||
}
|
||||
|
||||
// sharpenCode applies pitch sharpening to the innovation: code[i] += pitSharp ·
|
||||
// code[i-T0] for i in [T0, L_SUBFR).
|
||||
func sharpenCode(code []int16, T0 int, pitSharp int16) {
|
||||
for i := T0; i < cL_SUBFR; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], pitSharp))
|
||||
}
|
||||
}
|
||||
|
||||
// decodeAmr decodes one frame's parameters into 160 raw synthesis samples and
|
||||
// returns the four per-subframe LP coefficient sets for the post-filter.
|
||||
func (st *decoderStateDSP) decodeAmr(mode int, params []int16, synth []int16) []int16 {
|
||||
var lspMid, lspNew [cM]int16
|
||||
at := make([]int16, cNB_SUBFR*cMP1)
|
||||
|
||||
pi := 0
|
||||
if mode == dMR122 {
|
||||
st.lsf.dPlsf5(0, params[pi:pi+5], lspMid[:], lspNew[:])
|
||||
pi += 5
|
||||
intLpc1and3(st.lspOld[:], lspMid[:], lspNew[:], at)
|
||||
} else {
|
||||
st.lsf.dPlsf3(mode, 0, params[pi:pi+3], lspNew[:])
|
||||
pi += 3
|
||||
intLpc1to3(st.lspOld[:], lspNew[:], at)
|
||||
}
|
||||
copy(st.lspOld[:], lspNew[:])
|
||||
|
||||
var code [cL_SUBFR]int16
|
||||
exc := st.oldExc[cExcOff:] // current-subframe excitation window (40 samples)
|
||||
|
||||
azOff := 0
|
||||
subfrNr := -1
|
||||
evenSubfr := int16(0)
|
||||
var indexMR475 int16 // carried across the MR475 subframe pair
|
||||
|
||||
for iSubfr := 0; iSubfr < cL_FRAME; iSubfr += cL_SUBFR {
|
||||
subfrNr++
|
||||
evenSubfr = 1 - evenSubfr
|
||||
az := at[azOff:]
|
||||
|
||||
pitFlag := int16(iSubfr)
|
||||
if iSubfr == cL_FRAME_BY2 && mode != dMR475 && mode != dMR515 {
|
||||
pitFlag = 0
|
||||
}
|
||||
|
||||
// ---- pitch lag + adaptive codebook ----
|
||||
index := params[pi]
|
||||
pi++
|
||||
var T0, T0frac int16
|
||||
if mode != dMR122 {
|
||||
flag4 := int16(0)
|
||||
if mode == dMR475 || mode == dMR515 || mode == dMR59 || mode == dMR67 {
|
||||
flag4 = 1
|
||||
}
|
||||
deltaLow, deltaRange := int16(5), int16(9)
|
||||
if mode == dMR795 {
|
||||
deltaLow, deltaRange = 10, 19
|
||||
}
|
||||
t0min := sub(st.oldT0, deltaLow)
|
||||
if t0min < cPIT_MIN {
|
||||
t0min = cPIT_MIN
|
||||
}
|
||||
t0max := add(t0min, deltaRange)
|
||||
if t0max > cPIT_MAX {
|
||||
t0max = cPIT_MAX
|
||||
t0min = sub(t0max, deltaRange)
|
||||
}
|
||||
T0, T0frac = decLag3(index, t0min, t0max, pitFlag, st.oldT0, flag4)
|
||||
predLt(st.oldExc[:], cExcOff, T0, T0frac, cL_SUBFR, 1)
|
||||
} else {
|
||||
T0, T0frac = decLag6(index, cPIT_MIN_MR122, cPIT_MAX, pitFlag, st.oldT0)
|
||||
predLt(st.oldExc[:], cExcOff, T0, T0frac, cL_SUBFR, 0)
|
||||
}
|
||||
|
||||
// ---- fixed codebook + pitch sharpening ----
|
||||
var gainPit, gainCode int16
|
||||
switch mode {
|
||||
case dMR475, dMR515:
|
||||
posIdx := params[pi]
|
||||
pi++
|
||||
signs := params[pi]
|
||||
pi++
|
||||
decode2i40_9bits(int16(subfrNr), signs, posIdx, code[:])
|
||||
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
|
||||
case dMR59:
|
||||
posIdx := params[pi]
|
||||
pi++
|
||||
signs := params[pi]
|
||||
pi++
|
||||
decode2i40_11bits(signs, posIdx, code[:])
|
||||
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
|
||||
case dMR67:
|
||||
posIdx := params[pi]
|
||||
pi++
|
||||
signs := params[pi]
|
||||
pi++
|
||||
decode3i40_14bits(signs, posIdx, code[:])
|
||||
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
|
||||
case dMR74, dMR795:
|
||||
posIdx := params[pi]
|
||||
pi++
|
||||
signs := params[pi]
|
||||
pi++
|
||||
decode4i40_17bits(signs, posIdx, code[:])
|
||||
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
|
||||
case dMR102:
|
||||
dec8i40_31bits(params[pi:pi+7], code[:])
|
||||
pi += 7
|
||||
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
|
||||
case dMR122:
|
||||
gpIdx := params[pi]
|
||||
pi++
|
||||
gainPit = dGainPitch(mode, gpIdx)
|
||||
dec10i40_35bits(params[pi:pi+10], code[:])
|
||||
pi += 10
|
||||
sharpenCode(code[:], int(T0), shl(gainPit, 1))
|
||||
}
|
||||
|
||||
// ---- gain decode ----
|
||||
switch mode {
|
||||
case dMR475:
|
||||
if evenSubfr != 0 {
|
||||
indexMR475 = params[pi]
|
||||
pi++
|
||||
}
|
||||
gainPit, gainCode = st.pred.decGain(mode, indexMR475, code[:], evenSubfr)
|
||||
case dMR515, dMR59, dMR67, dMR74, dMR102:
|
||||
idx := params[pi]
|
||||
pi++
|
||||
gainPit, gainCode = st.pred.decGain(mode, idx, code[:], evenSubfr)
|
||||
case dMR795:
|
||||
gpIdx := params[pi]
|
||||
pi++
|
||||
gainPit = dGainPitch(mode, gpIdx)
|
||||
gcIdx := params[pi]
|
||||
pi++
|
||||
gainCode = st.pred.dGainCode(mode, gcIdx, code[:])
|
||||
case dMR122:
|
||||
gcIdx := params[pi]
|
||||
pi++
|
||||
gainCode = st.pred.dGainCode(mode, gcIdx, code[:])
|
||||
}
|
||||
|
||||
// store pitch sharpening for next subframe (not in MR475 even subframe)
|
||||
if mode != dMR475 || evenSubfr == 0 {
|
||||
st.sharp = gainPit
|
||||
if st.sharp > cSHARPMAX {
|
||||
st.sharp = cSHARPMAX
|
||||
}
|
||||
}
|
||||
|
||||
// ---- total excitation: gain_pit·adaptive + gain_code·code ----
|
||||
var pitchFac, tmpShift int16
|
||||
if mode <= dMR102 {
|
||||
pitchFac = gainPit
|
||||
tmpShift = 1
|
||||
} else {
|
||||
pitchFac = shr(gainPit, 1)
|
||||
tmpShift = 2
|
||||
}
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
L := L_mult(exc[i], pitchFac)
|
||||
L = L_mac(L, code[i], gainCode)
|
||||
L = L_shl(L, tmpShift)
|
||||
exc[i] = round_(L)
|
||||
}
|
||||
|
||||
// ---- LP synthesis ----
|
||||
synFilt(az, exc[:cL_SUBFR], synth[iSubfr:iSubfr+cL_SUBFR], cL_SUBFR, st.memSyn[:], false)
|
||||
copy(st.memSyn[:], synth[iSubfr+cL_SUBFR-cM:iSubfr+cL_SUBFR])
|
||||
|
||||
// slide the excitation buffer left by one subframe
|
||||
copy(st.oldExc[:cExcOff], st.oldExc[cL_SUBFR:cL_SUBFR+cExcOff])
|
||||
|
||||
st.oldT0 = T0
|
||||
azOff += cMP1
|
||||
}
|
||||
|
||||
return at
|
||||
}
|
||||
|
||||
// decodeSpeechFrame decodes one good speech frame end-to-end into 160 PCM
|
||||
// samples: parameter decode, post-filter, output high-pass, and the 13-bit
|
||||
// output truncation. Mirrors GSMFrameDecode for the speech path.
|
||||
func (st *decoderStateDSP) decodeSpeechFrame(mode int, params []int16, synth []int16) {
|
||||
az := st.decodeAmr(mode, params, synth)
|
||||
st.pst.postFilter(mode, synth, az)
|
||||
st.post.process(synth, cL_FRAME)
|
||||
for i := 0; i < cL_FRAME; i++ {
|
||||
synth[i] &^= 7 // truncate to 13 bits
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package amrnb
|
||||
|
||||
// Encoder-side bit serialization, ported from opencore-amrnb prm2bits.cpp
|
||||
// (Int2bin/Prm2bits) and the ETS->IETF reordering in ets_to_wmf.cpp
|
||||
// (ets_to_ietf). The chain is: parameter array -> ETS serial bits (each
|
||||
// parameter expanded MSB-first to its bitno width) -> IETF storage bytes (the
|
||||
// ETS bits permuted by reorderBits and packed MSB-first). The leading ToC byte
|
||||
// is added by the payload layer, not here.
|
||||
|
||||
// int2bin writes the low nBits of value into bits[0:nBits], MSB first
|
||||
// (bits[0] is the most significant bit). Mirrors Int2bin.
|
||||
func int2bin(value int16, nBits int, bits []int16) {
|
||||
for i := nBits - 1; i >= 0; i-- {
|
||||
bits[i] = value & 1
|
||||
value >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// prm2bits expands the encoder parameter array prm into the ETS serial bit
|
||||
// array for the given speech mode (length modeBits[mode]). Mirrors Prm2bits.
|
||||
func prm2bits(mode Mode, prm []int16) []int16 {
|
||||
bn := bitno[mode]
|
||||
ets := make([]int16, modeBits[mode])
|
||||
pos := 0
|
||||
for i, w := range bn {
|
||||
int2bin(prm[i], int(w), ets[pos:])
|
||||
pos += int(w)
|
||||
}
|
||||
return ets
|
||||
}
|
||||
|
||||
// etsToIetf permutes the ETS serial bits into IETF storage order via
|
||||
// reorderBits[mode] and packs them MSB-first into bytes. Mirrors the speech
|
||||
// branch of ets_to_ietf (without the ToC byte).
|
||||
func etsToIetf(mode Mode, ets []int16) []byte {
|
||||
nb := modeBits[mode]
|
||||
ro := reorderBits[mode]
|
||||
out := make([]byte, (nb+7)/8)
|
||||
for k := 0; k < nb; k++ {
|
||||
bit := ets[ro[k]] & 1
|
||||
out[k>>3] |= byte(bit) << uint(7-(k&7))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// packFrame serializes a mode's encoder parameters into the RFC 4867 speech
|
||||
// payload bytes (IETF storage order), the inverse of mimeUnsort.
|
||||
func packFrame(prm []int16, mode Mode) []byte {
|
||||
return etsToIetf(mode, prm2bits(mode, prm))
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package amrnb
|
||||
|
||||
// 10-pulse algebraic codebook search (35 bits, MR122), ported from
|
||||
// opencore-amrnb c1035pf.cpp. Reuses search10and8i40 (10 pulses, step 5), then
|
||||
// builds the codeword (±4096 pulses) and Gray-encodes the per-track position
|
||||
// indices (q_p). The exact encoder counterpart of dec10i40_35bits. Bit-exact
|
||||
// with the reference.
|
||||
|
||||
// build10Code assembles the 10-pulse innovation cod, filtered code y, and the
|
||||
// raw per-track position+sign indices (with the same-track ordering/swap logic
|
||||
// that the decoder inverts). Mirrors build_code (c1035pf.cpp).
|
||||
func build10Code(codvec, sign, cod, h, y, indx []int16) {
|
||||
const L = cL_CODE
|
||||
for i := 0; i < L; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
for t := 0; t < 5; t++ {
|
||||
indx[t] = -1
|
||||
}
|
||||
|
||||
var sgn [10]int16
|
||||
for k := 0; k < 10; k++ {
|
||||
i := codvec[k]
|
||||
index := int16((int32(i) * 6554) >> 15)
|
||||
track := i - (index + (index << 2)) // i - index*5
|
||||
|
||||
if sign[i] > 0 {
|
||||
cod[i] += 4096
|
||||
sgn[k] = 8192
|
||||
} else {
|
||||
cod[i] -= 4096
|
||||
sgn[k] = -8192
|
||||
index += 8
|
||||
}
|
||||
|
||||
temp := indx[track]
|
||||
switch {
|
||||
case temp < 0:
|
||||
indx[track] = index
|
||||
case (index^temp)&8 == 0:
|
||||
if temp <= index {
|
||||
indx[track+5] = index
|
||||
} else {
|
||||
indx[track+5] = temp
|
||||
indx[track] = index
|
||||
}
|
||||
default:
|
||||
if (temp & 7) <= (index & 7) {
|
||||
indx[track+5] = temp
|
||||
indx[track] = index
|
||||
} else {
|
||||
indx[track+5] = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < L; i++ {
|
||||
var s int32
|
||||
for k := 0; k < 10; k++ {
|
||||
ci := int(codvec[k])
|
||||
var hv int16
|
||||
if i-ci >= 0 {
|
||||
hv = h[i-ci]
|
||||
}
|
||||
s += (int32(hv) * int32(sgn[k])) >> 7
|
||||
}
|
||||
y[i] = int16((s + 0x80) >> 8)
|
||||
}
|
||||
}
|
||||
|
||||
// code10i40_35bits runs the full MR122 10-pulse search, writing the innovation
|
||||
// cod, filtered code y, and the 10-value Gray-coded index. Mirrors
|
||||
// code_10i40_35bits.
|
||||
func code10i40_35bits(x, cn, h, cod, y, indx []int16) {
|
||||
var dn, sign [cL_CODE]int16
|
||||
var rr [cL_CODE * cL_CODE]int16
|
||||
var ipos, codvec [10]int16
|
||||
var posMax [5]int16
|
||||
|
||||
corHX(h, x, dn[:], 2)
|
||||
setSign12k2(dn[:], cn, sign[:], posMax[:], 5, ipos[:], 5)
|
||||
corH(h, sign[:], rr[:])
|
||||
search10and8i40(10, 5, 5, dn[:], rr[:], ipos[:], posMax[:], codvec[:])
|
||||
build10Code(codvec[:], sign[:], cod, h, y, indx)
|
||||
|
||||
// q_p: Gray-encode each track's position index (keeping the sign bit for the
|
||||
// first five).
|
||||
for n := 0; n < 10; n++ {
|
||||
tmp := indx[n]
|
||||
if n < 5 {
|
||||
indx[n] = (tmp & 0x8) | gray[tmp&0x7]
|
||||
} else {
|
||||
indx[n] = gray[tmp&0x7]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package amrnb
|
||||
|
||||
// 2-pulse algebraic codebook search (9 bits, MR475/MR515), ported from
|
||||
// opencore-amrnb c2_9pf.cpp. code2i40_9bits sharpens the impulse response with
|
||||
// the pitch gain, builds the correlation gradient (corHX) and the rr criterion
|
||||
// matrix (corH), exhaustively searches the two pulse positions maximizing
|
||||
// ps²/energy (search2i40), then assembles the innovation vector, its filtered
|
||||
// version, and the codebook index (build2Code). The exact encoder counterpart
|
||||
// of the decoder's decode2i40_9bits. Bit-exact with the reference.
|
||||
|
||||
// per-subframe track table used to encode the pulse track-pair (c2_9pf.cpp).
|
||||
var trackTable = [20]int16{
|
||||
0, 1, 0, 1, -1, // subframe 1
|
||||
0, -1, 1, 0, 1, // subframe 2
|
||||
0, 1, 0, -1, 1, // subframe 3
|
||||
0, 1, -1, 0, 1, // subframe 4
|
||||
}
|
||||
|
||||
// search2i40 finds the two pulse positions (one per track of a track pair) that
|
||||
// maximize the codebook criterion ps²/energy. Mirrors search_2i40.
|
||||
func search2i40(subNr int16, dn, rr, codvec []int16) {
|
||||
const L = cL_CODE
|
||||
var ipos [2]int16
|
||||
psk := int16(-1)
|
||||
alpk := int16(1)
|
||||
codvec[0] = 0
|
||||
codvec[1] = 1
|
||||
|
||||
for track1 := int16(0); track1 < 2; track1++ {
|
||||
i := (subNr << 1) + (track1 << 3)
|
||||
ipos[0] = startPos[i]
|
||||
ipos[1] = startPos[i+1]
|
||||
|
||||
for i0 := ipos[0]; i0 < L; i0 += cSTEP {
|
||||
ps0 := dn[i0]
|
||||
alp0 := int32(rr[int(i0)*L+int(i0)]) << 14
|
||||
|
||||
sq := int16(-1)
|
||||
alp := int16(1)
|
||||
ix := ipos[1]
|
||||
|
||||
for i1 := ipos[1]; i1 < L; i1 += cSTEP {
|
||||
ps1 := ps0 + dn[i1]
|
||||
alp1 := alp0 + (int32(rr[int(i1)*L+int(i1)]) << 14)
|
||||
alp1 += int32(rr[int(i0)*L+int(i1)]) << 15
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq = sq1
|
||||
alp = alp16
|
||||
ix = i1
|
||||
}
|
||||
}
|
||||
|
||||
s := (int32(alpk)*int32(sq))<<1 - (int32(psk)*int32(alp))<<1
|
||||
if s > 0 {
|
||||
psk = sq
|
||||
alpk = alp
|
||||
codvec[0] = i0
|
||||
codvec[1] = ix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build2Code assembles the innovation vector cod (two ±8191/∓8192 pulses), its
|
||||
// filtered version y, the pulse signs, and the codebook index. Mirrors
|
||||
// build_code (c2_9pf.cpp).
|
||||
func build2Code(subNr int16, codvec, dnSign, cod, h, y []int16) (indx, sign int16) {
|
||||
const L = cL_CODE
|
||||
ptBase := int(subNr) + (int(subNr) << 2) // subNr*5
|
||||
for i := 0; i < L; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
|
||||
var sgn [2]int16
|
||||
rsign := int16(0)
|
||||
for k := 0; k < 2; k++ {
|
||||
i := codvec[k]
|
||||
j := dnSign[i]
|
||||
index := int16((int32(i) * 6554) >> 15) // pos/5
|
||||
track := i - 5*index
|
||||
first := trackTable[ptBase+int(track)]
|
||||
if k == 0 {
|
||||
track = 0
|
||||
if first != 0 {
|
||||
index += 64
|
||||
}
|
||||
} else {
|
||||
track = 1
|
||||
index <<= 3
|
||||
}
|
||||
if j > 0 {
|
||||
cod[i] = 8191
|
||||
sgn[k] = 32767
|
||||
rsign += 1 << uint(track)
|
||||
} else {
|
||||
cod[i] = -8192
|
||||
sgn[k] = -32768
|
||||
}
|
||||
indx += index
|
||||
}
|
||||
sign = rsign
|
||||
|
||||
c0, c1 := int(codvec[0]), int(codvec[1])
|
||||
for i := 0; i < L; i++ {
|
||||
var h0, h1 int16
|
||||
if i-c0 >= 0 {
|
||||
h0 = h[i-c0]
|
||||
}
|
||||
if i-c1 >= 0 {
|
||||
h1 = h[i-c1]
|
||||
}
|
||||
s := L_mult(h0, sgn[0])
|
||||
s = L_mac(s, h1, sgn[1])
|
||||
y[i] = round_(s)
|
||||
}
|
||||
return indx, sign
|
||||
}
|
||||
|
||||
// code2i40_9bits runs the full 2-pulse search for one subframe, returning the
|
||||
// codebook index and pulse signs and writing the innovation code and its
|
||||
// filtered version y. h is sharpened in place by the pitch gain. Mirrors
|
||||
// code_2i40_9bits.
|
||||
func code2i40_9bits(subNr int16, x, h []int16, T0, pitchSharp int16, code, y []int16) (index, sign int16) {
|
||||
sharp := shl(pitchSharp, 1)
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
h[i] = add(h[i], mult(h[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
|
||||
var dn, dn2, dnSign [cL_CODE]int16
|
||||
var rr [cL_CODE * cL_CODE]int16
|
||||
var codvec [2]int16
|
||||
|
||||
corHX(h, x, dn[:], 1)
|
||||
setSign(dn[:], dnSign[:], dn2[:], 8)
|
||||
corH(h, dnSign[:], rr[:])
|
||||
search2i40(subNr, dn[:], rr[:], codvec[:])
|
||||
index, sign = build2Code(subNr, codvec[:], dnSign[:], code, h, y)
|
||||
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
return index, sign
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package amrnb
|
||||
|
||||
// 2-pulse algebraic codebook search (11 bits, MR59), ported from opencore-amrnb
|
||||
// c2_11pf.cpp. Like c2_9pf but with a wider track structure (2×4 track pairs)
|
||||
// and its own index encoding. Reuses corHX/setSign/corH. The exact encoder
|
||||
// counterpart of decode2i40_11bits. Bit-exact with the reference.
|
||||
|
||||
var startPos1 = [2]int16{1, 3}
|
||||
var startPos2 = [4]int16{0, 1, 2, 4}
|
||||
|
||||
// search2i40_11 finds the two pulse positions maximizing ps²/energy across the
|
||||
// 2×4 track pairs. Mirrors search_2i40 (c2_11pf.cpp).
|
||||
func search2i40_11(dn, rr, codvec []int16) {
|
||||
const L = cL_CODE
|
||||
var ipos [2]int16
|
||||
psk := int16(-1)
|
||||
alpk := int16(1)
|
||||
codvec[0] = 0
|
||||
codvec[1] = 1
|
||||
|
||||
for track1 := 0; track1 < 2; track1++ {
|
||||
for track2 := 0; track2 < 4; track2++ {
|
||||
ipos[0] = startPos1[track1]
|
||||
ipos[1] = startPos2[track2]
|
||||
|
||||
for i0 := ipos[0]; i0 < L; i0 += cSTEP {
|
||||
ps0 := dn[i0]
|
||||
alp0 := int32(rr[int(i0)*L+int(i0)]) << 14
|
||||
|
||||
sq := int16(-1)
|
||||
alp := int16(1)
|
||||
ix := ipos[1]
|
||||
|
||||
for i1 := ipos[1]; i1 < L; i1 += cSTEP {
|
||||
ps1 := add(ps0, dn[i1])
|
||||
alp1 := alp0 + (int32(rr[int(i1)*L+int(i1)]) << 14)
|
||||
alp1 += int32(rr[int(i0)*L+int(i1)]) << 15
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq = sq1
|
||||
alp = alp16
|
||||
ix = i1
|
||||
}
|
||||
}
|
||||
|
||||
s := (int32(alpk)*int32(sq))<<1 - (int32(psk)*int32(alp))<<1
|
||||
if s > 0 {
|
||||
psk = sq
|
||||
alpk = alp
|
||||
codvec[0] = i0
|
||||
codvec[1] = ix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build2Code11 assembles the innovation, filtered code, signs and the 11-bit
|
||||
// index. Mirrors build_code (c2_11pf.cpp).
|
||||
func build2Code11(codvec, dnSign, cod, h, y []int16) (indx, sign int16) {
|
||||
const L = cL_CODE
|
||||
for i := 0; i < L; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
|
||||
var sgn [2]int16
|
||||
rsign := int16(0)
|
||||
for k := 0; k < 2; k++ {
|
||||
i := codvec[k]
|
||||
j := dnSign[i]
|
||||
index := int16((int32(i) * 6554) >> 15)
|
||||
tw := (index << 3) + (index << 1) // index*10
|
||||
tw >>= 1 // index*5
|
||||
track := i - tw // i % 5
|
||||
|
||||
switch track {
|
||||
case 0:
|
||||
track = 1
|
||||
index <<= 6
|
||||
case 1:
|
||||
if k == 0 {
|
||||
track = 0
|
||||
index <<= 1
|
||||
} else {
|
||||
track = 1
|
||||
index = (index << 6) + 16
|
||||
}
|
||||
case 2:
|
||||
track = 1
|
||||
index = (index << 6) + 32
|
||||
case 3:
|
||||
track = 0
|
||||
index = (index << 1) + 1
|
||||
case 4:
|
||||
track = 1
|
||||
index = (index << 6) + 48
|
||||
}
|
||||
|
||||
if j > 0 {
|
||||
cod[i] = 8191
|
||||
sgn[k] = 32767
|
||||
rsign = add(rsign, shl(1, track))
|
||||
} else {
|
||||
cod[i] = -8192
|
||||
sgn[k] = -32768
|
||||
}
|
||||
indx = add(indx, index)
|
||||
}
|
||||
sign = rsign
|
||||
|
||||
c0, c1 := int(codvec[0]), int(codvec[1])
|
||||
for i := 0; i < L; i++ {
|
||||
var h0, h1 int16
|
||||
if i-c0 >= 0 {
|
||||
h0 = h[i-c0]
|
||||
}
|
||||
if i-c1 >= 0 {
|
||||
h1 = h[i-c1]
|
||||
}
|
||||
s := L_mult(h0, sgn[0])
|
||||
s = L_mac(s, h1, sgn[1])
|
||||
y[i] = round_(s)
|
||||
}
|
||||
return indx, sign
|
||||
}
|
||||
|
||||
// code2i40_11bits runs the full MR59 2-pulse search. Mirrors code_2i40_11bits.
|
||||
func code2i40_11bits(x, h []int16, T0, pitchSharp int16, code, y []int16) (index, sign int16) {
|
||||
sharp := shl(pitchSharp, 1)
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
h[i] = add(h[i], mult(h[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
|
||||
var dn, dn2, dnSign [cL_CODE]int16
|
||||
var rr [cL_CODE * cL_CODE]int16
|
||||
var codvec [2]int16
|
||||
|
||||
corHX(h, x, dn[:], 1)
|
||||
setSign(dn[:], dnSign[:], dn2[:], 8)
|
||||
corH(h, dnSign[:], rr[:])
|
||||
search2i40_11(dn[:], rr[:], codvec[:])
|
||||
index, sign = build2Code11(codvec[:], dnSign[:], code, h, y)
|
||||
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
return index, sign
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package amrnb
|
||||
|
||||
// 3-pulse algebraic codebook search (14 bits, MR67), ported from opencore-amrnb
|
||||
// c3_14pf.cpp. A depth-first search: the first pulse ranges only over the
|
||||
// pruned candidate positions (dn2), then nested searches place the 2nd and 3rd
|
||||
// pulses; the track triple is cyclically rotated so each track gets the pruned
|
||||
// role. The exact encoder counterpart of decode3i40_14bits. Bit-exact with the
|
||||
// reference.
|
||||
|
||||
// search3i40 places three pulses maximizing the codebook criterion. Mirrors
|
||||
// search_3i40 (c3_14pf.cpp).
|
||||
func search3i40(dn, dn2, rr, codvec []int16) {
|
||||
const L = cL_CODE
|
||||
var ipos [3]int16
|
||||
psk := int16(-1)
|
||||
alpk := int16(1)
|
||||
codvec[0], codvec[1], codvec[2] = 0, 1, 2
|
||||
|
||||
for track1 := int16(1); track1 < 4; track1 += 2 {
|
||||
for track2 := int16(2); track2 < 5; track2 += 2 {
|
||||
ipos[0], ipos[1], ipos[2] = 0, track1, track2
|
||||
|
||||
for rot := 0; rot < 3; rot++ {
|
||||
for i0 := ipos[0]; i0 < L; i0 += cSTEP {
|
||||
if dn2[i0] < 0 {
|
||||
continue
|
||||
}
|
||||
ps0 := dn[i0]
|
||||
alp0 := int32(rr[int(i0)*L+int(i0)]) << 14
|
||||
|
||||
sq := int16(-1)
|
||||
alp := int16(1)
|
||||
ps := int16(0)
|
||||
ix := ipos[1]
|
||||
for i1 := ipos[1]; i1 < L; i1 += cSTEP {
|
||||
ps1 := ps0 + dn[i1]
|
||||
alp1 := alp0 + (int32(rr[int(i1)*L+int(i1)]) << 14)
|
||||
alp1 += int32(rr[int(i0)*L+int(i1)]) << 15
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq = sq1
|
||||
ps = ps1
|
||||
alp = alp16
|
||||
ix = i1
|
||||
}
|
||||
}
|
||||
i1 := ix
|
||||
|
||||
ps0 = ps
|
||||
alp0 = int32(alp) << 14
|
||||
sq = -1
|
||||
alp = 1
|
||||
ps = 0
|
||||
ix = ipos[2]
|
||||
for i2 := ipos[2]; i2 < L; i2 += cSTEP {
|
||||
ps1 := ps0 + dn[i2]
|
||||
alp1 := alp0 + (int32(rr[int(i2)*L+int(i2)]) << 12)
|
||||
alp1 += int32(rr[int(i1)*L+int(i2)]) << 13
|
||||
alp1 += int32(rr[int(i0)*L+int(i2)]) << 13
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq = sq1
|
||||
ps = ps1
|
||||
alp = alp16
|
||||
ix = i2
|
||||
}
|
||||
}
|
||||
i2 := ix
|
||||
|
||||
s := L_msu(L_mult(alpk, sq), psk, alp)
|
||||
if s > 0 {
|
||||
psk = sq
|
||||
alpk = alp
|
||||
codvec[0], codvec[1], codvec[2] = i0, i1, i2
|
||||
}
|
||||
}
|
||||
pos := ipos[2]
|
||||
ipos[2] = ipos[1]
|
||||
ipos[1] = ipos[0]
|
||||
ipos[0] = pos
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build3Code assembles the 3-pulse innovation, filtered code, signs and 14-bit
|
||||
// index. Mirrors build_code (c3_14pf.cpp).
|
||||
func build3Code(codvec, dnSign, cod, h, y []int16) (indx, sign int16) {
|
||||
const L = cL_CODE
|
||||
for i := 0; i < L; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
|
||||
var sgn [3]int16
|
||||
rsign := int16(0)
|
||||
for k := 0; k < 3; k++ {
|
||||
i := codvec[k]
|
||||
j := dnSign[i]
|
||||
index := int16((int32(i) * 6554) >> 15)
|
||||
track := i - index*5
|
||||
switch track {
|
||||
case 1:
|
||||
index <<= 4
|
||||
case 2:
|
||||
track = 2
|
||||
index <<= 8
|
||||
case 3:
|
||||
track = 1
|
||||
index = (index << 4) + 8
|
||||
case 4:
|
||||
track = 2
|
||||
index = (index << 8) + 128
|
||||
}
|
||||
if j > 0 {
|
||||
cod[i] = 8191
|
||||
sgn[k] = 32767
|
||||
track = 1 << uint(track)
|
||||
rsign += track
|
||||
} else {
|
||||
cod[i] = -8192
|
||||
sgn[k] = -32768
|
||||
}
|
||||
indx += index
|
||||
}
|
||||
sign = rsign
|
||||
|
||||
c0, c1, c2 := int(codvec[0]), int(codvec[1]), int(codvec[2])
|
||||
for i := 0; i < L; i++ {
|
||||
var h0, h1, h2 int16
|
||||
if i-c0 >= 0 {
|
||||
h0 = h[i-c0]
|
||||
}
|
||||
if i-c1 >= 0 {
|
||||
h1 = h[i-c1]
|
||||
}
|
||||
if i-c2 >= 0 {
|
||||
h2 = h[i-c2]
|
||||
}
|
||||
s := L_mac(L_mac(L_mult(h0, sgn[0]), h1, sgn[1]), h2, sgn[2])
|
||||
y[i] = round_(s)
|
||||
}
|
||||
return indx, sign
|
||||
}
|
||||
|
||||
// code3i40_14bits runs the full MR67 3-pulse search. Mirrors code_3i40_14bits.
|
||||
func code3i40_14bits(x, h []int16, T0, pitchSharp int16, code, y []int16) (index, sign int16) {
|
||||
sharp := shl(pitchSharp, 1)
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
h[i] = add(h[i], mult(h[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
|
||||
var dn, dn2, dnSign [cL_CODE]int16
|
||||
var rr [cL_CODE * cL_CODE]int16
|
||||
var codvec [3]int16
|
||||
|
||||
corHX(h, x, dn[:], 1)
|
||||
setSign(dn[:], dnSign[:], dn2[:], 6)
|
||||
corH(h, dnSign[:], rr[:])
|
||||
search3i40(dn[:], dn2[:], rr[:], codvec[:])
|
||||
index, sign = build3Code(codvec[:], dnSign[:], code, h, y)
|
||||
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
return index, sign
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package amrnb
|
||||
|
||||
// 4-pulse algebraic codebook search (17 bits, MR74/MR795), ported from
|
||||
// opencore-amrnb c4_17pf.cpp. A depth-first search over four pulses with dn2
|
||||
// pruning of the first pulse and cyclic track rotation; the index is Gray-coded
|
||||
// per position. The exact encoder counterpart of decode4i40_17bits. Bit-exact
|
||||
// with the reference.
|
||||
|
||||
// search4i40 places four pulses maximizing the codebook criterion. Mirrors
|
||||
// search_4i40 (c4_17pf.cpp).
|
||||
func search4i40(dn, dn2, rr, codvec []int16) {
|
||||
const L = cL_CODE
|
||||
var ipos [4]int16
|
||||
psk := int16(-1)
|
||||
alpk := int16(1)
|
||||
codvec[0], codvec[1], codvec[2], codvec[3] = 0, 1, 2, 3
|
||||
|
||||
for track := int16(3); track < 5; track++ {
|
||||
ipos[0], ipos[1], ipos[2], ipos[3] = 0, 1, 2, track
|
||||
|
||||
for rot := 0; rot < 4; rot++ {
|
||||
for i0 := ipos[0]; i0 < L; i0 += cSTEP {
|
||||
if dn2[i0] < 0 {
|
||||
continue
|
||||
}
|
||||
ps0 := dn[i0]
|
||||
alp0 := int32(rr[int(i0)*L+int(i0)]) << 14
|
||||
|
||||
sq := int16(-1)
|
||||
alp := int16(1)
|
||||
ps := int16(0)
|
||||
ix := ipos[1]
|
||||
for i1 := ipos[1]; i1 < L; i1 += cSTEP {
|
||||
ps1 := ps0 + dn[i1]
|
||||
alp1 := alp0 + (int32(rr[int(i1)*L+int(i1)]) << 14)
|
||||
alp1 += int32(rr[int(i0)*L+int(i1)]) << 15
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq, ps, alp, ix = sq1, ps1, alp16, i1
|
||||
}
|
||||
}
|
||||
i1 := ix
|
||||
|
||||
ps0 = ps
|
||||
alp0 = int32(alp) << 14
|
||||
sq, alp, ps, ix = -1, 1, 0, ipos[2]
|
||||
for i2 := ipos[2]; i2 < L; i2 += cSTEP {
|
||||
ps1 := ps0 + dn[i2]
|
||||
alp1 := alp0 + (int32(rr[int(i2)*L+int(i2)]) << 12)
|
||||
alp1 += int32(rr[int(i1)*L+int(i2)]) << 13
|
||||
alp1 += int32(rr[int(i0)*L+int(i2)]) << 13
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq, ps, alp, ix = sq1, ps1, alp16, i2
|
||||
}
|
||||
}
|
||||
i2 := ix
|
||||
|
||||
ps0 = ps
|
||||
alp0 = int32(alp) << 16
|
||||
sq, alp, ps, ix = -1, 1, 0, ipos[3]
|
||||
for i3 := ipos[3]; i3 < L; i3 += cSTEP {
|
||||
ps1 := ps0 + dn[i3]
|
||||
alp1 := alp0 + (int32(rr[int(i3)*L+int(i3)]) << 12)
|
||||
alp1 += int32(rr[int(i2)*L+int(i3)]) << 13
|
||||
alp1 += int32(rr[int(i1)*L+int(i3)]) << 13
|
||||
alp1 += int32(rr[int(i0)*L+int(i3)]) << 13
|
||||
sq1 := int16((int32(ps1) * int32(ps1)) >> 15)
|
||||
alp16 := int16((alp1 + 0x8000) >> 16)
|
||||
s := (int32(alp)*int32(sq1))<<1 - (int32(sq)*int32(alp16))<<1
|
||||
if s > 0 {
|
||||
sq, ps, alp, ix = sq1, ps1, alp16, i3
|
||||
}
|
||||
}
|
||||
|
||||
s := (int32(alpk)*int32(sq))<<1 - (int32(psk)*int32(alp))<<1
|
||||
if s > 0 {
|
||||
psk = sq
|
||||
alpk = alp
|
||||
codvec[0], codvec[1], codvec[2], codvec[3] = i0, i1, i2, ix
|
||||
}
|
||||
}
|
||||
pos := ipos[3]
|
||||
ipos[3] = ipos[2]
|
||||
ipos[2] = ipos[1]
|
||||
ipos[1] = ipos[0]
|
||||
ipos[0] = pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build4Code assembles the 4-pulse innovation, filtered code, signs and the
|
||||
// Gray-coded 17-bit index. Mirrors build_code (c4_17pf.cpp).
|
||||
func build4Code(codvec, dnSign, cod, h, y []int16) (indx, sign int16) {
|
||||
const L = cL_CODE
|
||||
for i := 0; i < L; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
|
||||
var sgn [4]int16
|
||||
rsign := int16(0)
|
||||
for k := 0; k < 4; k++ {
|
||||
i := codvec[k]
|
||||
j := dnSign[i]
|
||||
index := int16((int32(i) * 6554) >> 15)
|
||||
track := i - index*5
|
||||
index = gray[index]
|
||||
switch track {
|
||||
case 1:
|
||||
index <<= 3
|
||||
case 2:
|
||||
index <<= 6
|
||||
case 3:
|
||||
index <<= 10
|
||||
case 4:
|
||||
track = 3
|
||||
index = (index << 10) + 512
|
||||
}
|
||||
if j > 0 {
|
||||
cod[i] = 8191
|
||||
sgn[k] = 32767
|
||||
track = 1 << uint(track)
|
||||
rsign += track
|
||||
} else {
|
||||
cod[i] = -8192
|
||||
sgn[k] = -32768
|
||||
}
|
||||
indx += index
|
||||
}
|
||||
sign = rsign
|
||||
|
||||
c := [4]int{int(codvec[0]), int(codvec[1]), int(codvec[2]), int(codvec[3])}
|
||||
for i := 0; i < L; i++ {
|
||||
var s int32
|
||||
for k := 0; k < 4; k++ {
|
||||
if i-c[k] >= 0 {
|
||||
s = L_mac(s, h[i-c[k]], sgn[k])
|
||||
}
|
||||
}
|
||||
y[i] = round_(s)
|
||||
}
|
||||
return indx, sign
|
||||
}
|
||||
|
||||
// code4i40_17bits runs the full MR74/MR795 4-pulse search. Mirrors
|
||||
// code_4i40_17bits.
|
||||
func code4i40_17bits(x, h []int16, T0, pitchSharp int16, code, y []int16) (index, sign int16) {
|
||||
sharp := shl(pitchSharp, 1)
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
h[i] = add(h[i], mult(h[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
|
||||
var dn, dn2, dnSign [cL_CODE]int16
|
||||
var rr [cL_CODE * cL_CODE]int16
|
||||
var codvec [4]int16
|
||||
|
||||
corHX(h, x, dn[:], 1)
|
||||
setSign(dn[:], dnSign[:], dn2[:], 4)
|
||||
corH(h, dnSign[:], rr[:])
|
||||
search4i40(dn[:], dn2[:], rr[:], codvec[:])
|
||||
index, sign = build4Code(codvec[:], dnSign[:], code, h, y)
|
||||
|
||||
if T0 < cL_CODE {
|
||||
for i := T0; i < cL_CODE; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], sharp))
|
||||
}
|
||||
}
|
||||
return index, sign
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package amrnb
|
||||
|
||||
// 8-pulse algebraic codebook search (31 bits, MR102), ported from opencore-amrnb
|
||||
// c8_31pf.cpp. Drives search10and8i40 (8 pulses, step 4), then builds the
|
||||
// codeword and compresses the eight pulse positions / four track signs into the
|
||||
// 7-value transmitted index. The exact encoder counterpart of dec8i40_31bits
|
||||
// (decompressCode). Bit-exact with the reference.
|
||||
|
||||
// build8Code assembles the 8-pulse innovation cod and filtered code y, and the
|
||||
// linear track signs / pulse positions (with the same-track ordering/swap logic
|
||||
// that decompressCode inverts). Mirrors build_code (c8_31pf.cpp).
|
||||
func build8Code(codvec, sign, cod, h, y []int16) (signIndx [4]int16, posIndx [8]int16) {
|
||||
const L = cL_CODE
|
||||
for i := 0; i < L; i++ {
|
||||
cod[i] = 0
|
||||
}
|
||||
for t := 0; t < 4; t++ {
|
||||
posIndx[t] = -1
|
||||
signIndx[t] = -1
|
||||
}
|
||||
|
||||
var sgn [8]int16
|
||||
for k := 0; k < 8; k++ {
|
||||
i := codvec[k]
|
||||
j := sign[i]
|
||||
posIndex := i >> 2
|
||||
track := i & 3
|
||||
|
||||
var signIndex int16
|
||||
if j > 0 {
|
||||
cod[i] = cod[i] + 8191
|
||||
sgn[k] = 32767
|
||||
signIndex = 0
|
||||
} else {
|
||||
cod[i] = cod[i] - 8191
|
||||
sgn[k] = -32768
|
||||
signIndex = 1
|
||||
}
|
||||
|
||||
if posIndx[track] < 0 {
|
||||
posIndx[track] = posIndex
|
||||
signIndx[track] = signIndex
|
||||
} else if (signIndex^signIndx[track])&1 == 0 {
|
||||
if posIndx[track] <= posIndex {
|
||||
posIndx[track+4] = posIndex
|
||||
} else {
|
||||
posIndx[track+4] = posIndx[track]
|
||||
posIndx[track] = posIndex
|
||||
signIndx[track] = signIndex
|
||||
}
|
||||
} else {
|
||||
if posIndx[track] <= posIndex {
|
||||
posIndx[track+4] = posIndx[track]
|
||||
posIndx[track] = posIndex
|
||||
signIndx[track] = signIndex
|
||||
} else {
|
||||
posIndx[track+4] = posIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < L; i++ {
|
||||
var s int32
|
||||
for k := 0; k < 8; k++ {
|
||||
ci := int(codvec[k])
|
||||
if i-ci >= 0 {
|
||||
s = L_mac(s, h[i-ci], sgn[k])
|
||||
}
|
||||
}
|
||||
y[i] = round_(s)
|
||||
}
|
||||
return signIndx, posIndx
|
||||
}
|
||||
|
||||
// compress10 packs three position indices into one 10-bit value. Mirrors
|
||||
// compress10 (the inverse of decompress10).
|
||||
func compress10(A, B, C int16) int16 {
|
||||
ia := A >> 1
|
||||
ib := (B >> 1) * 5
|
||||
ic := (C >> 1) * 25
|
||||
indx := (ib + ic + ia) << 3
|
||||
indx += ((B & 1) << 1) + ((C & 1) << 2) + (A & 1)
|
||||
return indx
|
||||
}
|
||||
|
||||
// compressCode packs the four track signs and eight pulse positions into the
|
||||
// seven transmitted index values. Mirrors compress_code.
|
||||
func compressCode(signIndx, posIndx, indx []int16) {
|
||||
for i := 0; i < 4; i++ {
|
||||
indx[i] = signIndx[i]
|
||||
}
|
||||
indx[4] = compress10(posIndx[0], posIndx[4], posIndx[1])
|
||||
indx[5] = compress10(posIndx[2], posIndx[6], posIndx[5])
|
||||
|
||||
ib := (posIndx[7] >> 1) & 1
|
||||
ia := posIndx[3] >> 1
|
||||
if ib == 1 {
|
||||
ia = 4 - ia
|
||||
}
|
||||
ib = (posIndx[7] >> 1) * 5
|
||||
ib += ia
|
||||
ib <<= 5
|
||||
ib += 12
|
||||
ic := int16((int32(ib) * 1311) >> 15)
|
||||
ic <<= 2
|
||||
ia = posIndx[3] & 1
|
||||
ib = (posIndx[7] & 1) << 1
|
||||
indx[6] = ib + ic + ia
|
||||
}
|
||||
|
||||
// code8i40_31bits runs the full MR102 8-pulse search, writing the innovation
|
||||
// cod, filtered code y, and the 7-value index. Mirrors code_8i40_31bits.
|
||||
func code8i40_31bits(x, cn, h, cod, y, indx []int16) {
|
||||
var dn, sign [cL_CODE]int16
|
||||
var rr [cL_CODE * cL_CODE]int16
|
||||
var ipos, codvec [8]int16
|
||||
var posMax [4]int16
|
||||
|
||||
corHX2(h, x, dn[:], 2, 4, 4)
|
||||
setSign12k2(dn[:], cn, sign[:], posMax[:], 4, ipos[:], 4)
|
||||
corH(h, sign[:], rr[:])
|
||||
search10and8i40(8, 4, 4, dn[:], rr[:], ipos[:], posMax[:], codvec[:])
|
||||
signIndx, posIndx := build8Code(codvec[:], sign[:], cod, h, y)
|
||||
compressCode(signIndx[:], posIndx[:], indx)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package amrnb
|
||||
|
||||
// Algebraic-codebook search infrastructure, ported from opencore-amrnb
|
||||
// cor_h_x.cpp (cor_h_x) and set_sign.cpp (set_sign). corHX computes the
|
||||
// backward correlation dn[] between the search target and the weighted impulse
|
||||
// response (the gradient that drives pulse placement); setSign extracts the
|
||||
// per-position pulse signs and prunes each track to its strongest candidate
|
||||
// positions. These feed the per-mode pulse searches. Bit-exact with the
|
||||
// reference.
|
||||
|
||||
const (
|
||||
cNB_TRACK = 5 // number of pulse tracks
|
||||
cSTEP = 5 // codebook track step
|
||||
)
|
||||
|
||||
// corHX computes dn[i] = normalized 2·sum_j x[i+j]·h[j], the correlation between
|
||||
// the target x and the impulse response h, scaled so it fits 16 bits (sf is the
|
||||
// scaling factor: 2 for 12.2k, 1 otherwise). Mirrors cor_h_x.
|
||||
func corHX(h, x, dn []int16, sf int16) {
|
||||
// y32[i] = 2·sum_{j=0}^{L-1-i} x[i+j]·h[j]. Zero-padding x past L makes the
|
||||
// out-of-range taps contribute 0, so the whole set of positions is one L-tap
|
||||
// FIR over the padded input (firRaw). The per-term <<1 distributes over the
|
||||
// wrapping sum, so dst[i]<<1 is bit-identical to the per-term shift.
|
||||
var xpad [2 * cL_CODE]int16
|
||||
copy(xpad[:cL_CODE], x[:cL_CODE])
|
||||
var dst [cL_CODE]int32
|
||||
firRaw(dst[:], xpad[:], h[:cL_CODE])
|
||||
|
||||
var y32 [cL_CODE]int32
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
y32[i] = dst[i] << 1
|
||||
}
|
||||
|
||||
tot := int32(5)
|
||||
for k := 0; k < cNB_TRACK; k++ {
|
||||
var max int32
|
||||
for i := k; i < cL_CODE; i += cSTEP {
|
||||
s := y32[i]
|
||||
if s < 0 {
|
||||
s = -s
|
||||
}
|
||||
if s > max {
|
||||
max = s
|
||||
}
|
||||
}
|
||||
tot += max >> 1
|
||||
}
|
||||
|
||||
j := norm_l(tot) - sf
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
s := L_shl(y32[i], j)
|
||||
dn[i] = int16((s + 0x8000) >> 16)
|
||||
}
|
||||
}
|
||||
|
||||
// setSign records the sign of each dn[] entry (±32767), takes |dn|, and prunes
|
||||
// dn2[] to keep only the n strongest positions per track (the rest set to -1).
|
||||
// Mirrors set_sign.
|
||||
func setSign(dn, sign, dn2 []int16, n int16) {
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
val := dn[i]
|
||||
if val >= 0 {
|
||||
sign[i] = 32767
|
||||
} else {
|
||||
sign[i] = -32767
|
||||
val = negate(val)
|
||||
}
|
||||
dn[i] = val
|
||||
dn2[i] = val
|
||||
}
|
||||
|
||||
for i := 0; i < cNB_TRACK; i++ {
|
||||
for k := int16(0); k < 8-n; k++ {
|
||||
min := int16(0x7fff)
|
||||
pos := i
|
||||
for j := i; j < cL_CODE; j += cSTEP {
|
||||
if dn2[j] >= 0 && dn2[j] < min {
|
||||
min = dn2[j]
|
||||
pos = j
|
||||
}
|
||||
}
|
||||
dn2[pos] = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package amrnb
|
||||
|
||||
// Parameterized codebook-search correlation, ported from opencore-amrnb
|
||||
// cor_h_x2.cpp. corHX2 is the generalized form of corHX used by the multi-pulse
|
||||
// searches (MR102: 4 tracks, MR122: 5 tracks): dn[i] = normalized 2·Σ x[j]·h[j-i]
|
||||
// with a configurable track count and step. Bit-exact with the reference.
|
||||
|
||||
const cLOG2_OF_32 = 5
|
||||
|
||||
// corHX2 computes the normalized target↔impulse correlation dn[] over nbTrack
|
||||
// tracks with the given step, scaled by sf. Mirrors cor_h_x2.
|
||||
func corHX2(h, x, dn []int16, sf, nbTrack, step int16) {
|
||||
// y32[i] = 2·sum_{j=i}^{L-1} x[j]·h[j-i] = 2·sum_{j'=0}^{L-1-i} x[i+j']·h[j'].
|
||||
// Zero-padding x past L turns this into one L-tap FIR over the padded input
|
||||
// (firRaw); s<<1 == dst[i]<<1 holds mod 2^32 since <<1 distributes over the
|
||||
// wrapping sum.
|
||||
var xpad [2 * cL_CODE]int16
|
||||
copy(xpad[:cL_CODE], x[:cL_CODE])
|
||||
var dst [cL_CODE]int32
|
||||
firRaw(dst[:], xpad[:], h[:cL_CODE])
|
||||
|
||||
var y32 [cL_CODE]int32
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
y32[i] = dst[i] << 1
|
||||
}
|
||||
|
||||
tot := int32(cLOG2_OF_32)
|
||||
for k := int16(0); k < nbTrack; k++ {
|
||||
var max int32
|
||||
for i := k; i < cL_CODE; i += step {
|
||||
if a := L_abs(y32[i]); a > max {
|
||||
max = a
|
||||
}
|
||||
}
|
||||
tot += max >> 1
|
||||
}
|
||||
|
||||
j := norm_l(tot) - sf
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
dn[i] = round_(L_shl(y32[i], j))
|
||||
}
|
||||
}
|
||||
|
||||
// setSign12k2 combines the residual cn and the correlation dn to determine each
|
||||
// position's pulse sign, the per-track maximum positions (posMax), and a cyclic
|
||||
// pulse-track ordering (ipos, length 2*nbTrack) starting from the strongest
|
||||
// track — the prep for the multi-pulse search_10and8i40. dn is updated in place
|
||||
// (negated where the combined sign is negative). Mirrors set_sign12k2.
|
||||
func setSign12k2(dn, cn, sign, posMax []int16, nbTrack int16, ipos []int16, step int16) {
|
||||
const L = cL_CODE
|
||||
var en [cL_CODE]int16
|
||||
|
||||
s := int32(256)
|
||||
t := int32(256)
|
||||
for i := 0; i < L; i++ {
|
||||
s = L_mac(s, cn[i], cn[i])
|
||||
t += (int32(dn[i]) * int32(dn[i])) << 1
|
||||
}
|
||||
s = invSqrt(s)
|
||||
kCn := int16(L_shl(s, 5) >> 16)
|
||||
t = invSqrt(t)
|
||||
kDn := int16(t >> 11)
|
||||
|
||||
for i := L - 1; i >= 0; i-- {
|
||||
lTemp := (int32(kCn) * int32(cn[i])) << 1
|
||||
val := dn[i]
|
||||
ss := L_mac(lTemp, kDn, val)
|
||||
cor := round_(L_shl(ss, 10))
|
||||
if cor >= 0 {
|
||||
sign[i] = 32767
|
||||
} else {
|
||||
sign[i] = -32767
|
||||
cor = negate(cor)
|
||||
dn[i] = negate(val)
|
||||
}
|
||||
en[i] = cor
|
||||
}
|
||||
|
||||
maxOfAll := int16(-1)
|
||||
var pos int16
|
||||
for i := int16(0); i < nbTrack; i++ {
|
||||
max := int16(-1)
|
||||
for j := i; j < L; j += step {
|
||||
if en[j] > max {
|
||||
max = en[j]
|
||||
pos = j
|
||||
}
|
||||
}
|
||||
posMax[i] = pos
|
||||
if max > maxOfAll {
|
||||
maxOfAll = max
|
||||
ipos[0] = i
|
||||
}
|
||||
}
|
||||
|
||||
pos = ipos[0]
|
||||
ipos[nbTrack] = pos
|
||||
for i := int16(1); i < nbTrack; i++ {
|
||||
pos++
|
||||
if pos >= nbTrack {
|
||||
pos = 0
|
||||
}
|
||||
ipos[i] = pos
|
||||
ipos[i+nbTrack] = pos
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package amrnb
|
||||
|
||||
// Closed-loop long-term-prediction (adaptive codebook) step, ported from
|
||||
// opencore-amrnb cl_ltp.cpp. clLtp runs the fractional pitch search (pitchFr),
|
||||
// builds the adaptive codebook (predLt), filters it (convolve), computes the
|
||||
// pitch gain (gPitch) with mode-specific clamping (and MR122 quantization), then
|
||||
// forms the codebook-search target xn2 = xn - gain·y1 and updates res2. The
|
||||
// tone-stabilization gain clip (gpc_flag) is not applied (active-speech path).
|
||||
// Bit-exact with the reference for that path.
|
||||
|
||||
const cGP_CLIP = 15565 // 0.95 Q14 pitch-gain clip
|
||||
|
||||
type clLtpResult struct {
|
||||
T0, T0frac int16
|
||||
gainPit int16
|
||||
gpLimit int16
|
||||
gCoeff [5]int16
|
||||
pitchIndex int16
|
||||
gainPitIndex int16 // MR122 only
|
||||
hasGainPitIndex bool
|
||||
}
|
||||
|
||||
// clLtp performs the adaptive-codebook analysis for one subframe. exc[excOff] is
|
||||
// the current subframe; predLt writes it in place. xn2 (codebook target) and
|
||||
// res2 (residual) are written. Mirrors cl_ltp.
|
||||
func (st *pitchFrState) clLtp(mode, frameOffset int, Top, h1, exc []int16, excOff int, res2, xn, xn2, yl []int16) clLtpResult {
|
||||
var r clLtpResult
|
||||
resu3 := int16(0)
|
||||
r.T0, r.T0frac, resu3, r.pitchIndex = st.pitchFr(mode, Top, exc, excOff, xn, h1, cL_SUBFR, frameOffset)
|
||||
|
||||
predLt(exc, excOff, r.T0, r.T0frac, cL_SUBFR, resu3)
|
||||
convolve(exc[excOff:], h1, yl, cL_SUBFR)
|
||||
|
||||
r.gainPit = gPitch(mode, xn, yl, r.gCoeff[:], cL_SUBFR)
|
||||
r.gpLimit = maxInt16 // tone-stabilization clip disabled
|
||||
|
||||
if mode == dMR475 || mode == dMR515 {
|
||||
if r.gainPit > 13926 {
|
||||
r.gainPit = 13926
|
||||
}
|
||||
} else if mode == dMR122 {
|
||||
var cand, cind [3]int16
|
||||
r.gainPitIndex = qGainPitch(dMR122, r.gpLimit, &r.gainPit, cand[:], cind[:])
|
||||
r.hasGainPitIndex = true
|
||||
}
|
||||
|
||||
g := r.gainPit
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
xn2[i] = xn[i] - int16((int32(yl[i])*int32(g))>>14)
|
||||
res2[i] -= int16((int32(exc[excOff+i]) * int32(g)) >> 14)
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package amrnb
|
||||
|
||||
// Closed-loop fractional pitch search, ported from opencore-amrnb pitch_fr.cpp
|
||||
// (Pitch_fr, Norm_Corr, searchFrac, getRange) and inter_36.cpp (Interpol_3or6).
|
||||
// pitchFr refines the open-loop lag: it builds the normalized correlation of the
|
||||
// target with the filtered adaptive excitation over an integer search range,
|
||||
// picks the best integer lag, then interpolates to a fractional lag, and encodes
|
||||
// the result with encLag3/encLag6. Bit-exact with the reference.
|
||||
|
||||
const cL_INTER_SRCH = 4 // closed-loop pitch search interpolation half-length
|
||||
|
||||
// mode-dependent search parameters (pitch_fr.cpp mode_dep_parm), indexed by the
|
||||
// internal speech mode 0..7.
|
||||
var modeDepParm = [8]struct {
|
||||
maxFracLag, flag3, firstFrac, lastFrac int16
|
||||
deltaIntLow, deltaIntRange, deltaFrcLow int16
|
||||
deltaFrcRange, pitMin int16
|
||||
}{
|
||||
{84, 1, -2, 2, 5, 10, 5, 9, cPIT_MIN}, // MR475
|
||||
{84, 1, -2, 2, 5, 10, 5, 9, cPIT_MIN}, // MR515
|
||||
{84, 1, -2, 2, 3, 6, 5, 9, cPIT_MIN}, // MR59
|
||||
{84, 1, -2, 2, 3, 6, 5, 9, cPIT_MIN}, // MR67
|
||||
{84, 1, -2, 2, 3, 6, 5, 9, cPIT_MIN}, // MR74
|
||||
{84, 1, -2, 2, 3, 6, 10, 19, cPIT_MIN}, // MR795
|
||||
{84, 1, -2, 2, 3, 6, 5, 9, cPIT_MIN}, // MR102
|
||||
{94, 0, -3, 3, 3, 6, 5, 9, cPIT_MIN_MR122}, // MR122
|
||||
}
|
||||
|
||||
// pitchFrState carries the previous subframe's integer lag across subframes.
|
||||
type pitchFrState struct {
|
||||
T0prev int16
|
||||
}
|
||||
|
||||
func (st *pitchFrState) reset() { st.T0prev = 0 }
|
||||
|
||||
// getRange clamps a search range [t0min, t0max] of width deltaRange around T0.
|
||||
// Mirrors getRange.
|
||||
func getRange(T0, deltaLow, deltaRange, pitmin, pitmax int16) (t0min, t0max int16) {
|
||||
temp := T0 - deltaLow
|
||||
if temp < pitmin {
|
||||
temp = pitmin
|
||||
}
|
||||
t0min = temp
|
||||
temp += deltaRange
|
||||
if temp > pitmax {
|
||||
temp = pitmax
|
||||
t0min = pitmax - deltaRange
|
||||
}
|
||||
t0max = temp
|
||||
return t0min, t0max
|
||||
}
|
||||
|
||||
// normCorr computes the normalized correlation corr[t-tMin] of the target xn
|
||||
// with the adaptive excitation at each integer lag t in [tMin, tMax], filtered
|
||||
// by the impulse response h. exc[excOff] is the current subframe start. Mirrors
|
||||
// Norm_Corr.
|
||||
func normCorr(exc []int16, excOff int, xn, h []int16, Lsubfr int, tMin, tMax int16, corr []int16) {
|
||||
var excf, scaledExcf [cL_SUBFR]int16
|
||||
|
||||
k := -int(tMin)
|
||||
convolve(exc[excOff+k:], h, excf[:], Lsubfr)
|
||||
|
||||
var s int32
|
||||
for j := 0; j < Lsubfr; j++ {
|
||||
t := excf[j]
|
||||
scaledExcf[j] = t >> 2
|
||||
s += int32(t) * int32(t)
|
||||
}
|
||||
|
||||
var sExcf []int16
|
||||
var hFac, scaling int16
|
||||
if s <= (67108864 >> 1) {
|
||||
sExcf = excf[:]
|
||||
hFac = 12
|
||||
scaling = 0
|
||||
} else {
|
||||
sExcf = scaledExcf[:]
|
||||
hFac = 14
|
||||
scaling = 2
|
||||
}
|
||||
|
||||
xnf := xn[:Lsubfr] // hoist bounds out of the inner correlation loop
|
||||
sef := sExcf[:Lsubfr] // (both length Lsubfr)
|
||||
hf := h[:Lsubfr]
|
||||
for i := tMin; i <= tMax; i++ {
|
||||
sc := firDot(xnf, sef)
|
||||
s2 := firDot(sef, sef)
|
||||
s2 <<= 1
|
||||
s2 = invSqrt(s2)
|
||||
normH := int16(s2 >> 16)
|
||||
normL := int16((s2 >> 1) - (int32(normH) << 15))
|
||||
corrH := int16(sc >> 15)
|
||||
corrL := int16(sc - (int32(corrH) << 15))
|
||||
corr[int(i)-int(tMin)] = int16(mpy_32(corrH, corrL, normH, normL))
|
||||
|
||||
if i != tMax {
|
||||
k--
|
||||
temp := exc[excOff+k]
|
||||
for j := Lsubfr - 1; j >= 1; j-- {
|
||||
sef[j] = int16((int32(temp)*int32(hf[j]))>>uint(hFac)) + sef[j-1]
|
||||
}
|
||||
sef[0] = temp >> scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// interpol3or6 interpolates the correlation around corr[off] at fractional
|
||||
// position frac (1/3 if flag3 else 1/6). Mirrors Interpol_3or6.
|
||||
func interpol3or6(corr []int16, off int, frac, flag3 int16) int16 {
|
||||
if flag3 != 0 {
|
||||
frac <<= 1
|
||||
}
|
||||
if frac < 0 {
|
||||
frac += cUP_SAMP_MAX
|
||||
off--
|
||||
}
|
||||
f := int(frac)
|
||||
s := int32(0x4000)
|
||||
k := 0
|
||||
x1 := off
|
||||
x2 := off + 1
|
||||
for i := cL_INTER_SRCH >> 1; i != 0; i-- {
|
||||
s += int32(corr[x1]) * int32(inter6Pitch[f+k])
|
||||
x1--
|
||||
s += int32(corr[x2]) * int32(inter6Pitch[(cUP_SAMP_MAX-f)+k])
|
||||
x2++
|
||||
k += cUP_SAMP_MAX
|
||||
s += int32(corr[x1]) * int32(inter6Pitch[f+k])
|
||||
x1--
|
||||
s += int32(corr[x2]) * int32(inter6Pitch[(cUP_SAMP_MAX-f)+k])
|
||||
x2++
|
||||
k <<= 1
|
||||
}
|
||||
return int16(s >> 15)
|
||||
}
|
||||
|
||||
// searchFrac refines the fractional pitch around the integer lag by maximizing
|
||||
// the interpolated correlation, then normalizes the (lag, frac) pair. Mirrors
|
||||
// searchFrac.
|
||||
func searchFrac(lag, frac *int16, lastFrac int16, corr []int16, tMin int, flag3 int16) {
|
||||
off := int(*lag) - tMin
|
||||
max := interpol3or6(corr, off, *frac, flag3)
|
||||
for i := *frac + 1; i <= lastFrac; i++ {
|
||||
ci := interpol3or6(corr, off, i, flag3)
|
||||
if ci > max {
|
||||
max = ci
|
||||
*frac = i
|
||||
}
|
||||
}
|
||||
if flag3 == 0 {
|
||||
if *frac == -3 {
|
||||
*frac = 3
|
||||
*lag--
|
||||
}
|
||||
} else {
|
||||
if *frac == -2 {
|
||||
*frac = 1
|
||||
*lag--
|
||||
}
|
||||
if *frac == 2 {
|
||||
*frac = -1
|
||||
*lag++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pitchFr performs the closed-loop fractional pitch search for one subframe and
|
||||
// returns the integer lag T0, fractional part frac, the resolution flag resu3
|
||||
// (1=1/3, 0=1/6), and the encoded analysis index. Mirrors Pitch_fr.
|
||||
func (st *pitchFrState) pitchFr(mode int, Top []int16, exc []int16, excOff int, xn, h []int16, Lsubfr, iSubfr int) (T0, frac, resu3, anaIndex int16) {
|
||||
p := modeDepParm[mode]
|
||||
maxFracLag := p.maxFracLag
|
||||
flag3 := p.flag3
|
||||
frac = p.firstFrac
|
||||
lastFrac := p.lastFrac
|
||||
|
||||
deltaSearch := int16(1)
|
||||
var t0min, t0max int16
|
||||
if iSubfr == 0 || iSubfr == cL_FRAME_BY2 {
|
||||
if (mode != dMR475 && mode != dMR515) || iSubfr != cL_FRAME_BY2 {
|
||||
deltaSearch = 0
|
||||
frameOffset := int16(1)
|
||||
if iSubfr == 0 {
|
||||
frameOffset = 0
|
||||
}
|
||||
t0min, t0max = getRange(Top[frameOffset], p.deltaIntLow, p.deltaIntRange, p.pitMin, cPIT_MAX)
|
||||
} else {
|
||||
t0min, t0max = getRange(st.T0prev, p.deltaFrcLow, p.deltaFrcRange, p.pitMin, cPIT_MAX)
|
||||
}
|
||||
} else {
|
||||
t0min, t0max = getRange(st.T0prev, p.deltaFrcLow, p.deltaFrcRange, p.pitMin, cPIT_MAX)
|
||||
}
|
||||
|
||||
tMin := int16(int(t0min) - cL_INTER_SRCH)
|
||||
tMax := int16(int(t0max) + cL_INTER_SRCH)
|
||||
corr := make([]int16, int(tMax)-int(tMin)+1)
|
||||
normCorr(exc, excOff, xn, h, Lsubfr, tMin, tMax, corr)
|
||||
|
||||
max := corr[int(t0min)-int(tMin)]
|
||||
lag := t0min
|
||||
for i := t0min + 1; i <= t0max; i++ {
|
||||
if corr[int(i)-int(tMin)] >= max {
|
||||
max = corr[int(i)-int(tMin)]
|
||||
lag = i
|
||||
}
|
||||
}
|
||||
|
||||
if deltaSearch == 0 && lag > maxFracLag {
|
||||
frac = 0
|
||||
} else if deltaSearch != 0 && (mode == dMR475 || mode == dMR515 || mode == dMR59 || mode == dMR67) {
|
||||
tmpLag := st.T0prev
|
||||
if tmpLag-t0min > 5 {
|
||||
tmpLag = t0min + 5
|
||||
}
|
||||
if t0max-tmpLag > 4 {
|
||||
tmpLag = t0max - 4
|
||||
}
|
||||
switch {
|
||||
case lag == tmpLag || lag == tmpLag-1:
|
||||
searchFrac(&lag, &frac, lastFrac, corr, int(tMin), flag3)
|
||||
case lag == tmpLag-2:
|
||||
frac = 0
|
||||
searchFrac(&lag, &frac, lastFrac, corr, int(tMin), flag3)
|
||||
case lag == tmpLag+1:
|
||||
lastFrac = 0
|
||||
searchFrac(&lag, &frac, lastFrac, corr, int(tMin), flag3)
|
||||
default:
|
||||
frac = 0
|
||||
}
|
||||
} else {
|
||||
searchFrac(&lag, &frac, lastFrac, corr, int(tMin), flag3)
|
||||
}
|
||||
|
||||
if flag3 != 0 {
|
||||
flag4 := int16(0)
|
||||
if mode == dMR475 || mode == dMR515 || mode == dMR59 || mode == dMR67 {
|
||||
flag4 = 1
|
||||
}
|
||||
anaIndex = encLag3(lag, frac, st.T0prev, t0min, t0max, deltaSearch, flag4)
|
||||
} else {
|
||||
anaIndex = encLag6(lag, frac, t0min, deltaSearch)
|
||||
}
|
||||
|
||||
st.T0prev = lag
|
||||
resu3 = flag3
|
||||
return lag, frac, resu3, anaIndex
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
package amrnb
|
||||
|
||||
// Main encoder driver, ported from opencore-amrnb cod_amr.cpp (cod_amr) and the
|
||||
// per-frame wrapper sp_enc.cpp (Speech_Encode_Frame). It ties together the LP
|
||||
// analysis (lpc), LSP quantization (lsp), perceptual weighting + open-loop pitch
|
||||
// (pre_big/ol_ltp), and the four-subframe analysis-by-synthesis loop
|
||||
// (subframePreProc -> clLtp -> cbsearch -> gainQuant -> subframePostProc),
|
||||
// producing the parameter array consumed by prm2bits/packFrame.
|
||||
//
|
||||
// Scope: the active-speech path for all eight modes — MR515/MR59/MR67/MR74/MR102
|
||||
// via Qua_gain, MR122 via q_gain_code, MR475 via the joint two-subframe VQ
|
||||
// (mr475Loop), and MR795 via the gain-adapted quantizer. MR102 uses the weighted
|
||||
// open-loop pitch (pitchOlWgh). DTX/VAD is always-active. All eight modes are
|
||||
// bit-exact with the opencore-amrnb reference encoder.
|
||||
|
||||
// buffer offsets within oldSpeech (length cL_TOTAL).
|
||||
const (
|
||||
cSpeechOff = cL_TOTAL - cL_FRAME - cL_NEXT // 120: present frame start
|
||||
cPWindowOff = cL_TOTAL - cL_WINDOW // 80: LPC analysis window
|
||||
cPWin122Off = cPWindowOff - cL_NEXT // 40: EFR (MR122) window, no lookahead
|
||||
cNewSpchOff = cL_TOTAL - cL_FRAME // 160: new speech
|
||||
)
|
||||
|
||||
// coderState holds the persistent encoder analysis state across frames,
|
||||
// mirroring cod_amrState plus the Pre_Process state.
|
||||
type coderState struct {
|
||||
oldSpeech [cL_TOTAL]int16 // [history | present | lookahead]
|
||||
oldWsp [cPIT_MAX + cL_FRAME]int16 // weighted speech with pitch history
|
||||
oldExc [cExcOff + cL_FRAME]int16 // excitation with pitch+interp history
|
||||
|
||||
pre preProcessState
|
||||
lspS lspEncState
|
||||
pred gcPredState
|
||||
predUnq gcPredState // "unquantized" gain predictor (MR475 only)
|
||||
adapt gainAdaptState // gain adaptor (MR795 only)
|
||||
pit pitchFrState
|
||||
pitWght pitchOLWghtState // weighted open-loop pitch (MR102 only)
|
||||
lev levinsonState
|
||||
|
||||
oldLags [5]int16 // closed-loop lag history (MR102 weighted OLP)
|
||||
olGainFlg [2]int16 // per-half-frame open-loop gain flag (MR102)
|
||||
prmBuf [64]int16 // reused parameter array (max params = 57, MR122)
|
||||
|
||||
memSyn [cM]int16
|
||||
memW0 [cM]int16
|
||||
memErr [cM]int16
|
||||
memW [cM]int16 // weighting-filter memory for pre_big
|
||||
sharp int16
|
||||
|
||||
// MR475 joint-pair quantizer state carried from the even to the odd subframe
|
||||
sf0ExpGcode0, sf0FracGcode0 int16
|
||||
sf0FracCoeff, sf0ExpCoeff [5]int16
|
||||
sf0ExpTargetEn, sf0FracTargetEn int16
|
||||
}
|
||||
|
||||
func (st *coderState) reset() {
|
||||
*st = coderState{}
|
||||
st.lspS.reset()
|
||||
st.pred.reset()
|
||||
st.pit.reset()
|
||||
st.pitWght.reset()
|
||||
for i := range st.oldLags {
|
||||
st.oldLags[i] = 40
|
||||
}
|
||||
st.sharp = 0 // SHARPMIN
|
||||
}
|
||||
|
||||
// lpcAnalysis runs the windowed autocorrelation + Levinson chain, filling the
|
||||
// per-window LP filters in az: az[3*MP1] (A_new) always, and az[MP1] (A_mid) for
|
||||
// MR122. Mirrors lpc.
|
||||
func (st *coderState) lpcAnalysis(mode int, az []int16) {
|
||||
var rh, rl [cMP1]int16
|
||||
var rc [4]int16
|
||||
if mode == dMR122 {
|
||||
autocorr(st.oldSpeech[cPWin122Off:], cM, rh[:], rl[:], window_160_80)
|
||||
lagWindow(cM, rh[:], rl[:])
|
||||
st.lev.levinson(rh[:], rl[:], az[cMP1:], rc[:])
|
||||
|
||||
autocorr(st.oldSpeech[cPWin122Off:], cM, rh[:], rl[:], window_232_8)
|
||||
lagWindow(cM, rh[:], rl[:])
|
||||
st.lev.levinson(rh[:], rl[:], az[3*cMP1:], rc[:])
|
||||
return
|
||||
}
|
||||
autocorr(st.oldSpeech[cPWindowOff:], cM, rh[:], rl[:], window_200_40)
|
||||
lagWindow(cM, rh[:], rl[:])
|
||||
st.lev.levinson(rh[:], rl[:], az[3*cMP1:], rc[:])
|
||||
}
|
||||
|
||||
// preBig computes the perceptually weighted speech wsp for the two subframes of
|
||||
// one half-frame (frameOffset 0 or L_FRAME_BY2), updating the weighting-filter
|
||||
// memory memW. Mirrors pre_big.
|
||||
func (st *coderState) preBig(mode, frameOffset int, az []int16) {
|
||||
g1 := gamma1[:]
|
||||
if mode > dMR795 { // MR102, MR122
|
||||
g1 = gamma1_12k2[:]
|
||||
}
|
||||
aOffset := 0
|
||||
if frameOffset > 0 {
|
||||
aOffset = cMP1 * 2
|
||||
}
|
||||
fo := frameOffset
|
||||
for i := 0; i < 2; i++ {
|
||||
var ap1, ap2 [cMP1]int16
|
||||
weightAi(az[aOffset:], g1, ap1[:])
|
||||
weightAi(az[aOffset:], gamma2[:], ap2[:])
|
||||
residu(ap1[:], st.oldSpeech[:], cSpeechOff+fo, cL_SUBFR, st.oldWsp[cPIT_MAX+fo:])
|
||||
w := st.oldWsp[cPIT_MAX+fo : cPIT_MAX+fo+cL_SUBFR]
|
||||
synFilt(ap2[:], w, w, cL_SUBFR, st.memW[:], true)
|
||||
aOffset += cMP1
|
||||
fo += cL_SUBFR
|
||||
}
|
||||
}
|
||||
|
||||
// gainQuant quantizes the subframe gains for the non-MR475 modes, writing the
|
||||
// quantization index/indices into prm, returning the quantized gains and the
|
||||
// number of indices written, and advancing the predictor. res/exc are the LP
|
||||
// residual and adaptive-codebook excitation (used only by MR795). Mirrors the
|
||||
// non-MR475 branch of gainQuant.
|
||||
func (st *coderState) gainQuant(mode int, r clLtpResult, xn, xn2, y1, y2, code, res, exc, prm []int16) (gainPit, gainCode int16, nIdx int) {
|
||||
gp := st.pred.gcPred(mode, code)
|
||||
var qe122, qe int16
|
||||
switch {
|
||||
case mode == dMR122:
|
||||
gainPit = r.gainPit // already quantized in cl_ltp
|
||||
gainCode = gCode(xn2, y2)
|
||||
var idx int16
|
||||
idx, qe122, qe = qGainCode(mode, gp.expGcode0, gp.fracGcode0, &gainCode)
|
||||
prm[0] = idx
|
||||
nIdx = 1
|
||||
case mode == dMR795:
|
||||
fc, ec, cgFrac, cgExp := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
||||
gainPit = r.gainPit
|
||||
var gpIdx, gcIdx int16
|
||||
gainCode, qe122, qe, gpIdx, gcIdx = st.mr795GainQuant(res, exc, code, fc[:], ec[:],
|
||||
gp.expEn, gp.fracEn, gp.expGcode0, gp.fracGcode0, cgFrac, cgExp, r.gpLimit, &gainPit)
|
||||
prm[0], prm[1] = gpIdx, gcIdx
|
||||
nIdx = 2
|
||||
default:
|
||||
fc, ec, _, _ := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
||||
var idx int16
|
||||
idx, gainPit, gainCode, qe122, qe = quaGain(mode, gp.expGcode0, gp.fracGcode0, fc[:], ec[:], r.gpLimit)
|
||||
prm[0] = idx
|
||||
nIdx = 1
|
||||
}
|
||||
st.pred.update(qe122, qe)
|
||||
return
|
||||
}
|
||||
|
||||
// codAmr encodes one pre-processed 160-sample frame, returning the parameter
|
||||
// array (LSF indices followed by the per-subframe pitch/codebook/gain indices,
|
||||
// in prm2bits order). Mirrors cod_amr for the supported modes.
|
||||
func (st *coderState) codAmr(mode int, newSpeech []int16) ([]int16, error) {
|
||||
copy(st.oldSpeech[cNewSpchOff:], newSpeech[:cL_FRAME])
|
||||
|
||||
az := make([]int16, cNB_SUBFR*cMP1)
|
||||
azQ := make([]int16, cNB_SUBFR*cMP1)
|
||||
st.lpcAnalysis(mode, az)
|
||||
|
||||
prm := st.prmBuf[:len(bitno[Mode(mode)])] // reused; caller serializes it before the next frame
|
||||
ap := st.lspS.lsp(mode, az, azQ, prm)
|
||||
|
||||
st.preBig(mode, 0, az)
|
||||
st.preBig(mode, cL_FRAME_BY2, az)
|
||||
var Top [2]int16
|
||||
if mode == dMR475 || mode == dMR515 {
|
||||
Top[0] = pitchOl(mode, st.oldWsp[:], cPIT_MAX, cPIT_MIN, cPIT_MAX, cL_FRAME)
|
||||
Top[1] = Top[0]
|
||||
} else {
|
||||
Top[0] = st.olPitch(mode, 0)
|
||||
Top[1] = st.olPitch(mode, cL_FRAME_BY2)
|
||||
}
|
||||
|
||||
synth := make([]int16, cL_FRAME)
|
||||
if mode == dMR475 {
|
||||
st.mr475Loop(az, azQ, Top[:], prm, ap, synth)
|
||||
copy(st.oldExc[:cExcOff], st.oldExc[cL_FRAME:cL_FRAME+cExcOff])
|
||||
copy(st.oldWsp[:cPIT_MAX], st.oldWsp[cL_FRAME:cL_FRAME+cPIT_MAX])
|
||||
copy(st.oldSpeech[:cL_TOTAL-cL_FRAME], st.oldSpeech[cL_FRAME:cL_TOTAL])
|
||||
return prm, nil
|
||||
}
|
||||
for subfrNr := 0; subfrNr < cNB_SUBFR; subfrNr++ {
|
||||
iSubfr := subfrNr * cL_SUBFR
|
||||
aSub := az[subfrNr*cMP1:]
|
||||
aqSub := azQ[subfrNr*cMP1:]
|
||||
excBase := cExcOff + iSubfr
|
||||
|
||||
xn := make([]int16, cL_SUBFR)
|
||||
res := make([]int16, cL_SUBFR)
|
||||
errSig := make([]int16, cL_SUBFR)
|
||||
h1 := make([]int16, cL_SUBFR)
|
||||
subframePreProc(mode, aSub, aqSub, st.oldSpeech[:], cSpeechOff+iSubfr,
|
||||
st.memErr[:], st.memW0[:], st.oldExc[excBase:], h1, xn, res, errSig)
|
||||
|
||||
res2 := make([]int16, cL_SUBFR)
|
||||
copy(res2, res)
|
||||
|
||||
xn2 := make([]int16, cL_SUBFR)
|
||||
y1 := make([]int16, cL_SUBFR)
|
||||
r := st.pit.clLtp(mode, iSubfr, Top[:], h1, st.oldExc[:], excBase, res2, xn, xn2, y1)
|
||||
// MR102: feed the first/last closed-loop lag back into the OLP history
|
||||
if subfrNr == 0 && st.olGainFlg[0] > 0 {
|
||||
st.oldLags[1] = r.T0
|
||||
} else if subfrNr == 3 && st.olGainFlg[1] > 0 {
|
||||
st.oldLags[0] = r.T0
|
||||
}
|
||||
prm[ap] = r.pitchIndex
|
||||
ap++
|
||||
if r.hasGainPitIndex {
|
||||
prm[ap] = r.gainPitIndex
|
||||
ap++
|
||||
}
|
||||
|
||||
code := make([]int16, cL_SUBFR)
|
||||
y2 := make([]int16, cL_SUBFR)
|
||||
ncb := cbsearch(xn2, h1, r.T0, st.sharp, r.gainPit, res2, code, y2, mode, subfrNr, prm[ap:])
|
||||
ap += ncb
|
||||
|
||||
gainPit, gainCode, ng := st.gainQuant(mode, r, xn, xn2, y1, y2, code, res, st.oldExc[excBase:], prm[ap:])
|
||||
ap += ng
|
||||
|
||||
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfr, gainPit, gainCode,
|
||||
aqSub, synth, xn, code, y1, y2, st.memSyn[:], st.memErr[:], st.memW0[:],
|
||||
st.oldExc[:], cExcOff, &st.sharp)
|
||||
}
|
||||
|
||||
// slide the analysis buffers left by one frame
|
||||
copy(st.oldExc[:cExcOff], st.oldExc[cL_FRAME:cL_FRAME+cExcOff])
|
||||
copy(st.oldWsp[:cPIT_MAX], st.oldWsp[cL_FRAME:cL_FRAME+cPIT_MAX])
|
||||
copy(st.oldSpeech[:cL_TOTAL-cL_FRAME], st.oldSpeech[cL_FRAME:cL_TOTAL])
|
||||
|
||||
return prm, nil
|
||||
}
|
||||
|
||||
// mr475Loop runs the four-subframe analysis-by-synthesis loop for MR475, whose
|
||||
// gains are quantized two subframes at a time. Each even subframe is processed
|
||||
// tentatively (storing its energy terms and an unquantized synthesis), and at
|
||||
// the following odd subframe the joint VQ chooses one index for the pair; both
|
||||
// subframes' final syntheses are then redone with the reconstructed gains.
|
||||
// Mirrors the MR475 branch of cod_amr.
|
||||
func (st *coderState) mr475Loop(az, azQ, Top, prm []int16, ap int, synth []int16) {
|
||||
const mode = dMR475
|
||||
var memSynSave, memW0Save, memErrSave [cM]int16
|
||||
var h1Sf0, xnSf0, y2Sf0, codeSf0 [cL_SUBFR]int16
|
||||
var sharpSave, gainPitSf0, gainCodeSf0, T0Sf0, T0fracSf0 int16
|
||||
var iSubfrSf0, gainIdxPos int
|
||||
|
||||
for subfrNr := 0; subfrNr < cNB_SUBFR; subfrNr++ {
|
||||
iSubfr := subfrNr * cL_SUBFR
|
||||
evenSubfr := (subfrNr & 1) == 0
|
||||
aSub := az[subfrNr*cMP1:]
|
||||
aqSub := azQ[subfrNr*cMP1:]
|
||||
excBase := cExcOff + iSubfr
|
||||
|
||||
if evenSubfr {
|
||||
memSynSave = st.memSyn
|
||||
memW0Save = st.memW0
|
||||
memErrSave = st.memErr
|
||||
sharpSave = st.sharp
|
||||
}
|
||||
|
||||
// Both subframes of the pair build their target from memW0Save so the
|
||||
// even subframe's tentative synthesis carries into the odd one.
|
||||
xn := make([]int16, cL_SUBFR)
|
||||
res := make([]int16, cL_SUBFR)
|
||||
errSig := make([]int16, cL_SUBFR)
|
||||
h1 := make([]int16, cL_SUBFR)
|
||||
subframePreProc(mode, aSub, aqSub, st.oldSpeech[:], cSpeechOff+iSubfr,
|
||||
st.memErr[:], memW0Save[:], st.oldExc[excBase:], h1, xn, res, errSig)
|
||||
if evenSubfr {
|
||||
copy(h1Sf0[:], h1)
|
||||
}
|
||||
res2 := make([]int16, cL_SUBFR)
|
||||
copy(res2, res)
|
||||
|
||||
xn2 := make([]int16, cL_SUBFR)
|
||||
y1 := make([]int16, cL_SUBFR)
|
||||
r := st.pit.clLtp(mode, iSubfr, Top, h1, st.oldExc[:], excBase, res2, xn, xn2, y1)
|
||||
prm[ap] = r.pitchIndex
|
||||
ap++
|
||||
|
||||
code := make([]int16, cL_SUBFR)
|
||||
y2 := make([]int16, cL_SUBFR)
|
||||
ap += cbsearch(xn2, h1, r.T0, st.sharp, r.gainPit, res2, code, y2, mode, subfrNr, prm[ap:])
|
||||
|
||||
if evenSubfr {
|
||||
gainIdxPos = ap // reserve the joint gain index slot
|
||||
ap++
|
||||
|
||||
st.predUnq = st.pred // gc_pred_copy
|
||||
gpu := st.predUnq.gcPred(mode, code)
|
||||
st.sf0ExpGcode0, st.sf0FracGcode0 = gpu.expGcode0, gpu.fracGcode0
|
||||
fc, ec, cgFrac, cgExp := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
||||
st.sf0FracCoeff, st.sf0ExpCoeff = fc, ec
|
||||
st.sf0ExpTargetEn, st.sf0FracTargetEn = calcTargetEnergy(xn)
|
||||
st.predUnq.mr475UpdateUnqPred(st.sf0ExpGcode0, st.sf0FracGcode0, cgExp, cgFrac)
|
||||
|
||||
// tentative synthesis with the unquantized optimum gains
|
||||
gainCode := shl(cgFrac, cgExp+1)
|
||||
iSubfrSf0 = iSubfr
|
||||
copy(xnSf0[:], xn)
|
||||
copy(y2Sf0[:], y2)
|
||||
copy(codeSf0[:], code)
|
||||
T0Sf0, T0fracSf0 = r.T0, r.T0frac
|
||||
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfr, r.gainPit, gainCode,
|
||||
aqSub, synth, xn, code, y1, y2, memSynSave[:], st.memErr[:], memW0Save[:],
|
||||
st.oldExc[:], cExcOff, &st.sharp)
|
||||
st.sharp = sharpSave
|
||||
continue
|
||||
}
|
||||
|
||||
// odd subframe: joint gain quantization for the pair
|
||||
gpu := st.predUnq.gcPred(mode, code)
|
||||
fc, ec, _, _ := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
||||
tEnExp, tEnFrac := calcTargetEnergy(xn)
|
||||
idx, sf0gp, sf0gc, sf1gp, sf1gc := st.mr475GainQuant(code, gpu.expGcode0, gpu.fracGcode0,
|
||||
ec[:], fc[:], tEnExp, tEnFrac, r.gpLimit)
|
||||
prm[gainIdxPos] = idx
|
||||
gainPitSf0, gainCodeSf0 = sf0gp, sf0gc
|
||||
|
||||
// redo the even subframe's final synthesis with the quantized gains
|
||||
st.memErr = memErrSave
|
||||
predLt(st.oldExc[:], cExcOff+iSubfrSf0, T0Sf0, T0fracSf0, cL_SUBFR, 1)
|
||||
convolve(st.oldExc[cExcOff+iSubfrSf0:], h1Sf0[:], y1, cL_SUBFR)
|
||||
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfrSf0, gainPitSf0, gainCodeSf0,
|
||||
azQ[(subfrNr-1)*cMP1:], synth, xnSf0[:], codeSf0[:], y1, y2Sf0[:],
|
||||
st.memSyn[:], st.memErr[:], st.memW0[:], st.oldExc[:], cExcOff, &sharpSave)
|
||||
|
||||
// redo the odd subframe's analysis + synthesis with the final memories
|
||||
subframePreProc(mode, aSub, aqSub, st.oldSpeech[:], cSpeechOff+iSubfr,
|
||||
st.memErr[:], st.memW0[:], st.oldExc[excBase:], h1, xn, res, errSig)
|
||||
predLt(st.oldExc[:], excBase, r.T0, r.T0frac, cL_SUBFR, 1)
|
||||
convolve(st.oldExc[excBase:], h1, y1, cL_SUBFR)
|
||||
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfr, sf1gp, sf1gc,
|
||||
aqSub, synth, xn, code, y1, y2, st.memSyn[:], st.memErr[:], st.memW0[:],
|
||||
st.oldExc[:], cExcOff, &st.sharp)
|
||||
}
|
||||
}
|
||||
|
||||
// olPitch runs the open-loop pitch over the weighted-speech buffer for the
|
||||
// half-frame at frameOffset.
|
||||
func (st *coderState) olPitch(mode, frameOffset int) int16 {
|
||||
idx := 0
|
||||
if frameOffset != 0 {
|
||||
idx = 1
|
||||
}
|
||||
if mode == dMR102 {
|
||||
// weighted open-loop pitch with median/gain-flag adaptation
|
||||
return st.pitWght.pitchOlWgh(st.oldWsp[:], cPIT_MAX+frameOffset, cPIT_MIN, cPIT_MAX,
|
||||
cL_FRAME_BY2, st.oldLags[:], st.olGainFlg[:], idx)
|
||||
}
|
||||
pitMin := int16(cPIT_MIN)
|
||||
if mode == dMR122 {
|
||||
pitMin = cPIT_MIN_MR122
|
||||
}
|
||||
return pitchOl(mode, st.oldWsp[:], cPIT_MAX+frameOffset, int(pitMin), cPIT_MAX, cL_FRAME_BY2)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package amrnb
|
||||
|
||||
// Sign-weighted impulse-response autocorrelation matrix, ported from
|
||||
// opencore-amrnb cor_h.cpp. corH builds rr[i][j] (flattened, row-major, size
|
||||
// L_CODE*L_CODE): the diagonal holds the truncated energy of the weighted
|
||||
// impulse response (used to place a pulse at position i), and the off-diagonal
|
||||
// holds the sign-weighted cross-correlation between candidate pulse positions
|
||||
// i and j. This matrix drives the per-mode algebraic codebook pulse searches.
|
||||
// The reference's exact incremental Q15 roundings and pointer walk are
|
||||
// preserved (bit-exact).
|
||||
|
||||
// corH fills the flattened rr (length cL_CODE*cL_CODE) from the weighted impulse
|
||||
// response h and the per-position signs. Mirrors cor_h.
|
||||
func corH(h, sign, rr []int16) {
|
||||
var h2 [cL_CODE]int16
|
||||
|
||||
// energy of h, and the scaling of h into h2
|
||||
s := int32(1)
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
s += int32(h[i]) * int32(h[i])
|
||||
}
|
||||
s <<= 1
|
||||
if s < 0 { // overflow -> halve
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
h2[i] = h[i] >> 1
|
||||
}
|
||||
} else {
|
||||
s >>= 1
|
||||
s = invSqrt(s)
|
||||
var dec int16
|
||||
if s < 0x00ffffff {
|
||||
dec = int16(((s >> 9) * 32440) >> 15)
|
||||
} else {
|
||||
dec = 32440 // 0.99
|
||||
}
|
||||
for i := 0; i < cL_CODE; i++ {
|
||||
h2[i] = int16((int32(h[i])*int32(dec) + 0x20) >> 6)
|
||||
}
|
||||
}
|
||||
|
||||
const L = cL_CODE
|
||||
|
||||
// diagonal: rr[L-1-m][L-1-m] = sum_{k=0..m} h2[k]^2
|
||||
s = 0
|
||||
p := 0
|
||||
rr1 := (L-1)*L + (L - 1)
|
||||
for i := L >> 1; i != 0; i-- {
|
||||
t := int32(h2[p])
|
||||
p++
|
||||
s += t * t
|
||||
rr[rr1] = int16((s + 0x4000) >> 15)
|
||||
rr1 -= L + 1
|
||||
t = int32(h2[p])
|
||||
p++
|
||||
s += t * t
|
||||
rr[rr1] = int16((s + 0x4000) >> 15)
|
||||
rr1 -= L + 1
|
||||
}
|
||||
|
||||
// off-diagonal: sign-weighted cross-correlation at each distance dec
|
||||
pRrRef1 := (L - 1) * L
|
||||
for dec := 1; dec < L; dec += 2 {
|
||||
rr1 := pRrRef1 + (L - 1 - dec) // &rr[L-1][L-1-dec]
|
||||
rr2 := (L-1-dec)*L + (L - 1) // &rr[L-1-dec][L-1]
|
||||
rr3 := (L-1-(dec+1))*L + (L - 1)
|
||||
|
||||
var s2 int32
|
||||
s = 0
|
||||
pSign1 := L - 1
|
||||
pSign2 := L - 1 - dec
|
||||
ph2 := 0
|
||||
ph := dec
|
||||
|
||||
for i := L - dec - 1; i != 0; i-- {
|
||||
s += int32(h2[ph2]) * int32(h2[ph])
|
||||
ph++
|
||||
s2 += int32(h2[ph2]) * int32(h2[ph])
|
||||
ph2++
|
||||
tmp1 := int16((s + 0x4000) >> 15)
|
||||
tmp11 := int16((s2 + 0x4000) >> 15)
|
||||
tmp2 := int16((int32(sign[pSign1]) * int32(sign[pSign2])) >> 15)
|
||||
pSign2--
|
||||
tmp22 := int16((int32(sign[pSign1]) * int32(sign[pSign2])) >> 15)
|
||||
pSign1--
|
||||
|
||||
rr[rr2] = int16((int32(tmp1) * int32(tmp2)) >> 15)
|
||||
rr[rr1] = rr[rr2]
|
||||
rr1--
|
||||
rr[rr1] = int16((int32(tmp11) * int32(tmp22)) >> 15)
|
||||
rr[rr3] = rr[rr1]
|
||||
|
||||
rr1 -= L
|
||||
rr2 -= L + 1
|
||||
rr3 -= L + 1
|
||||
}
|
||||
|
||||
s += int32(h2[ph2]) * int32(h2[ph])
|
||||
tmp1 := int16((s + 0x4000) >> 15)
|
||||
tmp2 := int16((int32(sign[pSign1]) * int32(sign[pSign2])) >> 15)
|
||||
rr[rr1] = int16((int32(tmp1) * int32(tmp2)) >> 15)
|
||||
rr[rr2] = rr[rr1]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package amrnb
|
||||
|
||||
// Encoder analysis-by-synthesis filter kernels, ported from opencore-amrnb
|
||||
// convolve.cpp. convolve computes the (truncated) lower-triangular convolution
|
||||
// y[n] = sum_{i=0..n} x[i]·h[n-i] >> 12, used throughout the closed-loop pitch
|
||||
// and algebraic codebook searches to filter an excitation by the weighted
|
||||
// synthesis impulse response. The reference accumulates in non-saturating int32,
|
||||
// so a straightforward double loop is bit-exact.
|
||||
func convolve(x, h, y []int16, L int) {
|
||||
xf := x[:L] // hoist bounds: i<=n<L and n-i in [0,n]
|
||||
hf := h[:L]
|
||||
for n := 0; n < L; n++ {
|
||||
var s int32
|
||||
hn := hf[:n+1] // h[n-i] == hn[n-i], index in [0,n]
|
||||
for i := 0; i <= n; i++ {
|
||||
s += int32(xf[i]) * int32(hn[n-i])
|
||||
}
|
||||
y[n] = int16(s >> 12)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package amrnb
|
||||
|
||||
// Encoder LP analysis chain, ported from opencore-amrnb pre_proc.cpp
|
||||
// (Pre_Process), autocorr.cpp (Autocorr), lag_wind.cpp (Lag_window) and
|
||||
// levinson.cpp (Levinson), plus the 32-bit operators they need (Mpy_32, Div_32,
|
||||
// L_abs). Together these turn 8 kHz PCM into the order-10 LP coefficients A(z):
|
||||
// input high-pass + scaling, windowed autocorrelation, lag-window bandwidth
|
||||
// expansion, then the Levinson-Durbin recursion. Bit-exact with the reference.
|
||||
|
||||
// L_abs returns the saturated 32-bit absolute value (|MIN_32| -> MAX_32).
|
||||
// Mirrors L_abs.
|
||||
func L_abs(x int32) int32 {
|
||||
if x == minInt32 {
|
||||
return maxInt32
|
||||
}
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// mpy_32 multiplies two 32-bit values given in (hi,lo) double-precision form.
|
||||
// Mirrors the generic Mpy_32.
|
||||
func mpy_32(hi1, lo1, hi2, lo2 int16) int32 {
|
||||
L := L_mult(hi1, hi2)
|
||||
L = L_add(L, ((int32(hi1)*int32(lo2))>>15)<<1)
|
||||
L = L_add(L, ((int32(lo1)*int32(hi2))>>15)<<1)
|
||||
return L
|
||||
}
|
||||
|
||||
// div_32 divides a 32-bit numerator by a 32-bit denominator (given as hi,lo).
|
||||
// Mirrors Div_32.
|
||||
func div_32(num int32, denomHi, denomLo int16) int32 {
|
||||
approx := div_s(0x3fff, denomHi)
|
||||
result := mpy32_16(denomHi, denomLo, approx)
|
||||
result = 0x7fffffff - result
|
||||
hi, lo := L_Extract(result)
|
||||
result = mpy32_16(hi, lo, approx)
|
||||
hi, lo = L_Extract(result)
|
||||
nHi, nLo := L_Extract(num)
|
||||
result = mpy_32(nHi, nLo, hi, lo)
|
||||
return L_shl(result, 2)
|
||||
}
|
||||
|
||||
// preProcessState holds the encoder input high-pass biquad memory.
|
||||
type preProcessState struct {
|
||||
x0, x1 int16
|
||||
y1hi, y1lo, y2hi, y2lo int16
|
||||
}
|
||||
|
||||
// process applies the input high-pass filter (with 0.5 scaling) in place over
|
||||
// lg samples. Mirrors Pre_Process.
|
||||
func (st *preProcessState) process(signal []int16, lg int) {
|
||||
xn2 := st.x1
|
||||
xn1 := st.x0
|
||||
for i := 0; i < lg; i++ {
|
||||
L := int32(st.y1hi) * 7807
|
||||
L += (int32(st.y1lo) * 7807) >> 15
|
||||
L += int32(st.y2hi) * -3733
|
||||
st.y2hi = st.y1hi
|
||||
L += (int32(st.y2lo) * -3733) >> 15
|
||||
st.y2lo = st.y1lo
|
||||
L += int32(xn2) * 1899
|
||||
xn2 = xn1
|
||||
L += int32(xn1) * -3798
|
||||
xn1 = signal[i]
|
||||
L += int32(xn1) * 1899
|
||||
|
||||
signal[i] = int16((L + 0x800) >> 12)
|
||||
st.y1hi = int16(L >> 12)
|
||||
st.y1lo = int16((L << 3) - (int32(st.y1hi) << 15))
|
||||
}
|
||||
st.x1 = xn2
|
||||
st.x0 = xn1
|
||||
}
|
||||
|
||||
// autocorr windows x with wind and computes the order-m autocorrelation into
|
||||
// (rh,rl) double precision, returning the normalization shift applied. Mirrors
|
||||
// Autocorr.
|
||||
func autocorr(x []int16, m int, rh, rl, wind []int16) int16 {
|
||||
var y [cL_WINDOW]int16
|
||||
|
||||
var sum int32
|
||||
overflow := false
|
||||
b := 0
|
||||
for ; b < cL_WINDOW; b++ {
|
||||
temp := int16((int32(x[b])*int32(wind[b]) + 0x4000) >> 15)
|
||||
y[b] = temp
|
||||
sum += (int32(temp) * int32(temp)) << 1
|
||||
if sum < 0 {
|
||||
overflow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
overflShft := int16(0)
|
||||
if overflow {
|
||||
for k := b + 1; k < cL_WINDOW; k++ {
|
||||
y[k] = int16((int32(x[k])*int32(wind[k]) + 0x4000) >> 15)
|
||||
}
|
||||
for {
|
||||
overflShft += 4
|
||||
sum = 0
|
||||
for k := 0; k < cL_WINDOW; k++ {
|
||||
t := y[k] >> 2
|
||||
y[k] = t
|
||||
sum += (int32(t) * int32(t)) << 1
|
||||
}
|
||||
if sum > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sum++ // avoid all-zeros
|
||||
norm := norm_l(sum)
|
||||
sum <<= uint(norm)
|
||||
rh[0] = int16(sum >> 16)
|
||||
rl[0] = int16((sum >> 1) - (int32(rh[0]) << 15))
|
||||
|
||||
for i := 1; i <= m; i++ {
|
||||
// sum_{n=i}^{W-1} y[n]·y[n-i] = sum_k y[i+k]·y[k] (wrapping int32) via firDot
|
||||
s := firDot(y[i:cL_WINDOW], y[:cL_WINDOW-i])
|
||||
s <<= uint(norm + 1)
|
||||
rh[i] = int16(s >> 16)
|
||||
rl[i] = int16((s >> 1) - (int32(rh[i]) << 15))
|
||||
}
|
||||
|
||||
return norm - overflShft
|
||||
}
|
||||
|
||||
// lagWindow applies the lag-window bandwidth expansion to the autocorrelation
|
||||
// in place. Mirrors Lag_window.
|
||||
func lagWindow(m int, rh, rl []int16) {
|
||||
for i := 1; i <= m; i++ {
|
||||
x := mpy_32(rh[i], rl[i], lag_h[i-1], lag_l[i-1])
|
||||
rh[i] = int16(x >> 16)
|
||||
rl[i] = int16((x >> 1) - (int32(rh[i]) << 15))
|
||||
}
|
||||
}
|
||||
|
||||
// levinsonState carries the last stable A(z) for the unstable-filter fallback.
|
||||
type levinsonState struct {
|
||||
oldA [cMP1]int16
|
||||
}
|
||||
|
||||
// levinson runs the Levinson-Durbin recursion on the (rh,rl) autocorrelation,
|
||||
// producing order-10 LP coefficients A[0:M+1] and the first 4 reflection
|
||||
// coefficients rc[0:4]. Mirrors Levinson.
|
||||
func (st *levinsonState) levinson(rh, rl, a, rc []int16) {
|
||||
var ah, al, anh, anl [cMP1]int16
|
||||
|
||||
t1 := (int32(rh[1]) << 16) + (int32(rl[1]) << 1)
|
||||
t2 := L_abs(t1)
|
||||
t0 := div_32(t2, rh[0], rl[0]) // R[1]/R[0]
|
||||
if t1 > 0 {
|
||||
t0 = L_negate(t0)
|
||||
}
|
||||
kh, kl := L_Extract(t0)
|
||||
rc[0] = round_(t0)
|
||||
|
||||
t0 >>= 4
|
||||
ah[1] = int16(t0 >> 16)
|
||||
al[1] = int16((t0 >> 1) - (int32(ah[1]) << 15))
|
||||
|
||||
t0 = mpy_32(kh, kl, kh, kl) // K*K
|
||||
t0 = L_abs(t0)
|
||||
t0 = 0x7fffffff - t0 // 1 - K*K
|
||||
hi, lo := L_Extract(t0)
|
||||
t0 = mpy_32(rh[0], rl[0], hi, lo) // Alpha
|
||||
alpExp := norm_l(t0)
|
||||
t0 = t0 << uint(alpExp)
|
||||
alpH := int16(t0 >> 16)
|
||||
alpL := int16((t0 >> 1) - (int32(alpH) << 15))
|
||||
|
||||
for i := 2; i <= cM; i++ {
|
||||
t0 = 0
|
||||
for j := 1; j < i; j++ {
|
||||
t0 += (int32(rh[j]) * int32(al[i-j])) >> 15
|
||||
t0 += (int32(rl[j]) * int32(ah[i-j])) >> 15
|
||||
t0 += int32(rh[j]) * int32(ah[i-j])
|
||||
}
|
||||
t0 <<= 5
|
||||
t1 = (int32(rh[i]) << 16) + (int32(rl[i]) << 1)
|
||||
t0 += t1
|
||||
|
||||
t1 = L_abs(t0)
|
||||
t2 = div_32(t1, alpH, alpL)
|
||||
if t0 > 0 {
|
||||
t2 = L_negate(t2)
|
||||
}
|
||||
t2 = L_shl(t2, alpExp)
|
||||
kh = int16(t2 >> 16)
|
||||
kl = int16((t2 >> 1) - (int32(kh) << 15))
|
||||
|
||||
if i < 5 {
|
||||
rc[i-1] = int16((t2 + 0x8000) >> 16)
|
||||
}
|
||||
if abs_s(kh) > 32750 { // unstable filter -> reuse last A(z)
|
||||
copy(a[:cMP1], st.oldA[:])
|
||||
for k := 0; k < 4; k++ {
|
||||
rc[k] = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for j := 1; j < i; j++ {
|
||||
t0 = (int32(kh) * int32(al[i-j])) >> 15
|
||||
t0 += (int32(kl) * int32(ah[i-j])) >> 15
|
||||
t0 += int32(kh) * int32(ah[i-j])
|
||||
t0 += (int32(ah[j]) << 15) + int32(al[j])
|
||||
anh[j] = int16(t0 >> 15)
|
||||
anl[j] = int16(t0 - (int32(anh[j]) << 15))
|
||||
}
|
||||
anh[i] = int16(t2 >> 20)
|
||||
anl[i] = int16((t2 >> 5) - (int32(anh[i]) << 15))
|
||||
|
||||
t0 = mpy_32(kh, kl, kh, kl) // K*K
|
||||
t0 = L_abs(t0)
|
||||
t0 = 0x7fffffff - t0 // 1 - K*K
|
||||
hi = int16(t0 >> 16)
|
||||
lo = int16((t0 >> 1) - (int32(hi) << 15))
|
||||
|
||||
t0 = (int32(alpH) * int32(lo)) >> 15
|
||||
t0 += (int32(alpL) * int32(hi)) >> 15
|
||||
t0 += int32(alpH) * int32(hi)
|
||||
t0 <<= 1
|
||||
jn := norm_l(t0)
|
||||
t0 = t0 << uint(jn)
|
||||
alpH = int16(t0 >> 16)
|
||||
alpL = int16((t0 >> 1) - (int32(alpH) << 15))
|
||||
alpExp += jn
|
||||
|
||||
copy(ah[1:1+i], anh[1:1+i])
|
||||
copy(al[1:1+i], anl[1:1+i])
|
||||
}
|
||||
|
||||
a[0] = 4096
|
||||
for i := 1; i <= cM; i++ {
|
||||
t0 = (int32(ah[i]) << 15) + int32(al[i])
|
||||
a[i] = int16((t0 + 0x2000) >> 14)
|
||||
st.oldA[i] = a[i]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package amrnb
|
||||
|
||||
// Encoder LSP analysis/quantization wrapper, ported from opencore-amrnb lsp.cpp
|
||||
// (lsp). Per frame it converts the analysis A(z) to LSPs (azLsp), quantizes them
|
||||
// (qPlsf3/qPlsf5), and interpolates both the unquantized LP filters (az, for the
|
||||
// perceptual weighting) and the quantized ones (azQ, for synthesis) across the
|
||||
// four subframes, returning the LSF codebook indices. Bit-exact with the
|
||||
// reference.
|
||||
|
||||
// lspEncState holds the encoder's LSP predictor memories.
|
||||
type lspEncState struct {
|
||||
lspOld [cM]int16
|
||||
lspOldQ [cM]int16
|
||||
q qPlsfState
|
||||
}
|
||||
|
||||
func (st *lspEncState) reset() {
|
||||
copy(st.lspOld[:], lsp_init_data[:cM])
|
||||
copy(st.lspOldQ[:], lsp_init_data[:cM])
|
||||
st.q.reset()
|
||||
}
|
||||
|
||||
// lsp processes one frame's LP analysis. az holds the unquantized A(z) with the
|
||||
// per-window filters already placed (A_mid at [MP1] for MR122, A_new at [3*MP1]);
|
||||
// on return az and azQ hold the interpolated unquantized/quantized filters for
|
||||
// all four subframes. The LSF indices are written to anap; the count is
|
||||
// returned. Mirrors lsp.
|
||||
func (st *lspEncState) lsp(mode int, az, azQ, anap []int16) int {
|
||||
var lspNew [cM]int16
|
||||
if mode == dMR122 {
|
||||
var lspMid, lspMidQ, lspNewQ [cM]int16
|
||||
azLsp(az[cMP1:], lspMid[:], st.lspOld[:])
|
||||
azLsp(az[3*cMP1:], lspNew[:], lspMid[:])
|
||||
intLpc1and3_2(st.lspOld[:], lspMid[:], lspNew[:], az)
|
||||
|
||||
idx := st.q.qPlsf5(lspMid[:], lspNew[:], lspMidQ[:], lspNewQ[:])
|
||||
intLpc1and3(st.lspOldQ[:], lspMidQ[:], lspNewQ[:], azQ)
|
||||
copy(anap[:5], idx[:])
|
||||
copy(st.lspOldQ[:], lspNewQ[:])
|
||||
copy(st.lspOld[:], lspNew[:])
|
||||
return 5
|
||||
}
|
||||
|
||||
var lspNewQ [cM]int16
|
||||
azLsp(az[3*cMP1:], lspNew[:], st.lspOld[:])
|
||||
intLpc1to3_2(st.lspOld[:], lspNew[:], az)
|
||||
|
||||
idx := st.q.qPlsf3(mode, lspNew[:], lspNewQ[:])
|
||||
intLpc1to3(st.lspOldQ[:], lspNewQ[:], azQ)
|
||||
copy(anap[:3], idx[:])
|
||||
copy(st.lspOldQ[:], lspNewQ[:])
|
||||
copy(st.lspOld[:], lspNew[:])
|
||||
return 3
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package amrnb
|
||||
|
||||
// Open-loop pitch estimation, ported from opencore-amrnb pitch_ol.cpp
|
||||
// (Pitch_ol, Lag_max) and calc_cor.cpp (comp_corr). pitchOl scales the weighted
|
||||
// speech, computes its autocorrelation over the pitch-lag range, finds the best
|
||||
// lag in three sub-ranges, and selects among them favouring longer lags unless
|
||||
// a shorter lag's normalized correlation is significantly stronger. The DTX/VAD
|
||||
// tone-detection side path is omitted (dtx=0). Bit-exact with the reference for
|
||||
// the active-speech path.
|
||||
|
||||
const cTHRESHOLD = 27853 // 0.85, the open-loop lag-selection threshold
|
||||
|
||||
// lagMax finds the lag in [lagMin, lagMax] with maximum correlation and returns
|
||||
// it together with its normalized correlation. Mirrors Lag_max (dtx=0 path).
|
||||
func lagMax(corr []int32, scaled []int16, pitMax int, scalFac, scalFlag int16, Lframe, lagMax, lagMin int) (int16, int16) {
|
||||
max := int32(minInt32)
|
||||
pMax := int16(lagMax)
|
||||
for i := lagMax; i >= lagMin; i-- {
|
||||
if corr[i] >= max {
|
||||
max = corr[i]
|
||||
pMax = int16(i)
|
||||
}
|
||||
}
|
||||
|
||||
var t0 int32
|
||||
base := pitMax - int(pMax)
|
||||
for m := 0; m < Lframe; m++ {
|
||||
v := int32(scaled[base+m])
|
||||
t0 += v * v
|
||||
}
|
||||
t0 <<= 1
|
||||
t0 = invSqrt(t0)
|
||||
if scalFlag != 0 {
|
||||
if t0 > 0x3fffffff {
|
||||
t0 = maxInt32
|
||||
} else {
|
||||
t0 <<= 1
|
||||
}
|
||||
}
|
||||
|
||||
maxH := int16(max >> 16)
|
||||
maxL := int16((max >> 1) - (int32(maxH) << 15))
|
||||
enerH := int16(t0 >> 16)
|
||||
enerL := int16((t0 >> 1) - (int32(enerH) << 15))
|
||||
t0 = mpy_32(maxH, maxL, enerH, enerL)
|
||||
|
||||
var corMax int16
|
||||
if scalFlag != 0 {
|
||||
t0 = L_shr(t0, scalFac)
|
||||
if t0 > 0x0000FFFF {
|
||||
corMax = maxInt16
|
||||
} else if t0 < int32(-0x10000) {
|
||||
corMax = minInt16
|
||||
} else {
|
||||
corMax = int16(t0 >> 1)
|
||||
}
|
||||
} else {
|
||||
corMax = int16(t0)
|
||||
}
|
||||
return pMax, corMax
|
||||
}
|
||||
|
||||
// pitchOl returns the open-loop pitch lag for the weighted speech in
|
||||
// signal[off-pitMax : off+Lframe], using the given pitch-lag bounds and frame
|
||||
// length. Mirrors Pitch_ol (dtx=0). signal[off] is the current frame start.
|
||||
func pitchOl(mode int, signal []int16, off, pitMin, pitMax, Lframe int) int16 {
|
||||
// energy of signal[-pitMax .. Lframe-1]
|
||||
var t0 int32
|
||||
overflow := false
|
||||
for i := -pitMax; i < Lframe; i++ {
|
||||
v := int32(signal[off+i])
|
||||
t0 += (v * v) << 1
|
||||
if t0 < 0 {
|
||||
t0 = maxInt32
|
||||
overflow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
n := pitMax + Lframe
|
||||
var scaledArr [cPIT_MAX + cL_FRAME]int16 // stack: max pitMax+Lframe
|
||||
scaled := scaledArr[:n]
|
||||
var scalFac int16
|
||||
switch {
|
||||
case overflow: // t0 == MAX_32
|
||||
for k := 0; k < n; k++ {
|
||||
scaled[k] = int16(int32(signal[off-pitMax+k]) >> 3)
|
||||
}
|
||||
scalFac = 3
|
||||
case t0 < 1048576:
|
||||
for k := 0; k < n; k++ {
|
||||
scaled[k] = int16(int32(signal[off-pitMax+k]) << 3)
|
||||
}
|
||||
scalFac = -3
|
||||
default:
|
||||
for k := 0; k < n; k++ {
|
||||
scaled[k] = signal[off-pitMax+k]
|
||||
}
|
||||
scalFac = 0
|
||||
}
|
||||
|
||||
// correlation by lag: corr[lag] = 2 * sum_m scaled[pitMax+m]·scaled[pitMax+m-lag].
|
||||
// With the fixed window coef = scaled[pitMax:pitMax+Lframe] this is one sliding
|
||||
// FIR over scaled: dst[pitMax-lag] = sum_m scaled[pitMax-lag+m]·coef[m] equals
|
||||
// corr[lag]>>1. firRaw (AVX2) vectorises it; the wrapping int32 accumulation
|
||||
// makes the lane-parallel order bit-identical.
|
||||
var corrArr [cPIT_MAX + 1]int32 // stack
|
||||
corr := corrArr[:pitMax+1]
|
||||
var dstArr [cPIT_MAX + 1]int32
|
||||
dst := dstArr[:pitMax-pitMin+1]
|
||||
firRaw(dst, scaled, scaled[pitMax:pitMax+Lframe])
|
||||
for lag := pitMin; lag <= pitMax; lag++ {
|
||||
corr[lag] = dst[pitMax-lag] << 1
|
||||
}
|
||||
|
||||
scalFlag := int16(0)
|
||||
if mode == dMR122 {
|
||||
scalFlag = 1
|
||||
}
|
||||
|
||||
j := pitMin << 2
|
||||
pMax1, max1 := lagMax(corr, scaled, pitMax, scalFac, scalFlag, Lframe, pitMax, j)
|
||||
i := j - 1
|
||||
j = pitMin << 1
|
||||
pMax2, max2 := lagMax(corr, scaled, pitMax, scalFac, scalFlag, Lframe, i, j)
|
||||
i = j - 1
|
||||
pMax3, max3 := lagMax(corr, scaled, pitMax, scalFac, scalFlag, Lframe, i, pitMin)
|
||||
|
||||
if int16((int32(max1)*cTHRESHOLD)>>15) < max2 {
|
||||
max1 = max2
|
||||
pMax1 = pMax2
|
||||
}
|
||||
if int16((int32(max1)*cTHRESHOLD)>>15) < max3 {
|
||||
pMax1 = pMax3
|
||||
}
|
||||
return pMax1
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package amrnb
|
||||
|
||||
// Weighted open-loop pitch analysis for MR102, ported from opencore-amrnb
|
||||
// p_ol_wgh.cpp (Pitch_ol_wgh, Lag_max) and calc_cor.cpp (comp_corr). Unlike the
|
||||
// plain Pitch_ol used by the other modes, MR102 weights the lag correlations by
|
||||
// a fixed curve and, when the previous frame tracked pitch well, by a window
|
||||
// centred on the running median lag — then adapts whether to keep weighting.
|
||||
// It also emits a per-half-frame gain flag that gates the median update and the
|
||||
// closed-loop lag history. Bit-exact with the reference (non-DTX path).
|
||||
|
||||
// pitchOLWghtState is the weighted open-loop pitch state. Mirrors
|
||||
// pitchOLWghtState.
|
||||
type pitchOLWghtState struct {
|
||||
oldT0Med int16 // running median of past lags
|
||||
adaW int16 // adaptive weighting factor, Q15
|
||||
wghtFlg int16 // whether the median-window weighting is applied
|
||||
}
|
||||
|
||||
func (st *pitchOLWghtState) reset() {
|
||||
st.oldT0Med = 40
|
||||
st.adaW = 0
|
||||
st.wghtFlg = 0
|
||||
}
|
||||
|
||||
// compCorr computes corr[-i] = 2·Σ_j sig[j]·sig[j-i] (wrapping int32) for every
|
||||
// lag i in [pitMin, pitMax], where corr is indexed corr[corrOff-i]. Mirrors
|
||||
// comp_corr.
|
||||
func compCorr(sig []int16, sigOff, Lframe, pitMax, pitMin int, corr []int32, corrOff int) {
|
||||
// corr[corrOff-i] = 2·sum_j sig[sigOff+j]·sig[sigOff+j-i]. With the fixed
|
||||
// window coef = sig[sigOff:sigOff+Lframe] over x = sig[sigOff-pitMax:], this
|
||||
// is one sliding FIR: dst[pitMax-i] = sum_j sig[sigOff-i+j]·coef[j] equals
|
||||
// corr[corrOff-i]>>1. firRaw (AVX2) vectorises it, bit-identically (wrapping).
|
||||
var dstArr [cPIT_MAX + 1]int32
|
||||
dst := dstArr[:pitMax-pitMin+1]
|
||||
firRaw(dst, sig[sigOff-pitMax:], sig[sigOff:sigOff+Lframe])
|
||||
for i := pitMin; i <= pitMax; i++ {
|
||||
corr[corrOff-i] = dst[pitMax-i] << 1
|
||||
}
|
||||
}
|
||||
|
||||
// lagMaxWght finds the best (most weighted-correlated) lag and the open-loop
|
||||
// gain flag. Mirrors the weighted Lag_max (non-DTX path: cor_max is unused).
|
||||
func lagMaxWght(corr []int32, corrOff int, sig []int16, sigOff int, Lframe, lagMax, lagMin, oldLag, wghtFlg int16) (pMax, gainFlg int16) {
|
||||
wwi := 250 // &corrweight[250]
|
||||
wei := 123 + int(lagMax) - int(oldLag) // &corrweight[123 + lag_max - old_lag]
|
||||
max := int32(-2147483648) // MIN_32
|
||||
pMax = lagMax
|
||||
for i := lagMax; i >= lagMin; i-- {
|
||||
hi, lo := L_Extract(corr[corrOff-int(i)])
|
||||
t0 := mpy32_16(hi, lo, corrweight[wwi])
|
||||
wwi--
|
||||
if wghtFlg > 0 {
|
||||
hi, lo = L_Extract(t0)
|
||||
t0 = mpy32_16(hi, lo, corrweight[wei])
|
||||
wei--
|
||||
}
|
||||
if t0 >= max {
|
||||
max = t0
|
||||
pMax = i
|
||||
}
|
||||
}
|
||||
|
||||
var t0, t1 int32
|
||||
for j := 0; j < int(Lframe); j++ {
|
||||
a := sig[sigOff+j]
|
||||
b := sig[sigOff+j-int(pMax)]
|
||||
t0 = L_mac(t0, a, b)
|
||||
t1 = L_mac(t1, b, b)
|
||||
}
|
||||
temp := round_(t1)
|
||||
t1 = L_msu(t0, temp, 13107) // t0 - round(t1)*0.4
|
||||
gainFlg = round_(t1)
|
||||
return
|
||||
}
|
||||
|
||||
// pitchOlWgh runs the MR102 weighted open-loop pitch over one half-frame of the
|
||||
// weighted speech (sig[sigOff-pitMax : sigOff+Lframe]), updating the state and
|
||||
// the gain flag, and maintaining the median-lag history. Mirrors Pitch_ol_wgh
|
||||
// (non-DTX). idx selects the half-frame (0 or 1).
|
||||
func (st *pitchOLWghtState) pitchOlWgh(sig []int16, sigOff, pitMin, pitMax, Lframe int, oldLags, olGainFlg []int16, idx int) int16 {
|
||||
// energy of the (history + frame) signal, to choose the scaling
|
||||
var t0 int32
|
||||
for i := -pitMax; i < Lframe; i++ {
|
||||
t0 = L_mac(t0, sig[sigOff+i], sig[sigOff+i])
|
||||
}
|
||||
|
||||
// scaled signal occupies scal[pitMax-pitMax : pitMax+Lframe]; use a buffer
|
||||
// with the same [pitMax | Lframe] layout as the input.
|
||||
var scalArr [cPIT_MAX + cL_FRAME]int16 // stack
|
||||
scal := scalArr[:pitMax+Lframe]
|
||||
scalOff := pitMax
|
||||
switch {
|
||||
case t0 == maxInt32:
|
||||
for i := -pitMax; i < Lframe; i++ {
|
||||
scal[scalOff+i] = shr(sig[sigOff+i], 3)
|
||||
}
|
||||
case t0 < 1048576:
|
||||
for i := -pitMax; i < Lframe; i++ {
|
||||
scal[scalOff+i] = shl(sig[sigOff+i], 3)
|
||||
}
|
||||
default:
|
||||
for i := -pitMax; i < Lframe; i++ {
|
||||
scal[scalOff+i] = sig[sigOff+i]
|
||||
}
|
||||
}
|
||||
|
||||
var corrArr [cPIT_MAX + 1]int32 // stack
|
||||
corr := corrArr[:pitMax+1]
|
||||
corrOff := pitMax
|
||||
compCorr(scal, scalOff, Lframe, pitMax, pitMin, corr, corrOff)
|
||||
|
||||
pMax1, gainFlg := lagMaxWght(corr, corrOff, scal, scalOff, int16(Lframe),
|
||||
int16(pitMax), int16(pitMin), st.oldT0Med, st.wghtFlg)
|
||||
olGainFlg[idx] = gainFlg
|
||||
|
||||
if olGainFlg[idx] > 0 {
|
||||
for i := 4; i > 0; i-- {
|
||||
oldLags[i] = oldLags[i-1]
|
||||
}
|
||||
oldLags[0] = pMax1
|
||||
st.oldT0Med = gmedN(oldLags, 5)
|
||||
st.adaW = 32767
|
||||
} else {
|
||||
st.oldT0Med = pMax1
|
||||
st.adaW = mult(st.adaW, 29491) // ada_w *= 0.9
|
||||
}
|
||||
|
||||
if st.adaW < 9830 { // < 0.3
|
||||
st.wghtFlg = 0
|
||||
} else {
|
||||
st.wghtFlg = 1
|
||||
}
|
||||
return pMax1
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package amrnb
|
||||
|
||||
// Pitch-lag encoding, ported from opencore-amrnb enc_lag3.cpp and enc_lag6.cpp.
|
||||
// encLag3/encLag6 map a (T0, T0_frac) closed-loop pitch lag to the index the
|
||||
// bitstream carries — the exact inverses of the decoder's decLag3/decLag6.
|
||||
// Bit-exact with the reference.
|
||||
|
||||
// encLag3 encodes a 1/3-resolution pitch lag. deltaFlag==0 selects the 1st/3rd
|
||||
// subframe (absolute); otherwise the lag is coded relative to T0_min (or, with
|
||||
// flag4, the 4-bit scheme). Mirrors Enc_lag3.
|
||||
func encLag3(T0, T0frac, T0prev, T0min, T0max, deltaFlag, flag4 int16) int16 {
|
||||
var index, i, tmpInd, uplag, tmpLag, temp1, temp2 int16
|
||||
|
||||
if deltaFlag == 0 { // 1st or 3rd subframe
|
||||
if T0-85 <= 0 {
|
||||
index = (T0 << 1) + T0 - 58 + T0frac
|
||||
} else {
|
||||
index = T0 + 112
|
||||
}
|
||||
} else { // 2nd or 4th subframe
|
||||
if flag4 == 0 {
|
||||
i = T0 - T0min
|
||||
index = i + (i << 1) + 2 + T0frac
|
||||
} else {
|
||||
tmpLag = T0prev
|
||||
temp1 = tmpLag - T0min
|
||||
temp2 = temp1 - 5
|
||||
if temp2 > 0 {
|
||||
tmpLag = T0min + 5
|
||||
}
|
||||
temp1 = T0max - tmpLag
|
||||
temp2 = temp1 - 4
|
||||
if temp2 > 0 {
|
||||
tmpLag = T0max - 4
|
||||
}
|
||||
uplag = T0 + (T0 << 1) + T0frac
|
||||
i = tmpLag - 2
|
||||
tmpInd = i + (i << 1)
|
||||
temp1 = tmpInd - uplag
|
||||
if temp1 >= 0 {
|
||||
index = T0 - tmpLag + 5
|
||||
} else {
|
||||
i = tmpLag + 1
|
||||
i += i << 1
|
||||
if i > uplag {
|
||||
index = uplag - tmpInd + 3
|
||||
} else {
|
||||
index = T0 - tmpLag + 11
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// gPitch computes the optimal adaptive-codebook (pitch) gain in Q14, the ratio
|
||||
// <xn,y1>/<y1,y1> saturated to 1.2, where xn is the pitch target and y1 the
|
||||
// filtered adaptive excitation. It also returns the normalized correlations in
|
||||
// gCoeff for later gain quantization. Mirrors G_pitch (g_pitch.cpp).
|
||||
func gPitch(mode int, xn, y1, gCoeff []int16, Lsubfr int) int16 {
|
||||
// <y1,y1>
|
||||
var s int32
|
||||
for i := 0; i < Lsubfr; i++ {
|
||||
s += int32(y1[i]) * int32(y1[i])
|
||||
}
|
||||
var yy, expYy int16
|
||||
if s >= 0 && s < 0x40000000 {
|
||||
s = (s << 1) + 1
|
||||
expYy = norm_l(s)
|
||||
yy = round_(s << uint(expYy))
|
||||
} else {
|
||||
s = 0
|
||||
for i := 0; i < Lsubfr; i++ {
|
||||
tmp := y1[i] >> 2
|
||||
s += int32(tmp) * int32(tmp)
|
||||
}
|
||||
s = (s << 1) + 1
|
||||
expYy = norm_l(s)
|
||||
yy = round_(s << uint(expYy))
|
||||
expYy -= 4
|
||||
}
|
||||
|
||||
// <xn,y1> with overflow detection
|
||||
s = 0
|
||||
overflow := false
|
||||
for i := 0; i < Lsubfr; i++ {
|
||||
L := int32(xn[i]) * int32(y1[i])
|
||||
s1 := s
|
||||
s = s1 + L
|
||||
if (s1^L) > 0 && (s1^s) < 0 {
|
||||
overflow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
var xy, expXy int16
|
||||
if !overflow {
|
||||
s = (s << 1) + 1
|
||||
expXy = norm_l(s)
|
||||
xy = round_(s << uint(expXy))
|
||||
} else {
|
||||
s = 0
|
||||
for i := 0; i < Lsubfr; i++ {
|
||||
L := int32(y1[i] >> 2)
|
||||
s += int32(xn[i]) * L
|
||||
}
|
||||
s = (s << 1) + 1
|
||||
expXy = norm_l(s)
|
||||
xy = round_(s << uint(expXy))
|
||||
expXy -= 4
|
||||
}
|
||||
|
||||
gCoeff[0] = yy
|
||||
gCoeff[1] = 15 - expYy
|
||||
gCoeff[2] = xy
|
||||
gCoeff[3] = 15 - expXy
|
||||
|
||||
if xy < 4 {
|
||||
return 0
|
||||
}
|
||||
xy >>= 1
|
||||
gain := div_s(xy, yy)
|
||||
gain = shr(gain, expXy-expYy)
|
||||
if gain > 19661 {
|
||||
gain = 19661
|
||||
}
|
||||
if mode == dMR122 {
|
||||
gain &^= 3
|
||||
}
|
||||
return gain
|
||||
}
|
||||
|
||||
// encLag6 encodes a 1/6-resolution pitch lag (MR122). deltaFlag==0 selects the
|
||||
// 1st/3rd subframe. Mirrors Enc_lag6.
|
||||
func encLag6(T0, T0frac, T0min, deltaFlag int16) int16 {
|
||||
var index, i int16
|
||||
if deltaFlag == 0 {
|
||||
if T0 <= 94 {
|
||||
i = (T0 << 3) - (T0 << 1) - 105
|
||||
index = i + T0frac
|
||||
} else {
|
||||
index = T0 + 368
|
||||
}
|
||||
} else {
|
||||
temp := T0 - T0min
|
||||
i = (temp << 3) - (temp << 1) + 3
|
||||
index = i + T0frac
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// gCode computes the optimum innovation (fixed-codebook) gain
|
||||
// <xn2,y2>/<y2,y2> in Q1, used by the MR122 gain quantizer. Mirrors G_code.
|
||||
func gCode(xn2, y2 []int16) int16 {
|
||||
var s int32
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
s += int32(xn2[i]) * int32(y2[i]>>1)
|
||||
}
|
||||
s <<= 1
|
||||
expXy := norm_l(s + 1) // avoid the all-zeros case
|
||||
var xy int16
|
||||
if expXy < 17 {
|
||||
xy = int16(s >> uint(17-expXy))
|
||||
} else {
|
||||
xy = int16(s << uint(expXy-17))
|
||||
}
|
||||
if xy <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
s = 0
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
temp := y2[i] >> 1
|
||||
s += (int32(temp) * int32(temp)) >> 2
|
||||
}
|
||||
s <<= 3
|
||||
expYy := norm_l(s)
|
||||
var yy int16
|
||||
if expYy < 16 {
|
||||
yy = int16(s >> uint(16-expYy))
|
||||
} else {
|
||||
yy = int16(s << uint(expYy-16))
|
||||
}
|
||||
|
||||
gain := div_s(xy, yy)
|
||||
i := expXy + 5 - expYy // 15-1+9-18 = 5
|
||||
if i > 1 {
|
||||
gain >>= uint(i - 1)
|
||||
} else {
|
||||
gain <<= uint(1 - i)
|
||||
}
|
||||
return gain
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package amrnb
|
||||
|
||||
// Per-subframe target-signal and impulse-response setup, ported from
|
||||
// opencore-amrnb spreproc.cpp (subframePreProc). For each subframe it builds the
|
||||
// perceptually weighted LP filters A(z/γ1)/A(z/γ2), the weighted synthesis
|
||||
// impulse response h1, the LP residual (exc/res2), and the target signal xn for
|
||||
// the pitch search — feeding the closed-loop pitch and codebook searches. Uses
|
||||
// weightAi/synFilt/residu (all already ported). Bit-exact with the reference.
|
||||
|
||||
// perceptual-weighting spectral expansion factors (cod_amr.cpp).
|
||||
var gamma1 = [cM]int16{30802, 28954, 27217, 25584, 24049, 22606, 21250, 19975, 18777, 17650}
|
||||
var gamma1_12k2 = [cM]int16{29491, 26542, 23888, 21499, 19349, 17414, 15672, 14105, 12694, 11425}
|
||||
var gamma2 = [cM]int16{19661, 11797, 7078, 4247, 2548, 1529, 917, 550, 330, 198}
|
||||
|
||||
// subframePreProc computes, for one subframe:
|
||||
// - h1[0:L_SUBFR] the weighted synthesis impulse response,
|
||||
// - exc/res2 the LP residual,
|
||||
// - error the synthesis error,
|
||||
// - xn[0:L_SUBFR] the pitch-search target.
|
||||
//
|
||||
// a/aq are the unquantized/quantized LP coefficients for this subframe (M+1
|
||||
// each). speech is positioned so speech[speechOff-M : speechOff+L_SUBFR] is the
|
||||
// current subframe with history. memErr and memW0 are the synthesis-error and
|
||||
// weighting-filter memories (M each), read but not updated here. Mirrors
|
||||
// subframePreProc.
|
||||
func subframePreProc(mode int, a, aq, speech []int16, speechOff int, memErr, memW0 []int16, exc, h1, xn, res2, errSig []int16) {
|
||||
var ap1, ap2 [cMP1]int16
|
||||
g1 := gamma1[:]
|
||||
if mode == dMR122 || mode == dMR102 {
|
||||
g1 = gamma1_12k2[:]
|
||||
}
|
||||
weightAi(a, g1, ap1[:])
|
||||
weightAi(a, gamma2[:], ap2[:])
|
||||
|
||||
// h1 = impulse response of A(z/γ1) / (Aq(z)·A(z/γ2))
|
||||
var aiZero [cL_SUBFR]int16
|
||||
copy(aiZero[:cMP1], ap1[:cMP1]) // Ap1 then zeros
|
||||
zero := make([]int16, cM)
|
||||
synFilt(aq, aiZero[:], h1, cL_SUBFR, zero, false)
|
||||
for i := range zero {
|
||||
zero[i] = 0
|
||||
}
|
||||
synFilt(ap2[:], h1, h1, cL_SUBFR, zero, false)
|
||||
|
||||
// LP residual and synthesis error
|
||||
residu(aq, speech, speechOff, cL_SUBFR, res2)
|
||||
copy(exc[:cL_SUBFR], res2[:cL_SUBFR])
|
||||
|
||||
// error = synthesis of the residual (with error memory)
|
||||
memErrCopy := make([]int16, cM)
|
||||
copy(memErrCopy, memErr[:cM])
|
||||
synFilt(aq, exc, errSig, cL_SUBFR, memErrCopy, false)
|
||||
|
||||
// xn = weighted error (target for pitch search)
|
||||
// Residu(Ap1, error): error needs M history -> supplied as memErr before it.
|
||||
var eb [cM + cL_SUBFR]int16
|
||||
copy(eb[:cM], memErr[:cM])
|
||||
copy(eb[cM:], errSig[:cL_SUBFR])
|
||||
residu(ap1[:], eb[:], cM, cL_SUBFR, xn)
|
||||
|
||||
memW0Copy := make([]int16, cM)
|
||||
copy(memW0Copy, memW0[:cM])
|
||||
synFilt(ap2[:], xn, xn, cL_SUBFR, memW0Copy, false)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package amrnb
|
||||
|
||||
// Gain quantization (general modes), ported from opencore-amrnb qua_gain.cpp
|
||||
// (Qua_gain) and calc_en.cpp (calc_filt_energies). calcFiltEnergies forms the
|
||||
// five quadratic-form energy coefficients of the gain error from the target,
|
||||
// the adaptive codebook y1 and the filtered innovation y2; quaGain searches the
|
||||
// gain VQ table for the (pitch, code) gain pair minimizing that error, returning
|
||||
// the index and the dequantized gains — the exact encoder counterpart of
|
||||
// decGain. Bit-exact with the reference.
|
||||
|
||||
const (
|
||||
cVQ_SIZE_HIGHRATES = 128
|
||||
cVQ_SIZE_LOWRATES = 64
|
||||
)
|
||||
|
||||
// calcFiltEnergies computes the five energy coefficients (frac/exp) of the gain
|
||||
// MSE. gCoeff[0..3] are the <xn,y1>/<y1,y1> correlations from gPitch. For
|
||||
// MR795/MR475 it also returns the optimum unconstrained code gain. Mirrors
|
||||
// calc_filt_energies.
|
||||
func calcFiltEnergies(mode int, xn, xn2, y1, y2, gCoeff []int16) (fracCoeff, expCoeff [5]int16, codGainFrac, codGainExp int16) {
|
||||
fracCoeff[0] = gCoeff[0]
|
||||
expCoeff[0] = gCoeff[1]
|
||||
fracCoeff[1] = negate(gCoeff[2])
|
||||
expCoeff[1] = gCoeff[3] + 1
|
||||
|
||||
var s1, s2, s3 int32
|
||||
if mode != dMR795 && mode != dMR475 {
|
||||
s1, s2, s3 = 1, 1, 1
|
||||
}
|
||||
var scaledY2 [cL_SUBFR]int16
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
tmp := y2[i] >> 3
|
||||
scaledY2[i] = tmp
|
||||
s1 = L_mac(s1, tmp, tmp)
|
||||
s2 = L_mac(s2, xn[i], tmp)
|
||||
s3 = L_mac(s3, y1[i], tmp)
|
||||
}
|
||||
exp := norm_l(s1)
|
||||
fracCoeff[2] = int16(L_shl(s1, exp) >> 16)
|
||||
expCoeff[2] = -3 - exp
|
||||
exp = norm_l(s2)
|
||||
fracCoeff[3] = negate(int16(L_shl(s2, exp) >> 16))
|
||||
expCoeff[3] = 7 - exp
|
||||
exp = norm_l(s3)
|
||||
fracCoeff[4] = int16(L_shl(s3, exp) >> 16)
|
||||
expCoeff[4] = 7 - exp
|
||||
|
||||
if mode == dMR795 || mode == dMR475 {
|
||||
s1 = 0
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
s1 += int32(xn2[i]) * int32(scaledY2[i])
|
||||
}
|
||||
s1 <<= 1
|
||||
exp = norm_l(s1)
|
||||
frac := int16(L_shl(s1, exp) >> 16)
|
||||
exp = 6 - exp
|
||||
if frac <= 0 {
|
||||
codGainFrac = 0
|
||||
codGainExp = 0
|
||||
} else {
|
||||
codGainFrac = div_s(shr(frac, 1), fracCoeff[2])
|
||||
codGainExp = (exp - expCoeff[2]) - 14
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// quaGain searches the mode's gain VQ table for the (pitch, code) gain pair
|
||||
// minimizing the weighted gain MSE, returning the index and dequantized gains
|
||||
// plus the predictor-update energies. Mirrors Qua_gain.
|
||||
func quaGain(mode int, expGcode0, fracGcode0 int16, fracCoeff, expCoeff []int16, gpLimit int16) (index, gainPit, gainCod, quaEnerMR122, quaEner int16) {
|
||||
var tableGain []int16
|
||||
var tableLen int
|
||||
if mode == dMR102 || mode == dMR74 || mode == dMR67 {
|
||||
tableLen = cVQ_SIZE_HIGHRATES
|
||||
tableGain = table_gain_highrates
|
||||
} else {
|
||||
tableLen = cVQ_SIZE_LOWRATES
|
||||
tableGain = table_gain_lowrates
|
||||
}
|
||||
|
||||
gcode0 := int16(pow2(14, fracGcode0))
|
||||
expCode := expGcode0 - 11
|
||||
|
||||
var expMax [5]int16
|
||||
expMax[0] = expCoeff[0] - 13
|
||||
expMax[1] = expCoeff[1] - 14
|
||||
temp := shl(expCode, 1) + 15
|
||||
expMax[2] = add(expCoeff[2], temp)
|
||||
expMax[3] = add(expCoeff[3], expCode)
|
||||
expMax[4] = add(expCoeff[4], expCode+1)
|
||||
|
||||
eMax := expMax[0]
|
||||
for i := 1; i < 5; i++ {
|
||||
if expMax[i] > eMax {
|
||||
eMax = expMax[i]
|
||||
}
|
||||
}
|
||||
eMax++
|
||||
|
||||
var coeff, coeffLo [5]int16
|
||||
for i := 0; i < 5; i++ {
|
||||
j := eMax - expMax[i]
|
||||
coeff[i], coeffLo[i] = L_Extract(L_shr(int32(fracCoeff[i])<<16, j))
|
||||
}
|
||||
|
||||
distMin := int32(maxInt32)
|
||||
for i := 0; i < tableLen; i++ {
|
||||
p := i << 2
|
||||
gPitch := tableGain[p]
|
||||
gCode := tableGain[p+1]
|
||||
if gPitch <= gpLimit {
|
||||
gc := mult(gCode, gcode0)
|
||||
g2Pitch := mult(gPitch, gPitch)
|
||||
g2Code := mult(gc, gc)
|
||||
gPitCod := mult(gc, gPitch)
|
||||
L_tmp := mpy32_16(coeff[0], coeffLo[0], g2Pitch)
|
||||
L_tmp = L_add(L_tmp, mpy32_16(coeff[1], coeffLo[1], gPitch))
|
||||
L_tmp = L_add(L_tmp, mpy32_16(coeff[2], coeffLo[2], g2Code))
|
||||
L_tmp = L_add(L_tmp, mpy32_16(coeff[3], coeffLo[3], gc))
|
||||
L_tmp = L_add(L_tmp, mpy32_16(coeff[4], coeffLo[4], gPitCod))
|
||||
if L_tmp < distMin {
|
||||
distMin = L_tmp
|
||||
index = int16(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p := int(index) << 2
|
||||
gainPit = tableGain[p]
|
||||
gCode := tableGain[p+1]
|
||||
quaEnerMR122 = tableGain[p+2]
|
||||
quaEner = tableGain[p+3]
|
||||
|
||||
L_tmp := L_shr(L_mult(gCode, gcode0), 10-expGcode0)
|
||||
gainCod = int16(L_tmp >> 16)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package amrnb
|
||||
|
||||
// MR475 joint two-subframe gain quantization, ported from opencore-amrnb
|
||||
// qgain475.cpp (MR475_gain_quant, MR475_quant_store_results,
|
||||
// MR475_update_unq_pred) and the target-energy helper calc_en.cpp
|
||||
// (calc_target_energy). The 4.75 kbit/s mode quantizes the pitch and code gains
|
||||
// of two consecutive subframes jointly with a single 8-bit index into
|
||||
// table_gain_MR475, using an "unquantized" gain predictor (predUnq) to drive the
|
||||
// search and the real predictor (pred) for the reconstructed gains. Bit-exact
|
||||
// with the reference.
|
||||
|
||||
const (
|
||||
cMIN_QUA_ENER = -5443
|
||||
cMIN_QUA_ENER_MR122 = -32768
|
||||
cMAX_QUA_ENER = 3037
|
||||
cMAX_QUA_ENER_MR122 = 18284
|
||||
)
|
||||
|
||||
// calcTargetEnergy returns the energy of the LTP target xn as a normalized
|
||||
// (exponent, fraction) pair. Mirrors calc_target_energy.
|
||||
func calcTargetEnergy(xn []int16) (enExp, enFrac int16) {
|
||||
var s int32
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
s += int32(xn[i]) * int32(xn[i]) // 32-bit MAC, wraps like the reference
|
||||
}
|
||||
if s < 0 { // overflow wrapped negative
|
||||
s = maxInt32
|
||||
}
|
||||
exp := norm_l(s)
|
||||
enFrac = int16(L_shl(s, exp) >> 16)
|
||||
enExp = 16 - exp
|
||||
return
|
||||
}
|
||||
|
||||
// mr475StoreResults dequantizes one subframe's gains from a table entry p
|
||||
// (p[0]=gain_pit, p[1]=gain_code) given the predicted code gain gcode0, and
|
||||
// advances the predictor with the resulting quantized energy error. Mirrors
|
||||
// MR475_quant_store_results.
|
||||
func (pred *gcPredState) mr475StoreResults(p []int16, gcode0, expGcode0 int16) (gainPit, gainCod int16) {
|
||||
gainPit = p[0]
|
||||
gCode := p[1]
|
||||
|
||||
L := (int32(gCode) * int32(gcode0)) << 1
|
||||
L = L_shr(L, 10-expGcode0)
|
||||
gainCod = int16(L >> 16)
|
||||
|
||||
exp, frac := log2(int32(gCode))
|
||||
exp -= 12
|
||||
tmp := shr_r(frac, 5)
|
||||
quaEnerMR122 := (exp << 10) + tmp
|
||||
L2 := mpy32_16(exp, frac, 24660) << 13
|
||||
quaEner := int16((L2 + 0x00008000) >> 16)
|
||||
|
||||
pred.update(quaEnerMR122, quaEner)
|
||||
return
|
||||
}
|
||||
|
||||
// mr475UpdateUnqPred advances the "unquantized" predictor with the optimum
|
||||
// (unquantized) code gain of a subframe. Mirrors MR475_update_unq_pred.
|
||||
func (pred *gcPredState) mr475UpdateUnqPred(expGcode0, fracGcode0, codGainExp, codGainFrac int16) {
|
||||
var quaEner, quaEnerMR122 int16
|
||||
if codGainFrac <= 0 {
|
||||
quaEner = cMIN_QUA_ENER
|
||||
quaEnerMR122 = cMIN_QUA_ENER_MR122
|
||||
} else {
|
||||
fg := int16(pow2(14, fracGcode0))
|
||||
if codGainFrac >= fg {
|
||||
codGainFrac >>= 1
|
||||
codGainExp++
|
||||
}
|
||||
frac := div_s(codGainFrac, fg)
|
||||
tmp := codGainExp - expGcode0 - 1
|
||||
exp, fr := log2(int32(frac))
|
||||
exp += tmp
|
||||
quaEnerMR122 = shr_r(fr, 5) + (exp << 10)
|
||||
if quaEnerMR122 > cMAX_QUA_ENER_MR122 {
|
||||
quaEner = cMAX_QUA_ENER
|
||||
quaEnerMR122 = cMAX_QUA_ENER_MR122
|
||||
} else {
|
||||
L := L_shl(mpy32_16(exp, fr, 24660), 13)
|
||||
quaEner = round_(L)
|
||||
}
|
||||
}
|
||||
pred.update(quaEnerMR122, quaEner)
|
||||
}
|
||||
|
||||
// mr475GainQuant runs the joint pitch+code gain VQ for a subframe pair: it
|
||||
// searches table_gain_MR475 for the entry minimizing the combined weighted
|
||||
// error of both subframes (respecting the pitch-gain limit), stores the
|
||||
// reconstructed gains for both subframes, and returns the table index. The sf0
|
||||
// energy terms were stashed in st by the even-subframe pass; the sf1 terms are
|
||||
// passed in. Mirrors MR475_gain_quant.
|
||||
func (st *coderState) mr475GainQuant(sf1Code []int16, sf1ExpGcode0, sf1FracGcode0 int16,
|
||||
sf1ExpCoeff, sf1FracCoeff []int16, sf1ExpTargetEn, sf1FracTargetEn, gpLimit int16) (index, sf0GainPit, sf0GainCod, sf1GainPit, sf1GainCod int16) {
|
||||
|
||||
sf0Gcode0 := int16(pow2(14, st.sf0FracGcode0))
|
||||
sf1Gcode0 := int16(pow2(14, sf1FracGcode0))
|
||||
|
||||
// per-coefficient exponents (0..4: sf0, 5..9: sf1)
|
||||
var expMax [10]int16
|
||||
exp := st.sf0ExpGcode0 - 11
|
||||
expMax[0] = st.sf0ExpCoeff[0] - 13
|
||||
expMax[1] = st.sf0ExpCoeff[1] - 14
|
||||
expMax[2] = st.sf0ExpCoeff[2] + (15 + (exp << 1))
|
||||
expMax[3] = st.sf0ExpCoeff[3] + exp
|
||||
expMax[4] = st.sf0ExpCoeff[4] + (1 + exp)
|
||||
exp = sf1ExpGcode0 - 11
|
||||
expMax[5] = sf1ExpCoeff[0] - 13
|
||||
expMax[6] = sf1ExpCoeff[1] - 14
|
||||
expMax[7] = sf1ExpCoeff[2] + (15 + (exp << 1))
|
||||
expMax[8] = sf1ExpCoeff[3] + exp
|
||||
expMax[9] = sf1ExpCoeff[4] + (1 + exp)
|
||||
|
||||
// scale the two target energies to a common exponent, then nudge the sf0
|
||||
// coefficient exponents so the two subframes' errors are comparable.
|
||||
sf0FracTargetEn := st.sf0FracTargetEn
|
||||
if e := st.sf0ExpTargetEn - sf1ExpTargetEn; e > 0 {
|
||||
sf1FracTargetEn >>= uint(e)
|
||||
} else {
|
||||
sf0FracTargetEn >>= uint(-e)
|
||||
}
|
||||
adj := int16(0)
|
||||
if tmp := shr_r(sf1FracTargetEn, 1); tmp > sf0FracTargetEn {
|
||||
adj = 1
|
||||
} else if (sf0FracTargetEn+3)>>2 > sf1FracTargetEn {
|
||||
adj = -1
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
expMax[i] += adj
|
||||
}
|
||||
|
||||
// common exponent for all 10 coefficients
|
||||
mx := expMax[0]
|
||||
for i := 9; i > 0; i-- {
|
||||
if expMax[i] > mx {
|
||||
mx = expMax[i]
|
||||
}
|
||||
}
|
||||
mx++ // avoid overflow
|
||||
|
||||
var coeff, coeffLo [10]int16
|
||||
for i := 0; i < 10; i++ {
|
||||
var fc int16
|
||||
if i < 5 {
|
||||
fc = st.sf0FracCoeff[i]
|
||||
} else {
|
||||
fc = sf1FracCoeff[i-5]
|
||||
}
|
||||
L := L_shr(int32(fc)<<16, mx-expMax[i])
|
||||
coeff[i] = int16(L >> 16)
|
||||
coeffLo[i] = int16((L >> 1) - ((L >> 16) << 15))
|
||||
}
|
||||
|
||||
// exhaustive search over the 256-entry joint codebook
|
||||
distMin := int32(maxInt32)
|
||||
index = 0
|
||||
for i := 0; i < cMR475_VQ_SIZE; i++ {
|
||||
b := i * 4
|
||||
gPitch := table_gain_MR475[b]
|
||||
gCode := int16((int32(table_gain_MR475[b+1]) * int32(sf0Gcode0)) >> 15)
|
||||
g2Pitch := int16((int32(gPitch) * int32(gPitch)) >> 15)
|
||||
g2Code := int16((int32(gCode) * int32(gCode)) >> 15)
|
||||
gPitCod := int16((int32(gCode) * int32(gPitch)) >> 15)
|
||||
L := mpy32_16(coeff[0], coeffLo[0], g2Pitch) +
|
||||
mpy32_16(coeff[1], coeffLo[1], gPitch) +
|
||||
mpy32_16(coeff[2], coeffLo[2], g2Code) +
|
||||
mpy32_16(coeff[3], coeffLo[3], gCode) +
|
||||
mpy32_16(coeff[4], coeffLo[4], gPitCod)
|
||||
|
||||
tmp := gPitch - gpLimit
|
||||
gPitch1 := table_gain_MR475[b+2]
|
||||
if tmp <= 0 && gPitch1 <= gpLimit {
|
||||
gCode1 := int16((int32(table_gain_MR475[b+3]) * int32(sf1Gcode0)) >> 15)
|
||||
g2Pitch = int16((int32(gPitch1) * int32(gPitch1)) >> 15)
|
||||
g2Code = int16((int32(gCode1) * int32(gCode1)) >> 15)
|
||||
gPitCod = int16((int32(gCode1) * int32(gPitch1)) >> 15)
|
||||
L += mpy32_16(coeff[5], coeffLo[5], g2Pitch) +
|
||||
mpy32_16(coeff[6], coeffLo[6], gPitch1) +
|
||||
mpy32_16(coeff[7], coeffLo[7], g2Code) +
|
||||
mpy32_16(coeff[8], coeffLo[8], gCode1) +
|
||||
mpy32_16(coeff[9], coeffLo[9], gPitCod)
|
||||
if L < distMin {
|
||||
distMin = L
|
||||
index = int16(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reconstruct sf0 gains (updates the real predictor), re-predict, then sf1
|
||||
base := int(index) << 2
|
||||
sf0GainPit, sf0GainCod = st.pred.mr475StoreResults(table_gain_MR475[base:], sf0Gcode0, st.sf0ExpGcode0)
|
||||
gp := st.pred.gcPred(dMR475, sf1Code)
|
||||
sf1Gcode0 = int16(pow2(14, gp.fracGcode0))
|
||||
sf1GainPit, sf1GainCod = st.pred.mr475StoreResults(table_gain_MR475[base+2:], sf1Gcode0, gp.expGcode0)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package amrnb
|
||||
|
||||
// MR795 gain quantization, ported from opencore-amrnb qgain795.cpp
|
||||
// (MR795_gain_quant, MR795_gain_code_quant3, MR795_gain_code_quant_mod), the
|
||||
// gain adaptor g_adapt.cpp (gain_adapt) + gmed_n.cpp (gmed_n), and the unfiltered
|
||||
// energies calc_en.cpp (calc_unfilt_energies). MR795 jointly searches the three
|
||||
// nearest pitch-gain candidates against the code-gain VQ, then — when the LTP is
|
||||
// strong enough (alpha>0) — re-quantizes the code gain with a perceptually
|
||||
// adapted criterion. It emits two indices per subframe. Bit-exact with the
|
||||
// reference.
|
||||
|
||||
const (
|
||||
cLTP_GAIN_THR1 = 2721 // Q13
|
||||
cLTP_GAIN_THR2 = 5443 // Q13
|
||||
cLTPG_MEM_SIZE = 5
|
||||
)
|
||||
|
||||
// gmedN returns the median of the first n values of ind, matching the
|
||||
// index-based tie-breaking of the reference. Mirrors gmed_n.
|
||||
func gmedN(ind []int16, n int) int16 {
|
||||
tmp2 := make([]int16, n)
|
||||
copy(tmp2, ind[:n])
|
||||
tmp := make([]int16, n)
|
||||
ix := 0
|
||||
for i := 0; i < n; i++ {
|
||||
max := int16(-32767)
|
||||
for j := 0; j < n; j++ {
|
||||
if tmp2[j] >= max {
|
||||
max = tmp2[j]
|
||||
ix = j
|
||||
}
|
||||
}
|
||||
tmp2[ix] = -32768
|
||||
tmp[i] = int16(ix)
|
||||
}
|
||||
return ind[tmp[n>>1]]
|
||||
}
|
||||
|
||||
// gainAdaptState is the gain adaptor's persistent state. Mirrors GainAdaptState.
|
||||
type gainAdaptState struct {
|
||||
onset int16
|
||||
prevAlpha int16
|
||||
prevGc int16
|
||||
ltpgMem [cLTPG_MEM_SIZE]int16
|
||||
}
|
||||
|
||||
// gainAdapt computes the gain adaptation factor alpha (Q15) from the LTP coding
|
||||
// gain ltpg and the code gain, updating the onset/history state. Mirrors
|
||||
// gain_adapt.
|
||||
func (st *gainAdaptState) gainAdapt(ltpg, gainCod int16) int16 {
|
||||
var adapt int16
|
||||
switch {
|
||||
case ltpg <= cLTP_GAIN_THR1:
|
||||
adapt = 0
|
||||
case ltpg <= cLTP_GAIN_THR2:
|
||||
adapt = 1
|
||||
default:
|
||||
adapt = 2
|
||||
}
|
||||
|
||||
if tmp := shr_r(gainCod, 1); tmp > st.prevGc && gainCod > 200 {
|
||||
st.onset = 8
|
||||
} else if st.onset != 0 {
|
||||
st.onset--
|
||||
}
|
||||
if st.onset != 0 && adapt < 2 {
|
||||
adapt++
|
||||
}
|
||||
|
||||
st.ltpgMem[0] = ltpg
|
||||
filt := gmedN(st.ltpgMem[:], 5)
|
||||
var result int16
|
||||
if adapt == 0 {
|
||||
switch {
|
||||
case filt > 5443:
|
||||
result = 0
|
||||
case filt < 0:
|
||||
result = 16384
|
||||
default:
|
||||
filt = shl(filt, 2)
|
||||
result = 16384 - mult(24660, filt)
|
||||
}
|
||||
}
|
||||
if st.prevAlpha == 0 {
|
||||
result = shr(result, 1)
|
||||
}
|
||||
|
||||
st.prevAlpha = result
|
||||
st.prevGc = gainCod
|
||||
for i := cLTPG_MEM_SIZE - 1; i > 0; i-- {
|
||||
st.ltpgMem[i] = st.ltpgMem[i-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// calcUnfiltEnergies computes the unfiltered residual/excitation/cross/LTP-error
|
||||
// energies (as 4 normalized exp/frac pairs) and the LTP coding gain ltpg.
|
||||
// Mirrors calc_unfilt_energies.
|
||||
func calcUnfiltEnergies(res, exc, code []int16, gainPit int16, Lsubfr int) (fracEn, expEn [4]int16, ltpg int16) {
|
||||
var s1, s2, s3, s4 int32
|
||||
for i := 0; i < Lsubfr; i++ {
|
||||
tmp1 := res[i]
|
||||
tmp2 := exc[i]
|
||||
s1 += int32(tmp1) * int32(tmp1) // wrapping MAC, like the reference
|
||||
s2 += int32(tmp2) * int32(tmp2)
|
||||
s3 += int32(tmp2) * int32(code[i])
|
||||
Ltemp := L_shl(L_mult(tmp2, gainPit), 1)
|
||||
d := sub(tmp1, round_(Ltemp))
|
||||
s4 = L_mac(s4, d, d)
|
||||
}
|
||||
s1 <<= 1
|
||||
s2 <<= 1
|
||||
s3 <<= 1
|
||||
|
||||
if s1 < 0 { // overflow (sign bit set)
|
||||
s1 = maxInt32
|
||||
}
|
||||
if s1 < 400 {
|
||||
fracEn[0] = 0
|
||||
expEn[0] = -15
|
||||
} else {
|
||||
exp := norm_l(s1)
|
||||
fracEn[0] = int16(L_shl(s1, exp) >> 16)
|
||||
expEn[0] = 15 - exp
|
||||
}
|
||||
|
||||
if s2 < 0 {
|
||||
s2 = maxInt32
|
||||
}
|
||||
exp := norm_l(s2)
|
||||
fracEn[1] = int16(L_shl(s2, exp) >> 16)
|
||||
expEn[1] = 15 - exp
|
||||
|
||||
exp = norm_l(s3)
|
||||
fracEn[2] = int16(L_shl(s3, exp) >> 16)
|
||||
expEn[2] = 2 - exp
|
||||
|
||||
exp = norm_l(s4)
|
||||
ltpResEn := int16(L_shl(s4, exp) >> 16)
|
||||
expS4 := 15 - exp
|
||||
fracEn[3] = ltpResEn
|
||||
expEn[3] = expS4
|
||||
|
||||
if ltpResEn > 0 && fracEn[0] != 0 {
|
||||
predGain := div_s(shr(fracEn[0], 1), ltpResEn)
|
||||
e := sub(expS4, expEn[0])
|
||||
Ltemp := L_shr(int32(predGain)<<16, e+3)
|
||||
ltpgExp, ltpgFrac := log2(Ltemp)
|
||||
Ltemp = L_Comp(ltpgExp-27, ltpgFrac)
|
||||
ltpg = round_(L_shl(Ltemp, 13)) // Q13
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// mr795GainCodeQuant3 jointly searches the three pitch-gain candidates against
|
||||
// the code-gain VQ for the minimum weighted error, returning the selected pitch
|
||||
// and code gains, their indices, and the predictor-update energies. Mirrors
|
||||
// MR795_gain_code_quant3.
|
||||
func mr795GainCodeQuant3(expGcode0, gcode0 int16, gpCand, gpCind, fracCoeff, expCoeff []int16) (gainPit, gainPitInd, gainCod, gainCodInd, quaEnerMR122, quaEner int16) {
|
||||
var coeff, coeffLo, expMax [5]int16
|
||||
expCode := expGcode0 - 10
|
||||
expMax[0] = expCoeff[0] - 13
|
||||
expMax[1] = expCoeff[1] - 14
|
||||
expMax[2] = expCoeff[2] + shl(expCode, 1) + 15
|
||||
expMax[3] = expCoeff[3] + expCode
|
||||
expMax[4] = expCoeff[4] + (expCode + 1)
|
||||
|
||||
eMax := expMax[0]
|
||||
for i := 1; i < 5; i++ {
|
||||
if expMax[i] > eMax {
|
||||
eMax = expMax[i]
|
||||
}
|
||||
}
|
||||
eMax = add(eMax, 1)
|
||||
for i := 0; i < 5; i++ {
|
||||
coeff[i], coeffLo[i] = L_Extract(L_shr(int32(fracCoeff[i])<<16, eMax-expMax[i]))
|
||||
}
|
||||
|
||||
distMin := int32(maxInt32)
|
||||
var codInd, pitInd int16
|
||||
for j := 0; j < 3; j++ {
|
||||
gPitch := gpCand[j]
|
||||
g2Pitch := mult(gPitch, gPitch)
|
||||
Ltmp0 := mpy32_16(coeff[0], coeffLo[0], g2Pitch)
|
||||
Ltmp0 = L_add(Ltmp0, mpy32_16(coeff[1], coeffLo[1], gPitch))
|
||||
for i := 0; i < cNB_QUA_CODE; i++ {
|
||||
gCode := mult(qua_gain_code[i*3], gcode0)
|
||||
g2h, g2l := L_Extract(L_mult(gCode, gCode))
|
||||
gpch, gpcl := L_Extract(L_mult(gCode, gPitch))
|
||||
Ltmp := L_add(Ltmp0, mpy_32(coeff[2], coeffLo[2], g2h, g2l))
|
||||
Ltmp = L_add(Ltmp, mpy32_16(coeff[3], coeffLo[3], gCode))
|
||||
Ltmp = L_add(Ltmp, mpy_32(coeff[4], coeffLo[4], gpch, gpcl))
|
||||
if Ltmp < distMin {
|
||||
distMin = Ltmp
|
||||
codInd = int16(i)
|
||||
pitInd = int16(j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base := int(codInd) * 3
|
||||
gCode := qua_gain_code[base]
|
||||
quaEnerMR122 = qua_gain_code[base+1]
|
||||
quaEner = qua_gain_code[base+2]
|
||||
gainCod = int16(L_shr(L_mult(gCode, gcode0), 9-expGcode0) >> 16)
|
||||
gainCodInd = codInd
|
||||
gainPit = gpCand[pitInd]
|
||||
gainPitInd = gpCind[pitInd]
|
||||
return
|
||||
}
|
||||
|
||||
// mr795GainCodeQuantMod re-quantizes the code gain with the gain-adapted
|
||||
// criterion once the LTP is strong (alpha>0), returning the new index and
|
||||
// predictor energies and updating gainCod in place. Mirrors
|
||||
// MR795_gain_code_quant_mod.
|
||||
func mr795GainCodeQuantMod(gainPit, expGcode0, gcode0 int16, fracEn, expEn []int16, alpha, gainCodUnq int16, gainCod *int16) (index, quaEnerMR122, quaEner int16) {
|
||||
var coeff, coeffLo, expCoeff [5]int16
|
||||
gainCode := shl(*gainCod, 10-expGcode0) // Q1 -> Q11(-ec0)
|
||||
g2Pitch := mult(gainPit, gainPit)
|
||||
oneAlpha := add(32767-alpha, 1) // 32768 - alpha
|
||||
|
||||
Lt1 := L_shl(L_mult(alpha, fracEn[1]), 1)
|
||||
Lt1 = L_mult(int16(Lt1>>16), g2Pitch)
|
||||
expCoeff[1] = expEn[1] - 15
|
||||
|
||||
coeff[2] = mult(int16(L_shl(L_mult(alpha, fracEn[2]), 1)>>16), gainPit)
|
||||
expCoeff[2] = add(expEn[2], expGcode0-10)
|
||||
|
||||
coeff[3] = int16(L_shl(L_mult(alpha, fracEn[3]), 1) >> 16)
|
||||
expCoeff[3] = add(expEn[3], shl(expGcode0, 1)-7)
|
||||
|
||||
coeff[4] = mult(oneAlpha, fracEn[3])
|
||||
expCoeff[4] = add(expCoeff[3], 1)
|
||||
|
||||
Lt0, sqExp := sqrtLExp(L_mult(alpha, fracEn[0]))
|
||||
sqExp += 47
|
||||
expCoeff[0] = expEn[0] - sqExp
|
||||
|
||||
eMax := expCoeff[0] + 31
|
||||
for i := 1; i <= 4; i++ {
|
||||
if expCoeff[i] > eMax {
|
||||
eMax = expCoeff[i]
|
||||
}
|
||||
}
|
||||
Lt1 = L_shr(Lt1, eMax-expCoeff[1])
|
||||
for i := 2; i <= 4; i++ {
|
||||
coeff[i], coeffLo[i] = L_Extract(L_shr(int32(coeff[i])<<16, eMax-expCoeff[i]))
|
||||
}
|
||||
newExp := eMax - 31
|
||||
tmp := newExp - expCoeff[0]
|
||||
Lt0 = L_shr(Lt0, shr(tmp, 1))
|
||||
if tmp&0x1 != 0 {
|
||||
coeff[0], coeffLo[0] = L_Extract(Lt0)
|
||||
Lt0 = mpy32_16(coeff[0], coeffLo[0], 23170) // 1/sqrt(2) Q15
|
||||
}
|
||||
|
||||
distMin := int32(maxInt32)
|
||||
index = 0
|
||||
for i := 0; i < cNB_QUA_CODE; i++ {
|
||||
gCode := mult(qua_gain_code[i*3], gcode0)
|
||||
if gCode >= gainCode {
|
||||
break
|
||||
}
|
||||
g2h, g2l := L_Extract(L_mult(gCode, gCode))
|
||||
d := sub(gCode, gainCodUnq)
|
||||
d2h, d2l := L_Extract(L_mult(d, d))
|
||||
Ltmp := L_add(Lt1, mpy32_16(coeff[2], coeffLo[2], gCode))
|
||||
Ltmp = L_add(Ltmp, mpy_32(coeff[3], coeffLo[3], g2h, g2l))
|
||||
sq, e := sqrtLExp(Ltmp)
|
||||
sq = L_shr(sq, shr(e, 1))
|
||||
t := round_(L_sub(sq, Lt0))
|
||||
Ltmp = L_mult(t, t)
|
||||
Ltmp = L_add(Ltmp, mpy_32(coeff[4], coeffLo[4], d2h, d2l))
|
||||
if Ltmp < distMin {
|
||||
distMin = Ltmp
|
||||
index = int16(i)
|
||||
}
|
||||
}
|
||||
|
||||
base := int(index) * 3
|
||||
gCode := qua_gain_code[base]
|
||||
quaEnerMR122 = qua_gain_code[base+1]
|
||||
quaEner = qua_gain_code[base+2]
|
||||
*gainCod = int16(L_shr(L_mult(gCode, gcode0), 9-expGcode0) >> 16)
|
||||
return
|
||||
}
|
||||
|
||||
// mr795GainQuant orchestrates the MR795 gain quantization for one subframe,
|
||||
// updating *gainPit and returning the quantized gains, predictor energies and
|
||||
// the two analysis indices. Mirrors MR795_gain_quant.
|
||||
func (st *coderState) mr795GainQuant(res, exc, code, fracCoeff, expCoeff []int16,
|
||||
expCodeEn, fracCodeEn, expGcode0, fracGcode0, codGainFrac, codGainExp, gpLimit int16,
|
||||
gainPit *int16) (gainCod, quaEnerMR122, quaEner, gainPitIndex, gainCodIndex int16) {
|
||||
|
||||
var gpCand, gpCind [3]int16
|
||||
qGainPitch(dMR795, gpLimit, gainPit, gpCand[:], gpCind[:])
|
||||
gcode0 := int16(pow2(14, fracGcode0))
|
||||
|
||||
gp, gpInd, gc, gcInd, qe122, qe := mr795GainCodeQuant3(expGcode0, gcode0, gpCand[:], gpCind[:], fracCoeff, expCoeff)
|
||||
*gainPit = gp
|
||||
gainPitIndex, gainCod = gpInd, gc
|
||||
gainCodIndex, quaEnerMR122, quaEner = gcInd, qe122, qe
|
||||
|
||||
fracEn, expEn, ltpg := calcUnfiltEnergies(res, exc, code, *gainPit, cL_SUBFR)
|
||||
alpha := st.adapt.gainAdapt(ltpg, gainCod)
|
||||
if fracEn[0] != 0 && alpha > 0 {
|
||||
fracEn[3] = fracCodeEn
|
||||
expEn[3] = expCodeEn
|
||||
gainCodUnq := shl(codGainFrac, sub(codGainExp, expGcode0)+10)
|
||||
gainCodIndex, quaEnerMR122, quaEner = mr795GainCodeQuantMod(*gainPit, expGcode0, gcode0,
|
||||
fracEn[:], expEn[:], alpha, gainCodUnq, &gainCod)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package amrnb
|
||||
|
||||
// Separate pitch/code gain quantizers (MR122; pitch also used by MR795), ported
|
||||
// from opencore-amrnb q_gain_p.cpp (q_gain_pitch) and q_gain_c.cpp
|
||||
// (q_gain_code). qGainPitch picks the nearest quantized pitch gain; qGainCode
|
||||
// picks the code-gain index minimizing the error to the predicted gain. The
|
||||
// exact encoder counterparts of dGainPitch / dGainCode. Bit-exact with the
|
||||
// reference.
|
||||
|
||||
const (
|
||||
cNB_QUA_PITCH = 16
|
||||
cNB_QUA_CODE = 32
|
||||
)
|
||||
|
||||
// qGainPitch quantizes the pitch gain (in place) to the nearest table entry
|
||||
// within gpLimit, returning the index. For MR795 it also fills three candidate
|
||||
// gains/indices. Mirrors q_gain_pitch.
|
||||
func qGainPitch(mode int, gpLimit int16, gain *int16, gainCand, gainCind []int16) int16 {
|
||||
errMin := abs_s(sub(*gain, qua_gain_pitch[0]))
|
||||
index := int16(0)
|
||||
for i := int16(1); i < cNB_QUA_PITCH; i++ {
|
||||
if qua_gain_pitch[i] <= gpLimit {
|
||||
err := abs_s(sub(*gain, qua_gain_pitch[i]))
|
||||
if err < errMin {
|
||||
errMin = err
|
||||
index = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mode == dMR795 {
|
||||
var ii int16
|
||||
switch {
|
||||
case index == 0:
|
||||
ii = index
|
||||
case index == cNB_QUA_PITCH-1 || qua_gain_pitch[index+1] > gpLimit:
|
||||
ii = index - 2
|
||||
default:
|
||||
ii = index - 1
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
gainCind[i] = ii
|
||||
gainCand[i] = qua_gain_pitch[ii]
|
||||
ii++
|
||||
}
|
||||
*gain = qua_gain_pitch[index]
|
||||
} else if mode == dMR122 {
|
||||
*gain = qua_gain_pitch[index] &^ 3
|
||||
} else {
|
||||
*gain = qua_gain_pitch[index]
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// qGainCode quantizes the fixed-codebook gain (in place) against the predicted
|
||||
// gain, returning the index and the predictor-update energies. Mirrors
|
||||
// q_gain_code.
|
||||
func qGainCode(mode int, expGcode0, fracGcode0 int16, gain *int16) (index, quaEnerMR122, quaEner int16) {
|
||||
var gQ0 int16
|
||||
if mode == dMR122 {
|
||||
gQ0 = *gain >> 1 // Q1 -> Q0
|
||||
} else {
|
||||
gQ0 = *gain
|
||||
}
|
||||
|
||||
gcode0 := int16(pow2(expGcode0, fracGcode0))
|
||||
if mode == dMR122 {
|
||||
gcode0 = shl(gcode0, 4)
|
||||
} else {
|
||||
gcode0 = shl(gcode0, 5)
|
||||
}
|
||||
|
||||
errMin := gQ0 - int16((int32(gcode0)*int32(qua_gain_code[0]))>>15)
|
||||
if errMin < 0 {
|
||||
errMin = -errMin
|
||||
}
|
||||
index = 0
|
||||
for i := int16(1); i < cNB_QUA_CODE; i++ {
|
||||
err := gQ0 - int16((int32(gcode0)*int32(qua_gain_code[i*3]))>>15)
|
||||
if err < 0 {
|
||||
err = -err
|
||||
}
|
||||
if err < errMin {
|
||||
errMin = err
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
p := int(index) * 3
|
||||
temp := int16((int32(gcode0) * int32(qua_gain_code[p])) >> 15)
|
||||
if mode == dMR122 {
|
||||
*gain = temp << 1
|
||||
} else {
|
||||
*gain = temp
|
||||
}
|
||||
quaEnerMR122 = qua_gain_code[p+1]
|
||||
quaEner = qua_gain_code[p+2]
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package amrnb
|
||||
|
||||
// Encoder LSF quantization, ported from opencore-amrnb q_plsf_3.cpp (Q_plsf_3,
|
||||
// Vq_subvec3, Vq_subvec4) and lsfwt.cpp (Lsf_wt). Q_plsf_3 is the split-VQ LSF
|
||||
// quantizer for all modes except 12.2 kbit/s: it converts the analysis LSPs to
|
||||
// weighted LSFs, removes the MA prediction, and searches the LSF codebooks for
|
||||
// the indices the bitstream carries. It is the exact encoder counterpart of the
|
||||
// decoder's dPlsf3 — feeding its indices back through dPlsf3 reproduces the same
|
||||
// quantized LSPs. Bit-exact with the reference.
|
||||
|
||||
// qPlsfState holds the encoder LSF MA-predictor memory. Mirrors Q_plsfState.
|
||||
type qPlsfState struct {
|
||||
pastRQ [cM]int16
|
||||
}
|
||||
|
||||
func (st *qPlsfState) reset() {
|
||||
for i := range st.pastRQ {
|
||||
st.pastRQ[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// lsfWt computes the LSF weighting factors (squared, Q13<<3). Mirrors Lsf_wt.
|
||||
func lsfWt(lsf, wf []int16) {
|
||||
wf[0] = lsf[1]
|
||||
for i := 1; i <= 8; i++ {
|
||||
wf[i] = lsf[i+1] - lsf[i-1]
|
||||
}
|
||||
wf[9] = 16384 - lsf[8] // reference's pointer ends at lsf[M-2] here, not lsf[M-1]
|
||||
|
||||
for i := 0; i < cM; i++ {
|
||||
wgt := wf[i]
|
||||
temp := wgt - 1843
|
||||
if temp > 0 {
|
||||
temp = int16((int32(temp) * 6242) >> 15)
|
||||
wgt = 1843 - temp
|
||||
} else {
|
||||
temp = int16((int32(wgt) * 28160) >> 15)
|
||||
wgt = 3427 - temp
|
||||
}
|
||||
wf[i] = shl(wgt, 3)
|
||||
}
|
||||
}
|
||||
|
||||
// vqSubvec3 finds the weighted nearest codebook entry for a 3-element LSF
|
||||
// residual, writing the chosen entry back into lsfR1[0:3]. useHalf searches only
|
||||
// every second codebook entry. Mirrors Vq_subvec3.
|
||||
func vqSubvec3(lsfR1, dico, wf1 []int16, dicoSize int, useHalf bool) int16 {
|
||||
distMin := int32(maxInt32)
|
||||
index := int16(0)
|
||||
stride := 0
|
||||
if useHalf {
|
||||
stride = 3
|
||||
}
|
||||
l0, l1, l2 := lsfR1[0], lsfR1[1], lsfR1[2]
|
||||
w0, w1, w2 := wf1[0], wf1[1], wf1[2]
|
||||
|
||||
di := 0
|
||||
advance := 3 + stride
|
||||
for i := 0; i < dicoSize; i++ {
|
||||
row := dico[di : di+3] // hoist the per-dim bounds checks
|
||||
di += advance
|
||||
t := int16((int32(w0) * int32(l0-row[0])) >> 15)
|
||||
dist := int32(t) * int32(t)
|
||||
t = int16((int32(w1) * int32(l1-row[1])) >> 15)
|
||||
dist += int32(t) * int32(t)
|
||||
t = int16((int32(w2) * int32(l2-row[2])) >> 15)
|
||||
dist += int32(t) * int32(t)
|
||||
|
||||
if dist < distMin {
|
||||
distMin = dist
|
||||
index = int16(i)
|
||||
}
|
||||
}
|
||||
|
||||
pd := 3 * int(index)
|
||||
if useHalf {
|
||||
pd += 3 * int(index)
|
||||
}
|
||||
lsfR1[0] = dico[pd]
|
||||
lsfR1[1] = dico[pd+1]
|
||||
lsfR1[2] = dico[pd+2]
|
||||
return index
|
||||
}
|
||||
|
||||
// vqSubvec4 finds the weighted nearest codebook entry for a 4-element LSF
|
||||
// residual, writing the chosen entry back into lsfR1[0:4]. Mirrors Vq_subvec4.
|
||||
func vqSubvec4(lsfR1, dico, wf1 []int16, dicoSize int) int16 {
|
||||
distMin := int32(maxInt32)
|
||||
index := int16(0)
|
||||
l0, l1, l2, l3 := lsfR1[0], lsfR1[1], lsfR1[2], lsfR1[3]
|
||||
w0, w1, w2, w3 := wf1[0], wf1[1], wf1[2], wf1[3]
|
||||
|
||||
di := 0
|
||||
for i := 0; i < dicoSize; i++ {
|
||||
row := dico[di : di+4] // hoist the per-dim bounds checks
|
||||
di += 4
|
||||
t := int16((int32(w0) * int32(l0-row[0])) >> 15)
|
||||
dist := int32(t) * int32(t)
|
||||
t = int16((int32(w1) * int32(l1-row[1])) >> 15)
|
||||
dist += int32(t) * int32(t)
|
||||
t = int16((int32(w2) * int32(l2-row[2])) >> 15)
|
||||
dist += int32(t) * int32(t)
|
||||
t = int16((int32(w3) * int32(l3-row[3])) >> 15)
|
||||
dist += int32(t) * int32(t)
|
||||
|
||||
if dist < distMin {
|
||||
distMin = dist
|
||||
index = int16(i)
|
||||
}
|
||||
}
|
||||
|
||||
pd := int(index) << 2
|
||||
lsfR1[0] = dico[pd]
|
||||
lsfR1[1] = dico[pd+1]
|
||||
lsfR1[2] = dico[pd+2]
|
||||
lsfR1[3] = dico[pd+3]
|
||||
return index
|
||||
}
|
||||
|
||||
// qPlsf3 quantizes the analysis LSPs for the given mode into three split-VQ
|
||||
// indices and the quantized LSP vector, updating the predictor. Mirrors
|
||||
// Q_plsf_3 (non-DTX path). Returns the three indices.
|
||||
func (st *qPlsfState) qPlsf3(mode int, lsp1 []int16, lsp1q []int16) [3]int16 {
|
||||
var lsf1, wf1, lsfP, lsfR1, lsf1q [cM]int16
|
||||
|
||||
lspLsf(lsp1, lsf1[:], cM)
|
||||
lsfWt(lsf1[:], wf1[:])
|
||||
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := int16((int32(st.pastRQ[i]) * int32(pred_fac_3[i])) >> 15)
|
||||
lsfP[i] = mean_lsf_3[i] + temp
|
||||
lsfR1[i] = lsf1[i] - lsfP[i]
|
||||
}
|
||||
|
||||
var indice [3]int16
|
||||
switch {
|
||||
case mode == dMR475 || mode == dMR515:
|
||||
indice[0] = vqSubvec3(lsfR1[0:], dico1_lsf_3, wf1[0:], cDICO1_SIZE, false)
|
||||
indice[1] = vqSubvec3(lsfR1[3:], dico2_lsf_3, wf1[3:], cDICO2_SIZE/2, true)
|
||||
indice[2] = vqSubvec4(lsfR1[6:], mr515_3_lsf, wf1[6:], cMR515_3_SIZE)
|
||||
case mode == dMR795:
|
||||
indice[0] = vqSubvec3(lsfR1[0:], mr795_1_lsf, wf1[0:], cMR795_1_SIZE, false)
|
||||
indice[1] = vqSubvec3(lsfR1[3:], dico2_lsf_3, wf1[3:], cDICO2_SIZE, false)
|
||||
indice[2] = vqSubvec4(lsfR1[6:], dico3_lsf_3, wf1[6:], cDICO3_SIZE)
|
||||
default: // MR59, MR67, MR74, MR102
|
||||
indice[0] = vqSubvec3(lsfR1[0:], dico1_lsf_3, wf1[0:], cDICO1_SIZE, false)
|
||||
indice[1] = vqSubvec3(lsfR1[3:], dico2_lsf_3, wf1[3:], cDICO2_SIZE, false)
|
||||
indice[2] = vqSubvec4(lsfR1[6:], dico3_lsf_3, wf1[6:], cDICO3_SIZE)
|
||||
}
|
||||
|
||||
for i := 0; i < cM; i++ {
|
||||
lsf1q[i] = add(lsfR1[i], lsfP[i])
|
||||
st.pastRQ[i] = lsfR1[i]
|
||||
}
|
||||
|
||||
reorderLsf(lsf1q[:], cLSF_GAP, cM)
|
||||
lsfLsp(lsf1q[:], lsp1q, cM)
|
||||
return indice
|
||||
}
|
||||
|
||||
// LSF-5 split-VQ codebook sizes (q_plsf_5_tbl.cpp).
|
||||
const (
|
||||
cDICO1_5_SIZE = 128
|
||||
cDICO2_5_SIZE = 256
|
||||
cDICO3_5_SIZE = 256
|
||||
cDICO4_5_SIZE = 256
|
||||
cDICO5_5_SIZE = 64
|
||||
)
|
||||
|
||||
// vqSubvec finds the weighted nearest 4-element codebook entry (two LSFs from
|
||||
// each of the two LSF vectors), writing it back into lsfR1[0:2]/lsfR2[0:2].
|
||||
// Mirrors Vq_subvec.
|
||||
func vqSubvec(lsfR1, lsfR2, dico, wf1, wf2 []int16, dicoSize int) int16 {
|
||||
distMin := int32(maxInt32)
|
||||
index := int16(0)
|
||||
w10, w11 := wf1[0], wf1[1]
|
||||
w20, w21 := wf2[0], wf2[1]
|
||||
aux1 := int32(lsfR1[0]) * int32(w10)
|
||||
aux2 := int32(lsfR1[1]) * int32(w11)
|
||||
aux3 := int32(lsfR2[0]) * int32(w20)
|
||||
aux4 := int32(lsfR2[1]) * int32(w21)
|
||||
|
||||
for i := 0; i < dicoSize; i++ {
|
||||
row := dico[i*4 : i*4+4] // hoist the per-dim bounds checks
|
||||
temp := int16((aux1 - int32(w10)*int32(row[0])) >> 15)
|
||||
dist := int32(temp) * int32(temp)
|
||||
if dist >= distMin {
|
||||
continue
|
||||
}
|
||||
temp = int16((aux2 - int32(w11)*int32(row[1])) >> 15)
|
||||
dist += int32(temp) * int32(temp)
|
||||
if dist >= distMin {
|
||||
continue
|
||||
}
|
||||
temp = int16((aux3 - int32(w20)*int32(row[2])) >> 15)
|
||||
dist += int32(temp) * int32(temp)
|
||||
if dist >= distMin {
|
||||
continue
|
||||
}
|
||||
temp = int16((aux4 - int32(w21)*int32(row[3])) >> 15)
|
||||
dist += int32(temp) * int32(temp)
|
||||
if dist < distMin {
|
||||
distMin = dist
|
||||
index = int16(i)
|
||||
}
|
||||
}
|
||||
|
||||
pd := int(index) << 2
|
||||
lsfR1[0] = dico[pd]
|
||||
lsfR1[1] = dico[pd+1]
|
||||
lsfR2[0] = dico[pd+2]
|
||||
lsfR2[1] = dico[pd+3]
|
||||
return index
|
||||
}
|
||||
|
||||
// vqSubvecS is vqSubvec with sign selection: each codebook entry is tried as-is
|
||||
// and negated; the index LSB encodes the sign. Mirrors Vq_subvec_s.
|
||||
func vqSubvecS(lsfR1, lsfR2, dico, wf1, wf2 []int16, dicoSize int) int16 {
|
||||
distMin := int32(maxInt32)
|
||||
index := int16(0)
|
||||
sign := int16(0)
|
||||
l10, l11 := lsfR1[0], lsfR1[1]
|
||||
l20, l21 := lsfR2[0], lsfR2[1]
|
||||
w10, w11 := wf1[0], wf1[1]
|
||||
w20, w21 := wf2[0], wf2[1]
|
||||
|
||||
for i := 0; i < dicoSize; i++ {
|
||||
row := dico[i*4 : i*4+4] // hoist the per-dim bounds checks
|
||||
temp := row[0]
|
||||
t1 := int16((int32(w10) * int32(l10-temp)) >> 15)
|
||||
t2 := int16((int32(w10) * int32(l10+temp)) >> 15)
|
||||
dist1 := int32(t1) * int32(t1)
|
||||
dist2 := int32(t2) * int32(t2)
|
||||
|
||||
temp = row[1]
|
||||
t1 = int16((int32(w11) * int32(l11-temp)) >> 15)
|
||||
t2 = int16((int32(w11) * int32(l11+temp)) >> 15)
|
||||
dist1 += int32(t1) * int32(t1)
|
||||
dist2 += int32(t2) * int32(t2)
|
||||
|
||||
if dist1 >= distMin && dist2 >= distMin {
|
||||
continue
|
||||
}
|
||||
|
||||
temp = row[2]
|
||||
t1 = int16((int32(w20) * int32(l20-temp)) >> 15)
|
||||
t2 = int16((int32(w20) * int32(l20+temp)) >> 15)
|
||||
dist1 += int32(t1) * int32(t1)
|
||||
dist2 += int32(t2) * int32(t2)
|
||||
|
||||
temp = row[3]
|
||||
t1 = int16((int32(w21) * int32(l21-temp)) >> 15)
|
||||
t2 = int16((int32(w21) * int32(l21+temp)) >> 15)
|
||||
dist1 += int32(t1) * int32(t1)
|
||||
dist2 += int32(t2) * int32(t2)
|
||||
|
||||
if dist1 < distMin {
|
||||
distMin = dist1
|
||||
index = int16(i)
|
||||
sign = 0
|
||||
}
|
||||
if dist2 < distMin {
|
||||
distMin = dist2
|
||||
index = int16(i)
|
||||
sign = 1
|
||||
}
|
||||
}
|
||||
|
||||
pd := int(index) << 2
|
||||
index <<= 1
|
||||
if sign != 0 {
|
||||
lsfR1[0] = -dico[pd]
|
||||
lsfR1[1] = -dico[pd+1]
|
||||
lsfR2[0] = -dico[pd+2]
|
||||
lsfR2[1] = -dico[pd+3]
|
||||
index++
|
||||
} else {
|
||||
lsfR1[0] = dico[pd]
|
||||
lsfR1[1] = dico[pd+1]
|
||||
lsfR2[0] = dico[pd+2]
|
||||
lsfR2[1] = dico[pd+3]
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// qPlsf5 quantizes two LSP vectors (MR122) into five indices and the two
|
||||
// quantized LSP vectors, updating the predictor. The exact encoder counterpart
|
||||
// of dPlsf5. Mirrors Q_plsf_5.
|
||||
func (st *qPlsfState) qPlsf5(lsp1, lsp2, lsp1q, lsp2q []int16) [5]int16 {
|
||||
var lsf1, lsf2, wf1, wf2, lsfP, lsfR1, lsfR2, lsf1q, lsf2q [cM]int16
|
||||
|
||||
lspLsf(lsp1, lsf1[:], cM)
|
||||
lspLsf(lsp2, lsf2[:], cM)
|
||||
lsfWt(lsf1[:], wf1[:])
|
||||
lsfWt(lsf2[:], wf2[:])
|
||||
|
||||
for i := 0; i < cM; i++ {
|
||||
lsfP[i] = mean_lsf_5[i] + int16((int32(st.pastRQ[i])*cLSP_PRED_FAC_MR122)>>15)
|
||||
lsfR1[i] = lsf1[i] - lsfP[i]
|
||||
lsfR2[i] = lsf2[i] - lsfP[i]
|
||||
}
|
||||
|
||||
var indice [5]int16
|
||||
indice[0] = vqSubvec(lsfR1[0:], lsfR2[0:], dico1_lsf_5, wf1[0:], wf2[0:], cDICO1_5_SIZE)
|
||||
indice[1] = vqSubvec(lsfR1[2:], lsfR2[2:], dico2_lsf_5, wf1[2:], wf2[2:], cDICO2_5_SIZE)
|
||||
indice[2] = vqSubvecS(lsfR1[4:], lsfR2[4:], dico3_lsf_5, wf1[4:], wf2[4:], cDICO3_5_SIZE)
|
||||
indice[3] = vqSubvec(lsfR1[6:], lsfR2[6:], dico4_lsf_5, wf1[6:], wf2[6:], cDICO4_5_SIZE)
|
||||
indice[4] = vqSubvec(lsfR1[8:], lsfR2[8:], dico5_lsf_5, wf1[8:], wf2[8:], cDICO5_5_SIZE)
|
||||
|
||||
for i := 0; i < cM; i++ {
|
||||
lsf1q[i] = lsfR1[i] + lsfP[i]
|
||||
lsf2q[i] = lsfR2[i] + lsfP[i]
|
||||
st.pastRQ[i] = lsfR2[i]
|
||||
}
|
||||
|
||||
reorderLsf(lsf1q[:], cLSF_GAP, cM)
|
||||
reorderLsf(lsf2q[:], cLSF_GAP, cM)
|
||||
lsfLsp(lsf1q[:], lsp1q, cM)
|
||||
lsfLsp(lsf2q[:], lsp2q, cM)
|
||||
return indice
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package amrnb
|
||||
|
||||
// Multi-pulse algebraic codebook search, ported from opencore-amrnb s10_8pf.cpp
|
||||
// (search_10and8i40). This parameterized depth-first search places 8 (MR102) or
|
||||
// 10 (MR122) pulses, two tracks at a time, using a precomputed interleaved
|
||||
// scratch (temp1) to cache the partial ps/alp values of the second pulse of each
|
||||
// pair across the first pulse's positions. The pulse-track ordering (ipos) is
|
||||
// rotated across the outer loop. The single most intricate function in the
|
||||
// codec; the exact incremental criterion arithmetic is preserved (bit-exact).
|
||||
func search10and8i40(nbPulse, step, nbTracks int16, dn, rr, ipos, posMax, codvec []int16) {
|
||||
const L = cL_CODE
|
||||
gsmefr := nbPulse == 10
|
||||
|
||||
var index [10]int16
|
||||
var temp1 [2 * cL_CODE]int16
|
||||
|
||||
i0 := posMax[ipos[0]]
|
||||
index[0] = i0
|
||||
|
||||
psk := int16(-1)
|
||||
alpk := int16(1)
|
||||
for i := int16(0); i < nbPulse; i++ {
|
||||
codvec[i] = i
|
||||
}
|
||||
|
||||
var i1, i2, i3, i4, i5, i6, i7 int16
|
||||
|
||||
for outer := int16(1); outer < nbTracks; outer++ {
|
||||
i1 = posMax[ipos[1]]
|
||||
index[1] = i1
|
||||
|
||||
ps0 := int16(int32(dn[i0]) + int32(dn[i1]))
|
||||
alp0 := int32(rr[int(i0)*L+int(i0)]) << 12
|
||||
alp0 += int32(rr[int(i1)*L+int(i1)]) << 12
|
||||
alp0 += int32(rr[int(i0)*L+int(i1)]) << 13
|
||||
alp0 += 0x8000
|
||||
|
||||
// ---- pulses 2 & 3 ----
|
||||
ti := 0
|
||||
for i3v := ipos[3]; i3v < L; i3v += step {
|
||||
rrRow := rr[int(i3v)*L : int(i3v)*L+L]
|
||||
s := int32(rrRow[int(i3v)]) >> 1
|
||||
s += int32(rrRow[int(i0)])
|
||||
s += int32(rrRow[int(i1)])
|
||||
temp1[ti] = ps0 + dn[i3v]
|
||||
temp1[ti+1] = int16((s + 2) >> 2)
|
||||
ti += 2
|
||||
}
|
||||
|
||||
sq := int16(-1)
|
||||
alp := int16(1)
|
||||
ps := int16(0)
|
||||
ia := ipos[2]
|
||||
ib := ipos[3]
|
||||
sbase := alp0 >> 12
|
||||
for j := ipos[2]; j < L; j += step {
|
||||
rrRow := rr[int(j)*L : int(j)*L+L]
|
||||
alp1 := (sbase + int32(rrRow[int(j)])) >> 1
|
||||
alp1 += int32(rrRow[int(i0)])
|
||||
alp1 += int32(rrRow[int(i1)])
|
||||
ps1 := dn[j]
|
||||
ti = 0
|
||||
for i3v := ipos[3]; i3v < L; i3v += step {
|
||||
ps2 := ps1 + temp1[ti]
|
||||
sq2 := int16((int32(ps2) * int32(ps2)) >> 15)
|
||||
alp2 := (alp1 + int32(rrRow[int(i3v)])) >> 2
|
||||
alp2 = (alp2 + int32(temp1[ti+1])) >> 1
|
||||
ti += 2
|
||||
if int32(sq2)*int32(alp) > int32(sq)*alp2 {
|
||||
sq, ps, alp, ia, ib = sq2, ps2, int16(alp2), j, i3v
|
||||
}
|
||||
}
|
||||
}
|
||||
i2, i3 = ia, ib
|
||||
index[2], index[3] = ia, ib
|
||||
|
||||
// ---- pulses 4 & 5 ----
|
||||
alp0 = (int32(alp) << 15) + 0x8000
|
||||
ti = 0
|
||||
for i5v := ipos[5]; i5v < L; i5v += step {
|
||||
rrRow := rr[int(i5v)*L : int(i5v)*L+L]
|
||||
s := int32(rrRow[int(i5v)]) >> 1
|
||||
s += int32(rrRow[int(i0)]) + int32(rrRow[int(i1)]) + int32(rrRow[int(i2)]) + int32(rrRow[int(i3)])
|
||||
temp1[ti] = ps + dn[i5v]
|
||||
temp1[ti+1] = int16((s + 2) >> 2)
|
||||
ti += 2
|
||||
}
|
||||
sq, alp, ps, ia, ib = -1, 1, 0, ipos[4], ipos[5]
|
||||
for j := ipos[4]; j < L; j += step {
|
||||
rrRow := rr[int(j)*L : int(j)*L+L]
|
||||
alp1 := alp0 + (int32(rrRow[int(j)]) << 11)
|
||||
alp1 += int32(rrRow[int(i0)]) << 12
|
||||
alp1 += int32(rrRow[int(i1)]) << 12
|
||||
alp1 += int32(rrRow[int(i2)]) << 12
|
||||
alp1 += int32(rrRow[int(i3)]) << 12
|
||||
ps1 := dn[j]
|
||||
ti = 0
|
||||
for i5v := ipos[5]; i5v < L; i5v += step {
|
||||
ps2 := ps1 + temp1[ti]
|
||||
alp2 := alp1 + (int32(rrRow[int(i5v)]) << 12)
|
||||
alp16 := int16((alp2 + (int32(temp1[ti+1]) << 14)) >> 16)
|
||||
ti += 2
|
||||
sq2 := int16((int32(ps2) * int32(ps2)) >> 15)
|
||||
if int32(sq2)*int32(alp) > int32(sq)*int32(alp16) {
|
||||
sq, ps, alp, ia, ib = sq2, ps2, alp16, j, i5v
|
||||
}
|
||||
}
|
||||
}
|
||||
i4, i5 = ia, ib
|
||||
index[4], index[5] = ia, ib
|
||||
|
||||
// ---- pulses 6 & 7 ----
|
||||
alp0 = (int32(alp) << 15) + 0x8000
|
||||
ti = 0
|
||||
for i7v := ipos[7]; i7v < L; i7v += step {
|
||||
rrRow := rr[int(i7v)*L : int(i7v)*L+L]
|
||||
s := int32(rrRow[int(i7v)]) >> 1
|
||||
s += int32(rrRow[int(i0)]) + int32(rrRow[int(i1)]) + int32(rrRow[int(i2)]) +
|
||||
int32(rrRow[int(i3)]) + int32(rrRow[int(i4)]) + int32(rrRow[int(i5)])
|
||||
temp1[ti] = ps + dn[i7v]
|
||||
temp1[ti+1] = int16((s + 4) >> 3)
|
||||
ti += 2
|
||||
}
|
||||
sq, alp, ps, ia, ib = -1, 1, 0, ipos[6], ipos[7]
|
||||
for j := ipos[6]; j < L; j += step {
|
||||
rrRow := rr[int(j)*L : int(j)*L+L]
|
||||
alp1 := alp0 + (int32(rrRow[int(j)]) << 10)
|
||||
alp1 += int32(rrRow[int(i0)]) << 11
|
||||
alp1 += int32(rrRow[int(i1)]) << 11
|
||||
alp1 += int32(rrRow[int(i2)]) << 11
|
||||
alp1 += int32(rrRow[int(i3)]) << 11
|
||||
alp1 += int32(rrRow[int(i4)]) << 11
|
||||
alp1 += int32(rrRow[int(i5)]) << 11
|
||||
ps1 := dn[j]
|
||||
ti = 0
|
||||
for i7v := ipos[7]; i7v < L; i7v += step {
|
||||
ps2 := ps1 + temp1[ti]
|
||||
alp2 := alp1 + (int32(rrRow[int(i7v)]) << 11)
|
||||
alp16 := int16((alp2 + (int32(temp1[ti+1]) << 14)) >> 16)
|
||||
ti += 2
|
||||
sq2 := int16((int32(ps2) * int32(ps2)) >> 15)
|
||||
if int32(sq2)*int32(alp) > int32(sq)*int32(alp16) {
|
||||
sq, ps, alp, ia, ib = sq2, ps2, alp16, j, i7v
|
||||
}
|
||||
}
|
||||
}
|
||||
i6, i7 = ia, ib
|
||||
index[6], index[7] = ia, ib
|
||||
|
||||
// ---- pulses 8 & 9 (MR122 only) ----
|
||||
if gsmefr {
|
||||
alp0 = (int32(alp) << 15) + 0x8000
|
||||
ti = 0
|
||||
for i9v := ipos[9]; i9v < L; i9v += step {
|
||||
rrRow := rr[int(i9v)*L : int(i9v)*L+L]
|
||||
s := int32(rrRow[int(i9v)]) >> 1
|
||||
s += int32(rrRow[int(i0)]) + int32(rrRow[int(i1)]) + int32(rrRow[int(i2)]) +
|
||||
int32(rrRow[int(i3)]) + int32(rrRow[int(i4)]) + int32(rrRow[int(i5)]) +
|
||||
int32(rrRow[int(i6)]) + int32(rrRow[int(i7)])
|
||||
temp1[ti] = ps + dn[i9v]
|
||||
temp1[ti+1] = int16((s + 4) >> 3)
|
||||
ti += 2
|
||||
}
|
||||
sq, alp, ps, ia, ib = -1, 1, 0, ipos[8], ipos[9]
|
||||
for j := ipos[8]; j < L; j += step {
|
||||
rrRow := rr[int(j)*L : int(j)*L+L]
|
||||
alp1 := alp0 + (int32(rrRow[int(j)]) << 9)
|
||||
alp1 += int32(rr[int(i0)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i1)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i2)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i3)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i4)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i5)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i6)*L+int(j)]) << 10
|
||||
alp1 += int32(rr[int(i7)*L+int(j)]) << 10
|
||||
ps1 := dn[j]
|
||||
ti = 0
|
||||
for i9v := ipos[9]; i9v < L; i9v += step {
|
||||
ps2 := ps1 + temp1[ti]
|
||||
sq2 := int16((int32(ps2) * int32(ps2)) >> 15)
|
||||
alp2 := alp1 + (int32(rrRow[int(i9v)]) << 10)
|
||||
alp16 := int16((alp2 + (int32(temp1[ti+1]) << 13)) >> 16)
|
||||
ti += 2
|
||||
if int32(sq2)*int32(alp) > int32(sq)*int32(alp16) {
|
||||
sq, ps, alp, ia, ib = sq2, ps2, alp16, j, i9v
|
||||
}
|
||||
}
|
||||
}
|
||||
index[8], index[9] = ia, ib
|
||||
}
|
||||
|
||||
// ---- keep the best ----
|
||||
if int32(alpk)*int32(sq) > int32(psk)*int32(alp) {
|
||||
psk = sq
|
||||
alpk = alp
|
||||
copy(codvec[:nbPulse], index[:nbPulse])
|
||||
}
|
||||
|
||||
// rotate ipos[1..nbPulse-1] left by one
|
||||
pos := ipos[1]
|
||||
for j := int16(1); j < nbPulse-1; j++ {
|
||||
ipos[j] = ipos[j+1]
|
||||
}
|
||||
ipos[nbPulse-1] = pos
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package amrnb
|
||||
|
||||
// Per-subframe codebook dispatch and post-processing, ported from opencore-amrnb
|
||||
// cbsearch.cpp (cbsearch) and spstproc.cpp (subframePostProc). cbsearch routes
|
||||
// to the mode's algebraic codebook search (and applies the MR102/MR122 pitch
|
||||
// sharpening around it); subframePostProc forms the total excitation, runs the
|
||||
// local synthesis, and updates the synthesis/error/weighting filter memories
|
||||
// for the next subframe. Bit-exact with the reference.
|
||||
|
||||
// cbsearch runs the fixed-codebook search for the mode, writing the innovation
|
||||
// code and filtered code y and the analysis indices into anap (returning the
|
||||
// count). Mirrors cbsearch.
|
||||
func cbsearch(x, h []int16, T0, pitchSharp, gainPit int16, res2, code, y []int16, mode, subNr int, anap []int16) int {
|
||||
switch {
|
||||
case mode == dMR475 || mode == dMR515:
|
||||
idx, sgn := code2i40_9bits(int16(subNr), x, h, T0, pitchSharp, code, y)
|
||||
anap[0], anap[1] = idx, sgn
|
||||
return 2
|
||||
case mode == dMR59:
|
||||
idx, sgn := code2i40_11bits(x, h, T0, pitchSharp, code, y)
|
||||
anap[0], anap[1] = idx, sgn
|
||||
return 2
|
||||
case mode == dMR67:
|
||||
idx, sgn := code3i40_14bits(x, h, T0, pitchSharp, code, y)
|
||||
anap[0], anap[1] = idx, sgn
|
||||
return 2
|
||||
case mode == dMR74 || mode == dMR795:
|
||||
idx, sgn := code4i40_17bits(x, h, T0, pitchSharp, code, y)
|
||||
anap[0], anap[1] = idx, sgn
|
||||
return 2
|
||||
case mode == dMR102:
|
||||
ps := shl(pitchSharp, 1)
|
||||
for i := T0; i < cL_SUBFR; i++ {
|
||||
h[i] = add(h[i], mult(h[i-T0], ps))
|
||||
}
|
||||
code8i40_31bits(x, res2, h, code, y, anap[0:7])
|
||||
for i := T0; i < cL_SUBFR; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], ps))
|
||||
}
|
||||
return 7
|
||||
default: // MR122
|
||||
ps := shl(gainPit, 1)
|
||||
for i := T0; i < cL_SUBFR; i++ {
|
||||
h[i] = add(h[i], int16((int32(h[i-T0])*int32(ps))>>15))
|
||||
}
|
||||
code10i40_35bits(x, res2, h, code, y, anap[0:10])
|
||||
for i := T0; i < cL_SUBFR; i++ {
|
||||
code[i] = add(code[i], mult(code[i-T0], ps))
|
||||
}
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
// subframePostProc builds the total excitation (gain_pit·adaptive +
|
||||
// gain_code·code), runs the local synthesis (updating mem_syn), and refreshes
|
||||
// the error and weighting-filter memories. sharp is updated with the clamped
|
||||
// pitch gain. Mirrors subframePostProc.
|
||||
func subframePostProc(speech []int16, speechOff, mode, iSubfr int, gainPit, gainCode int16, aq, synth, xn, code, y1, y2, memSyn, memErr, memW0, exc []int16, excOff int, sharp *int16) {
|
||||
var tempShift, kShift, pitchFac int16
|
||||
if mode != dMR122 {
|
||||
tempShift, kShift, pitchFac = 1, 16-2-1, gainPit
|
||||
} else {
|
||||
tempShift, kShift, pitchFac = 2, 16-4-1, gainPit>>1
|
||||
}
|
||||
|
||||
if gainPit < cSHARPMAX {
|
||||
*sharp = gainPit
|
||||
} else {
|
||||
*sharp = cSHARPMAX
|
||||
}
|
||||
|
||||
base := excOff + iSubfr
|
||||
for i := 0; i < cL_SUBFR; i++ {
|
||||
L := (int32(exc[base+i]) * int32(pitchFac)) << 1
|
||||
L += (int32(code[i]) * int32(gainCode)) << 1
|
||||
L <<= uint(tempShift)
|
||||
exc[base+i] = int16((L + 0x8000) >> 16)
|
||||
}
|
||||
|
||||
synFilt(aq, exc[base:base+cL_SUBFR], synth[iSubfr:iSubfr+cL_SUBFR], cL_SUBFR, memSyn, true)
|
||||
|
||||
for i, j := cL_SUBFR-cM, 0; i < cL_SUBFR; i, j = i+1, j+1 {
|
||||
memErr[j] = speech[speechOff+iSubfr+i] - synth[iSubfr+i]
|
||||
temp := int16((int32(y1[i]) * int32(gainPit)) >> 14)
|
||||
temp += int16((int32(y2[i]) * int32(gainCode)) >> uint(kShift))
|
||||
memW0[j] = xn[i] - temp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package amrnb
|
||||
|
||||
// Fixed-codebook gain prediction, ported from opencore-amrnb gc_pred.cpp plus
|
||||
// the 32-bit composite operators it needs (L_Extract, L_Comp, Mpy_32_16 from
|
||||
// the ETSI basicop set). gc_pred runs the MA energy predictor that yields the
|
||||
// predicted code gain (gcode0) used by the gain dequantizers; gc_pred_update
|
||||
// feeds back the quantized energy each subframe. Bit-exact with the reference.
|
||||
|
||||
const (
|
||||
cNPRED = 4
|
||||
cMEAN_ENER_MR122 = 783741 // Q17
|
||||
cMIN_ENERGY = -14336 // Q10
|
||||
cMIN_ENERGY_MR122 = -2381 // Q10
|
||||
)
|
||||
|
||||
// MA prediction coefficients (gc_pred.cpp).
|
||||
var gcPred = [cNPRED]int16{5571, 4751, 2785, 1556}
|
||||
var gcPredMR122 = [cNPRED]int16{44, 37, 22, 12}
|
||||
|
||||
// L_Extract splits a 32-bit value into a 16-bit high part and a 15-bit low
|
||||
// part such that L ≈ (hi<<16) + (lo<<1). Mirrors L_Extract.
|
||||
func L_Extract(L int32) (hi, lo int16) {
|
||||
hi = int16(L >> 16)
|
||||
lo = int16((L >> 1) - (int32(hi) << 15))
|
||||
return hi, lo
|
||||
}
|
||||
|
||||
// L_Comp recombines a (hi,lo) pair back into a 32-bit value. Mirrors L_Comp.
|
||||
func L_Comp(hi, lo int16) int32 {
|
||||
return L_mac(L_deposit_h(hi), lo, 1)
|
||||
}
|
||||
|
||||
// mpy32_16 multiplies a 32-bit value (given as hi,lo) by a 16-bit value,
|
||||
// matching the optimized opencore-amrnb Mpy_32_16 (truncated low-part shift).
|
||||
func mpy32_16(hi, lo, n int16) int32 {
|
||||
return L_add(L_mult(hi, n), ((int32(lo)*int32(n))>>15)<<1)
|
||||
}
|
||||
|
||||
// gcPredState is the MA energy-predictor memory (two parallel histories: the
|
||||
// 20·log10 form for most modes and the log2 form for MR122). Mirrors
|
||||
// gc_predState.
|
||||
type gcPredState struct {
|
||||
pastQuaEn [cNPRED]int16 // Q10, 20*log10(qua_err)
|
||||
pastQuaEnMR122 [cNPRED]int16 // Q10, log2(qua_err)
|
||||
}
|
||||
|
||||
// reset initializes both predictor histories to their energy floor. Mirrors
|
||||
// gc_pred_reset.
|
||||
func (st *gcPredState) reset() {
|
||||
for i := 0; i < cNPRED; i++ {
|
||||
st.pastQuaEn[i] = cMIN_ENERGY
|
||||
st.pastQuaEnMR122[i] = cMIN_ENERGY_MR122
|
||||
}
|
||||
}
|
||||
|
||||
// update shifts the new quantized energies into the predictor memory. Mirrors
|
||||
// gc_pred_update.
|
||||
func (st *gcPredState) update(quaEnerMR122, quaEner int16) {
|
||||
st.pastQuaEn[3] = st.pastQuaEn[2]
|
||||
st.pastQuaEnMR122[3] = st.pastQuaEnMR122[2]
|
||||
st.pastQuaEn[2] = st.pastQuaEn[1]
|
||||
st.pastQuaEnMR122[2] = st.pastQuaEnMR122[1]
|
||||
st.pastQuaEn[1] = st.pastQuaEn[0]
|
||||
st.pastQuaEnMR122[1] = st.pastQuaEnMR122[0]
|
||||
st.pastQuaEnMR122[0] = quaEnerMR122
|
||||
st.pastQuaEn[0] = quaEner
|
||||
}
|
||||
|
||||
// gcPredResult carries gc_pred's outputs: the exponent/fraction of the
|
||||
// predicted gain factor gcode0, and (MR795 only) of the innovation energy.
|
||||
type gcPredResult struct {
|
||||
expGcode0, fracGcode0 int16
|
||||
expEn, fracEn int16
|
||||
}
|
||||
|
||||
// gcPred computes the predicted fixed-codebook gain factor from the innovation
|
||||
// energy and the predictor memory. Mirrors gc_pred (gc_pred.cpp).
|
||||
func (st *gcPredState) gcPred(mode int, code []int16) gcPredResult {
|
||||
var r gcPredResult
|
||||
|
||||
var enerCode int32
|
||||
for i := cL_SUBFR >> 2; i != 0; i-- {
|
||||
base := (cL_SUBFR>>2 - i) * 4
|
||||
for k := 0; k < 4; k++ {
|
||||
tmp := code[base+k]
|
||||
enerCode += (int32(tmp) * int32(tmp)) >> 3
|
||||
}
|
||||
}
|
||||
enerCode <<= 4
|
||||
if enerCode>>31 != 0 { // saturation
|
||||
enerCode = maxInt32
|
||||
}
|
||||
|
||||
if mode == dMR122 {
|
||||
enerCode = int32(round_(enerCode)) * 26214 << 1
|
||||
exp, frac := log2(enerCode)
|
||||
lTemp1 := int32(exp-30) << 16
|
||||
enerCode = lTemp1 + (int32(frac) << 1)
|
||||
|
||||
ener := int32(cMEAN_ENER_MR122)
|
||||
for i := 0; i < cNPRED; i++ {
|
||||
lt := (int32(st.pastQuaEnMR122[i]) * int32(gcPredMR122[i])) << 1
|
||||
ener = L_add(ener, lt)
|
||||
}
|
||||
|
||||
lTemp1 = L_sub(ener, enerCode)
|
||||
r.expGcode0 = int16(lTemp1 >> 17)
|
||||
lTemp2 := int32(r.expGcode0) << 15
|
||||
lTemp1 >>= 2
|
||||
r.fracGcode0 = int16(lTemp1 - lTemp2)
|
||||
return r
|
||||
}
|
||||
|
||||
// all modes except 12.2
|
||||
expCode := norm_l(enerCode)
|
||||
enerCode = L_shl(enerCode, expCode)
|
||||
exp, frac := log2Norm(enerCode, expCode)
|
||||
|
||||
lTemp2 := (int32(exp) * -24660) << 1
|
||||
lTmp := (int32(frac) * -24660) >> 15
|
||||
if lTmp&0x00010000 != 0 { // sign-extend
|
||||
lTmp |= int32(-0x10000) // 0xffff0000
|
||||
}
|
||||
lTmp <<= 1
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
|
||||
switch mode {
|
||||
case dMR102:
|
||||
lTemp2 = int32(16678) << 7
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
case dMR795:
|
||||
r.fracEn = int16(enerCode >> 16)
|
||||
r.expEn = int16(-11 - expCode)
|
||||
lTemp2 = int32(17062) << 7
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
case dMR74:
|
||||
lTemp2 = int32(32588) << 6
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
case dMR67:
|
||||
lTemp2 = int32(32268) << 6
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
default: // MR59, MR515, MR475
|
||||
lTemp2 = int32(16678) << 7
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
}
|
||||
|
||||
// gcode0 = sum(pred[i]*past_qua_en[i]) - ener_code + mean_ener (Q24)
|
||||
if lTmp > 0x001fffff {
|
||||
lTmp = maxInt32
|
||||
} else if lTmp < -2097152 {
|
||||
lTmp = minInt32
|
||||
} else {
|
||||
lTmp <<= 10
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
lTemp2 = (int32(gcPred[i]) * int32(st.pastQuaEn[i])) << 1
|
||||
lTmp = L_add(lTmp, lTemp2)
|
||||
}
|
||||
gcode0 := int16(lTmp >> 16) // Q8
|
||||
|
||||
// gcode0 = pow(2, 0.166*gcode0)
|
||||
if mode == dMR74 { // IS641 bit-exactness
|
||||
lTmp = int32(gcode0) * 5439 << 1
|
||||
} else {
|
||||
lTmp = int32(gcode0) * 5443 << 1
|
||||
}
|
||||
if lTmp < 0 {
|
||||
lTmp = ^((^lTmp) >> 8)
|
||||
} else {
|
||||
lTmp >>= 8 // -> Q16
|
||||
}
|
||||
r.expGcode0 = int16(lTmp >> 16)
|
||||
var lTemp1 int32
|
||||
if lTmp < 0 {
|
||||
lTemp1 = ^((^lTmp) >> 1)
|
||||
} else {
|
||||
lTemp1 = lTmp >> 1
|
||||
}
|
||||
lTemp2 = int32(r.expGcode0) << 15
|
||||
r.fracGcode0 = int16(L_sub(lTemp1, lTemp2))
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package amrnb
|
||||
|
||||
// LSP/LSF conversions and interpolation, ported function-by-function from the
|
||||
// opencore-amrnb common sources (lsp_az.cpp, lsp_lsf.cpp, int_lpc.cpp). These
|
||||
// turn the decoded line-spectral representation into the per-subframe LP
|
||||
// synthesis coefficients A(z) (order 10). Bit-exact with the reference.
|
||||
|
||||
// getLspPol finds the coefficients of F1(z) or F2(z) from the (cosine-domain)
|
||||
// LSPs by expanding the product polynomial. f must have length 6. Mirrors
|
||||
// Get_lsp_pol (lsp_az.cpp).
|
||||
func getLspPol(lsp []int16, f []int32) {
|
||||
fi := 0
|
||||
li := 0
|
||||
f[fi] = 0x01000000 // f[0] = 1.0
|
||||
fi++
|
||||
f[fi] = (-int32(lsp[li])) << 10 // f[1] = -2.0 * lsp[0]
|
||||
fi++
|
||||
li++
|
||||
li++ // advance lsp pointer
|
||||
|
||||
for i := int16(2); i <= 5; i++ {
|
||||
f[fi] = f[fi-2]
|
||||
for j := int16(1); j < i; j++ {
|
||||
hi := int16(f[fi-1] >> 16)
|
||||
lo := int16((f[fi-1] >> 1) - (int32(hi) << 15))
|
||||
t0 := int32(hi) * int32(lsp[li])
|
||||
t0 += (int32(lo) * int32(lsp[li])) >> 15
|
||||
f[fi] += f[fi-2]
|
||||
f[fi] -= t0 << 2
|
||||
fi--
|
||||
}
|
||||
f[fi] -= int32(lsp[li]) << 10
|
||||
li++
|
||||
fi += int(i)
|
||||
li++
|
||||
}
|
||||
}
|
||||
|
||||
// lspAz converts line spectral pairs to order-10 LP coefficients a[0:11].
|
||||
// Mirrors Lsp_Az (lsp_az.cpp).
|
||||
func lspAz(lsp []int16, a []int16) {
|
||||
var f1, f2 [6]int32
|
||||
getLspPol(lsp[0:], f1[:])
|
||||
getLspPol(lsp[1:], f2[:])
|
||||
for i := 5; i > 0; i-- {
|
||||
f1[i] += f1[i-1]
|
||||
f2[i] -= f2[i-1]
|
||||
}
|
||||
a[0] = 4096
|
||||
for i, j := 1, 10; i <= 5; i, j = i+1, j-1 {
|
||||
t0 := f1[i] + f2[i]
|
||||
t1 := f1[i] - f2[i]
|
||||
t0 += 1 << 12
|
||||
t1 += 1 << 12
|
||||
a[i] = int16(t0 >> 13)
|
||||
a[j] = int16(t1 >> 13)
|
||||
}
|
||||
}
|
||||
|
||||
// lsfLsp converts normalized LSFs (Q15, 0..0.5) to LSPs (cosine domain) by
|
||||
// table lookup and interpolation. Mirrors Lsf_lsp (lsp_lsf.cpp).
|
||||
func lsfLsp(lsf, lsp []int16, m int16) {
|
||||
for i := int16(0); i < m; i++ {
|
||||
ind := lsf[i] >> 8 // b8-b15
|
||||
offset := lsf[i] & 0x00ff // b0-b7
|
||||
L_tmp := (int32(lspLsfTable[ind+1]) - int32(lspLsfTable[ind])) * int32(offset) >> 8
|
||||
lsp[i] = lspLsfTable[ind] + int16(L_tmp)
|
||||
}
|
||||
}
|
||||
|
||||
// lspLsf converts LSPs (cosine domain) to normalized LSFs (Q15) via the acos
|
||||
// table and per-segment slope. Mirrors Lsp_lsf (lsp_lsf.cpp).
|
||||
func lspLsf(lsp, lsf []int16, m int16) {
|
||||
ind := int16(63)
|
||||
for i := m - 1; i >= 0; i-- {
|
||||
temp := lsp[i]
|
||||
for lspLsfTable[ind] < temp {
|
||||
ind--
|
||||
}
|
||||
L_tmp := (int32(temp) - int32(lspLsfTable[ind])) * int32(lspLsfSlope[ind])
|
||||
L_tmp = (L_tmp + 0x800) >> 12
|
||||
lsf[i] = int16(L_tmp) + (ind << 8)
|
||||
}
|
||||
}
|
||||
|
||||
// intLpc1and3 interpolates LSPs across the four subframes (old/mid/new LSP
|
||||
// sets) and produces A(z) for each, into Az (length 4*cMP1). Used by modes that
|
||||
// transmit a mid-frame LSP set. Mirrors Int_lpc_1and3 (int_lpc.cpp).
|
||||
func intLpc1and3(lspOld, lspMid, lspNew []int16, Az []int16) {
|
||||
var lsp [cM]int16
|
||||
for i := 0; i < cM; i++ {
|
||||
lsp[i] = (lspOld[i] >> 1) + (lspMid[i] >> 1)
|
||||
}
|
||||
lspAz(lsp[:], Az[0:]) // subframe 1
|
||||
lspAz(lspMid, Az[cMP1:]) // subframe 2
|
||||
for i := 0; i < cM; i++ {
|
||||
lsp[i] = (lspMid[i] >> 1) + (lspNew[i] >> 1)
|
||||
}
|
||||
lspAz(lsp[:], Az[2*cMP1:]) // subframe 3
|
||||
lspAz(lspNew, Az[3*cMP1:]) // subframe 4
|
||||
}
|
||||
|
||||
// intLpc1to3 interpolates between only the past-frame and present-frame LSP
|
||||
// sets (no mid set) across the four subframes. Mirrors Int_lpc_1to3.
|
||||
func intLpc1to3(lspOld, lspNew []int16, Az []int16) {
|
||||
var lsp [cM]int16
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := lspOld[i] - (lspOld[i] >> 2)
|
||||
lsp[i] = temp + (lspNew[i] >> 2)
|
||||
}
|
||||
lspAz(lsp[:], Az[0:]) // subframe 1
|
||||
for i := 0; i < cM; i++ {
|
||||
lsp[i] = (lspNew[i] >> 1) + (lspOld[i] >> 1)
|
||||
}
|
||||
lspAz(lsp[:], Az[cMP1:]) // subframe 2
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := lspNew[i] - (lspNew[i] >> 2)
|
||||
lsp[i] = temp + (lspOld[i] >> 2)
|
||||
}
|
||||
lspAz(lsp[:], Az[2*cMP1:]) // subframe 3
|
||||
lspAz(lspNew, Az[3*cMP1:]) // subframe 4
|
||||
}
|
||||
|
||||
// intLpc1and3_2 interpolates only subframes 1 and 3 (leaving 2 and 4 as the
|
||||
// directly-computed A_mid/A_new), used for the unquantized analysis filters.
|
||||
// Mirrors Int_lpc_1and3_2.
|
||||
func intLpc1and3_2(lspOld, lspMid, lspNew, az []int16) {
|
||||
var lsp [cM]int16
|
||||
for i := 0; i < cM; i++ {
|
||||
lsp[i] = (lspOld[i] >> 1) + (lspMid[i] >> 1)
|
||||
}
|
||||
lspAz(lsp[:], az[0:]) // subframe 1
|
||||
for i := 0; i < cM; i++ {
|
||||
lsp[i] = (lspMid[i] >> 1) + (lspNew[i] >> 1)
|
||||
}
|
||||
lspAz(lsp[:], az[2*cMP1:]) // subframe 3
|
||||
}
|
||||
|
||||
// intLpc1to3_2 interpolates subframes 1, 2 and 3 (leaving 4 as A_new). Mirrors
|
||||
// Int_lpc_1to3_2.
|
||||
func intLpc1to3_2(lspOld, lspNew, az []int16) {
|
||||
var lsp [cM]int16
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := lspOld[i] - (lspOld[i] >> 2)
|
||||
lsp[i] = temp + (lspNew[i] >> 2)
|
||||
}
|
||||
lspAz(lsp[:], az[0:]) // subframe 1
|
||||
for i := 0; i < cM; i++ {
|
||||
lsp[i] = (lspNew[i] >> 1) + (lspOld[i] >> 1)
|
||||
}
|
||||
lspAz(lsp[:], az[cMP1:]) // subframe 2
|
||||
for i := 0; i < cM; i++ {
|
||||
temp := lspNew[i] - (lspNew[i] >> 2)
|
||||
lsp[i] = temp + (lspOld[i] >> 2)
|
||||
}
|
||||
lspAz(lsp[:], az[2*cMP1:]) // subframe 3
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package amrnb
|
||||
|
||||
// Higher-level fixed-point math operators, ported function-by-function from the
|
||||
// opencore-amrnb common sources (log2_norm.cpp, log2.cpp, pow2.cpp,
|
||||
// inv_sqrt.cpp, sqrt_l.cpp, shr_r.cpp, l_shr_r.cpp). Each uses the ROM tables
|
||||
// in tables_math.go and is bit-exact with the reference by construction.
|
||||
|
||||
// shr_r is shr with rounding: shr(var1, var2) plus 1 if the most significant
|
||||
// discarded bit is set. Mirrors shr_r.cpp.
|
||||
func shr_r(var1, var2 int16) int16 {
|
||||
if var2 > 15 {
|
||||
return 0
|
||||
}
|
||||
out := shr(var1, var2)
|
||||
if var2 > 0 {
|
||||
if var1&(int16(1)<<uint(var2-1)) != 0 {
|
||||
out = int16(out + 1) // Word16 wraparound, matching the reference
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// L_shr_r is L_shr with rounding. Mirrors l_shr_r.cpp.
|
||||
func L_shr_r(L_var1 int32, var2 int16) int32 {
|
||||
if var2 > 31 {
|
||||
return 0
|
||||
}
|
||||
result := L_shr(L_var1, var2)
|
||||
if var2 > 0 {
|
||||
if L_var1&(int32(1)<<uint(var2-1)) != 0 {
|
||||
result = result + 1 // Word32 wraparound
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// log2Norm computes Log2 of a pre-normalized value. exp is norm_l of the
|
||||
// original input; it returns the integer and fractional parts of the base-2
|
||||
// log. Mirrors Log2_norm (log2_norm.cpp).
|
||||
func log2Norm(L_x int32, exp int16) (exponent, fraction int16) {
|
||||
if L_x <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
exponent = sub(30, exp)
|
||||
L_x = L_shr(L_x, 9)
|
||||
i := extract_h(L_x) // b25-b31
|
||||
L_x = L_shr(L_x, 1)
|
||||
a := extract_l(L_x) & 0x7fff // b10-b24
|
||||
i = sub(i, 32)
|
||||
L_y := L_deposit_h(log2_tbl[i])
|
||||
tmp := sub(log2_tbl[i], log2_tbl[i+1])
|
||||
L_y = L_msu(L_y, tmp, a)
|
||||
fraction = extract_h(L_y)
|
||||
return exponent, fraction
|
||||
}
|
||||
|
||||
// log2 normalizes L_x and returns the integer and fractional parts of its
|
||||
// base-2 logarithm. Mirrors Log2 (log2.cpp).
|
||||
func log2(L_x int32) (exponent, fraction int16) {
|
||||
exp := norm_l(L_x)
|
||||
return log2Norm(L_shl(L_x, exp), exp)
|
||||
}
|
||||
|
||||
// pow2 computes 2^(exponent + fraction) in Q0..Q31. Mirrors Pow2 (pow2.cpp).
|
||||
func pow2(exponent, fraction int16) int32 {
|
||||
L_x := L_mult(fraction, 32) // fraction << 6
|
||||
i := extract_h(L_x)
|
||||
L_x = L_shr(L_x, 1)
|
||||
a := extract_l(L_x) & 0x7fff
|
||||
L_x = L_deposit_h(pow2_tbl[i])
|
||||
tmp := sub(pow2_tbl[i], pow2_tbl[i+1])
|
||||
L_x = L_msu(L_x, tmp, a)
|
||||
exp := sub(30, exponent)
|
||||
return L_shr_r(L_x, exp)
|
||||
}
|
||||
|
||||
// invSqrt returns an approximation of 1/sqrt(L_x) by table lookup and linear
|
||||
// interpolation. Mirrors Inv_sqrt (inv_sqrt.cpp).
|
||||
func invSqrt(L_x int32) int32 {
|
||||
if L_x <= 0 {
|
||||
return 0x3fffffff
|
||||
}
|
||||
exp := norm_l(L_x)
|
||||
L_x = L_shl(L_x, exp)
|
||||
exp = sub(30, exp)
|
||||
if exp&1 == 0 { // even exponent -> shift right
|
||||
L_x = L_shr(L_x, 1)
|
||||
}
|
||||
exp = shr(exp, 1)
|
||||
exp = add(exp, 1)
|
||||
L_x = L_shr(L_x, 9)
|
||||
i := extract_h(L_x)
|
||||
L_x = L_shr(L_x, 1)
|
||||
a := extract_l(L_x) & 0x7fff
|
||||
i = sub(i, 16)
|
||||
L_y := L_deposit_h(inv_sqrt_tbl[i])
|
||||
tmp := sub(inv_sqrt_tbl[i], inv_sqrt_tbl[i+1])
|
||||
L_y = L_msu(L_y, tmp, a)
|
||||
return L_shr(L_y, exp) // denormalization
|
||||
}
|
||||
|
||||
// sqrtLExp returns sqrt(L_x) together with the right shift (exp) that must be
|
||||
// applied to the result to denormalize it: the true value is the return value
|
||||
// shifted right by exp/2. Mirrors sqrt_l_exp (sqrt_l.cpp).
|
||||
func sqrtLExp(L_x int32) (result int32, exp int16) {
|
||||
if L_x <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
e := norm_l(L_x) &^ 1 // next lower even normalization exponent (clear bit 0)
|
||||
L_x = L_shl(L_x, e)
|
||||
L_x = L_shr(L_x, 9)
|
||||
i := extract_h(L_x) // 16 <= i <= 63
|
||||
L_x = L_shr(L_x, 1)
|
||||
a := extract_l(L_x) & 0x7fff
|
||||
i = sub(i, 16) // 0 <= i <= 47
|
||||
L_y := L_deposit_h(sqrt_l_tbl[i])
|
||||
tmp := sub(sqrt_l_tbl[i], sqrt_l_tbl[i+1])
|
||||
L_y = L_msu(L_y, tmp, a)
|
||||
return L_y, e
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package amrnb
|
||||
|
||||
// Output post-processing building blocks, ported from opencore-amrnb
|
||||
// weight_a.cpp (Weight_Ai), preemph.cpp (preemphasis) and post_pro.cpp
|
||||
// (Post_Process). weightAi applies gamma bandwidth expansion to an LP filter;
|
||||
// preemphasis is the 1st-order y[n]=x[n]-g·x[n-1]; postProcess is the decoder's
|
||||
// output high-pass biquad with the ×2 output scaling. Bit-exact with the
|
||||
// reference.
|
||||
|
||||
// weightAi computes the bandwidth-expanded coefficients a_exp[i] = a[i]·fac[i-1]
|
||||
// (with a_exp[0]=a[0]). Mirrors Weight_Ai.
|
||||
func weightAi(a []int16, fac []int16, aExp []int16) {
|
||||
aExp[0] = a[0]
|
||||
for i := 1; i <= cM; i++ {
|
||||
aExp[i] = int16((int32(a[i])*int32(fac[i-1]) + 0x4000) >> 15)
|
||||
}
|
||||
}
|
||||
|
||||
// preemphasis applies y[n] = x[n] - g·x[n-1] in place over L samples, carrying
|
||||
// one sample of memory. Mirrors preemphasis (preemph.cpp).
|
||||
func preemphasis(memPre *int16, signal []int16, g int16, L int) {
|
||||
temp := signal[L-1]
|
||||
for i := L - 1; i >= 1; i-- {
|
||||
signal[i] = sub(signal[i], mult(g, signal[i-1]))
|
||||
}
|
||||
signal[0] = sub(signal[0], mult(g, *memPre))
|
||||
*memPre = temp
|
||||
}
|
||||
|
||||
// post-process output high-pass biquad coefficients (post_pro.cpp).
|
||||
var postB = [3]int16{7699, -15398, 7699}
|
||||
var postA = [3]int16{8192, 15836, -7667}
|
||||
|
||||
// postProcessState holds the biquad's double-precision input/output memory.
|
||||
type postProcessState struct {
|
||||
x0, x1 int16
|
||||
y1hi, y1lo, y2hi, y2lo int16
|
||||
}
|
||||
|
||||
// process applies the output high-pass filter (with ×2 gain) in place over lg
|
||||
// samples. The feedback memory is kept as (hi,lo) double precision. Mirrors
|
||||
// Post_Process.
|
||||
func (st *postProcessState) process(signal []int16, lg int) {
|
||||
a1, a2 := int32(postA[1]), int32(postA[2])
|
||||
b0, b1, b2 := int32(postB[0]), int32(postB[1]), int32(postB[2])
|
||||
for i := 0; i < lg; i++ {
|
||||
x2 := st.x1
|
||||
st.x1 = st.x0
|
||||
st.x0 = signal[i]
|
||||
|
||||
L := int32(st.y1hi) * a1
|
||||
L += (int32(st.y1lo) * a1) >> 15
|
||||
L += int32(st.y2hi) * a2
|
||||
L += (int32(st.y2lo) * a2) >> 15
|
||||
L += int32(st.x0) * b0
|
||||
L += int32(st.x1) * b1
|
||||
L += int32(x2) * b2
|
||||
|
||||
L = L_shl(L, 3)
|
||||
signal[i] = round_(L_shl(L, 1))
|
||||
|
||||
st.y2hi = st.y1hi
|
||||
st.y2lo = st.y1lo
|
||||
st.y1hi = int16(L >> 16)
|
||||
st.y1lo = int16((L >> 1) - (int32(st.y1hi) << 15))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package amrnb
|
||||
|
||||
// Adaptive formant post-filter, ported from opencore-amrnb pstfilt.cpp. For each
|
||||
// subframe it builds the weighted LP filters A(z/γ3) and A(z/γ4), forms the
|
||||
// residual through A(z/γ3), derives a spectral-tilt compensation coefficient
|
||||
// from the truncated impulse response, applies it via preemphasis, synthesizes
|
||||
// through 1/A(z/γ4), and finally matches the energy back to the synthesis with
|
||||
// AGC. Uses the building blocks weightAi/residu/synFilt/preemphasis/agc; this is
|
||||
// the orchestration that chains them. Bit-exact with the reference.
|
||||
|
||||
const (
|
||||
cL_H = 22 // truncated impulse-response length
|
||||
cMU = 26214 // tilt compensation factor (0.8)
|
||||
)
|
||||
|
||||
// γ bandwidth-expansion factors (pstfilt.cpp).
|
||||
var gamma3MR122 = [cM]int16{22938, 16057, 11240, 7868, 5508, 3856, 2699, 1889, 1322, 925}
|
||||
var gamma3 = [cM]int16{18022, 9912, 5451, 2998, 1649, 907, 499, 274, 151, 83}
|
||||
var gamma4MR122 = [cM]int16{24576, 18432, 13824, 10368, 7776, 5832, 4374, 3281, 2461, 1846}
|
||||
var gamma4 = [cM]int16{22938, 16057, 11240, 7868, 5508, 3856, 2699, 1889, 1322, 925}
|
||||
|
||||
// postFilterState holds the formant post-filter memory across frames.
|
||||
type postFilterState struct {
|
||||
res2 [cL_SUBFR]int16
|
||||
memSynPst [cM]int16
|
||||
preemph int16
|
||||
agc agcState
|
||||
synthBuf [cM + cL_FRAME]int16 // M history + current frame
|
||||
}
|
||||
|
||||
func (st *postFilterState) reset() {
|
||||
*st = postFilterState{}
|
||||
}
|
||||
|
||||
// postFilter post-filters one 160-sample synthesis frame in place. az4 holds the
|
||||
// four per-subframe LP coefficient sets (4*cMP1). Mirrors Post_Filter.
|
||||
func (st *postFilterState) postFilter(mode int, syn []int16, az4 []int16) {
|
||||
var ap3, ap4 [cMP1]int16
|
||||
var h [cL_H]int16
|
||||
|
||||
synWork := st.synthBuf[cM:] // syn_work[i] == synthBuf[M+i]
|
||||
copy(synWork[:cL_FRAME], syn[:cL_FRAME])
|
||||
|
||||
azOff := 0
|
||||
for iSubfr := 0; iSubfr < cL_FRAME; iSubfr += cL_SUBFR {
|
||||
az := az4[azOff:]
|
||||
if mode == dMR122 || mode == dMR102 {
|
||||
weightAi(az, gamma3MR122[:], ap3[:])
|
||||
weightAi(az, gamma4MR122[:], ap4[:])
|
||||
} else {
|
||||
weightAi(az, gamma3[:], ap3[:])
|
||||
weightAi(az, gamma4[:], ap4[:])
|
||||
}
|
||||
|
||||
// Residual through A(z/γ3); syn_work[iSubfr] == synthBuf[M+iSubfr].
|
||||
residu(ap3[:], st.synthBuf[:], cM+iSubfr, cL_SUBFR, st.res2[:])
|
||||
|
||||
// Truncated impulse response of A(z/γ3)/A(z/γ4).
|
||||
copy(h[:cMP1], ap3[:cMP1])
|
||||
for i := cMP1; i < cL_H; i++ {
|
||||
h[i] = 0
|
||||
}
|
||||
synFilt(ap4[:], h[:], h[:], cL_H, h[cMP1:], false)
|
||||
|
||||
// Tilt compensation coefficient from the impulse-response autocorr.
|
||||
var lTmp int32
|
||||
for i := cL_H - 1; i >= 0; i-- {
|
||||
t2 := int32(h[i]) * int32(h[i])
|
||||
if t2 == 0x40000000 {
|
||||
break // reference sets overflow and stops without adding
|
||||
}
|
||||
lTmp = L_add(lTmp, t2<<1)
|
||||
}
|
||||
temp1 := int16(lTmp >> 16)
|
||||
|
||||
lTmp = 0
|
||||
for i := cL_H - 2; i >= 0; i-- {
|
||||
t2 := int32(h[i]) * int32(h[i+1])
|
||||
if t2 == 0x40000000 {
|
||||
break
|
||||
}
|
||||
lTmp = L_add(lTmp, t2<<1)
|
||||
}
|
||||
temp2 := int16(lTmp >> 16)
|
||||
|
||||
if temp2 <= 0 {
|
||||
temp2 = 0
|
||||
} else {
|
||||
lt := (int32(temp2) * cMU) >> 15
|
||||
if lt&0x00010000 != 0 {
|
||||
lt |= int32(-0x10000)
|
||||
}
|
||||
temp2 = int16(lt)
|
||||
temp2 = div_s(temp2, temp1)
|
||||
}
|
||||
|
||||
preemphasis(&st.preemph, st.res2[:], temp2, cL_SUBFR)
|
||||
|
||||
synFilt(ap4[:], st.res2[:], syn[iSubfr:], cL_SUBFR, st.memSynPst[:], true)
|
||||
|
||||
st.agc.agc(synWork[iSubfr:], syn[iSubfr:], cAGC_FAC, cL_SUBFR)
|
||||
|
||||
azOff += cMP1
|
||||
}
|
||||
|
||||
// Carry the last M samples as history for the next frame.
|
||||
copy(st.synthBuf[:cM], synWork[cL_FRAME-cM:cL_FRAME])
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package amrnb
|
||||
|
||||
// LP synthesis and analysis filters, ported from opencore-amrnb syn_filt.cpp
|
||||
// and residu.cpp (order 10). synFilt runs the all-pole synthesis 1/A(z) over a
|
||||
// subframe, carrying M samples of filter memory; residu runs the all-zero
|
||||
// analysis A(z). Both accumulate products in wrapping int32 (the reference's
|
||||
// non-saturating MAC/MSU), so a straightforward per-output loop is bit-exact;
|
||||
// synFilt's output clamp reproduces the reference's exact boundary behaviour.
|
||||
|
||||
// synClamp reproduces the Syn_filt overflow clamp: in-range values are an
|
||||
// arithmetic >>12, out-of-range saturate to ±MAX_16. The asymmetric boundary
|
||||
// (s == 0x07ffffff -> MIN_16) is a reference quirk preserved deliberately.
|
||||
func synClamp(s int32) int16 {
|
||||
if uint32(s+134217728) < 0x0fffffff {
|
||||
return int16(s >> 12)
|
||||
} else if s > 0x07ffffff {
|
||||
return maxInt16
|
||||
}
|
||||
return minInt16
|
||||
}
|
||||
|
||||
// synFilt computes y = x convolved with 1/A(z) over lg samples, where a[0:M+1]
|
||||
// are the LP coefficients (a[0]=4096, Q12). mem[0:M] holds the M past output
|
||||
// samples; if update is true it is refreshed with the last M outputs. Mirrors
|
||||
// Syn_filt.
|
||||
func synFilt(a, x, y []int16, lg int, mem []int16, update bool) {
|
||||
var yy [cL_FRAME + cM]int16
|
||||
copy(yy[:cM], mem[:cM])
|
||||
|
||||
af := a[:cMP1] // hoist bounds: a[0..M]
|
||||
xf := x[:lg]
|
||||
for i := 0; i < lg; i++ {
|
||||
s := int32(0x800) + int32(af[0])*int32(xf[i])
|
||||
yw := yy[i : i+cM] // yy[cM+i-j] == yw[cM-j], all in [0,cM)
|
||||
s -= int32(af[1]) * int32(yw[cM-1])
|
||||
s -= int32(af[2]) * int32(yw[cM-2])
|
||||
s -= int32(af[3]) * int32(yw[cM-3])
|
||||
s -= int32(af[4]) * int32(yw[cM-4])
|
||||
s -= int32(af[5]) * int32(yw[cM-5])
|
||||
s -= int32(af[6]) * int32(yw[cM-6])
|
||||
s -= int32(af[7]) * int32(yw[cM-7])
|
||||
s -= int32(af[8]) * int32(yw[cM-8])
|
||||
s -= int32(af[9]) * int32(yw[cM-9])
|
||||
s -= int32(af[10]) * int32(yw[cM-10])
|
||||
out := synClamp(s)
|
||||
yy[cM+i] = out
|
||||
y[i] = out
|
||||
}
|
||||
|
||||
if update {
|
||||
copy(mem[:cM], y[lg-cM:lg])
|
||||
}
|
||||
}
|
||||
|
||||
// residu computes the LP residual A(z)·input over inputLen samples. buf holds
|
||||
// the input with at least M samples of history before start (buf[start-M:start]
|
||||
// are the past samples). Output goes to residual[0:inputLen]. Output is
|
||||
// truncated (not saturated) to 16 bits, matching the reference. Mirrors Residu.
|
||||
func residu(coef, buf []int16, start, inputLen int, residual []int16) {
|
||||
// res[i] = (0x800 + sum_{j=0..M} coef[j]·buf[start+i-j]) >> 12. Reversing the
|
||||
// coefficients turns the inner sum into a fixed-window FIR over buf[start-M:]
|
||||
// (res[i] = sum_k arev[k]·buf[start-M+i+k]), so it vectorises via firRaw. The
|
||||
// reference accumulates in plain wrapping int32, so the lane-parallel order is
|
||||
// bit-identical, and adding the 0x800 rounding bias after the sum is exact
|
||||
// (integer addition is associative mod 2^32).
|
||||
var arev [cMP1]int16
|
||||
for k := 0; k <= cM; k++ {
|
||||
arev[k] = coef[cM-k]
|
||||
}
|
||||
var dstArr [cL_SUBFR]int32
|
||||
var dst []int32
|
||||
if inputLen <= cL_SUBFR {
|
||||
dst = dstArr[:inputLen]
|
||||
} else {
|
||||
dst = make([]int32, inputLen)
|
||||
}
|
||||
firRaw(dst, buf[start-cM:], arev[:])
|
||||
for i := 0; i < inputLen; i++ {
|
||||
residual[i] = int16((0x800 + dst[i]) >> 12)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Package amrnb implements the AMR-NB (3GPP TS 26.071 / 26.090) narrowband
|
||||
// speech codec in pure Go, with RFC 4867 RTP payload framing.
|
||||
//
|
||||
// AMR-NB operates on 8 kHz mono audio in 20 ms frames (160 samples). It defines
|
||||
// eight speech coding modes (0..7) spanning 4.75 to 12.2 kbit/s, plus a comfort
|
||||
// noise (SID) frame used by DTX.
|
||||
package amrnb
|
||||
|
||||
// SampleRate is the fixed AMR-NB sampling rate in Hz.
|
||||
const SampleRate = 8000
|
||||
|
||||
// FrameSamples is the number of PCM samples in one 20 ms AMR-NB frame.
|
||||
const FrameSamples = 160
|
||||
|
||||
// Mode identifies an AMR-NB speech coding mode (0..7).
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
Mode0475 Mode = iota // 4.75 kbit/s
|
||||
Mode0515 // 5.15 kbit/s
|
||||
Mode0590 // 5.90 kbit/s
|
||||
Mode0670 // 6.70 kbit/s
|
||||
Mode0740 // 7.40 kbit/s
|
||||
Mode0795 // 7.95 kbit/s
|
||||
Mode1020 // 10.2 kbit/s
|
||||
Mode1220 // 12.2 kbit/s
|
||||
numSpeechModes
|
||||
)
|
||||
|
||||
// Frame type (FT) values carried in the RFC 4867 ToC byte. Values 0..7 are the
|
||||
// speech modes; the remainder describe comfort noise and no-data frames. Note
|
||||
// that AMR-NB places SID at FT 8 (AMR-WB uses FT 9).
|
||||
const (
|
||||
FTMode0475 = 0
|
||||
FTMode0515 = 1
|
||||
FTMode0590 = 2
|
||||
FTMode0670 = 3
|
||||
FTMode0740 = 4
|
||||
FTMode0795 = 5
|
||||
FTMode1020 = 6
|
||||
FTMode1220 = 7
|
||||
FTSID = 8 // comfort noise (SID)
|
||||
FTSpeechLost = 14 // speech lost (RFC 4867)
|
||||
FTNoData = 15 // no data
|
||||
)
|
||||
|
||||
// modeBits is the number of speech bits per AMR-NB mode (0..7). These are the
|
||||
// canonical bit counts from 3GPP TS 26.101 (and RFC 4867 Table 1a).
|
||||
var modeBits = [numSpeechModes]int{
|
||||
95, // 4.75
|
||||
103, // 5.15
|
||||
118, // 5.90
|
||||
134, // 6.70
|
||||
148, // 7.40
|
||||
159, // 7.95
|
||||
204, // 10.2
|
||||
244, // 12.2
|
||||
}
|
||||
|
||||
// sidBits is the number of bits in an AMR-NB SID (comfort noise) frame.
|
||||
const sidBits = 39
|
||||
|
||||
// frameBitsByFT returns the number of payload bits for a given frame type and
|
||||
// whether the frame type is a recognized one that carries bits. SID and speech
|
||||
// modes carry bits; SPEECH_LOST and NO_DATA carry none.
|
||||
func frameBitsByFT(ft int) (bits int, ok bool) {
|
||||
switch {
|
||||
case ft >= FTMode0475 && ft <= FTMode1220:
|
||||
return modeBits[ft], true
|
||||
case ft == FTSID:
|
||||
return sidBits, true
|
||||
case ft == FTSpeechLost, ft == FTNoData:
|
||||
return 0, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// frameBytesByFT returns the number of octet-aligned bytes occupied by a frame
|
||||
// of the given type (bits rounded up to whole octets).
|
||||
func frameBytesByFT(ft int) (bytes int, ok bool) {
|
||||
bits, ok := frameBitsByFT(ft)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return (bits + 7) / 8, true
|
||||
}
|
||||
|
||||
// Bits returns the number of speech bits produced by this mode.
|
||||
func (m Mode) Bits() int {
|
||||
if m < 0 || m >= numSpeechModes {
|
||||
return 0
|
||||
}
|
||||
return modeBits[m]
|
||||
}
|
||||
|
||||
// Bytes returns the octet-aligned byte count of a frame in this mode.
|
||||
func (m Mode) Bytes() int { return (m.Bits() + 7) / 8 }
|
||||
|
||||
// FrameType returns the RFC 4867 frame-type value for this mode.
|
||||
func (m Mode) FrameType() int { return int(m) }
|
||||
|
||||
// Valid reports whether m is a defined speech mode.
|
||||
func (m Mode) Valid() bool { return m >= Mode0475 && m < numSpeechModes }
|
||||
|
||||
// Bitrate returns the nominal bit rate of the mode in bit/s.
|
||||
func (m Mode) Bitrate() int {
|
||||
switch m {
|
||||
case Mode0475:
|
||||
return 4750
|
||||
case Mode0515:
|
||||
return 5150
|
||||
case Mode0590:
|
||||
return 5900
|
||||
case Mode0670:
|
||||
return 6700
|
||||
case Mode0740:
|
||||
return 7400
|
||||
case Mode0795:
|
||||
return 7950
|
||||
case Mode1020:
|
||||
return 10200
|
||||
case Mode1220:
|
||||
return 12200
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package amrnb
|
||||
|
||||
import "errors"
|
||||
|
||||
// RFC 4867 RTP payload framing for AMR-NB. Two formats are supported:
|
||||
//
|
||||
// - octet-aligned (octet-align=1): every field starts on an octet boundary.
|
||||
// Layout: [CMR(1 byte)] [ToC byte per frame] [speech bytes per frame].
|
||||
// - bandwidth-efficient: fields are bit-packed with no padding.
|
||||
// Layout: CMR(4 bits) then, per frame, ToC(6 bits) then the speech bits.
|
||||
//
|
||||
// Only the fields VoiceBlender needs are modelled. Interleaving and the
|
||||
// CRC/robust-sorting options are not used (they are negotiated off by default
|
||||
// and we never offer them).
|
||||
|
||||
// noCMR is the "no mode request" CMR value (15) emitted when we have no
|
||||
// preference to signal to the peer.
|
||||
const noCMR = 15
|
||||
|
||||
// frame is one decoded ToC entry plus its raw speech bits.
|
||||
type frame struct {
|
||||
ft int // frame type (FT)
|
||||
q bool // quality bit (Q): true = good frame
|
||||
data []byte // speech bits, octet-aligned, left-justified in the final byte
|
||||
}
|
||||
|
||||
var (
|
||||
errShortPayload = errors.New("amrnb: payload too short")
|
||||
errBadFrameType = errors.New("amrnb: unknown frame type")
|
||||
)
|
||||
|
||||
// packPayloadOctet builds an octet-aligned payload carrying the given frames
|
||||
// with the supplied CMR (use noCMR for none).
|
||||
func packPayloadOctet(cmr int, frames []frame) []byte {
|
||||
out := []byte{byte(cmr&0x0F) << 4}
|
||||
for i, f := range frames {
|
||||
last := i == len(frames)-1
|
||||
toc := byte(f.ft&0x0F) << 3
|
||||
if !last {
|
||||
toc |= 0x80 // F bit: more frames follow
|
||||
}
|
||||
if f.q {
|
||||
toc |= 0x04 // Q bit
|
||||
}
|
||||
out = append(out, toc)
|
||||
}
|
||||
for _, f := range frames {
|
||||
nb, _ := frameBytesByFT(f.ft)
|
||||
buf := make([]byte, nb)
|
||||
copy(buf, f.data)
|
||||
out = append(out, buf...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// unpackPayloadOctet parses an octet-aligned payload into its frames and CMR.
|
||||
func unpackPayloadOctet(p []byte) (cmr int, frames []frame, err error) {
|
||||
if len(p) < 1 {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
cmr = int(p[0] >> 4)
|
||||
idx := 1
|
||||
// Read ToC bytes until the F bit clears.
|
||||
var tocs []byte
|
||||
for {
|
||||
if idx >= len(p) {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
toc := p[idx]
|
||||
idx++
|
||||
tocs = append(tocs, toc)
|
||||
if toc&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, toc := range tocs {
|
||||
ft := int(toc>>3) & 0x0F
|
||||
q := toc&0x04 != 0
|
||||
nb, ok := frameBytesByFT(ft)
|
||||
if !ok {
|
||||
return 0, nil, errBadFrameType
|
||||
}
|
||||
if idx+nb > len(p) {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
data := make([]byte, nb)
|
||||
copy(data, p[idx:idx+nb])
|
||||
idx += nb
|
||||
frames = append(frames, frame{ft: ft, q: q, data: data})
|
||||
}
|
||||
return cmr, frames, nil
|
||||
}
|
||||
|
||||
// packPayloadBandwidthEfficient builds a bandwidth-efficient payload.
|
||||
func packPayloadBandwidthEfficient(cmr int, frames []frame) []byte {
|
||||
w := &bitWriter{}
|
||||
w.writeBits(uint32(cmr&0x0F), 4)
|
||||
for i, f := range frames {
|
||||
last := i == len(frames)-1
|
||||
fBit := uint32(0)
|
||||
if !last {
|
||||
fBit = 1
|
||||
}
|
||||
w.writeBits(fBit, 1)
|
||||
w.writeBits(uint32(f.ft&0x0F), 4)
|
||||
q := uint32(0)
|
||||
if f.q {
|
||||
q = 1
|
||||
}
|
||||
w.writeBits(q, 1)
|
||||
}
|
||||
for _, f := range frames {
|
||||
bits, _ := frameBitsByFT(f.ft)
|
||||
w.writeBitsFromBytes(f.data, bits)
|
||||
}
|
||||
return w.bytes()
|
||||
}
|
||||
|
||||
// unpackPayloadBandwidthEfficient parses a bandwidth-efficient payload.
|
||||
func unpackPayloadBandwidthEfficient(p []byte) (cmr int, frames []frame, err error) {
|
||||
r := &bitReader{data: p}
|
||||
c, ok := r.readBits(4)
|
||||
if !ok {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
cmr = int(c)
|
||||
type toc struct {
|
||||
ft int
|
||||
q bool
|
||||
}
|
||||
var tocs []toc
|
||||
for {
|
||||
fBit, ok := r.readBits(1)
|
||||
if !ok {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
ft, ok := r.readBits(4)
|
||||
if !ok {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
qBit, ok := r.readBits(1)
|
||||
if !ok {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
if _, valid := frameBitsByFT(int(ft)); !valid {
|
||||
return 0, nil, errBadFrameType
|
||||
}
|
||||
tocs = append(tocs, toc{ft: int(ft), q: qBit == 1})
|
||||
if fBit == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, t := range tocs {
|
||||
bits, _ := frameBitsByFT(t.ft)
|
||||
data, ok := r.readBitsToBytes(bits)
|
||||
if !ok {
|
||||
return 0, nil, errShortPayload
|
||||
}
|
||||
frames = append(frames, frame{ft: t.ft, q: t.q, data: data})
|
||||
}
|
||||
return cmr, frames, nil
|
||||
}
|
||||
|
||||
// bitWriter accumulates bits MSB-first into a byte slice.
|
||||
type bitWriter struct {
|
||||
buf []byte
|
||||
cur byte
|
||||
nimb int // number of bits filled in cur
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeBit(b uint32) {
|
||||
w.cur |= byte(b&1) << uint(7-w.nimb)
|
||||
w.nimb++
|
||||
if w.nimb == 8 {
|
||||
w.buf = append(w.buf, w.cur)
|
||||
w.cur = 0
|
||||
w.nimb = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeBits(v uint32, n int) {
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
w.writeBit(v >> uint(i))
|
||||
}
|
||||
}
|
||||
|
||||
// writeBitsFromBytes writes the first n bits of data, MSB-first.
|
||||
func (w *bitWriter) writeBitsFromBytes(data []byte, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
bit := uint32(0)
|
||||
if byteIdx := i / 8; byteIdx < len(data) {
|
||||
bit = uint32(data[byteIdx]>>uint(7-i%8)) & 1
|
||||
}
|
||||
w.writeBit(bit)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) bytes() []byte {
|
||||
out := w.buf
|
||||
if w.nimb > 0 {
|
||||
out = append(out, w.cur)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// bitReader reads bits MSB-first from a byte slice.
|
||||
type bitReader struct {
|
||||
data []byte
|
||||
pos int // bit position
|
||||
}
|
||||
|
||||
func (r *bitReader) readBits(n int) (uint32, bool) {
|
||||
var v uint32
|
||||
for i := 0; i < n; i++ {
|
||||
if r.pos >= len(r.data)*8 {
|
||||
return 0, false
|
||||
}
|
||||
bit := uint32(r.data[r.pos/8]>>uint(7-r.pos%8)) & 1
|
||||
v = (v << 1) | bit
|
||||
r.pos++
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
// readBitsToBytes reads n bits and returns them left-justified in a byte slice.
|
||||
func (r *bitReader) readBitsToBytes(n int) ([]byte, bool) {
|
||||
out := make([]byte, (n+7)/8)
|
||||
for i := 0; i < n; i++ {
|
||||
if r.pos >= len(r.data)*8 {
|
||||
return nil, false
|
||||
}
|
||||
bit := r.data[r.pos/8] >> uint(7-r.pos%8) & 1
|
||||
out[i/8] |= bit << uint(7-i%8)
|
||||
r.pos++
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package amrnb
|
||||
|
||||
// SIMD-accelerated non-saturating int16 FIR, used by the hot filter kernels.
|
||||
// The reference accumulates products into a plain int32 (no per-term
|
||||
// saturation), and integer addition is associative modulo 2^32, so accumulating
|
||||
// in the lane-parallel order an AVX2 kernel uses yields a bit-identical result.
|
||||
// firRawGeneric is the portable fallback and the oracle the assembly is
|
||||
// fuzz-tested against.
|
||||
|
||||
// firRawGeneric computes the raw (pre-round, pre-shift) FIR sums
|
||||
// dst[o] = sum_t x[o+t]*coef[t] for every output o, accumulating in wrapping
|
||||
// int32. The amd64 build vectorises this across 8 outputs at a time; this is
|
||||
// the portable fallback and the fuzz-test oracle. Requires
|
||||
// len(x) >= len(dst)+len(coef)-1.
|
||||
func firRawGeneric(dst []int32, x []int16, coef []int16) {
|
||||
taps := len(coef)
|
||||
for o := range dst {
|
||||
xw := x[o : o+taps]
|
||||
var s int32
|
||||
for t, c := range coef {
|
||||
s += int32(xw[t]) * int32(c)
|
||||
}
|
||||
dst[o] = s
|
||||
}
|
||||
}
|
||||
|
||||
// firDotGeneric computes the wrapping-int32 dot product sum_i a[i]*b[i] over the
|
||||
// first len(a) elements (requires len(b) >= len(a)). The amd64 build vectorises
|
||||
// it with VPMADDWD; this is the portable fallback and the fuzz-test oracle.
|
||||
func firDotGeneric(a, b []int16) int32 {
|
||||
bb := b[:len(a)]
|
||||
var s int32
|
||||
for i, av := range a {
|
||||
s += int32(av) * int32(bb[i])
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//go:build amd64
|
||||
|
||||
package amrnb
|
||||
|
||||
import "golang.org/x/sys/cpu"
|
||||
|
||||
var useAVX2 = cpu.X86.HasAVX2
|
||||
|
||||
// firRaw computes dst[o] = sum_t x[o+t]*coef[t] (wrapping int32). On AVX2 it
|
||||
// vectorises across 8 outputs per iteration; otherwise it falls back.
|
||||
func firRaw(dst []int32, x []int16, coef []int16) {
|
||||
if useAVX2 && len(dst) >= 8 && len(coef) <= maxFIRTaps {
|
||||
var c32 [maxFIRTaps]int32
|
||||
for i, c := range coef {
|
||||
c32[i] = int32(c)
|
||||
}
|
||||
firRawAVX2(dst, x, c32[:len(coef)])
|
||||
return
|
||||
}
|
||||
firRawGeneric(dst, x, coef)
|
||||
}
|
||||
|
||||
// maxFIRTaps bounds the stack-resident sign-extended coefficient buffer. It
|
||||
// covers the longest FIR used: the open-loop pitch correlation window, which
|
||||
// spans a whole frame (L_FRAME = 160 taps).
|
||||
const maxFIRTaps = 256
|
||||
|
||||
// firRawAVX2 is implemented in simd_amd64.s. coef32 holds sign-extended taps.
|
||||
//
|
||||
//go:noescape
|
||||
func firRawAVX2(dst []int32, x []int16, coef32 []int32)
|
||||
|
||||
// firDot computes the wrapping-int32 dot product sum_i a[i]*b[i] over len(a)
|
||||
// elements. On AVX2 it uses VPMADDWD across 16 int16 per iteration; otherwise it
|
||||
// falls back.
|
||||
func firDot(a, b []int16) int32 {
|
||||
if useAVX2 && len(a) >= 16 {
|
||||
return firDotAVX2(a, b)
|
||||
}
|
||||
return firDotGeneric(a, b)
|
||||
}
|
||||
|
||||
// firDotAVX2 is implemented in simd_amd64.s. It reads len(a) int16 from a and b.
|
||||
//
|
||||
//go:noescape
|
||||
func firDotAVX2(a, b []int16) int32
|
||||
@@ -0,0 +1,121 @@
|
||||
//go:build amd64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func firRawAVX2(dst []int32, x []int16, coef32 []int32)
|
||||
// dst[o] = sum_t x[o+t]*coef32[t], vectorised across 8 outputs per iteration:
|
||||
// lane k accumulates output og+k, so no per-output horizontal reduction.
|
||||
TEXT ·firRawAVX2(SB), NOSPLIT, $0-72
|
||||
MOVQ dst_base+0(FP), DI
|
||||
MOVQ dst_len+8(FP), CX // number of outputs
|
||||
MOVQ x_base+24(FP), SI // &x[og] (group base)
|
||||
MOVQ coef32_base+48(FP), BX
|
||||
MOVQ coef32_len+56(FP), R9 // taps
|
||||
|
||||
MOVQ CX, R10
|
||||
SHRQ $3, R10 // R10 = groups of 8 outputs
|
||||
JZ firtail
|
||||
|
||||
grouploop:
|
||||
VPXOR Y0, Y0, Y0
|
||||
MOVQ SI, R11 // x cursor for this group
|
||||
MOVQ BX, R12 // coef cursor
|
||||
MOVQ R9, R13 // tap counter
|
||||
|
||||
taploop:
|
||||
VPMOVSXWD (R11), Y1 // 8 int16 x[og+t..og+t+7] -> 8 int32
|
||||
VPBROADCASTD (R12), Y2 // coef32[t] in all 8 lanes
|
||||
VPMULLD Y2, Y1, Y3
|
||||
VPADDD Y3, Y0, Y0
|
||||
ADDQ $2, R11 // next tap shifts x window by 1 int16
|
||||
ADDQ $4, R12
|
||||
DECQ R13
|
||||
JNZ taploop
|
||||
|
||||
VMOVDQU Y0, (DI)
|
||||
ADDQ $32, DI // 8 int32 stored
|
||||
ADDQ $16, SI // next group: x base += 8 int16
|
||||
DECQ R10
|
||||
JNZ grouploop
|
||||
|
||||
firtail:
|
||||
ANDQ $7, CX // remaining outputs (<8)
|
||||
JZ firdone
|
||||
|
||||
tailout:
|
||||
MOVQ SI, R11
|
||||
MOVQ BX, R12
|
||||
MOVQ R9, R13
|
||||
XORL AX, AX
|
||||
|
||||
tailtap:
|
||||
MOVWLSX (R11), DX
|
||||
MOVL (R12), R8
|
||||
IMULL R8, DX
|
||||
ADDL DX, AX
|
||||
ADDQ $2, R11
|
||||
ADDQ $4, R12
|
||||
DECQ R13
|
||||
JNZ tailtap
|
||||
|
||||
MOVL AX, (DI)
|
||||
ADDQ $4, DI
|
||||
ADDQ $2, SI // next single output: x base += 1 int16
|
||||
DECQ CX
|
||||
JNZ tailout
|
||||
|
||||
firdone:
|
||||
VZEROUPPER
|
||||
RET
|
||||
|
||||
// func firDotAVX2(a, b []int16) int32
|
||||
// Returns sum_i a[i]*b[i] (wrapping int32) over len(a) elements. VPMADDWD folds
|
||||
// each int16 pair into an int32 (exact mod 2^32); lanes accumulate, then a
|
||||
// horizontal sum + scalar tail. All adds wrap, so the order matches the scalar.
|
||||
TEXT ·firDotAVX2(SB), NOSPLIT, $0-52
|
||||
MOVQ a_base+0(FP), SI
|
||||
MOVQ a_len+8(FP), CX
|
||||
MOVQ b_base+24(FP), DI
|
||||
VPXOR Y0, Y0, Y0 // accumulator: 8 int32 lanes
|
||||
|
||||
MOVQ CX, R10
|
||||
SHRQ $4, R10 // groups of 16 int16
|
||||
JZ dtail
|
||||
|
||||
dloop:
|
||||
VMOVDQU (SI), Y1 // 16 int16 a
|
||||
VMOVDQU (DI), Y2 // 16 int16 b
|
||||
VPMADDWD Y2, Y1, Y3 // 8 int32: a[2k]*b[2k] + a[2k+1]*b[2k+1]
|
||||
VPADDD Y3, Y0, Y0
|
||||
ADDQ $32, SI
|
||||
ADDQ $32, DI
|
||||
DECQ R10
|
||||
JNZ dloop
|
||||
|
||||
dtail:
|
||||
// horizontal sum of the 8 int32 lanes -> AX
|
||||
VEXTRACTI128 $1, Y0, X1
|
||||
VPADDD X1, X0, X0 // 4 int32
|
||||
VPSHUFD $0x4E, X0, X1 // [2,3,0,1]
|
||||
VPADDD X1, X0, X0 // 2 int32
|
||||
VPSHUFD $0xB1, X0, X1 // [1,0,3,2]
|
||||
VPADDD X1, X0, X0 // 1 int32
|
||||
MOVD X0, AX
|
||||
|
||||
ANDQ $15, CX // remaining elements (<16)
|
||||
JZ ddone
|
||||
|
||||
dscalar:
|
||||
MOVWLSX (SI), DX
|
||||
MOVWLSX (DI), R8
|
||||
IMULL R8, DX
|
||||
ADDL DX, AX
|
||||
ADDQ $2, SI
|
||||
ADDQ $2, DI
|
||||
DECQ CX
|
||||
JNZ dscalar
|
||||
|
||||
ddone:
|
||||
VZEROUPPER
|
||||
MOVL AX, ret+48(FP)
|
||||
RET
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !amd64
|
||||
|
||||
package amrnb
|
||||
|
||||
// firRaw falls back to the portable implementation on non-amd64 targets.
|
||||
func firRaw(dst []int32, x []int16, coef []int16) { firRawGeneric(dst, x, coef) }
|
||||
|
||||
// firDot falls back to the portable implementation on non-amd64 targets.
|
||||
func firDot(a, b []int16) int32 { return firDotGeneric(a, b) }
|
||||
@@ -0,0 +1,62 @@
|
||||
// Code generated from opencore-amrnb — DO NOT EDIT.
|
||||
//
|
||||
// ROM tables extracted verbatim from the Apache-2.0 opencore-amrnb C
|
||||
// reference; element counts are verified against the C array sizes.
|
||||
|
||||
package amrnb
|
||||
|
||||
var startPos = []int16{
|
||||
0, 2, 0, 3, 0, 2, 0, 3, 1, 3, 2, 4,
|
||||
1, 4, 1, 4,
|
||||
}
|
||||
|
||||
var gray = []int16{
|
||||
0, 1, 3, 2, 6, 4, 5, 7,
|
||||
}
|
||||
|
||||
var dgray = []int16{
|
||||
0, 1, 3, 2, 5, 6, 4, 7,
|
||||
}
|
||||
|
||||
var ph_imp_low_MR795 = []int16{
|
||||
26777, 801, 2505, -683, -1382, 582, 604, -1274, 3511, -5894, 4534, -499,
|
||||
-1940, 3011, -5058, 5614, -1990, -1061, -1459, 4442, -700, -5335, 4609, 452,
|
||||
-589, -3352, 2953, 1267, -1212, -2590, 1731, 3670, -4475, -975, 4391, -2537,
|
||||
949, -1363, -979, 5734,
|
||||
}
|
||||
|
||||
var ph_imp_mid_MR795 = []int16{
|
||||
30274, 3831, -4036, 2972, -1048, -1002, 2477, -3043, 2815, -2231, 1753, -1611,
|
||||
1714, -1775, 1543, -1008, 429, -169, 472, -1264, 2176, -2706, 2523, -1621,
|
||||
344, 826, -1529, 1724, -1657, 1701, -2063, 2644, -3060, 2897, -1978, 557,
|
||||
780, -1369, 842, 655,
|
||||
}
|
||||
|
||||
var ph_imp_low = []int16{
|
||||
14690, 11518, 1268, -2761, -5671, 7514, -35, -2807, -3040, 4823, 2952, -8424,
|
||||
3785, 1455, 2179, -8637, 8051, -2103, -1454, 777, 1108, -2385, 2254, -363,
|
||||
-674, -2103, 6046, -5681, 1072, 3123, -5058, 5312, -2329, -3728, 6924, -3889,
|
||||
675, -1775, 29, 10145,
|
||||
}
|
||||
|
||||
var ph_imp_mid = []int16{
|
||||
30274, 3831, -4036, 2972, -1048, -1002, 2477, -3043, 2815, -2231, 1753, -1611,
|
||||
1714, -1775, 1543, -1008, 429, -169, 472, -1264, 2176, -2706, 2523, -1621,
|
||||
344, 826, -1529, 1724, -1657, 1701, -2063, 2644, -3060, 2897, -1978, 557,
|
||||
780, -1369, 842, 655,
|
||||
}
|
||||
|
||||
// inter_6_pred_lt is the 1/6-resolution pitch interpolation FIR
|
||||
// (pred_lt.cpp), FIR_SIZE = L_INTER10*UP_SAMP_MAX+1 = 61.
|
||||
var inter_6_pred_lt = []int16{
|
||||
29443, 28346, 25207, 20449, 14701, 8693, 3143, -1352, -4402, -5865, -5850, -4673,
|
||||
-2783, -672, 1211, 2536, 3130, 2991, 2259, 1170, 0, -1001, -1652, -1868,
|
||||
-1666, -1147, -464, 218, 756, 1060, 1099, 904, 550, 135, -245, -514,
|
||||
-634, -602, -451, -231, 0, 191, 308, 340, 296, 198, 78, -36,
|
||||
-120, -163, -165, -132, -79, -19, 34, 73, 91, 89, 70, 38,
|
||||
0,
|
||||
}
|
||||
|
||||
// inter6Pitch: 1/6-resolution interpolation FIR for the closed-loop pitch
|
||||
// search (inter_36_tab.cpp), FIR_SIZE = UP_SAMP_MAX*L_INTER_SRCH+1 = 25.
|
||||
var inter6Pitch = []int16{29519, 28316, 24906, 19838, 13896, 7945, 2755, -1127, -3459, -4304, -3969, -2899, -1561, -336, 534, 970, 1023, 823, 516, 220, 0, -131, -194, -215, 0}
|
||||
@@ -0,0 +1,186 @@
|
||||
// Code generated from opencore-amrnb — DO NOT EDIT.
|
||||
//
|
||||
// ROM tables extracted verbatim from the Apache-2.0 opencore-amrnb C
|
||||
// reference; element counts are verified against the C array sizes.
|
||||
|
||||
package amrnb
|
||||
|
||||
var qua_gain_pitch = []int16{
|
||||
0, 3277, 6556, 8192, 9830, 11469, 12288, 13107, 13926, 14746, 15565, 16384,
|
||||
17203, 18022, 18842, 19661,
|
||||
}
|
||||
|
||||
var qua_gain_code = []int16{
|
||||
159, -3776, -22731, 206, -3394, -20428, 268, -3005, -18088, 349, -2615, -15739,
|
||||
419, -2345, -14113, 482, -2138, -12867, 554, -1932, -11629, 637, -1726, -10387,
|
||||
733, -1518, -9139, 842, -1314, -7906, 969, -1106, -6656, 1114, -900, -5416,
|
||||
1281, -694, -4173, 1473, -487, -2931, 1694, -281, -1688, 1948, -75, -445,
|
||||
2241, 133, 801, 2577, 339, 2044, 2963, 545, 3285, 3408, 752, 4530,
|
||||
3919, 958, 5772, 4507, 1165, 7016, 5183, 1371, 8259, 5960, 1577, 9501,
|
||||
6855, 1784, 10745, 7883, 1991, 11988, 9065, 2197, 13231, 10425, 2404, 14474,
|
||||
12510, 2673, 16096, 16263, 3060, 18429, 21142, 3448, 20763, 27485, 3836, 23097,
|
||||
27485, 3836, 23097,
|
||||
}
|
||||
|
||||
var table_gain_highrates = []int16{
|
||||
577, 662, -2692, -16214, 806, 1836, -1185, -7135, 3109, 1052, -2008, -12086,
|
||||
4181, 1387, -1600, -9629, 2373, 1425, -1560, -9394, 3248, 1985, -1070, -6442,
|
||||
1827, 2320, -840, -5056, 941, 3314, -313, -1885, 2351, 2977, -471, -2838,
|
||||
3616, 2420, -777, -4681, 3451, 3096, -414, -2490, 2955, 4301, 72, 434,
|
||||
1848, 4500, 139, 836, 3884, 5416, 413, 2484, 1187, 7210, 835, 5030,
|
||||
3083, 9000, 1163, 7002, 7384, 883, -2267, -13647, 5962, 1506, -1478, -8900,
|
||||
5155, 2134, -963, -5800, 7944, 2009, -1052, -6335, 6507, 2250, -885, -5327,
|
||||
7670, 2752, -588, -3537, 5952, 3016, -452, -2724, 4898, 3764, -125, -751,
|
||||
6989, 3588, -196, -1177, 8174, 3978, -43, -260, 6064, 4404, 107, 645,
|
||||
7709, 5087, 320, 1928, 5523, 6021, 569, 3426, 7769, 7126, 818, 4926,
|
||||
6060, 7938, 977, 5885, 5594, 11487, 1523, 9172, 10581, 1356, -1633, -9831,
|
||||
9049, 1597, -1391, -8380, 9794, 2035, -1033, -6220, 8946, 2415, -780, -4700,
|
||||
10296, 2584, -681, -4099, 9407, 2734, -597, -3595, 8700, 3218, -356, -2144,
|
||||
9757, 3395, -277, -1669, 10177, 3892, -75, -454, 9170, 4528, 148, 891,
|
||||
10152, 5004, 296, 1781, 9114, 5735, 497, 2993, 10500, 6266, 628, 3782,
|
||||
10110, 7631, 919, 5534, 8844, 8727, 1117, 6728, 8956, 12496, 1648, 9921,
|
||||
12924, 976, -2119, -12753, 11435, 1755, -1252, -7539, 12138, 2328, -835, -5024,
|
||||
11388, 2368, -810, -4872, 10700, 3064, -429, -2580, 12332, 2861, -530, -3192,
|
||||
11722, 3327, -307, -1848, 11270, 3700, -150, -904, 10861, 4413, 110, 663,
|
||||
12082, 4533, 150, 902, 11283, 5205, 354, 2132, 11960, 6305, 637, 3837,
|
||||
11167, 7534, 900, 5420, 12128, 8329, 1049, 6312, 10969, 10777, 1429, 8604,
|
||||
10300, 17376, 2135, 12853, 13899, 1681, -1316, -7921, 12580, 2045, -1026, -6179,
|
||||
13265, 2439, -766, -4610, 14033, 2989, -465, -2802, 13452, 3098, -413, -2482,
|
||||
12396, 3658, -167, -1006, 13510, 3780, -119, -713, 12880, 4272, 62, 374,
|
||||
13533, 4861, 253, 1523, 12667, 5457, 424, 2552, 13854, 6106, 590, 3551,
|
||||
13031, 6483, 678, 4084, 13557, 7721, 937, 5639, 12957, 9311, 1213, 7304,
|
||||
13714, 11551, 1532, 9221, 12591, 15206, 1938, 11667, 15113, 1540, -1445, -8700,
|
||||
15072, 2333, -832, -5007, 14527, 2511, -723, -4352, 14692, 3199, -365, -2197,
|
||||
15382, 3560, -207, -1247, 14133, 3960, -50, -300, 15102, 4236, 50, 298,
|
||||
14332, 4824, 242, 1454, 14846, 5451, 422, 2542, 15306, 6083, 584, 3518,
|
||||
14329, 6888, 768, 4623, 15060, 7689, 930, 5602, 14406, 9426, 1231, 7413,
|
||||
15387, 9741, 1280, 7706, 14824, 14271, 1844, 11102, 13600, 24939, 2669, 16067,
|
||||
16396, 1969, -1082, -6517, 16817, 2832, -545, -3283, 15713, 2843, -539, -3248,
|
||||
16104, 3336, -303, -1825, 16384, 3963, -49, -294, 16940, 4579, 165, 992,
|
||||
15711, 4599, 171, 1030, 16222, 5448, 421, 2537, 16832, 6382, 655, 3945,
|
||||
15745, 7141, 821, 4944, 16326, 7469, 888, 5343, 16611, 8624, 1100, 6622,
|
||||
17028, 10418, 1379, 8303, 15905, 11817, 1565, 9423, 16878, 14690, 1887, 11360,
|
||||
16515, 20870, 2406, 14483, 18142, 2083, -999, -6013, 19401, 3178, -375, -2257,
|
||||
17508, 3426, -264, -1589, 20054, 4027, -25, -151, 18069, 4249, 54, 326,
|
||||
18952, 5066, 314, 1890, 17711, 5402, 409, 2461, 19835, 6192, 610, 3676,
|
||||
17950, 7014, 795, 4784, 21318, 7877, 966, 5816, 17910, 9289, 1210, 7283,
|
||||
19144, 9290, 1210, 7284, 20517, 11381, 1510, 9089, 18075, 14485, 1866, 11234,
|
||||
19999, 17882, 2177, 13108, 18842, 32764, 3072, 18494,
|
||||
}
|
||||
|
||||
var table_gain_lowrates = []int16{
|
||||
10813, 28753, 2879, 17333, 20480, 2785, -570, -3431, 18841, 6594, 703, 4235,
|
||||
6225, 7413, 876, 5276, 17203, 10444, 1383, 8325, 21626, 1269, -1731, -10422,
|
||||
21135, 4423, 113, 683, 11304, 1556, -1430, -8609, 19005, 12820, 1686, 10148,
|
||||
17367, 2498, -731, -4398, 17858, 4833, 244, 1472, 9994, 2498, -731, -4398,
|
||||
17530, 7864, 964, 5802, 14254, 1884, -1147, -6907, 15892, 3153, -387, -2327,
|
||||
6717, 1802, -1213, -7303, 18186, 20193, 2357, 14189, 18022, 3031, -445, -2678,
|
||||
16711, 5857, 528, 3181, 8847, 4014, -30, -180, 15892, 8970, 1158, 6972,
|
||||
18022, 1392, -1594, -9599, 16711, 4096, 0, 0, 8192, 655, -2708, -16305,
|
||||
15237, 13926, 1808, 10884, 14254, 3112, -406, -2444, 14090, 4669, 193, 1165,
|
||||
5406, 2703, -614, -3697, 13434, 6553, 694, 4180, 12451, 901, -2237, -13468,
|
||||
12451, 2662, -637, -3833, 3768, 655, -2708, -16305, 14745, 23511, 2582, 15543,
|
||||
19169, 2457, -755, -4546, 20152, 5079, 318, 1913, 6881, 4096, 0, 0,
|
||||
20480, 8560, 1089, 6556, 19660, 737, -2534, -15255, 19005, 4259, 58, 347,
|
||||
7864, 2088, -995, -5993, 11468, 12288, 1623, 9771, 15892, 1474, -1510, -9090,
|
||||
15728, 4628, 180, 1086, 9175, 1433, -1552, -9341, 16056, 7004, 793, 4772,
|
||||
14827, 737, -2534, -15255, 15073, 2252, -884, -5321, 5079, 1228, -1780, -10714,
|
||||
13271, 17326, 2131, 12827, 16547, 2334, -831, -5002, 15073, 5816, 518, 3118,
|
||||
3932, 3686, -156, -938, 14254, 8601, 1096, 6598, 16875, 778, -2454, -14774,
|
||||
15073, 3809, -107, -646, 6062, 614, -2804, -16879, 9338, 9256, 1204, 7251,
|
||||
13271, 1761, -1247, -7508, 13271, 3522, -223, -1343, 2457, 1966, -1084, -6529,
|
||||
11468, 5529, 443, 2668, 10485, 737, -2534, -15255, 11632, 3194, -367, -2212,
|
||||
1474, 778, -2454, -14774,
|
||||
}
|
||||
|
||||
// table_gain_MR475 is the 4.75k joint pitch+code gain VQ (qgain475_tab.cpp).
|
||||
// 4 values per entry: gain_pit, gain_code, then two unused slots reused by the
|
||||
// decoder only for gain_pit/gain_code (energies are recomputed).
|
||||
var table_gain_MR475 = []int16{
|
||||
812, 128, 542, 140, 2873, 1135, 2266, 3402, 2067, 563, 12677, 647,
|
||||
4132, 1798, 5601, 5285, 7689, 374, 3735, 441, 10912, 2638, 11807, 2494,
|
||||
20490, 797, 5218, 675, 6724, 8354, 5282, 1696, 1488, 428, 5882, 452,
|
||||
5332, 4072, 3583, 1268, 2469, 901, 15894, 1005, 14982, 3271, 10331, 4858,
|
||||
3635, 2021, 2596, 835, 12360, 4892, 12206, 1704, 13432, 1604, 9118, 2341,
|
||||
3968, 1538, 5479, 9936, 3795, 417, 1359, 414, 3640, 1569, 7995, 3541,
|
||||
11405, 645, 8552, 635, 4056, 1377, 16608, 6124, 11420, 700, 2007, 607,
|
||||
12415, 1578, 11119, 4654, 13680, 1708, 11990, 1229, 7996, 7297, 13231, 5715,
|
||||
2428, 1159, 2073, 1941, 6218, 6121, 3546, 1804, 8925, 1802, 8679, 1580,
|
||||
13935, 3576, 13313, 6237, 6142, 1130, 5994, 1734, 14141, 4662, 11271, 3321,
|
||||
12226, 1551, 13931, 3015, 5081, 10464, 9444, 6706, 1689, 683, 1436, 1306,
|
||||
7212, 3933, 4082, 2713, 7793, 704, 15070, 802, 6299, 5212, 4337, 5357,
|
||||
6676, 541, 6062, 626, 13651, 3700, 11498, 2408, 16156, 716, 12177, 751,
|
||||
8065, 11489, 6314, 2256, 4466, 496, 7293, 523, 10213, 3833, 8394, 3037,
|
||||
8403, 966, 14228, 1880, 8703, 5409, 16395, 4863, 7420, 1979, 6089, 1230,
|
||||
9371, 4398, 14558, 3363, 13559, 2873, 13163, 1465, 5534, 1678, 13138, 14771,
|
||||
7338, 600, 1318, 548, 4252, 3539, 10044, 2364, 10587, 622, 13088, 669,
|
||||
14126, 3526, 5039, 9784, 15338, 619, 3115, 590, 16442, 3013, 15542, 4168,
|
||||
15537, 1611, 15405, 1228, 16023, 9299, 7534, 4976, 1990, 1213, 11447, 1157,
|
||||
12512, 5519, 9475, 2644, 7716, 2034, 13280, 2239, 16011, 5093, 8066, 6761,
|
||||
10083, 1413, 5002, 2347, 12523, 5975, 15126, 2899, 18264, 2289, 15827, 2527,
|
||||
16265, 10254, 14651, 11319, 1797, 337, 3115, 397, 3510, 2928, 4592, 2670,
|
||||
7519, 628, 11415, 656, 5946, 2435, 6544, 7367, 8238, 829, 4000, 863,
|
||||
10032, 2492, 16057, 3551, 18204, 1054, 6103, 1454, 5884, 7900, 18752, 3468,
|
||||
1864, 544, 9198, 683, 11623, 4160, 4594, 1644, 3158, 1157, 15953, 2560,
|
||||
12349, 3733, 17420, 5260, 6106, 2004, 2917, 1742, 16467, 5257, 16787, 1680,
|
||||
17205, 1759, 4773, 3231, 7386, 6035, 14342, 10012, 4035, 442, 4194, 458,
|
||||
9214, 2242, 7427, 4217, 12860, 801, 11186, 825, 12648, 2084, 12956, 6554,
|
||||
9505, 996, 6629, 985, 10537, 2502, 15289, 5006, 12602, 2055, 15484, 1653,
|
||||
16194, 6921, 14231, 5790, 2626, 828, 5615, 1686, 13663, 5778, 3668, 1554,
|
||||
11313, 2633, 9770, 1459, 14003, 4733, 15897, 6291, 6278, 1870, 7910, 2285,
|
||||
16978, 4571, 16576, 3849, 15248, 2311, 16023, 3244, 14459, 17808, 11847, 2763,
|
||||
1981, 1407, 1400, 876, 4335, 3547, 4391, 4210, 5405, 680, 17461, 781,
|
||||
6501, 5118, 8091, 7677, 7355, 794, 8333, 1182, 15041, 3160, 14928, 3039,
|
||||
20421, 880, 14545, 852, 12337, 14708, 6904, 1920, 4225, 933, 8218, 1087,
|
||||
10659, 4084, 10082, 4533, 2735, 840, 20657, 1081, 16711, 5966, 15873, 4578,
|
||||
10871, 2574, 3773, 1166, 14519, 4044, 20699, 2627, 15219, 2734, 15274, 2186,
|
||||
6257, 3226, 13125, 19480, 7196, 930, 2462, 1618, 4515, 3092, 13852, 4277,
|
||||
10460, 833, 17339, 810, 16891, 2289, 15546, 8217, 13603, 1684, 3197, 1834,
|
||||
15948, 2820, 15812, 5327, 17006, 2438, 16788, 1326, 15671, 8156, 11726, 8556,
|
||||
3762, 2053, 9563, 1317, 13561, 6790, 12227, 1936, 8180, 3550, 13287, 1778,
|
||||
16299, 6599, 16291, 7758, 8521, 2551, 7225, 2645, 18269, 7489, 16885, 2248,
|
||||
17882, 2884, 17265, 3328, 9417, 20162, 11042, 8320, 1286, 620, 1431, 583,
|
||||
5993, 2289, 3978, 3626, 5144, 752, 13409, 830, 5553, 2860, 11764, 5908,
|
||||
10737, 560, 5446, 564, 13321, 3008, 11946, 3683, 19887, 798, 9825, 728,
|
||||
13663, 8748, 7391, 3053, 2515, 778, 6050, 833, 6469, 5074, 8305, 2463,
|
||||
6141, 1865, 15308, 1262, 14408, 4547, 13663, 4515, 3137, 2983, 2479, 1259,
|
||||
15088, 4647, 15382, 2607, 14492, 2392, 12462, 2537, 7539, 2949, 12909, 12060,
|
||||
5468, 684, 3141, 722, 5081, 1274, 12732, 4200, 15302, 681, 7819, 592,
|
||||
6534, 2021, 16478, 8737, 13364, 882, 5397, 899, 14656, 2178, 14741, 4227,
|
||||
14270, 1298, 13929, 2029, 15477, 7482, 15815, 4572, 2521, 2013, 5062, 1804,
|
||||
5159, 6582, 7130, 3597, 10920, 1611, 11729, 1708, 16903, 3455, 16268, 6640,
|
||||
9306, 1007, 9369, 2106, 19182, 5037, 12441, 4269, 15919, 1332, 15357, 3512,
|
||||
11898, 14141, 16101, 6854, 2010, 737, 3779, 861, 11454, 2880, 3564, 3540,
|
||||
9057, 1241, 12391, 896, 8546, 4629, 11561, 5776, 8129, 589, 8218, 588,
|
||||
18728, 3755, 12973, 3149, 15729, 758, 16634, 754, 15222, 11138, 15871, 2208,
|
||||
4673, 610, 10218, 678, 15257, 4146, 5729, 3327, 8377, 1670, 19862, 2321,
|
||||
15450, 5511, 14054, 5481, 5728, 2888, 7580, 1346, 14384, 5325, 16236, 3950,
|
||||
15118, 3744, 15306, 1435, 14597, 4070, 12301, 15696, 7617, 1699, 2170, 884,
|
||||
4459, 4567, 18094, 3306, 12742, 815, 14926, 907, 15016, 4281, 15518, 8368,
|
||||
17994, 1087, 2358, 865, 16281, 3787, 15679, 4596, 16356, 1534, 16584, 2210,
|
||||
16833, 9697, 15929, 4513, 3277, 1085, 9643, 2187, 11973, 6068, 9199, 4462,
|
||||
8955, 1629, 10289, 3062, 16481, 5155, 15466, 7066, 13678, 2543, 5273, 2277,
|
||||
16746, 6213, 16655, 3408, 20304, 3363, 18688, 1985, 14172, 12867, 15154, 15703,
|
||||
4473, 1020, 1681, 886, 4311, 4301, 8952, 3657, 5893, 1147, 11647, 1452,
|
||||
15886, 2227, 4582, 6644, 6929, 1205, 6220, 799, 12415, 3409, 15968, 3877,
|
||||
19859, 2109, 9689, 2141, 14742, 8830, 14480, 2599, 1817, 1238, 7771, 813,
|
||||
19079, 4410, 5554, 2064, 3687, 2844, 17435, 2256, 16697, 4486, 16199, 5388,
|
||||
8028, 2763, 3405, 2119, 17426, 5477, 13698, 2786, 19879, 2720, 9098, 3880,
|
||||
18172, 4833, 17336, 12207, 5116, 996, 4935, 988, 9888, 3081, 6014, 5371,
|
||||
15881, 1667, 8405, 1183, 15087, 2366, 19777, 7002, 11963, 1562, 7279, 1128,
|
||||
16859, 1532, 15762, 5381, 14708, 2065, 20105, 2155, 17158, 8245, 17911, 6318,
|
||||
5467, 1504, 4100, 2574, 17421, 6810, 5673, 2888, 16636, 3382, 8975, 1831,
|
||||
20159, 4737, 19550, 7294, 6658, 2781, 11472, 3321, 19397, 5054, 18878, 4722,
|
||||
16439, 2373, 20430, 4386, 11353, 26526, 11593, 3068, 2866, 1566, 5108, 1070,
|
||||
9614, 4915, 4939, 3536, 7541, 878, 20717, 851, 6938, 4395, 16799, 7733,
|
||||
10137, 1019, 9845, 964, 15494, 3955, 15459, 3430, 18863, 982, 20120, 963,
|
||||
16876, 12887, 14334, 4200, 6599, 1220, 9222, 814, 16942, 5134, 5661, 4898,
|
||||
5488, 1798, 20258, 3962, 17005, 6178, 17929, 5929, 9365, 3420, 7474, 1971,
|
||||
19537, 5177, 19003, 3006, 16454, 3788, 16070, 2367, 8664, 2743, 9445, 26358,
|
||||
10856, 1287, 3555, 1009, 5606, 3622, 19453, 5512, 12453, 797, 20634, 911,
|
||||
15427, 3066, 17037, 10275, 18883, 2633, 3913, 1268, 19519, 3371, 18052, 5230,
|
||||
19291, 1678, 19508, 3172, 18072, 10754, 16625, 6845, 3134, 2298, 10869, 2437,
|
||||
15580, 6913, 12597, 3381, 11116, 3297, 16762, 2424, 18853, 6715, 17171, 9887,
|
||||
12743, 2605, 8937, 3140, 19033, 7764, 18347, 3880, 20475, 3682, 19602, 3380,
|
||||
13044, 19373, 10526, 23124,
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Code generated from opencore-amrnb — DO NOT EDIT.
|
||||
//
|
||||
// ROM tables extracted verbatim from the Apache-2.0 opencore-amrnb C
|
||||
// reference; element counts are verified against the C array sizes.
|
||||
|
||||
package amrnb
|
||||
|
||||
var window_200_40 = []int16{
|
||||
2621, 2623, 2629, 2638, 2651, 2668, 2689, 2713, 2741, 2772, 2808, 2847,
|
||||
2890, 2936, 2986, 3040, 3097, 3158, 3223, 3291, 3363, 3438, 3517, 3599,
|
||||
3685, 3774, 3867, 3963, 4063, 4166, 4272, 4382, 4495, 4611, 4731, 4853,
|
||||
4979, 5108, 5240, 5376, 5514, 5655, 5800, 5947, 6097, 6250, 6406, 6565,
|
||||
6726, 6890, 7057, 7227, 7399, 7573, 7750, 7930, 8112, 8296, 8483, 8672,
|
||||
8863, 9057, 9252, 9450, 9650, 9852, 10055, 10261, 10468, 10677, 10888, 11101,
|
||||
11315, 11531, 11748, 11967, 12187, 12409, 12632, 12856, 13082, 13308, 13536, 13764,
|
||||
13994, 14225, 14456, 14688, 14921, 15155, 15389, 15624, 15859, 16095, 16331, 16568,
|
||||
16805, 17042, 17279, 17516, 17754, 17991, 18228, 18465, 18702, 18939, 19175, 19411,
|
||||
19647, 19882, 20117, 20350, 20584, 20816, 21048, 21279, 21509, 21738, 21967, 22194,
|
||||
22420, 22644, 22868, 23090, 23311, 23531, 23749, 23965, 24181, 24394, 24606, 24816,
|
||||
25024, 25231, 25435, 25638, 25839, 26037, 26234, 26428, 26621, 26811, 26999, 27184,
|
||||
27368, 27548, 27727, 27903, 28076, 28247, 28415, 28581, 28743, 28903, 29061, 29215,
|
||||
29367, 29515, 29661, 29804, 29944, 30081, 30214, 30345, 30472, 30597, 30718, 30836,
|
||||
30950, 31062, 31170, 31274, 31376, 31474, 31568, 31659, 31747, 31831, 31911, 31988,
|
||||
32062, 32132, 32198, 32261, 32320, 32376, 32428, 32476, 32521, 32561, 32599, 32632,
|
||||
32662, 32688, 32711, 32729, 32744, 32755, 32763, 32767, 32767, 32741, 32665, 32537,
|
||||
32359, 32129, 31850, 31521, 31143, 30716, 30242, 29720, 29151, 28538, 27879, 27177,
|
||||
26433, 25647, 24821, 23957, 23055, 22117, 21145, 20139, 19102, 18036, 16941, 15820,
|
||||
14674, 13505, 12315, 11106, 9879, 8637, 7381, 6114, 4838, 3554, 2264, 971,
|
||||
}
|
||||
|
||||
var window_160_80 = []int16{
|
||||
2621, 2624, 2633, 2648, 2668, 2695, 2727, 2765, 2809, 2859, 2915, 2976,
|
||||
3043, 3116, 3194, 3279, 3368, 3464, 3565, 3671, 3783, 3900, 4023, 4151,
|
||||
4285, 4423, 4567, 4716, 4870, 5029, 5193, 5362, 5535, 5714, 5897, 6084,
|
||||
6277, 6473, 6674, 6880, 7089, 7303, 7521, 7742, 7968, 8197, 8430, 8667,
|
||||
8907, 9151, 9398, 9648, 9902, 10158, 10417, 10680, 10945, 11212, 11482, 11755,
|
||||
12030, 12307, 12586, 12867, 13150, 13435, 13722, 14010, 14299, 14590, 14882, 15175,
|
||||
15469, 15764, 16060, 16356, 16653, 16950, 17248, 17546, 17844, 18141, 18439, 18736,
|
||||
19033, 19330, 19625, 19920, 20214, 20507, 20799, 21090, 21380, 21668, 21954, 22239,
|
||||
22522, 22803, 23083, 23360, 23635, 23907, 24177, 24445, 24710, 24972, 25231, 25488,
|
||||
25741, 25991, 26238, 26482, 26722, 26959, 27192, 27422, 27647, 27869, 28087, 28300,
|
||||
28510, 28715, 28916, 29113, 29305, 29493, 29676, 29854, 30028, 30197, 30361, 30519,
|
||||
30673, 30822, 30966, 31105, 31238, 31366, 31489, 31606, 31718, 31825, 31926, 32021,
|
||||
32111, 32195, 32273, 32346, 32413, 32475, 32530, 32580, 32624, 32662, 32695, 32721,
|
||||
32742, 32756, 32765, 32767, 32767, 32756, 32720, 32661, 32578, 32471, 32341, 32188,
|
||||
32012, 31813, 31592, 31349, 31084, 30798, 30492, 30165, 29818, 29453, 29068, 28666,
|
||||
28247, 27810, 27358, 26891, 26408, 25913, 25404, 24883, 24350, 23807, 23255, 22693,
|
||||
22124, 21548, 20965, 20378, 19786, 19191, 18593, 17994, 17395, 16796, 16199, 15604,
|
||||
15012, 14424, 13842, 13265, 12696, 12135, 11582, 11039, 10507, 9986, 9477, 8981,
|
||||
8499, 8031, 7579, 7143, 6723, 6321, 5937, 5571, 5225, 4898, 4591, 4305,
|
||||
4041, 3798, 3577, 3378, 3202, 3048, 2918, 2812, 2729, 2669, 2633, 2621,
|
||||
}
|
||||
|
||||
var window_232_8 = []int16{
|
||||
2621, 2623, 2627, 2634, 2644, 2656, 2671, 2689, 2710, 2734, 2760, 2789,
|
||||
2821, 2855, 2893, 2933, 2975, 3021, 3069, 3120, 3173, 3229, 3288, 3350,
|
||||
3414, 3481, 3550, 3622, 3697, 3774, 3853, 3936, 4021, 4108, 4198, 4290,
|
||||
4385, 4482, 4582, 4684, 4788, 4895, 5004, 5116, 5230, 5346, 5464, 5585,
|
||||
5708, 5833, 5960, 6090, 6221, 6355, 6491, 6629, 6769, 6910, 7054, 7200,
|
||||
7348, 7498, 7649, 7803, 7958, 8115, 8274, 8434, 8597, 8761, 8926, 9093,
|
||||
9262, 9432, 9604, 9778, 9952, 10129, 10306, 10485, 10665, 10847, 11030, 11214,
|
||||
11399, 11586, 11773, 11962, 12152, 12342, 12534, 12727, 12920, 13115, 13310, 13506,
|
||||
13703, 13901, 14099, 14298, 14497, 14698, 14898, 15100, 15301, 15504, 15706, 15909,
|
||||
16112, 16316, 16520, 16724, 16928, 17132, 17337, 17541, 17746, 17950, 18155, 18359,
|
||||
18564, 18768, 18972, 19175, 19379, 19582, 19785, 19987, 20189, 20390, 20591, 20792,
|
||||
20992, 21191, 21390, 21588, 21785, 21981, 22177, 22372, 22566, 22759, 22951, 23143,
|
||||
23333, 23522, 23710, 23897, 24083, 24268, 24451, 24633, 24814, 24994, 25172, 25349,
|
||||
25525, 25699, 25871, 26042, 26212, 26380, 26546, 26711, 26874, 27035, 27195, 27353,
|
||||
27509, 27664, 27816, 27967, 28115, 28262, 28407, 28550, 28691, 28830, 28967, 29102,
|
||||
29234, 29365, 29493, 29619, 29743, 29865, 29985, 30102, 30217, 30330, 30440, 30548,
|
||||
30654, 30757, 30858, 30956, 31052, 31146, 31237, 31326, 31412, 31495, 31576, 31655,
|
||||
31730, 31804, 31874, 31942, 32008, 32071, 32131, 32188, 32243, 32295, 32345, 32392,
|
||||
32436, 32477, 32516, 32552, 32585, 32615, 32643, 32668, 32690, 32709, 32726, 32740,
|
||||
32751, 32759, 32765, 32767, 32767, 32097, 30112, 26895, 22576, 17333, 11380, 4962,
|
||||
}
|
||||
|
||||
// lag_h/lag_l: lag-window (bandwidth expansion) coefficients,
|
||||
// double precision (lag_wind_tab.cpp).
|
||||
var lag_h = []int16{32728, 32619, 32438, 32187, 31867, 31480, 31029, 30517, 29946, 29321}
|
||||
var lag_l = []int16{11904, 17280, 30720, 25856, 24192, 28992, 24384, 7360, 19520, 14784}
|
||||
@@ -0,0 +1,946 @@
|
||||
// Code generated from opencore-amrnb — DO NOT EDIT.
|
||||
//
|
||||
// ROM tables extracted verbatim from the Apache-2.0 opencore-amrnb C
|
||||
// reference; element counts are verified against the C array sizes.
|
||||
|
||||
package amrnb
|
||||
|
||||
var past_rq_init = []int16{
|
||||
-258, -318, -439, -634, -656, -773, -711, -502, -268, -193, -2, 125,
|
||||
122, -39, -9, 105, 129, 283, 372, 575, -277, -324, -197, -487,
|
||||
-445, -362, -292, -27, 177, 543, 342, 517, 516, 130, 27, -104,
|
||||
-120, -140, -74, -56, -564, -943, -1520, -965, -814, -526, -322, -2,
|
||||
159, 657, -312, -284, -386, -597, -493, -526, -418, -229, 105, 449,
|
||||
-557, -870, -1075, -919, -950, -752, -709, -316, 62, 486, -314, -191,
|
||||
-203, -330, -160, -103, -51, 131, 338, 515,
|
||||
}
|
||||
|
||||
var mean_lsf_3 = []int16{
|
||||
1546, 2272, 3778, 5488, 6972, 8382, 10047, 11229, 12766, 13714,
|
||||
}
|
||||
|
||||
var pred_fac_3 = []int16{
|
||||
9556, 10769, 12571, 13292, 14381, 11651, 10588, 9767, 8593, 6484,
|
||||
}
|
||||
|
||||
var dico1_lsf_3 = []int16{
|
||||
6, 82, -131, 154, -56, -735, 183, -65, -265, 9, -210, -361,
|
||||
113, 718, 1817, 1010, 1214, 1573, 857, 1333, 2276, 827, 1568, 1933,
|
||||
717, 1989, 2206, 838, 1172, 1823, 721, 1000, 2154, 286, 476, 1509,
|
||||
-247, -531, 230, 147, -82, 569, 26, -177, -944, -27, -273, 692,
|
||||
-164, -264, -183, 224, 790, 1039, 899, 946, 601, 485, 771, 1150,
|
||||
524, 677, 903, -140, 375, 778, 410, 676, 429, 301, 530, 1009,
|
||||
719, 646, 38, 226, 367, 40, 145, -45, -505, 290, 121, -121,
|
||||
302, 127, 166, -124, -383, -956, -358, -455, -977, 715, 878, 894,
|
||||
978, 923, 211, 477, 272, 64, 188, -78, 17, -143, -65, 38,
|
||||
643, 586, 621, -134, -426, -651, 347, 545, 2820, 1188, 2726, 2442,
|
||||
142, -80, 1735, 283, 130, 461, -262, -399, -1145, -411, 155, 430,
|
||||
329, 375, 779, 53, -226, -139, -129, -236, 1682, 285, 744, 1327,
|
||||
738, 697, 1664, 312, 409, 266, 325, 720, 135, 1, 221, 453,
|
||||
8, 203, 145, 299, 640, 760, 29, 468, 638, 103, 429, 379,
|
||||
420, 954, 932, 1326, 1210, 1258, 704, 1012, 1152, -166, -444, -266,
|
||||
-316, -130, -376, 191, 1151, 1904, -240, -543, -1260, -112, 268, 1207,
|
||||
70, 1062, 1583, 278, 1360, 1574, -258, -272, -768, 19, 563, 2240,
|
||||
-3, -265, 135, -295, -591, -388, 140, 354, -206, -260, -504, -795,
|
||||
-433, -718, -1319, 109, 331, 962, -429, -87, 652, -296, 426, 1019,
|
||||
-239, 775, 851, 489, 1334, 1073, -334, -332, 25, 543, 1206, 1807,
|
||||
326, 61, 727, 578, 849, 1405, -208, -277, 329, -152, 64, 669,
|
||||
-434, -678, -727, -454, -71, 251, 605, 480, 254, -482, 11, 996,
|
||||
-289, 395, 486, 722, 1049, 1440, -30, -316, -786, -106, -115, -619,
|
||||
861, 1474, 1412, 1055, 1366, 1184, 812, 1237, 925, 42, -251, -576,
|
||||
342, 141, -454, -168, -80, 1359, -342, -656, -1763, 100, 821, 725,
|
||||
990, 747, 800, 332, 440, 568, 663, 379, 852, 112, 165, -369,
|
||||
597, 910, 282, -8, 834, 1281, -352, 572, 695, 462, 2246, 1806,
|
||||
345, 190, 1374, 416, 915, 2166, 168, -82, 280, -516, -446, 840,
|
||||
47, 533, 44, -362, -711, -1143, 22, 193, 1472, -85, 233, 1813,
|
||||
-62, 579, 1504, 550, 944, 1749, 723, 650, 1148, 972, 884, 1395,
|
||||
-425, 643, 0, 1000, 952, 1098, 249, 1446, 672, -334, -87, 2172,
|
||||
-554, 1882, 2672, 140, 1826, 1853, 920, 1749, 2590, 1076, 1933, 2038,
|
||||
-137, -443, -1555, 1269, 1174, 468, -493, -122, 1521, -451, 1033, 1214,
|
||||
482, 1695, 1118, 815, 649, 384, -446, -692, 107, -319, -605, -118,
|
||||
-207, -505, 525, -468, -12, 2736, 75, 1934, 1305, 880, 2358, 2267,
|
||||
1285, 1575, 2004, -48, -304, -1186, -435, -461, -251, -366, -404, -547,
|
||||
-289, -605, -597, -538, -810, -165, -120, 3, 356, 639, 1241, 1502,
|
||||
96, 177, 750, -435, -585, -1174, -356, 109, -79, -485, 288, 2005,
|
||||
9, 1116, 731, 880, 2134, 946, -265, 1585, 1065, 1157, 1210, 843,
|
||||
-498, -668, 431, 374, 321, -229, 1440, 2101, 1381, 449, 461, 1155,
|
||||
-105, 39, -384, -263, 367, 182, -371, -660, 773, -188, 1151, 971,
|
||||
1333, 1632, 1435, 774, 1267, 1221, -482, -832, -1489, -237, -210, 860,
|
||||
890, 1615, 1064, 472, 1062, 1192, 185, 1077, 989, -568, -992, -1704,
|
||||
-449, -902, -2043, -142, -377, -458, -210, -554, -1029, -11, 1133, 2265,
|
||||
-329, -675, -893, -250, 657, 1187, 519, 1510, 1779, 520, 539, 1403,
|
||||
527, 1421, 1302, -563, -871, -1248, -147, -463, 879, -76, 2334, 2840,
|
||||
563, 2573, 2385, 632, 1926, 2920, 719, 2023, 1840, -545, -723, 1108,
|
||||
129, -125, 884, 1417, 1632, 925, -94, 1566, 1751, -341, 1533, 1551,
|
||||
591, 395, -274, -76, 981, 2831, 153, 2985, 1844, 1032, 2565, 2749,
|
||||
1508, 2832, 1879, 791, 1199, 538, -190, -453, 1489, -278, -548, 1158,
|
||||
-245, 1941, 2044, 1024, 1560, 1650, 512, 253, 466, -62, -323, 1151,
|
||||
-473, -376, 507, -433, 1380, 2162, 899, 1943, 1445, 134, 704, 440,
|
||||
460, 525, -28, -450, 279, 1338, 0, 971, 252, -445, -627, -991,
|
||||
-348, -602, -1424, 398, 712, 1656, -107, 314, -178, 93, 2226, 2238,
|
||||
518, 849, 656, -462, -711, -447, 174, -34, 1191, -119, 42, 1005,
|
||||
-372, 274, 758, 1036, 2352, 1838, 675, 1724, 1498, 430, 1286, 2133,
|
||||
-129, -439, 0, -373, 800, 2144, 6, 1587, 2478, 478, 596, 2128,
|
||||
-428, -736, 1505, 385, 178, 980, 139, 449, 1225, -526, -842, -982,
|
||||
145, 1554, 1242, 623, 1448, 656, 349, 1016, 1482, 31, -280, 415,
|
||||
-316, 724, 1641, 360, 1058, 556, -436, -358, 1201, -355, 1123, 1939,
|
||||
401, 1584, 2248, -527, -1012, 355, 233, 238, 2233, -550, -897, -639,
|
||||
-365, -501, 1957, 389, 1860, 1621, 162, 1132, 1264, -237, 1174, 1390,
|
||||
-640, -411, 116, -228, 1694, 2298, 1639, 2186, 2267, 562, 1273, 2658,
|
||||
323, 338, 1774, 578, 1107, 852, 22, 594, 934, -143, 718, 446,
|
||||
}
|
||||
|
||||
var dico2_lsf_3 = []int16{
|
||||
50, 71, -9, -338, -698, -1407, 102, -138, -820, -310, -469, -1147,
|
||||
414, 67, -267, 1060, 814, 1441, 1548, 1360, 1272, 1754, 1895, 1661,
|
||||
2019, 2133, 1820, 1808, 2318, 1845, 644, -93, 454, 858, 329, -136,
|
||||
489, -258, -128, -198, -745, -41, -52, -265, -985, 346, 137, 479,
|
||||
-1741, -748, -684, -1163, -1725, -367, -895, -1145, -784, -488, -946, -968,
|
||||
-85, -390, -725, 215, -340, -171, 1020, 916, 1969, 564, 179, 746,
|
||||
662, 977, 1734, 887, 622, 914, 939, 856, 1165, 309, 688, 803,
|
||||
917, 161, 570, 118, -20, -283, -816, -42, 204, -1228, -325, -462,
|
||||
-963, -202, -143, -988, -484, -361, -702, -978, -477, -302, -790, -1188,
|
||||
-100, -786, -1088, -1054, -947, -1684, -202, -843, -782, -1039, -1378, -901,
|
||||
-624, -110, -85, 356, 213, -10, -493, 364, 774, 425, 822, 479,
|
||||
-83, 557, 520, -992, -1560, -572, -603, -741, -26, -502, -638, -903,
|
||||
209, 306, 147, -316, -593, -596, -85, -211, -225, -918, -529, 117,
|
||||
233, -439, -738, 1101, 751, 633, 1457, 1716, 1511, 1765, 1457, 910,
|
||||
1122, 1156, 849, 1354, 868, 470, -871, -1150, -1796, -871, -861, -992,
|
||||
-118, 155, 212, -1051, -849, -606, -1117, -1849, -2750, -1019, -1427, -1869,
|
||||
370, -184, -414, 959, 493, 104, 958, 1039, 543, 154, 653, 201,
|
||||
1249, 507, 150, 663, 503, 230, 623, 777, 675, 659, 88, -110,
|
||||
843, 244, 224, 382, 541, 302, 724, 433, 666, 1166, 734, 341,
|
||||
-138, 20, -397, -1183, -424, -46, -321, -352, -124, 1333, 1021, 1080,
|
||||
262, 366, 723, 922, 283, -551, 31, -636, -611, -689, -697, -415,
|
||||
-952, -779, -201, -1329, -598, -359, -953, -1285, 166, 493, 305, 221,
|
||||
846, 703, 610, 840, 936, 774, -723, -1324, -1261, -357, -1025, -1388,
|
||||
-1096, -1376, -365, -1416, -1881, -608, -1798, -1727, -674, -545, -1173, -703,
|
||||
678, 786, 148, -123, 696, 1288, 644, 350, -10, 414, 614, 15,
|
||||
137, 344, -211, -814, -1512, -819, -391, -930, -588, 47, -591, -898,
|
||||
-909, -1097, -163, -1272, -1167, -157, -1464, -1525, -389, -1274, -1188, -624,
|
||||
671, 213, 454, 124, -274, -525, -729, -496, -152, -1344, 122, 135,
|
||||
-2905, -589, -394, -1728, 441, -50, 1476, 904, 787, 316, 236, -440,
|
||||
-347, 217, 413, -911, -917, 121, -455, -932, 202, -92, -465, -375,
|
||||
488, 390, 474, 876, 729, 316, -1815, -1312, -669, 87, 962, 432,
|
||||
563, -249, -1058, 250, 285, 1105, 1141, 427, 696, -1038, -1664, -1582,
|
||||
-948, 346, 160, -309, -272, -858, 670, 624, 1250, -944, -408, -666,
|
||||
-606, -320, -384, -492, 230, 65, 334, -50, -16, -16, -690, -1397,
|
||||
1791, 1716, 1399, 2478, 2063, 1404, 1245, 1471, 1426, -382, -1037, -2,
|
||||
173, -398, 1145, 1491, 2024, 1801, 772, 1274, 1506, 1429, 1735, 2001,
|
||||
1079, 1218, 1273, -1154, -1851, -1329, -808, -1133, -1096, -451, -1033, -1722,
|
||||
65, 578, -84, -1476, -2434, -1778, -765, -1366, -494, -218, -594, -931,
|
||||
337, -236, 562, 2357, 2662, 1938, 1489, 1276, 874, 189, 358, 374,
|
||||
-1519, -2281, -2346, -967, -1271, -2095, -628, -1188, -1542, 1661, 1043, 546,
|
||||
565, 1061, 732, -64, -836, -434, -436, -96, 203, 1078, 1216, 1636,
|
||||
907, 1534, 986, 326, 965, 845, 142, -84, 197, 470, 2379, 1570,
|
||||
1133, 470, 1214, 395, 1376, 1200, 1125, 1042, 348, -543, -1234, -376,
|
||||
-215, -181, 481, -1947, -1621, -210, -750, -1185, 390, 29, -399, 27,
|
||||
820, 1236, 755, 695, 979, 409, -174, 1197, 1035, 912, 1356, 1846,
|
||||
-992, -1437, 484, -1485, -1700, 208, -412, 1204, 1432, -271, 896, 1144,
|
||||
-416, 1777, 1434, -1696, -2644, -204, -1789, -1551, 1033, -1656, -1559, 1303,
|
||||
-1253, -1589, 1081, -669, -1095, -66, -682, 320, -345, 659, 305, 1069,
|
||||
-1292, -804, -19, -1635, -1291, 29, -1683, -497, 71, -287, -7, -100,
|
||||
-494, -962, -237, 852, 1881, 1740, -1217, -1387, 227, -660, 302, 373,
|
||||
96, 1087, 1257, -1074, -1669, 160, 485, 2076, 1798, -934, -220, 552,
|
||||
-596, -612, 237, 336, 1720, 879, 643, 629, 434, 1267, 522, 1633,
|
||||
15, 244, -441, 1475, 717, 184, 1819, 1590, 1709, 988, 261, 937,
|
||||
2093, 2345, 1520, 2139, 1858, 1606, -577, -579, -1203, -956, 135, -488,
|
||||
-464, 51, -338, -629, -348, -723, 1146, 2073, 1442, 2192, 1466, 911,
|
||||
-1444, -1572, -2278, 1400, 710, 1297, 1335, 633, 928, 1434, 2194, 2594,
|
||||
2422, 2204, 1881, 982, 2242, 1854, 380, 792, 1145, -63, -539, 414,
|
||||
-252, -964, -314, -1261, -683, -780, -831, -526, -1005, -1666, -1135, -424,
|
||||
-1611, -452, -299, 1268, 1048, 642, 1147, 853, 856, -675, -336, 139,
|
||||
2268, 1343, 1418, 29, 768, 797, -1224, 423, 564, -1318, -1082, 245,
|
||||
-1302, -812, 573, -1298, -1617, 646, -968, 834, 723, 993, 1652, 2027,
|
||||
-191, -817, 432, 662, 60, 198, 626, 997, 1330, 1648, 1963, 1289,
|
||||
-1597, -93, -45, -1088, 37, -84, 1653, 2607, 2337, 1065, 2040, 2377,
|
||||
1139, 2326, 2118, 859, 357, 1510, 664, 1227, 1099, 479, 1360, 912,
|
||||
1897, 1754, 2019, 1168, 1909, 1784, 399, 34, 256, -593, -304, -1053,
|
||||
547, 1694, 1407, 647, -99, -341, 1492, 1647, 1190, 38, -644, -212,
|
||||
395, 846, 222, -704, -765, -716, -724, -1964, -2804, -150, 291, -82,
|
||||
1233, 1459, 1007, -140, -155, 153, 439, 297, 1568, -1529, -410, -636,
|
||||
1536, 455, -237, -1328, -139, -260, 531, 554, 868, 269, 1264, 606,
|
||||
-233, 883, 463, 742, 600, -120, -73, 421, 212, -439, -58, 804,
|
||||
-1286, -1241, 728, 294, -490, 50, -591, -905, -1254, 42, -687, 147,
|
||||
-25, 273, 596, -311, 1213, 601, -754, 849, 584, 429, 607, 587,
|
||||
-602, -166, 461, -796, -823, 777, 1380, 910, 1755, 119, 1417, 972,
|
||||
-219, -880, -1596, -1049, -1010, 438, -713, -1379, 78, 0, -447, -1179,
|
||||
-1136, -1319, -1573, 2248, 1767, 1309, 946, 1583, 1432, 1150, 482, 436,
|
||||
-469, -1108, 618, -447, -966, 1088, -1252, -1515, -114, -1104, -2008, -579,
|
||||
210, 613, 497, -1975, -1437, 642, -1269, -856, 1011, -1646, -1185, 1063,
|
||||
-1555, -672, 1204, -1692, -1114, 623, -979, -1326, -1277, 539, -147, 894,
|
||||
-1354, -897, -434, 888, 475, 428, 153, -384, 338, -1492, -511, 359,
|
||||
-974, -1115, -470, 105, -550, 677, -937, -1145, 877, 380, -260, 210,
|
||||
1685, 924, 1256, 1775, 1190, 1095, 1419, 631, 533, 627, 299, -347,
|
||||
-411, -534, 647, -650, 29, -595, -378, -1367, 1563, 1402, 1121, 1465,
|
||||
1089, 1410, 648, -2096, -1090, -6, 311, -194, -869, -639, -831, 416,
|
||||
-1162, -1224, 1349, -1247, -941, 1813, -2193, -1987, 453, -619, -1367, -956,
|
||||
-1606, -1972, -1507, -1175, -1057, -1104, -377, 601, 201, 1876, 825, 374,
|
||||
-430, -1323, 29, -1397, -1249, -1331, -1007, -1504, 960, -1401, -2009, 197,
|
||||
-1379, -1949, -236, -1077, 123, 422, 615, 1269, 546, -306, 1526, 904,
|
||||
1194, 1788, 1177, -626, -884, -1526, 199, 766, 1504, -1065, 862, 197,
|
||||
-1034, -1773, -887, -800, 145, 599, -1134, -519, 626, -1205, -1926, 500,
|
||||
-910, -1041, -1395, -1476, -1567, -969, -523, 842, 34, 1794, 646, 862,
|
||||
-1207, -1888, -1002, -78, -9, -672, 1044, 759, 80, -600, 1139, 1019,
|
||||
57, 2000, 1422, -833, 1414, 1121, -1202, 1630, 1260, -461, 1420, 1244,
|
||||
1537, 975, 253, -283, 324, -359, 599, -195, 106, 588, 62, -587,
|
||||
-757, 645, 205, 51, 1201, 758, -1209, 673, -390, -624, 1581, 941,
|
||||
-151, 1023, 735, 2820, 1301, 690, -302, 524, -99, -900, -1588, -1189,
|
||||
1084, 251, 238, 2014, 1792, 1010, 1245, 1633, 1741, -1227, -1540, -1208,
|
||||
-621, 456, -109, 40, -65, 788, -805, -699, -1350, -583, 904, 832,
|
||||
-801, 532, 594, 1972, 1408, 1351, -1177, -1880, -2114, -773, 568, 948,
|
||||
-1015, 1079, 1260, -1111, 482, -130, 1778, 1044, 780, -1491, 245, 912,
|
||||
-316, -1141, -917, -536, -1442, -2346, -785, -1546, -1988, -2003, 257, 909,
|
||||
-1849, -633, -1209, -1538, -1918, -1054, 1606, 2239, 1576, -567, -1500, -1544,
|
||||
-1279, 195, 1369, -817, 293, 1219, -525, 630, 1197, -1698, -2425, -1840,
|
||||
-303, 731, 747, -1169, -251, 269, -950, -75, 1684, -1182, -453, 1005,
|
||||
-1599, 585, 378, -2075, -571, -427, -529, -1159, -1171, -283, -205, -564,
|
||||
-796, 1246, 717, 2277, 927, 539, -454, 559, 440, -717, 1460, 1615,
|
||||
-1030, 1052, 1610, -1169, -138, 847, 226, 39, -612, -1251, -106, -729,
|
||||
-651, 968, 1302, -714, -636, 1727, 353, 1069, 410, -798, -156, 1099,
|
||||
-574, 918, 446, -1310, 1012, 466, 1408, 1591, 765, 1429, 1380, 1757,
|
||||
1949, 1956, 2378, 1578, 2047, 2148, 916, 98, -7, 1893, 1418, 2141,
|
||||
348, 1405, 1579, 152, 1134, 1801, -267, 154, 1395, -1166, 469, 1054,
|
||||
-1142, -405, -1073, -1341, -2264, -1581, -364, 869, 1706, -1162, 549, 1550,
|
||||
-1225, -1932, -1666, -1485, -1977, -2055, -1727, -906, -98, -1897, 233, 1492,
|
||||
892, 108, -331, -1728, -1170, -1700, -1060, 1980, 1790, -1070, -1741, -1909,
|
||||
-11, 1539, 1317, -1600, 94, 497, 421, 443, -197, -1578, -349, -994,
|
||||
-599, -539, 1140, -965, -1419, -129, -1341, 175, -447, -375, 1311, 2055,
|
||||
-371, -650, -307, -1073, 605, 365, -2057, -113, 430, 652, 914, 967,
|
||||
-1012, -1586, -2323, 1505, 1248, 559, 262, -486, -401, -1727, 1342, 1546,
|
||||
50, 56, 432, -330, 119, -604, -1517, -1080, -810, 946, 1127, 1055,
|
||||
-1400, -1703, -1712, -1270, -704, -1317, 807, 1821, 1143, 2760, 1606, 2171,
|
||||
1120, 409, -150, -147, 404, 959, 2439, 1911, 2189, -906, -141, -866,
|
||||
-904, -142, -458, -557, -708, -1679, -830, -1431, -1583, -1842, -1346, -1086,
|
||||
-1604, -272, 915, -1196, 772, 1056, -638, -1234, -1897, -500, -81, -822,
|
||||
-1289, -1613, -735, -117, 785, 168, -1090, 1133, 922, -1096, -746, 1384,
|
||||
287, -547, -1063, -1376, -2201, -1204, -2176, -1570, -1757, -1511, -2241, -771,
|
||||
-1737, 1099, 830, -1588, 724, 1243, -1542, 693, 805, -1690, -240, 1665,
|
||||
-1700, -4, -668, 2149, 816, 1042, -818, -1841, 22, -764, -507, 449,
|
||||
-1151, -617, 289, -843, -1596, -240, 498, -234, -657, -752, 480, 1678,
|
||||
-319, -481, 193, -811, 171, -119, -2128, -202, -848, 1717, 1140, 1700,
|
||||
}
|
||||
|
||||
var dico3_lsf_3 = []int16{
|
||||
67, -17, 66, -12, -1690, -581, -104, -272, -1076, -1186, -1845, -376,
|
||||
-1140, -926, -420, -58, -259, -656, -1134, -553, 1788, 1227, 455, 129,
|
||||
462, 441, -240, -528, 840, 514, 130, -75, 1114, 623, 153, 216,
|
||||
1068, 564, -6, -276, 1119, 727, 190, -68, 704, 306, 119, -264,
|
||||
329, 61, -100, 156, 364, 123, 183, -208, -171, -123, 220, -65,
|
||||
-306, -62, 402, 17, -660, -938, -266, 0, 385, 235, 276, 285,
|
||||
320, 268, -336, -200, -724, 17, -84, 381, -544, 429, 494, 519,
|
||||
-117, 288, 304, 329, 643, 157, 701, 508, 1200, 625, 796, 608,
|
||||
998, 421, 492, 632, 1204, 780, 446, 132, 1257, 844, 547, 449,
|
||||
829, 658, 541, 470, 1132, 1258, 918, 639, 547, 51, 423, 279,
|
||||
9, 392, 83, 94, 542, 543, 229, -147, -198, 129, 194, -185,
|
||||
-863, -1321, -302, 30, -597, -629, -19, 114, -900, -1081, 466, 353,
|
||||
-1483, -1573, 15, -143, -1708, -2059, -751, 196, -1876, -2067, -642, -258,
|
||||
-2335, -1470, -450, -564, -584, -186, -872, -414, -1805, -988, -1125, -1310,
|
||||
-726, -1129, 28, 169, -1039, -864, -718, -246, 484, 36, -233, -49,
|
||||
265, 67, 289, 467, 178, 543, 810, 540, 84, 282, 672, 703,
|
||||
-975, -777, 129, 287, -938, -227, 955, 595, -1617, -289, 836, 649,
|
||||
-1847, -215, 1106, 718, -2034, -1085, 650, 440, -2101, -529, 907, 575,
|
||||
-2011, -336, 670, 204, -2389, -692, 360, 137, -2156, -2204, -9, 280,
|
||||
-266, 119, 39, 193, 78, -59, -120, 226, -975, -858, -781, -1095,
|
||||
-619, -413, -451, -842, -1216, -1321, -813, -883, -1376, -1615, -394, -428,
|
||||
-737, -1113, -549, -790, -880, -975, -967, -642, -985, -886, -1273, -1361,
|
||||
-473, -804, -1401, -1407, 160, -265, -919, -275, -248, -250, -718, -380,
|
||||
97, -103, -375, -229, -415, -193, -135, -555, 628, 361, 119, 216,
|
||||
579, 364, 391, 209, 634, 522, -154, -148, 526, 389, 170, 33,
|
||||
105, 267, 64, 380, -1503, -1000, -30, -369, -1070, 58, 647, 223,
|
||||
-1520, -291, 621, 307, -1531, 156, 762, 404, -2029, 141, 734, 499,
|
||||
-1849, -650, 306, 512, -187, -104, -59, 438, 134, -230, 156, -186,
|
||||
-61, -260, -16, 10, -569, -3, -421, -297, -1725, -521, -346, 178,
|
||||
-1362, -59, -44, 157, -2146, -461, -470, -349, -2170, -1, -369, -121,
|
||||
-1579, -373, -900, -1015, -1117, -591, -613, -784, -561, 122, -75, -449,
|
||||
-4, -171, -123, -372, 192, 168, -76, -132, 252, -107, 340, 210,
|
||||
392, 509, 272, 181, -109, 145, 218, 119, -416, -263, 485, 265,
|
||||
-181, -8, -286, 226, -244, -218, 69, -290, -158, 191, -1, -64,
|
||||
-592, -90, 213, -96, 255, 435, 178, -80, -369, -18, -33, -80,
|
||||
-42, 415, 140, -222, 1143, 651, 649, 329, 767, 556, 249, 235,
|
||||
948, 413, 442, 279, 141, 339, 356, 557, -470, -170, 99, 237,
|
||||
-569, -800, 352, 565, 282, 473, 470, 332, -199, -690, -1284, -917,
|
||||
-193, -426, -800, -1122, -26, -371, -490, -193, 637, 595, 519, 330,
|
||||
408, -115, 79, 12, 477, 87, -103, -376, -666, -347, -277, -291,
|
||||
-510, -481, 169, 297, -829, -738, -205, -171, -320, -540, 328, 283,
|
||||
-859, -958, 442, -2, 556, 686, 130, 56, 1383, 1012, 755, 427,
|
||||
612, 741, 628, 553, -339, -796, 134, 277, -633, -1085, -2, -246,
|
||||
-880, -1035, -1607, -1064, -994, -474, -1138, -488, -414, -795, 73, -206,
|
||||
-8, -139, 439, 204, -176, -578, 23, 131, -269, -757, -191, 245,
|
||||
-109, -338, 112, 316, 120, -406, -118, 611, -180, -186, -645, 115,
|
||||
-173, 34, -518, -489, -151, 61, -583, -844, 220, -138, -681, -1020,
|
||||
391, -17, -598, -321, 157, -295, 129, 155, -926, -875, -987, 285,
|
||||
241, -83, -125, -125, 620, 597, 432, 92, 393, 78, 409, 61,
|
||||
-393, -739, -413, -748, 83, 54, 361, 27, -1084, 130, -337, -694,
|
||||
-1565, 297, 318, -19, -1873, 36, 51, -317, -2323, -246, 231, -84,
|
||||
-2306, -783, 40, -179, -2233, -930, -474, -462, -754, -86, -288, -626,
|
||||
-2411, -455, -63, 171, -1099, -1094, -26, -143, -1193, -455, -406, -381,
|
||||
-605, -210, -96, -51, -580, -476, -276, -15, -1195, -634, -1203, -881,
|
||||
-378, -221, -669, -952, 594, 178, -403, -676, 763, 327, 601, 290,
|
||||
172, 300, 203, 157, -56, -336, 356, 24, -228, -296, -259, -29,
|
||||
-186, 263, 416, 14, -353, 373, -12, -216, 257, 96, 174, 57,
|
||||
-1526, -616, -954, -499, -497, -152, -333, 125, 105, 200, 179, -97,
|
||||
-331, -224, 765, 697, 760, 256, 301, 59, 455, -85, 204, 288,
|
||||
-514, 240, 251, -109, 256, 417, -34, -413, 101, 430, 384, 156,
|
||||
-31, -10, 206, 426, 589, 145, 143, 71, 808, 906, 333, 349,
|
||||
986, 938, 589, 331, 1300, 824, 187, 509, 1062, 653, 379, 466,
|
||||
1462, 937, 401, 274, 787, 861, 265, 2, 609, 553, 28, 305,
|
||||
926, 340, 106, 386, 241, -267, -147, 225, -178, -534, 347, 502,
|
||||
-643, -381, 397, 30, -651, -733, -435, 398, -407, -726, -484, -248,
|
||||
-789, -914, -438, -476, -498, -390, 75, -295, -964, -590, -606, 150,
|
||||
-121, -49, -155, -78, 935, 550, 389, 38, -321, 127, 424, 315,
|
||||
-285, -113, 283, 259, 658, 203, 322, 486, 903, 505, 748, 417,
|
||||
611, 423, 555, 512, 239, -83, -578, -19, -339, -731, 349, 13,
|
||||
-934, -1399, -114, -360, 107, 692, 182, 90, -1243, -1538, -1551, -725,
|
||||
-568, -903, -1363, -525, -517, -853, -861, -1004, -168, -690, -835, 63,
|
||||
-137, -556, -547, 144, -286, -817, 485, 319, -147, -408, 526, 246,
|
||||
-347, -434, 297, -28, -290, -471, -1110, -1285, -460, -359, -988, -794,
|
||||
1347, 1299, 690, 523, 1216, 1068, 1094, 757, 825, 1140, 752, 494,
|
||||
1252, 1365, 1195, 898, 521, 1053, 532, 432, -334, -216, -313, -263,
|
||||
-160, 52, -472, -155, 127, 136, -380, 44, 851, 410, -162, -489,
|
||||
123, -255, -796, -667, 1090, 917, 789, 493, 1397, 1197, 558, 202,
|
||||
-51, -118, -342, -701, 83, 108, -42, -441, 61, 95, 287, 256,
|
||||
-27, 89, 524, 531, 351, 227, 592, 545, 697, 155, -164, 307,
|
||||
638, 274, -489, -50, 754, 240, -166, -124, -116, -579, -1212, -63,
|
||||
190, -295, -1040, -1296, 147, -376, -177, -113, 841, 1241, 1051, 668,
|
||||
2, 293, 551, 304, -1096, -953, -248, 376, -750, -965, 87, 516,
|
||||
-275, -516, 689, 391, -379, -643, 876, 594, -390, -1013, -645, 573,
|
||||
-107, -568, -689, -826, -1025, -27, -328, -203, 861, 749, 548, 233,
|
||||
-1660, -1043, 451, 108, -660, -620, 430, 236, 21, -396, -1158, -631,
|
||||
1372, 1298, 967, 577, 1125, 1125, 589, 454, -323, -865, -467, 153,
|
||||
-468, -699, -804, -509, -392, -718, -204, -35, -603, -1093, -567, -162,
|
||||
-505, -1004, -102, 350, 219, 224, 423, 252, 395, 591, 608, 363,
|
||||
-746, -96, 373, 172, 171, 295, 714, 339, 233, 77, 107, 277,
|
||||
157, 153, -499, -356, 1547, 1073, 576, 494, -292, -339, -504, -592,
|
||||
-903, -72, -619, -481, -1594, -1117, -567, -254, -793, -507, -564, -291,
|
||||
-492, -532, 502, 560, -382, 427, 600, 230, -227, 477, 251, 75,
|
||||
285, 842, 813, 476, -1310, -1333, 186, 377, -587, -917, 643, 381,
|
||||
-1186, -553, 411, 82, -1127, -820, -174, -540, -604, 119, 543, 205,
|
||||
-380, 657, 909, 567, 112, -298, -374, 114, -857, -251, 56, 159,
|
||||
401, 345, -34, -140, -111, -607, 41, 614, 355, -114, -77, 474,
|
||||
578, 56, 1450, 924, 1098, 1420, 741, 400, 246, 22, 588, 313,
|
||||
-121, 327, 831, 472, -1138, -608, 856, 552, -1241, -1072, 638, 600,
|
||||
-358, 254, -333, -303, -646, 739, 358, 74, 1226, 1671, 1221, 849,
|
||||
2241, 1624, 983, 636, 1841, 1477, 749, 384, 350, 263, 87, 128,
|
||||
-1902, -941, -144, -64, -1734, -255, 288, -31, -2644, -1238, 366, 235,
|
||||
-1643, -1092, -1344, -304, -541, -1075, -1116, 123, -1178, -252, -816, -180,
|
||||
-1016, 533, 565, 233, -487, -430, -188, 334, 867, 1236, 534, 171,
|
||||
-1590, -1607, 635, 630, -2196, 310, 924, 412, -2358, -328, 956, 529,
|
||||
-2639, -377, 630, 278, -2602, 317, 799, 299, -2406, 133, 340, 31,
|
||||
-2156, -1468, 131, 125, -1184, -490, -139, 46, -744, 447, 891, 564,
|
||||
67, -451, 646, 604, -553, -429, -876, 396, 162, -66, 1305, 915,
|
||||
479, 579, 1088, 794, 450, 278, 566, 324, -1057, -154, 148, -177,
|
||||
-2545, 168, 1070, 592, -2351, -42, 819, 345, -2344, -707, 721, 250,
|
||||
-2175, -1497, -309, 122, -78, -73, 120, 173, -4, 262, -263, -261,
|
||||
-431, -64, -405, -732, -2609, 116, -83, -193, -1525, -944, -477, -725,
|
||||
-508, 307, 170, 172, 832, 417, 832, 686, -225, 177, 894, 818,
|
||||
-482, -389, 1279, 1039, -383, 201, -350, 40, 730, 635, 226, 526,
|
||||
503, 462, 338, 398, 535, 714, 40, -282, 1482, 1471, 1085, 731,
|
||||
1561, 1072, 909, 693, 1419, 1282, 889, 879, 1153, 728, 1186, 840,
|
||||
-226, 1130, 949, 689, -494, -986, -1556, -128, -568, -721, -713, -26,
|
||||
317, 524, 70, 135, -405, -865, -1766, -652, -174, -801, 885, 773,
|
||||
-153, -91, 1099, 751, -506, -1149, 853, 646, 241, 782, 519, 539,
|
||||
1853, 1700, 1101, 684, -1249, -1486, -464, 188, -893, -1409, -1312, -341,
|
||||
-135, 438, -175, 18, 1111, 976, 319, 208, -1430, -1768, 83, 458,
|
||||
-530, -1000, 307, 129, -840, -15, -29, -356, -911, -924, -1147, -242,
|
||||
-119, -528, 127, -133, -761, -765, 190, -83, -315, 895, 522, 231,
|
||||
-222, 102, -63, -428, 316, 699, 379, 70, 25, 716, 314, -108,
|
||||
507, 874, 566, 238, 108, 941, 519, 195, 425, -60, -427, 257,
|
||||
139, -103, -630, 446, 334, 370, 412, 48, -172, -690, -283, 557,
|
||||
187, -286, 158, 483, 140, 270, -344, -631, 924, 579, -116, 132,
|
||||
142, 466, -68, -64, 230, -145, -302, -542, -803, -912, 1018, 737,
|
||||
-773, 1015, 630, 297, -2596, 95, 445, 336, -2122, 491, 510, 191,
|
||||
-1253, 161, -2, -324, -1450, -633, -712, -105, -842, -254, -411, 100,
|
||||
-640, -290, 1010, 763, -650, 313, 1169, 730, 140, 505, 1030, 766,
|
||||
772, 287, 1067, 823, 495, 749, 305, 323, -164, 462, 78, 399,
|
||||
-342, -874, 69, 597, -16, 620, 621, 337, -138, -444, -265, 218,
|
||||
84, -450, 953, 666, -222, -803, 541, 604, -921, -1376, 244, 116,
|
||||
-841, -723, 630, 588, 140, 663, 294, 368, 935, 1046, 881, 759,
|
||||
1746, 1464, 916, 628, 436, 963, 281, 1, -119, 74, 542, 213,
|
||||
1, -567, 301, 241, 260, 435, 222, 396, 936, 957, 1108, 703,
|
||||
510, 506, 808, 478, 601, 694, 960, 620, 972, 741, 980, 600,
|
||||
834, 717, 767, 684, 643, 972, 935, 638, 501, 661, 720, 851,
|
||||
-105, -632, -303, -117, -429, 130, 789, 442, -522, -188, 704, 373,
|
||||
-759, 42, 814, 523, -531, -1137, 373, 578, -682, -1203, -455, 285,
|
||||
-1163, -1577, -1098, 44, 81, -82, 712, 363, 477, 246, 954, 622,
|
||||
1604, 1622, 1277, 891, 1409, 859, 924, 892, 774, 1041, 947, 1142,
|
||||
40, -546, -75, 288, -616, -106, -697, -26, -169, -160, -891, -739,
|
||||
-279, -384, -1029, -350, 1781, 1308, 1046, 816, 1580, 1533, 1472, 1178,
|
||||
1505, 1076, 1216, 899, 890, 904, 564, 654, 920, 692, 1021, 856,
|
||||
-493, 132, 177, 505, 71, 195, -28, 97, 456, 351, -164, 88,
|
||||
439, 278, -40, 350, 1395, 949, 234, -95, -805, -472, 38, -163,
|
||||
367, -98, 489, 523, 1025, 1178, 1212, 906, 319, 1314, 814, 461,
|
||||
-123, -543, -804, 447, -748, -324, -897, -1127, -737, -501, -789, -713,
|
||||
715, 777, 1239, 922, 1949, 1939, 1368, 865, 730, 880, 758, 388,
|
||||
-871, 454, 17, -251, -381, -810, -1583, 239, -521, -966, -792, 259,
|
||||
-890, -1358, -770, -73, 166, 349, -212, 323, -840, -301, 473, 435,
|
||||
-679, -464, 728, 351, -156, -199, 667, 432, 29, -252, 415, 480,
|
||||
-731, -379, 145, 559, -528, -631, -1158, -159, 445, 273, 123, 639,
|
||||
373, -126, 800, 568, 84, -162, 720, 712, -830, -536, -185, 222,
|
||||
408, 452, 501, 771, -897, -1355, -67, 442, -792, -1406, 566, 602,
|
||||
167, -326, 509, 330, -95, -626, -730, -344, 1668, 1217, 779, 455,
|
||||
1316, 828, 584, 719, 404, -31, 1013, 789, 89, 107, 891, 549,
|
||||
871, 1581, 917, 671, 866, 1479, 1289, 854, 391, 1068, 1122, 812,
|
||||
78, -562, 345, 563, 429, -103, 417, 787, -122, -437, 411, 788,
|
||||
-913, -417, 602, 754, -226, -16, 151, 760, -700, 118, -104, -14,
|
||||
-1128, 48, 284, 393, -390, -419, -639, -116, -910, 306, 316, -13,
|
||||
1207, 984, 821, 669, -1195, -693, 140, -213, -884, -416, -199, -558,
|
||||
-616, 245, -404, -664, 262, 56, -617, -724, -85, -491, -320, -656,
|
||||
-570, -831, -129, -528, -1506, -63, -367, -385, -358, -321, 4, 51,
|
||||
-366, -214, 319, 511, 146, 671, -17, -291, -110, 464, -139, -496,
|
||||
-202, 220, -312, -631, -660, -73, -655, -820, -662, -653, -1288, -857,
|
||||
-430, -953, -959, -264, -49, -468, -72, -381, -350, -563, -193, -407,
|
||||
55, -408, -803, 11, -309, 649, 188, -198, -512, 461, -79, -458,
|
||||
-1318, -263, -134, -523, -1657, -435, -495, -765, 57, -347, -414, 434,
|
||||
-1141, -242, -664, -857, 34, -68, -707, -338,
|
||||
}
|
||||
|
||||
var mr515_3_lsf = []int16{
|
||||
419, 163, -30, -262, -455, -789, -1430, -721, 1006, 664, 269, 25,
|
||||
619, 260, 183, 96, -968, -1358, -388, 135, -693, 835, 456, 154,
|
||||
1105, 703, 569, 363, 1625, 1326, 985, 748, -220, 219, 76, -208,
|
||||
-1455, -1662, 49, 149, -964, -172, -752, -336, 625, 209, -250, -66,
|
||||
-1017, -838, -2, 317, -2168, -1485, -138, 123, -1876, -2099, -521, 85,
|
||||
-967, -366, -695, -881, -921, -1011, -763, -949, -124, -256, -352, -660,
|
||||
178, 463, 354, 304, -1744, -591, -282, 79, -2249, 175, 867, 499,
|
||||
-138, -180, -181, -21, -2291, -1241, -460, -520, -771, 451, -10, -308,
|
||||
271, -65, 4, 214, -279, -435, -43, -348, -670, 35, -65, -211,
|
||||
806, 535, 85, 297, 57, 239, 722, 493, 225, 661, 840, 547,
|
||||
-540, -376, 14, 349, 469, 721, 331, 162, -544, -752, -62, -10,
|
||||
398, -88, 724, 701, -19, -533, -94, 601, 136, -71, -681, -747,
|
||||
-166, -344, 261, -50, 161, -52, 485, 337, -1675, 50, 190, -93,
|
||||
-2282, -231, -194, -82, -95, -595, -154, 128, 894, 501, 588, 457,
|
||||
-345, 206, 122, 110, -631, -227, -569, 3, 408, 239, 397, 226,
|
||||
-197, -2, 128, 491, 1281, 904, 292, 215, 538, 306, 259, 509,
|
||||
-677, -1047, 13, 321, -679, -588, -358, -212, -558, 243, 646, 479,
|
||||
486, 342, 634, 532, 107, 802, 331, 136, -112, -398, -1031, -286,
|
||||
-326, -705, 288, 272, 1299, 1144, 1178, 860, -423, 121, -385, -148,
|
||||
-295, -302, -834, -819, 16, -24, -201, -476, 555, 91, -245, 294,
|
||||
-38, -379, -962, -1221, -1191, -1518, -273, -395, -390, -1013, -645, 573,
|
||||
-1843, -1030, 505, 468, 744, 947, 609, 493, -689, -1172, -628, -135,
|
||||
-1026, 195, 411, 196, 1582, 1147, 575, 337, -1239, -777, -648, -142,
|
||||
595, 825, 967, 735, -1206, -970, -81, -342, -745, 13, -72, 375,
|
||||
454, 19, 1407, 921, -1647, -172, 861, 562, 928, 1537, 1063, 740,
|
||||
-2472, -952, 264, 82, -502, -965, -1334, 123, 867, 1236, 534, 171,
|
||||
-2320, -460, 780, 363, -1190, -617, 252, -61, -174, 34, 1011, 788,
|
||||
-2333, 247, 423, 153, -16, -355, 262, 449, -1576, -1073, -544, -371,
|
||||
-615, -305, 1051, 805, 687, 528, 6, -182, 935, 875, 1002, 809,
|
||||
199, 257, 126, 76, -584, -1138, 599, 556, -1105, -1391, -1591, -519,
|
||||
-977, -1325, 108, 347, -722, -975, 365, 101, -145, 681, 249, -153,
|
||||
0, -334, -570, 159, 412, 285, -336, -617, -953, -966, 887, 689,
|
||||
-1251, 84, -185, -398, -592, 433, 1044, 653, 85, 329, -40, 361,
|
||||
-433, -705, 466, 574, -154, 654, 592, 290, -167, 72, 349, 175,
|
||||
674, 297, 977, 720, 1235, 1204, 757, 488, -400, -269, 538, 372,
|
||||
-1350, -1387, -1194, -91, 1262, 876, 775, 700, -599, -38, -430, -722,
|
||||
1976, 1630, 991, 608, 111, 276, -226, -96, -947, -388, -11, -7,
|
||||
-303, -531, -839, 338, 1734, 1710, 1405, 1013, -516, -855, -645, 210,
|
||||
-688, -416, 513, 230, -822, -637, -1146, -320, -952, -658, -694, 183,
|
||||
-114, -623, 818, 674, -191, -204, 731, 635, 51, 1221, 883, 576,
|
||||
-954, -431, 826, 598, -342, -755, -900, -407, -1126, -354, -206, -512,
|
||||
-547, -810, -357, -620, 66, 515, -73, -410, -872, -945, -1444, -1227,
|
||||
191, -17, -544, -231, -1540, -544, -901, -886,
|
||||
}
|
||||
|
||||
var mr795_1_lsf = []int16{
|
||||
-890, -1550, -2541, -819, -970, 175, -826, -1234, -762, -599, -22, 634,
|
||||
-811, -987, -902, -323, 203, 26, -383, -235, -781, -399, 1262, 906,
|
||||
-932, -1399, -1380, -624, 93, 87, -414, -539, -691, 37, 633, 510,
|
||||
-387, -476, -1330, 399, 66, 263, -407, -49, -335, -417, 1041, 1865,
|
||||
-779, -1089, -1440, -746, -858, 832, -581, -759, -371, -673, -506, 2088,
|
||||
-560, -634, -1179, 271, 241, 14, -438, -244, -397, 463, 1202, 1047,
|
||||
-606, -797, -1438, -51, -323, 481, -224, -584, -527, 494, 881, 682,
|
||||
-433, -306, -1002, 554, 659, 222, 171, -160, -353, 681, 1798, 1565,
|
||||
-852, -1181, -1695, -336, -666, 114, -581, -756, -744, -195, 375, 497,
|
||||
-465, -804, -1098, 154, 282, -131, -50, -191, -719, 323, 732, 1542,
|
||||
-722, -819, -1404, 105, -250, 185, -178, -502, -742, 321, 510, 1111,
|
||||
-323, -567, -966, 127, 484, 338, -160, 52, -338, 732, 1367, 1554,
|
||||
-626, -802, -1696, -286, -586, 676, -695, -343, -370, -490, 295, 1893,
|
||||
-630, -574, -1014, -80, 645, -69, -6, -318, -364, 782, 1450, 1038,
|
||||
-313, -733, -1395, 120, 60, 477, -264, -585, -123, 711, 1245, 633,
|
||||
-91, -355, -1016, 771, 758, 261, 253, 81, -474, 930, 2215, 1720,
|
||||
-808, -1099, -1925, -560, -782, 169, -804, -1074, -188, -626, -55, 1405,
|
||||
-694, -716, -1194, -660, 354, 329, -514, -55, -543, 366, 1033, 1182,
|
||||
-658, -959, -1357, -55, -184, 93, -605, -286, -662, 404, 449, 827,
|
||||
-286, -350, -1263, 628, 306, 227, -16, 147, -623, 186, 923, 2146,
|
||||
-674, -890, -1606, -443, -228, 339, -369, -790, -409, 231, 86, 1469,
|
||||
-448, -581, -1061, 594, 450, -177, -124, -170, -447, 671, 1159, 1404,
|
||||
-476, -667, -1511, -77, -138, 716, -177, -372, -381, 451, 934, 915,
|
||||
-250, -432, -822, 272, 828, 446, 26, 19, -31, 698, 1692, 2168,
|
||||
-646, -977, -1924, -179, -473, 268, -379, -745, -691, 11, 127, 1033,
|
||||
-488, -917, -825, 61, 323, 135, 147, -145, -686, 685, 786, 1682,
|
||||
-506, -848, -1297, 35, 90, 222, -23, -346, -670, 455, 591, 1287,
|
||||
-203, -593, -1086, 652, 352, 437, 39, 63, -457, 841, 1265, 2105,
|
||||
-520, -882, -1584, -328, -711, 1421, -596, -342, -70, 209, 173, 1928,
|
||||
-423, -598, -921, 421, 605, -38, -2, -245, -127, 896, 1969, 1135,
|
||||
-379, -518, -1579, 173, 118, 753, -55, -381, -52, 985, 1021, 753,
|
||||
-2, -291, -891, 753, 992, 423, 264, 131, -196, 895, 2274, 2543,
|
||||
-635, -1088, -2499, -529, -982, 526, -764, -830, -548, -436, 316, 599,
|
||||
-675, -940, -746, -57, 236, -11, -201, -81, -798, 16, 845, 1558,
|
||||
-737, -985, -1212, -468, 17, 290, -279, -584, -700, 183, 822, 705,
|
||||
-265, -492, -1187, 421, 152, 468, -390, 166, -268, 39, 1550, 1868,
|
||||
-635, -966, -1571, -453, -492, 910, -284, -1027, -75, -181, -133, 1852,
|
||||
-445, -624, -1174, 420, 367, -49, -389, -212, -169, 707, 1073, 1208,
|
||||
-539, -710, -1449, 83, -163, 484, -236, -543, -355, 338, 1175, 814,
|
||||
-246, -309, -958, 606, 760, 60, 166, -8, -163, -306, 1849, 2563,
|
||||
-747, -1025, -1783, -419, -446, 209, -718, -566, -534, -506, 693, 857,
|
||||
-463, -697, -1082, 325, 431, -206, -15, -8, -763, 545, 919, 1518,
|
||||
-611, -783, -1313, 256, -55, 208, -165, -348, -662, 321, 680, 930,
|
||||
-326, -429, -951, 484, 446, 570, -197, 72, -73, 909, 1455, 1741,
|
||||
-563, -737, -1974, -124, -416, 718, -478, -404, -314, -16, 446, 1636,
|
||||
-551, -537, -750, -58, 638, 214, 55, -185, -271, 1148, 1301, 1212,
|
||||
-483, -671, -1264, 117, 285, 543, -204, -391, -111, 513, 1538, 854,
|
||||
-114, -190, -978, 877, 595, 464, 260, 260, -311, 748, 2283, 2216,
|
||||
-517, -945, -2171, -326, -708, 378, -812, -691, -232, -560, 687, 1409,
|
||||
-732, -690, -836, -359, 645, 386, -265, 62, -678, 145, 1644, 1208,
|
||||
-555, -988, -1233, -78, 14, 114, -327, -358, -489, 392, 677, 697,
|
||||
-201, -236, -1140, 693, 449, 178, -243, 256, -433, 611, 1385, 2456,
|
||||
-612, -901, -1464, -307, -17, 499, -315, -667, -254, 256, 428, 1463,
|
||||
-486, -422, -1056, 655, 370, 18, -102, -185, -276, 755, 1578, 1335,
|
||||
-488, -603, -1418, 182, -93, 870, -73, -458, -348, 835, 862, 957,
|
||||
-282, -333, -746, 547, 839, 428, 273, -89, 13, 940, 1708, 2576,
|
||||
-418, -1084, -1758, -44, -358, 259, -497, -643, -560, 99, 557, 961,
|
||||
-421, -766, -917, 295, 326, 184, 175, 15, -626, 532, 878, 1981,
|
||||
-443, -768, -1275, 221, 156, 268, 39, -363, -505, 695, 772, 1140,
|
||||
-162, -459, -912, 709, 444, 658, 25, 303, -312, 1268, 1410, 1715,
|
||||
-297, -766, -1836, -263, -108, 1070, -406, -13, -129, 57, 438, 2734,
|
||||
-374, -487, -835, 304, 696, 164, 104, -235, 5, 1611, 1900, 1399,
|
||||
-229, -582, -1325, 405, 192, 817, -87, -438, 111, 1028, 1199, 993,
|
||||
68, -175, -934, 1033, 1117, 451, 478, 200, -248, 2127, 2696, 2042,
|
||||
-835, -1323, -2131, -799, -692, 466, -812, -1032, -469, -622, 288, 920,
|
||||
-701, -841, -1070, -411, 512, 8, -390, -91, -744, -30, 1043, 1161,
|
||||
-822, -1148, -1156, -294, -46, 110, -411, -374, -678, 214, 531, 668,
|
||||
-406, -420, -1194, 487, 232, 303, -318, 91, -472, 123, 1232, 2445,
|
||||
-722, -952, -1495, -738, -675, 1332, -543, -606, -211, -95, -98, 1508,
|
||||
-549, -514, -1193, 473, 211, 73, -288, -112, -389, 537, 1332, 1258,
|
||||
-567, -755, -1545, 71, -283, 632, -170, -481, -493, 681, 1002, 817,
|
||||
-356, -331, -877, 419, 706, 346, 241, -34, -326, 377, 1950, 1883,
|
||||
-727, -1075, -1625, -233, -543, 116, -524, -806, -585, -73, 478, 729,
|
||||
-288, -925, -1143, 173, 447, -52, 68, -229, -606, 449, 529, 1797,
|
||||
-591, -875, -1363, 183, -144, 324, -103, -452, -666, 623, 488, 1176,
|
||||
-238, -511, -1004, 326, 552, 458, 136, 108, -319, 626, 1343, 1883,
|
||||
-490, -646, -1730, -186, -449, 984, -738, -76, -170, -550, 755, 2560,
|
||||
-496, -510, -947, 210, 694, -52, 84, -322, -199, 1090, 1625, 1224,
|
||||
-376, -603, -1396, 343, 74, 632, -175, -502, -32, 972, 1332, 734,
|
||||
52, -295, -1113, 1065, 918, 160, 393, 107, -397, 1214, 2649, 1741,
|
||||
-632, -1201, -1891, -719, -277, 353, -651, -880, -122, -211, 209, 1338,
|
||||
-562, -714, -1059, -208, 388, 159, -320, -61, -551, 293, 1092, 1443,
|
||||
-648, -865, -1253, -49, -143, 305, -401, -227, -585, 561, 532, 927,
|
||||
-117, -443, -1188, 507, 436, 292, -79, 233, -458, 671, 1025, 2396,
|
||||
-633, -842, -1525, -308, -286, 640, -373, -621, -407, 418, 253, 1305,
|
||||
-315, -581, -1137, 572, 685, -281, 61, -68, -371, 991, 1101, 1498,
|
||||
-493, -683, -1362, -47, 164, 704, -256, -314, -268, 631, 949, 1052,
|
||||
-118, -348, -833, 68, 1180, 568, 152, 117, 34, 1113, 1902, 2239,
|
||||
-601, -959, -1706, -143, -489, 480, -332, -655, -574, 54, 353, 1192,
|
||||
-462, -652, -796, 150, 549, 112, 195, -111, -515, 679, 1108, 1647,
|
||||
-558, -749, -1217, -9, 272, 341, -53, -265, -535, 489, 843, 1298,
|
||||
-120, -482, -1032, 632, 543, 408, 179, 306, -526, 1124, 1464, 2244,
|
||||
-417, -786, -1562, -224, -384, 1364, -377, -459, -25, 385, 489, 2174,
|
||||
-332, -651, -829, 544, 553, 61, 22, -113, -89, 1128, 1725, 1524,
|
||||
-216, -373, -1653, 161, 316, 908, -165, -222, -67, 1362, 1175, 789,
|
||||
73, -252, -767, 738, 932, 616, 362, 246, -126, 787, 2654, 3027,
|
||||
-691, -1106, -2190, -565, -588, 524, -590, -979, -490, -263, 397, 982,
|
||||
-577, -837, -945, -22, 435, -49, -190, -118, -629, -88, 1240, 1513,
|
||||
-636, -1051, -1019, -291, 189, 259, -257, -470, -629, 145, 945, 894,
|
||||
-326, -364, -1094, 543, 260, 630, -202, 189, -209, 357, 1379, 2091,
|
||||
-569, -1075, -1449, -714, -239, 919, -420, -705, -84, -109, -114, 2407,
|
||||
-413, -529, -1177, 482, 368, 131, -186, -72, -131, 861, 1255, 1220,
|
||||
-611, -658, -1341, 227, -121, 631, -176, -489, -218, 745, 1175, 957,
|
||||
-321, -148, -936, 671, 966, 216, 340, -3, -143, 469, 1848, 2437,
|
||||
-729, -961, -1683, -213, -254, 321, -511, -438, -521, -126, 725, 903,
|
||||
-340, -685, -1032, 316, 480, 20, 23, -89, -551, 353, 1051, 1789,
|
||||
-544, -757, -1364, 298, -25, 436, -100, -392, -519, 467, 754, 1078,
|
||||
-210, -398, -1078, 620, 658, 630, 33, 147, -178, 921, 1687, 1921,
|
||||
-325, -528, -1978, 2, -285, 910, -371, -490, -230, 0, 597, 2010,
|
||||
-496, -395, -834, 37, 945, 245, 181, -160, -144, 1481, 1373, 1357,
|
||||
-355, -601, -1270, 298, 322, 672, -193, -336, 77, 1089, 1533, 922,
|
||||
177, -39, -1125, 996, 781, 536, 456, 366, -432, 1415, 2440, 2279,
|
||||
-466, -758, -2325, -303, -509, 387, -727, -557, 66, -145, 643, 1248,
|
||||
-544, -676, -916, -225, 862, 588, -152, 40, -533, 423, 1423, 1558,
|
||||
-572, -843, -1145, -128, 85, 461, -238, -257, -584, 605, 748, 861,
|
||||
24, -202, -1409, 797, 487, 303, -181, 364, -182, 616, 1378, 2942,
|
||||
-494, -852, -1441, -292, 61, 812, -84, -723, -182, 555, 532, 1506,
|
||||
-365, -493, -1057, 822, 588, 11, -14, -18, -230, 1001, 1401, 1451,
|
||||
-474, -569, -1292, 302, 62, 1062, -70, -376, -222, 982, 974, 1149,
|
||||
-196, -234, -795, 479, 1098, 499, 362, 58, 70, 1147, 2069, 2857,
|
||||
-487, -878, -1824, 73, -288, 348, -358, -500, -508, 199, 721, 1242,
|
||||
-78, -697, -795, 361, 536, 196, 374, 110, -735, 847, 1051, 1896,
|
||||
-366, -713, -1182, 315, 320, 429, 72, -215, -450, 759, 886, 1363,
|
||||
-30, -428, -834, 861, 627, 796, 118, 468, -279, 1355, 1883, 1893,
|
||||
-188, -642, -1612, 63, -175, 1198, -418, -211, 51, 414, 587, 2601,
|
||||
-234, -557, -858, 424, 889, 222, 136, -101, 83, 1413, 2278, 1383,
|
||||
-84, -445, -1389, 414, 313, 1045, 29, -343, 65, 1552, 1647, 980,
|
||||
183, -91, -829, 1273, 1413, 360, 553, 272, -107, 1587, 3149, 2603,
|
||||
}
|
||||
|
||||
var mean_lsf_5 = []int16{
|
||||
1384, 2077, 3420, 5108, 6742, 8122, 9863, 11092, 12714, 13701,
|
||||
}
|
||||
|
||||
var dico1_lsf_5 = []int16{
|
||||
-451, -1065, -529, -1305, -450, -756, -497, -863, -384, -619, -413, -669,
|
||||
-317, -538, -331, -556, -414, -508, -424, -378, -274, -324, -434, -614,
|
||||
-226, -500, -232, -514, -263, -377, -298, -410, -151, -710, -174, -818,
|
||||
-149, -412, -156, -429, -288, -462, -186, -203, -170, -302, -191, -321,
|
||||
-131, -147, -297, -395, -228, -214, -245, -192, -67, -316, -71, -327,
|
||||
-104, -205, -94, -183, -143, -38, -193, -95, 16, -76, -124, -248,
|
||||
23, -237, 24, -244, 18, -136, 44, -111, -33, -24, -25, 0,
|
||||
149, 19, 23, -143, 158, -169, 174, -181, 133, -55, 165, -26,
|
||||
111, 84, 98, 75, 87, 183, -115, -11, -8, 130, 11, 170,
|
||||
254, 77, 205, 17, 183, 112, 262, 194, 202, 287, 95, 189,
|
||||
-42, -105, 234, 179, 39, 186, 163, 345, 332, 199, 299, 161,
|
||||
-54, 285, -78, 281, -133, 141, -182, 111, 249, 341, 271, 364,
|
||||
93, 403, 75, 391, 92, 510, -138, 220, -185, -29, -34, 361,
|
||||
-115, 320, 3, 554, 99, 286, 218, 591, -245, 406, -268, 453,
|
||||
0, 580, 25, 606, 275, 532, 148, 450, -73, 739, -285, 518,
|
||||
-288, 94, -203, 674, -140, -74, 205, 714, -114, 299, 176, 923,
|
||||
182, 557, 240, 705, -16, 513, 485, 593, 293, 384, 451, 617,
|
||||
-38, 50, 563, 529, 303, 209, 459, 363, 433, 452, 450, 454,
|
||||
367, 606, 477, 741, 432, 353, 368, 267, 361, 716, 273, 583,
|
||||
453, 166, 510, 172, 201, 629, 274, 191, 568, 639, 302, 298,
|
||||
634, 387, 643, 350, 587, 560, 612, 565, 600, 788, 487, 672,
|
||||
512, 1015, 321, 333, 357, 854, -125, 413, 474, 712, 17, -151,
|
||||
564, 285, 270, -241, 971, 889, 489, 220, 510, 896, 549, 924,
|
||||
327, 825, 290, 911, 540, 1108, 158, 805, 199, 957, 511, 730,
|
||||
100, 874, 13, 791, 435, 632, 676, 972, 249, 900, 467, 1218,
|
||||
781, 1074, 585, 785, -23, 669, 267, 1043, 619, 1084, 615, 1145,
|
||||
622, 905, 916, 1049, 80, 331, 584, 1075, 89, 639, 988, 961,
|
||||
770, 720, 798, 699, 492, 447, 899, 627, 271, 1188, 725, 1333,
|
||||
87, 603, 832, 1603, 616, 1127, 890, 1505, 1000, 1156, 866, 1009,
|
||||
995, 827, 1149, 858, 817, 1450, 773, 1320, 500, 1389, 312, 1153,
|
||||
-20, 1084, 64, 1283, 2, 1172, 399, 1869, 514, 1706, 502, 1636,
|
||||
886, 1522, 416, 600, 1131, 1350, 1275, 1390, 889, 1795, 914, 1766,
|
||||
227, 1183, 1250, 1826, 505, 1854, 919, 2353, -199, 431, 152, 1735,
|
||||
-213, -28, 392, 1334, -153, -52, 978, 1151, -323, -400, 813, 1703,
|
||||
-136, 84, 1449, 2015, -331, -143, -137, 1192, -256, 534, -157, 1031,
|
||||
-307, -439, 542, 731, -329, -420, -97, 616, -362, -168, -322, 366,
|
||||
-247, -110, -211, 89, -196, -309, 20, 59, -364, -463, -286, 89,
|
||||
-336, 175, -432, 141, -379, -190, -434, -196, -79, 150, -278, -227,
|
||||
-280, 166, -555, -422, -155, 541, -366, 54, -29, -83, -301, -774,
|
||||
186, 628, -397, -264, 242, 293, -197, -585, 124, 410, 53, -133,
|
||||
10, 340, -570, -1065, 65, -446, 68, -493, 383, 937, -357, -711,
|
||||
-359, -250, -677, -1068, 292, -26, 363, 6, 607, 1313, -127, -10,
|
||||
1513, 1886, 713, 972, 1469, 2181, 1443, 2016,
|
||||
}
|
||||
|
||||
var dico2_lsf_5 = []int16{
|
||||
-1631, -1600, -1796, -2290, -1027, -1770, -1100, -2025, -1277, -1388, -1367, -1534,
|
||||
-947, -1461, -972, -1524, -999, -1222, -1020, -1172, -815, -987, -992, -1371,
|
||||
-1216, -1006, -1289, -1094, -744, -1268, -755, -1293, -862, -923, -905, -984,
|
||||
-678, -1051, -685, -1050, -1087, -985, -1062, -679, -989, -641, -1127, -976,
|
||||
-762, -654, -890, -806, -833, -1091, -706, -629, -621, -806, -640, -812,
|
||||
-775, -634, -779, -543, -996, -565, -1075, -580, -546, -611, -572, -619,
|
||||
-760, -290, -879, -526, -823, -462, -795, -253, -553, -415, -589, -439,
|
||||
-533, -340, -692, -935, -505, -772, -702, -1131, -263, -306, -971, -483,
|
||||
-445, -74, -555, -548, -614, -129, -693, -234, -396, -246, -475, -250,
|
||||
-265, -404, -376, -514, -417, -510, -300, -313, -334, -664, -463, -814,
|
||||
-386, -704, -337, -615, -234, -201, -233, -239, -167, -567, -203, -619,
|
||||
-147, -415, -115, -352, -166, -750, -171, -761, -270, -879, -264, -903,
|
||||
-367, -744, 43, -475, 14, -653, 43, -670, 11, -448, -59, -521,
|
||||
-126, -119, -155, -613, -42, -863, -27, -931, 136, -483, 183, -468,
|
||||
55, -298, 55, -304, 313, -609, 313, -720, 322, -167, 100, -541,
|
||||
-3, -119, -111, -187, 233, -236, 260, -234, 26, -165, 134, -45,
|
||||
-40, -549, 360, -203, 378, -388, 450, -383, 275, 20, 182, -103,
|
||||
246, -111, 431, 37, 462, -146, 487, -157, -284, -59, 503, -184,
|
||||
24, 53, -3, 54, 122, 259, 333, 66, 484, 104, 436, 68,
|
||||
195, 116, 190, 206, 269, -9, 482, 352, 382, 285, 399, 277,
|
||||
452, 256, 69, 186, 13, 297, -13, 259, -95, 30, 56, 394,
|
||||
196, 425, 205, 456, 281, 577, 15, 191, 375, 290, 407, 576,
|
||||
-56, 227, 544, 405, 0, 549, -92, 528, -229, 351, -245, 338,
|
||||
-362, 435, 167, 527, -75, 302, 91, 824, 129, 599, 496, 679,
|
||||
186, 749, 153, 737, -281, 600, -348, 615, -236, 769, 41, 881,
|
||||
38, 890, -220, 841, -357, 883, -393, 903, -634, 474, -444, 850,
|
||||
-175, 678, -493, 242, -519, 785, -714, 582, -541, 366, -543, 434,
|
||||
-597, 500, -765, 222, -702, 917, -743, 962, -869, 501, -899, 548,
|
||||
-379, 200, -435, 157, -819, 214, -861, 157, -614, 40, -632, 94,
|
||||
-883, -54, -741, 516, -501, 298, -614, -171, -870, -161, -865, -23,
|
||||
-818, 93, -1015, -267, -662, -359, -549, 2, -442, -121, -377, 0,
|
||||
-227, 33, -414, -126, -129, 212, -934, 34, -1082, -282, -1119, -268,
|
||||
-710, -825, -420, -191, -1076, -928, -917, -93, -628, -358, 97, 7,
|
||||
-206, -393, -101, 24, -203, 38, -168, 83, -599, -423, -279, 426,
|
||||
-700, 118, -75, 206, -981, -673, -680, 417, -367, 37, -279, 474,
|
||||
-129, -318, 319, 296, -626, -39, 343, 602, -696, -39, -303, 940,
|
||||
104, 233, -380, 137, -36, 269, -75, -214, 120, 43, -529, -477,
|
||||
459, 164, -202, -229, -49, -167, 609, 792, 98, -220, 915, 148,
|
||||
293, 283, 869, 91, 575, 394, 326, -78, 717, 67, 365, -323,
|
||||
616, -36, 731, 27, 619, 238, 632, 273, 448, 99, 801, 476,
|
||||
869, 273, 685, 64, 789, 72, 1021, 217, 793, 459, 734, 360,
|
||||
646, 480, 360, 322, 429, 464, 638, 430, 756, 363, 1000, 404,
|
||||
683, 528, 602, 615, 655, 413, 946, 687, 937, 602, 904, 604,
|
||||
555, 737, 786, 662, 467, 654, 362, 589, 929, 710, 498, 478,
|
||||
415, 420, 693, 883, 813, 683, 781, 925, 913, 939, 726, 732,
|
||||
491, 853, 531, 948, 734, 963, 315, 808, 761, 755, 1144, 760,
|
||||
655, 1076, 826, 1057, 1091, 838, 1003, 808, 1047, 1133, 659, 1101,
|
||||
992, 1050, 1074, 1075, 971, 694, 1226, 1054, 571, 841, 884, 1404,
|
||||
1379, 1096, 1080, 861, 1231, 735, 1284, 760, 1272, 991, 1367, 1053,
|
||||
1257, 700, 1050, 534, 988, 453, 1264, 599, 1140, 679, 1621, 815,
|
||||
1384, 521, 1317, 393, 1564, 805, 1448, 686, 1068, 648, 875, 307,
|
||||
1083, 361, 1047, 317, 1417, 964, 675, 571, 1152, 79, 1114, -47,
|
||||
1530, 311, 1721, 314, 1166, 689, 514, -94, 349, 282, 1412, 328,
|
||||
1025, 487, -65, 57, 805, 970, 36, 62, 769, -263, 791, -346,
|
||||
637, 699, -137, 620, 534, 541, -735, 194, 711, 300, -268, -863,
|
||||
926, 769, -708, -428, 506, 174, -892, -630, 435, 547, -1435, -258,
|
||||
621, 471, -1018, -1368, -393, 521, -920, -686, -25, 20, -982, -1156,
|
||||
340, 9, -1558, -1135, -352, 48, -1579, -402, -887, 6, -1156, -888,
|
||||
-548, -352, -1643, -1168, -159, 610, -2024, -963, -225, 193, -1656, -1960,
|
||||
-245, -493, -964, -1680, -936, -635, -1299, -1744, -1388, -604, -1540, -835,
|
||||
-1397, -135, -1588, -290, -1670, -712, -2011, -1632, -1663, -27, -2258, -811,
|
||||
-1157, 184, -1265, 189, -1367, 586, -2011, 201, -790, 712, -1210, 3,
|
||||
-1033, 808, -1251, 830, -111, 635, -1636, 447, -463, -949, -445, -928,
|
||||
-504, -1162, -501, -1211, 144, -351, -372, -1052, -283, -1059, -279, -1123,
|
||||
-575, -1438, -587, -1614, -935, -984, 229, 690, -921, -719, -403, 1362,
|
||||
-685, -465, 874, 397, -509, -46, 317, 1334, -485, 456, 813, 439,
|
||||
-411, 339, 898, 1067, -425, 46, 1441, 497, -909, -800, 1465, 1046,
|
||||
-254, -321, 1430, 1165, 68, 350, 1034, 666, 370, 11, 1311, 790,
|
||||
143, 232, 1041, 1562, -114, 663, 1616, 1078, 454, 579, 1275, 1040,
|
||||
-76, 909, 752, 1067, 153, 512, 348, 1214, 614, 385, 1843, 808,
|
||||
269, 1034, 203, 1086, 652, 1017, 1783, 1130, 429, 1327, 387, 1384,
|
||||
-49, 1183, -72, 1215, -416, 1001, 544, 1749, -352, 1223, -502, 1199,
|
||||
-589, 569, -227, 1630, -142, 1578, -230, 1715, -714, 1288, -838, 1398,
|
||||
1131, 1357, -208, 1232, 437, 965, -929, 818, 811, 1410, 859, 1507,
|
||||
164, 1212, 1387, 1793, 484, 1874, 456, 2063, 996, 1170, 1326, 1402,
|
||||
1316, 1360, 1135, 1262, 1234, 1618, 1361, 1768, 1421, 1227, 1584, 1347,
|
||||
854, 672, 1685, 1566, 1139, 1270, 2016, 1825, 1773, 1581, 1532, 1460,
|
||||
1487, 946, 1659, 1021, 1744, 1212, 1392, 977, 1772, 1161, 1826, 1164,
|
||||
1718, 1429, 1973, 1591, 1185, 864, 2132, 1061, 1799, 814, 1838, 757,
|
||||
2104, 1315, 2054, 1258, 2113, 915, 2331, 930, 1467, 1147, 2590, 1439,
|
||||
2245, 1744, 2090, 1620, 2358, 1454, 2666, 1506, 1876, 1837, 2070, 1975,
|
||||
1739, 1577, 682, 1289, 1584, 2045, 1454, 2098, 2498, 2004, 2711, 2066,
|
||||
726, 1588, 2756, 2336, 228, 847, 2456, 1659, 36, 301, 1942, 1957,
|
||||
-446, -96, 2154, 1396, 1533, 1101, 14, 608, -923, -732, 1383, 1982,
|
||||
1345, 952, -680, 321, 1281, 1268, -1594, 365, 941, 946, -1737, -822,
|
||||
2374, 2787, 1821, 2788,
|
||||
}
|
||||
|
||||
var dico3_lsf_5 = []int16{
|
||||
-1812, -2275, -1879, -2537, -1640, -1848, -1695, -2004, -1220, -1912, -1221, -2106,
|
||||
-1559, -1588, -1573, -1556, -1195, -1615, -1224, -1727, -1359, -1151, -1616, -1948,
|
||||
-1274, -1391, -1305, -1403, -1607, -1179, -1676, -1311, -1443, -1478, -1367, -898,
|
||||
-1256, -1059, -1331, -1134, -982, -1133, -1149, -1504, -1080, -1308, -1020, -1183,
|
||||
-980, -1486, -967, -1495, -988, -922, -1047, -1077, -838, -1179, -858, -1222,
|
||||
-1131, -1041, -1064, -767, -872, -1157, -701, -880, -706, -906, -774, -1016,
|
||||
-578, -1080, -801, -1478, -591, -1111, -592, -1146, -713, -1388, -640, -1376,
|
||||
-597, -1059, -416, -903, -686, -832, -661, -708, -444, -868, -490, -921,
|
||||
-374, -776, -619, -1170, -585, -549, -769, -795, -435, -659, -530, -741,
|
||||
-498, -837, -357, -597, -279, -871, -243, -887, -282, -665, -280, -667,
|
||||
-165, -560, -394, -903, -362, -410, -448, -583, -409, -574, -313, -357,
|
||||
-637, -548, -570, -436, -896, -504, -382, -757, -58, -481, -165, -618,
|
||||
-191, -374, -234, -382, -222, -683, -25, -480, -418, -359, -730, -353,
|
||||
-324, -157, -432, -322, -394, -303, -284, -104, -601, -289, -556, -196,
|
||||
-588, -150, -659, -608, -473, -24, -68, -448, -474, -8, -506, -45,
|
||||
-748, -184, -844, -252, -901, -91, -584, -97, -652, 138, -764, -131,
|
||||
-678, -12, -670, 165, -259, -3, -840, -107, -909, 37, -992, 44,
|
||||
-854, -415, -839, 13, -1001, -271, -1026, -309, -798, -478, -832, -488,
|
||||
-943, 168, -1112, -387, -1185, -101, -1183, -40, -941, -316, -1030, -770,
|
||||
-1044, -625, -1081, -538, -1224, -299, -1312, -436, -1197, -663, -1167, -161,
|
||||
-1216, -690, -1237, -831, -1432, -720, -1403, -493, -898, -740, -922, -801,
|
||||
-1102, -402, -1579, -964, -1061, -638, -1269, -1438, -1499, -934, -1502, -895,
|
||||
-1598, -564, -1723, -717, -606, -597, -1166, -1085, -1369, -468, -1946, -1493,
|
||||
-1838, -953, -1932, -931, -1499, -188, -1635, -421, -1457, -338, -1448, -22,
|
||||
-1942, -422, -2006, -249, -496, -114, -1910, -755, -1289, 174, -1451, -109,
|
||||
-482, -257, -1221, -508, -1617, 151, -1694, 208, -654, 107, -1651, 29,
|
||||
-1141, 279, -1215, 306, -1228, -506, -730, -175, -1236, -101, -969, 551,
|
||||
-870, 278, -823, 315, -563, 376, -1051, 228, -507, 280, -599, 281,
|
||||
-758, 253, -305, 379, -755, -134, -611, 660, -824, 536, -817, 646,
|
||||
-413, 49, -341, 177, -453, 526, -482, 589, -71, 339, -657, 264,
|
||||
-244, 295, -237, 315, -387, 569, -506, -9, -377, 14, -160, 661,
|
||||
-216, 40, -308, -46, 95, 214, -242, 167, -86, 192, -56, 27,
|
||||
-76, 31, 36, 309, -106, -182, -113, 74, -441, -22, 23, 139,
|
||||
81, -11, 44, 15, -87, -137, -118, -207, -158, -58, 272, -92,
|
||||
-156, -441, 8, -136, 128, -221, 101, -218, 40, -197, -76, -456,
|
||||
9, -445, 33, -423, 226, 60, 73, -222, 156, -399, 280, -318,
|
||||
245, -341, 166, -499, 339, -190, 327, -219, 325, -137, -89, -596,
|
||||
100, -627, 144, -677, 487, 28, 252, -391, 214, -41, 282, -28,
|
||||
99, -286, 331, 49, 459, -388, 565, -369, 436, 28, 336, -9,
|
||||
397, -167, 618, 34, 596, -17, 561, -140, 299, 79, 522, 125,
|
||||
203, 2, 244, 288, 255, 211, 175, 82, 596, 187, 517, 108,
|
||||
381, 255, 365, 297, 497, 352, 327, -82, 25, 210, 371, 245,
|
||||
261, 3, 545, 449, 140, 294, 44, 295, 212, 347, 244, 494,
|
||||
331, 528, 201, 307, 349, 411, 613, 284, 614, 413, 464, 322,
|
||||
624, 397, 97, 200, -160, 384, 149, 362, 495, 525, 269, 585,
|
||||
33, 491, -121, 433, 427, 611, 498, 516, 171, 443, 497, 666,
|
||||
440, 275, 566, 575, 146, 639, 155, 670, -33, 173, 212, 696,
|
||||
-166, 601, -191, 695, -489, 503, 175, 742, 214, 476, 372, 1083,
|
||||
578, 530, 586, 777, 425, 874, 315, 841, 374, 848, -165, 565,
|
||||
35, 991, -39, 1062, 329, 712, 786, 840, 645, 795, 661, 676,
|
||||
571, 918, 632, 1079, 673, 817, 318, 388, 874, 1012, 564, 848,
|
||||
880, 620, 557, 479, 671, 453, 692, 468, 840, 642, 844, 645,
|
||||
506, 428, 897, 567, 837, 387, 962, 499, 691, 561, 939, 926,
|
||||
783, 296, 790, 268, 1028, 530, 874, 329, 548, 143, 675, 291,
|
||||
503, 66, 1041, 359, 786, 97, 805, 33, 837, 470, 511, 49,
|
||||
1092, 327, 1174, 323, 3, 242, 872, 474, 689, 429, 1329, 678,
|
||||
1042, 620, 1109, 664, 321, 193, 889, 950, 1153, 874, 893, 635,
|
||||
877, 862, 948, 913, 1293, 665, 1320, 639, 997, 793, 1402, 1030,
|
||||
1176, 1012, 1110, 959, 1410, 925, 1403, 915, 543, 862, 1116, 1222,
|
||||
835, 1190, 835, 1190, 959, 1148, 1147, 1376, 1300, 1193, 1415, 1231,
|
||||
1335, 1341, 746, 1092, 1711, 1283, 1389, 1073, 1334, 1566, 1153, 1475,
|
||||
1645, 1137, 1825, 1220, 1056, 1382, 1521, 1730, 1632, 1545, 1620, 1542,
|
||||
855, 1596, 865, 1667, 693, 885, 1716, 1519, 1167, 1296, 2209, 1760,
|
||||
1952, 1493, 2020, 1482, 1534, 1866, 1694, 2008, 1566, 748, 1761, 825,
|
||||
294, 1392, 1084, 2058, 621, 1315, 365, 1287, 198, 1028, 488, 1408,
|
||||
249, 403, 1014, 1561, 324, 363, 1645, 1044, 193, 367, 2034, 1859,
|
||||
-251, 579, 750, 994, -243, 30, 1325, 879, -28, -169, 624, 917,
|
||||
-453, 159, 186, 1370, -614, 6, 537, 392, -94, -291, 781, 229,
|
||||
-128, -298, 245, 491, -701, -648, 972, 789, -501, -640, 178, 255,
|
||||
-365, -390, -255, 317, -958, -294, -191, 228, -775, -447, 157, -237,
|
||||
-657, -720, -407, 92, -117, -611, 334, -230, -679, -1084, -144, -317,
|
||||
-901, -861, -738, -360, -85, -727, -90, -787, 100, -22, -391, -263,
|
||||
-56, -73, -337, -754, 5, -189, -706, -624, 89, -344, -135, -1113,
|
||||
-353, -237, -684, -1135, -275, -1102, -269, -1203, 152, 145, -722, -1232,
|
||||
49, 80, -1248, -776, -248, 391, -732, -547, 469, 218, -255, -864,
|
||||
69, 366, -166, -485, -688, 191, -1212, -1196, -170, -169, -1308, -1631,
|
||||
321, 470, -1419, -1243, -64, 272, -1361, -248, 492, 565, -721, -609,
|
||||
195, 485, -573, -133, 427, 202, -171, -118, 199, 575, 2, -31,
|
||||
694, 755, -1366, -39, 552, 557, -489, 271, 680, 537, 13, -453,
|
||||
855, 954, -133, -52, -81, 738, -1169, 637, 1055, 1059, -95, 676,
|
||||
1259, 1081, 489, 305, -449, 954, -534, 996, -969, 866, -1058, 1059,
|
||||
-1294, 618, -1416, 617, -458, 1366, -159, 1821, -774, -528, -14, 1110,
|
||||
-1202, -901, -772, 433, -1256, -1255, -1011, -302, -602, -585, -759, -1618,
|
||||
-760, -1549, -840, -1921, -816, -539, -1769, -2235, -227, -36, -2034, -1831,
|
||||
-2107, -1126, -2471, -1816, -1470, 252, -2701, -415, -571, -467, 1509, 1554,
|
||||
2180, 1975, 2326, 2020,
|
||||
}
|
||||
|
||||
var dico4_lsf_5 = []int16{
|
||||
-1857, -1681, -1857, -1755, -2056, -1150, -2134, -1654, -1619, -1099, -1704, -1131,
|
||||
-1345, -1608, -1359, -1638, -1338, -1293, -1325, -1265, -1664, -1649, -1487, -851,
|
||||
-1346, -1832, -1413, -2188, -1282, -681, -1785, -1649, -966, -1082, -1183, -1676,
|
||||
-1054, -1073, -1142, -1158, -1207, -744, -1274, -997, -934, -1383, -927, -1416,
|
||||
-1010, -1305, -783, -955, -1049, -900, -993, -817, -737, -823, -972, -1189,
|
||||
-738, -1094, -738, -1154, -784, -801, -810, -786, -892, -520, -1000, -818,
|
||||
-644, -965, -577, -882, -541, -694, -671, -917, -595, -642, -646, -615,
|
||||
-956, -621, -925, -515, -727, -483, -815, -485, -840, -578, -440, -713,
|
||||
-578, -325, -657, -670, -386, -570, -441, -666, -514, -787, -392, -529,
|
||||
-522, -453, -487, -423, -616, -585, -617, -157, -662, -268, -680, -348,
|
||||
-322, -323, -632, -444, -304, -430, -332, -458, -277, -468, -659, -793,
|
||||
-319, -636, -227, -554, -373, -347, -334, -210, -456, -192, -530, -242,
|
||||
-216, -198, -366, -370, -338, -161, -409, -748, -107, -380, -294, -643,
|
||||
-223, -665, -234, -741, -141, -496, -130, -510, -139, -327, -172, -305,
|
||||
-306, -580, -164, -263, -262, -172, -67, -402, 31, -366, -10, -436,
|
||||
-86, -527, 71, -377, -22, -609, -12, -678, -67, -319, 63, -191,
|
||||
35, -181, -39, -242, 126, -167, -140, -544, 155, -297, 174, -297,
|
||||
38, -8, 117, -380, 197, -452, 240, -522, 223, -103, 110, -187,
|
||||
87, -155, 169, -47, 157, 26, -83, -100, 128, 80, 209, -62,
|
||||
6, 7, 22, 5, 318, -20, 248, -45, -200, -63, 156, -69,
|
||||
250, -183, 369, -126, -113, -76, -142, -122, -64, -254, -31, 35,
|
||||
-177, -71, -7, 171, 93, 27, 108, 212, -330, -209, -123, -70,
|
||||
-279, 95, -96, 20, -188, -61, -314, 87, -300, -78, -354, -134,
|
||||
11, 122, -140, 122, -275, 152, -293, 140, -82, 138, -321, -111,
|
||||
-480, -156, -359, 76, -254, -40, -635, -96, -522, 79, -507, 8,
|
||||
-268, 303, -539, 68, -446, 61, -522, 306, 111, 189, -435, 122,
|
||||
-379, 166, -571, -398, -632, -74, -747, -95, -455, 194, -952, 83,
|
||||
-798, 192, -755, 192, -781, -162, -619, 234, -663, -297, -488, -109,
|
||||
-964, -132, -838, -68, -843, 58, -1112, -86, -805, -299, -944, -253,
|
||||
-778, -50, -965, -549, -352, -98, -992, -343, -1117, -315, -1117, -307,
|
||||
-1155, -374, -637, -230, -1166, -43, -1299, -100, -925, -393, -1274, -600,
|
||||
-689, -130, -1479, -312, -1321, -254, -1464, -442, -1292, -613, -1261, -503,
|
||||
-1501, -368, -1322, 26, -1432, -66, -1743, -161, -1644, -467, -1760, -548,
|
||||
-1393, -568, -1556, -871, -1495, -1034, -1387, -571, -1917, -528, -1783, -123,
|
||||
-1897, -231, -2054, -323, -2052, -906, -1976, -567, -1917, -620, -2047, -989,
|
||||
-1077, -370, -2031, -704, -2355, -749, -2740, -1089, -1909, 159, -2012, 248,
|
||||
-626, -123, -2339, -962, -669, -408, -1379, -1174, -452, -364, -1044, -735,
|
||||
-132, 183, -1620, -752, -547, -307, -777, -1261, -98, 41, -880, -1091,
|
||||
-257, 97, -1602, -1833, 31, -26, -644, -561, -180, -546, -385, -1095,
|
||||
-410, -802, -414, -827, -457, -970, -490, -1109, -215, -916, -144, -937,
|
||||
-493, -1269, -517, -1507, 181, 101, -332, -889, -836, -937, -559, -429,
|
||||
-629, -547, -183, -337, -545, -82, -250, -286, 5, -132, -348, -252,
|
||||
-293, -472, -158, 100, -29, 197, -236, -424, -861, -213, -140, -7,
|
||||
-427, -443, 187, -97, -684, -736, -293, 258, -368, -152, -150, 392,
|
||||
-609, 175, -142, 299, -138, 152, -119, 329, -486, -52, 293, 198,
|
||||
-183, 117, 175, 331, -58, -274, 231, 300, -288, 330, -305, 372,
|
||||
-111, 409, -9, 423, 83, 256, 67, 367, -19, 248, 91, 113,
|
||||
-35, 406, -191, 154, 238, 296, 5, 197, 141, 221, 313, 198,
|
||||
211, 421, 244, 334, 88, 426, -243, 454, 202, 552, -5, 403,
|
||||
291, 185, 219, 301, 251, 138, 128, 69, 197, 288, -140, -61,
|
||||
188, 361, 197, 598, 442, 273, 290, 143, 472, 482, 157, 370,
|
||||
415, 321, 372, 385, 402, 552, 155, 24, 550, 263, -11, 21,
|
||||
360, 227, 147, -254, 424, 97, 366, -13, 375, 141, 449, 232,
|
||||
396, 507, 474, 272, 701, 324, 362, -47, 587, 148, 543, 69,
|
||||
400, -51, 561, 59, 220, -10, 352, 147, 206, 211, 653, 185,
|
||||
563, 297, 565, 284, 594, 121, 766, 192, 398, 118, 642, 434,
|
||||
233, 264, 481, 467, 129, -165, 699, 239, 90, 26, 342, 474,
|
||||
-55, 27, 388, 94, -172, 0, 725, 379, -60, 337, 370, 465,
|
||||
95, 319, 806, 595, 78, 260, 497, 851, 210, 560, 458, 574,
|
||||
-464, 202, 497, 625, -202, 152, 48, 712, -20, 566, 100, 715,
|
||||
455, 468, 411, 605, 319, 646, 195, 615, 401, 538, 680, 739,
|
||||
201, 667, 434, 954, 454, 425, 646, 491, 606, 681, 416, 508,
|
||||
497, 822, 426, 815, 660, 647, 628, 716, 697, 466, 618, 457,
|
||||
685, 460, 365, 309, 721, 567, 836, 601, 609, 300, 825, 459,
|
||||
943, 687, 681, 533, 915, 598, 591, 243, 876, 451, 874, 420,
|
||||
786, 317, 732, 220, 922, 317, 1108, 367, 531, 466, 1028, 649,
|
||||
1053, 615, 1034, 553, 829, 602, 1021, 799, 927, 803, 878, 763,
|
||||
799, 496, 1373, 773, 585, 770, 803, 930, 1099, 793, 1222, 862,
|
||||
1209, 895, 1025, 727, 772, 845, 1172, 1115, 867, 1021, 830, 1013,
|
||||
841, 910, 506, 703, 1239, 1077, 620, 819, 1196, 1083, 1155, 1081,
|
||||
1142, 907, 1547, 1121, 1309, 648, 1343, 612, 1484, 988, 1479, 937,
|
||||
985, 1328, 955, 1341, 429, 910, 841, 1338, 564, 1179, 412, 1156,
|
||||
1427, 1320, 1434, 1330, 640, 760, 1726, 1410, 190, 555, 1073, 1005,
|
||||
426, 257, 839, 980, 235, 231, 1520, 1167, 109, 293, 1014, 1569,
|
||||
305, 142, 1148, 539, -291, -108, 1213, 972, 22, -216, 667, 828,
|
||||
-482, 438, 453, 1431, -581, -422, 789, 387, -358, -454, 174, 780,
|
||||
-36, -372, 390, -134, -629, 160, -306, 751, -1258, -331, 177, 522,
|
||||
-248, 574, -251, 639, -531, 407, -596, 394, -419, 789, -617, 801,
|
||||
-986, 399, -857, 727, -7, 518, -703, 310, -1143, -24, -1002, 287,
|
||||
-960, 363, -1299, 312, -1534, 245, -1557, 305, 28, 153, -859, -175,
|
||||
-33, 332, -1398, -154, 212, 410, -593, -197, -1092, -704, -904, -65,
|
||||
282, 367, -918, -686, 345, 93, -258, -357, 696, 644, -693, -28,
|
||||
448, 493, -273, 193, 527, 546, -243, -513, 384, -136, 273, -353,
|
||||
512, -142, 537, -198, 941, 750, 83, 248, 578, 861, -56, 592,
|
||||
842, 44, 892, 24, 33, 890, -16, 982, 831, 1398, 1535, 1898,
|
||||
1716, 1376, 1948, 1465,
|
||||
}
|
||||
|
||||
var dico5_lsf_5 = []int16{
|
||||
-1002, -929, -1096, -1203, -641, -931, -604, -961, -779, -673, -835, -788,
|
||||
-416, -664, -458, -766, -652, -521, -662, -495, -1023, -509, -1023, -428,
|
||||
-444, -552, -368, -449, -479, -211, -1054, -903, -316, -249, -569, -591,
|
||||
-569, -275, -541, -191, -716, -188, -842, -264, -333, -248, -318, -228,
|
||||
-275, 1, -567, -228, -115, -221, -238, -374, -197, -507, -222, -579,
|
||||
-258, -432, -61, -244, -345, 2, -338, 39, -215, -169, -58, 0,
|
||||
-56, -6, -203, -131, 1, -186, -5, -211, 6, -380, 11, -418,
|
||||
-116, 131, -134, 113, 89, -4, 71, -2, -19, -192, 262, 24,
|
||||
189, 151, -133, -109, 186, -153, 166, -219, 37, 139, 193, 171,
|
||||
337, 124, 158, -61, 141, 226, -13, 190, 231, 34, 354, 109,
|
||||
316, 201, 244, 164, 330, -85, 390, -84, 254, 327, 257, 335,
|
||||
491, 147, 476, 105, 54, 77, 437, 370, 421, 314, 449, 342,
|
||||
329, 126, 673, 292, 571, 388, 243, 193, 653, 320, 621, 280,
|
||||
194, 380, 517, 581, 45, 323, 111, 422, 489, 395, 734, 534,
|
||||
622, 546, 486, 502, 318, 572, 189, 550, 385, 422, -157, 153,
|
||||
-125, 382, -197, 386, -263, 334, 228, 697, -188, 1, 51, 297,
|
||||
-507, 213, -376, 397, -24, 255, -547, 89, -502, -94, 387, 179,
|
||||
-620, 68, -684, 112, -642, -350, -260, 172, -438, -324, 264, 648,
|
||||
-964, -4, -1121, 7, -134, 134, -1133, -306, 143, 96, -420, -497,
|
||||
-1221, -350, -1527, -685, -161, 72, 873, 691, 732, 283, 921, 353,
|
||||
334, 475, 1095, 821, 864, 524, 843, 497, 714, 711, 788, 750,
|
||||
1076, 714, 1204, 753,
|
||||
}
|
||||
|
||||
var lspLsfTable = []int16{
|
||||
32767, 32729, 32610, 32413, 32138, 31786, 31357, 30853, 30274, 29622, 28899, 28106,
|
||||
27246, 26320, 25330, 24279, 23170, 22006, 20788, 19520, 18205, 16846, 15447, 14010,
|
||||
12540, 11039, 9512, 7962, 6393, 4808, 3212, 1608, 0, -1608, -3212, -4808,
|
||||
-6393, -7962, -9512, -11039, -12540, -14010, -15447, -16846, -18205, -19520, -20788, -22006,
|
||||
-23170, -24279, -25330, -26320, -27246, -28106, -28899, -29622, -30274, -30853, -31357, -31786,
|
||||
-32138, -32413, -32610, -32729, -32768,
|
||||
}
|
||||
|
||||
var lspLsfSlope = []int16{
|
||||
-26887, -8812, -5323, -3813, -2979, -2444, -2081, -1811, -1608, -1450, -1322, -1219,
|
||||
-1132, -1059, -998, -946, -901, -861, -827, -797, -772, -750, -730, -713,
|
||||
-699, -687, -677, -668, -662, -657, -654, -652, -652, -654, -657, -662,
|
||||
-668, -677, -687, -699, -713, -730, -750, -772, -797, -827, -861, -901,
|
||||
-946, -998, -1059, -1132, -1219, -1322, -1450, -1608, -1811, -2081, -2444, -2979,
|
||||
-3813, -5323, -8812, -26887,
|
||||
}
|
||||
|
||||
var lsp_init_data = []int16{
|
||||
30000, 26000, 21000, 15000, 8000, 0, -8000, -15000, -21000, -26000,
|
||||
}
|
||||
|
||||
var grid = []int16{
|
||||
32760, 32723, 32588, 32364, 32051, 31651, 31164, 30591, 29935, 29196, 28377, 27481,
|
||||
26509, 25465, 24351, 23170, 21926, 20621, 19260, 17846, 16384, 14876, 13327, 11743,
|
||||
10125, 8480, 6812, 5126, 3425, 1714, 0, -1714, -3425, -5126, -6812, -8480,
|
||||
-10125, -11743, -13327, -14876, -16384, -17846, -19260, -20621, -21926, -23170, -24351, -25465,
|
||||
-26509, -27481, -28377, -29196, -29935, -30591, -31164, -31651, -32051, -32364, -32588, -32723,
|
||||
-32760,
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Code generated from opencore-amrnb — DO NOT EDIT.
|
||||
//
|
||||
// ROM tables extracted verbatim from the Apache-2.0 opencore-amrnb C
|
||||
// reference; element counts are verified against the C array sizes.
|
||||
|
||||
package amrnb
|
||||
|
||||
var log2_tbl = []int16{
|
||||
0, 1455, 2866, 4236, 5568, 6863, 8124, 9352, 10549, 11716, 12855, 13967,
|
||||
15054, 16117, 17156, 18172, 19167, 20142, 21097, 22033, 22951, 23852, 24735, 25603,
|
||||
26455, 27291, 28113, 28922, 29716, 30497, 31266, 32023, 32767,
|
||||
}
|
||||
|
||||
var pow2_tbl = []int16{
|
||||
16384, 16743, 17109, 17484, 17867, 18258, 18658, 19066, 19484, 19911, 20347, 20792,
|
||||
21247, 21713, 22188, 22674, 23170, 23678, 24196, 24726, 25268, 25821, 26386, 26964,
|
||||
27554, 28158, 28774, 29405, 30048, 30706, 31379, 32066, 32767,
|
||||
}
|
||||
|
||||
var inv_sqrt_tbl = []int16{
|
||||
32767, 31790, 30894, 30070, 29309, 28602, 27945, 27330, 26755, 26214, 25705, 25225,
|
||||
24770, 24339, 23930, 23541, 23170, 22817, 22479, 22155, 21845, 21548, 21263, 20988,
|
||||
20724, 20470, 20225, 19988, 19760, 19539, 19326, 19119, 18919, 18725, 18536, 18354,
|
||||
18176, 18004, 17837, 17674, 17515, 17361, 17211, 17064, 16921, 16782, 16646, 16514,
|
||||
16384,
|
||||
}
|
||||
|
||||
var sqrt_l_tbl = []int16{
|
||||
16384, 16888, 17378, 17854, 18318, 18770, 19212, 19644, 20066, 20480, 20886, 21283,
|
||||
21674, 22058, 22435, 22806, 23170, 23530, 23884, 24232, 24576, 24915, 25249, 25580,
|
||||
25905, 26227, 26545, 26859, 27170, 27477, 27780, 28081, 28378, 28672, 28963, 29251,
|
||||
29537, 29819, 30099, 30377, 30652, 30924, 31194, 31462, 31727, 31991, 32252, 32511,
|
||||
32767, 32767,
|
||||
}
|
||||
|
||||
var overflow_tbl = []int32{
|
||||
2147483647, 1073741823, 536870911, 268435455, 134217727, 67108863, 33554431, 16777215, 8388607, 4194303, 2097151, 1048575,
|
||||
524287, 262143, 131071, 65535, 32767, 16383, 8191, 4095, 2047, 1023, 511, 255,
|
||||
127, 63, 31, 15, 7, 3, 1, 0,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Code generated from opencore-amrnb corrwght_tab.cpp. DO NOT EDIT.
|
||||
|
||||
package amrnb
|
||||
|
||||
// corrweight is the open-loop pitch weighting curve used by Pitch_ol_wgh (MR102).
|
||||
var corrweight = [251]int16{
|
||||
20473, 20506, 20539, 20572, 20605, 20644, 20677, 20716, 20749, 20788,
|
||||
20821, 20860, 20893, 20932, 20972, 21011, 21050, 21089, 21129, 21168,
|
||||
21207, 21247, 21286, 21332, 21371, 21417, 21456, 21502, 21542, 21588,
|
||||
21633, 21679, 21725, 21771, 21817, 21863, 21909, 21961, 22007, 22059,
|
||||
22105, 22158, 22210, 22263, 22315, 22367, 22420, 22472, 22531, 22584,
|
||||
22643, 22702, 22761, 22820, 22879, 22938, 23003, 23062, 23128, 23193,
|
||||
23252, 23324, 23390, 23455, 23527, 23600, 23665, 23744, 23816, 23888,
|
||||
23967, 24045, 24124, 24202, 24288, 24366, 24451, 24537, 24628, 24714,
|
||||
24805, 24904, 24995, 25094, 25192, 25297, 25395, 25500, 25611, 25723,
|
||||
25834, 25952, 26070, 26188, 26313, 26444, 26575, 26706, 26844, 26988,
|
||||
27132, 27283, 27440, 27597, 27761, 27931, 28108, 28285, 28475, 28665,
|
||||
28869, 29078, 29295, 29524, 29760, 30002, 30258, 30527, 30808, 31457,
|
||||
32767, 32767, 32767, 32767, 32767, 32767, 32767, 31457, 30808, 30527,
|
||||
30258, 30002, 29760, 29524, 29295, 29078, 28869, 28665, 28475, 28285,
|
||||
28108, 27931, 27761, 27597, 27440, 27283, 27132, 26988, 26844, 26706,
|
||||
26575, 26444, 26313, 26188, 26070, 25952, 25834, 25723, 25611, 25500,
|
||||
25395, 25297, 25192, 25094, 24995, 24904, 24805, 24714, 24628, 24537,
|
||||
24451, 24366, 24288, 24202, 24124, 24045, 23967, 23888, 23816, 23744,
|
||||
23665, 23600, 23527, 23455, 23390, 23324, 23252, 23193, 23128, 23062,
|
||||
23003, 22938, 22879, 22820, 22761, 22702, 22643, 22584, 22531, 22472,
|
||||
22420, 22367, 22315, 22263, 22210, 22158, 22105, 22059, 22007, 21961,
|
||||
21909, 21863, 21817, 21771, 21725, 21679, 21633, 21588, 21542, 21502,
|
||||
21456, 21417, 21371, 21332, 21286, 21247, 21207, 21168, 21129, 21089,
|
||||
21050, 21011, 20972, 20932, 20893, 20860, 20821, 20788, 20749, 20716,
|
||||
20677, 20644, 20605, 20572, 20539, 20506, 20473, 20434, 20401, 20369,
|
||||
20336,
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Code generated from opencore-amrnb (3GPP TS 26.101) — DO NOT EDIT.
|
||||
//
|
||||
// Per-mode RFC 4867 / IETF bit-reordering tables (bitreorder_tab.cpp) and the
|
||||
// parameter bit widths (bitno_tab.cpp). reorderBits[m][k] is the ETS serial bit
|
||||
// index that supplies the k-th transmitted (IETF storage order) speech bit, so
|
||||
// the table is a permutation of 0..modeBits[m]-1. bitno[m] lists the bit width
|
||||
// of each encoder parameter, MSB-first, summing to modeBits[m].
|
||||
|
||||
package amrnb
|
||||
|
||||
// bitno gives the bit width of each serial parameter per speech mode (0..7).
|
||||
var bitno = [numSpeechModes][]int16{
|
||||
Mode0475: {8, 8, 7, 8, 7, 2, 8, 4, 7, 2, 4, 7, 2, 8, 4, 7, 2},
|
||||
Mode0515: {8, 8, 7, 8, 7, 2, 6, 4, 7, 2, 6, 4, 7, 2, 6, 4, 7, 2, 6},
|
||||
Mode0590: {8, 9, 9, 8, 9, 2, 6, 4, 9, 2, 6, 8, 9, 2, 6, 4, 9, 2, 6},
|
||||
Mode0670: {8, 9, 9, 8, 11, 3, 7, 4, 11, 3, 7, 8, 11, 3, 7, 4, 11, 3, 7},
|
||||
Mode0740: {8, 9, 9, 8, 13, 4, 7, 5, 13, 4, 7, 8, 13, 4, 7, 5, 13, 4, 7},
|
||||
Mode0795: {9, 9, 9, 8, 13, 4, 4, 5, 6, 13, 4, 4, 5, 8, 13, 4, 4, 5, 6, 13, 4, 4, 5},
|
||||
Mode1020: {8, 9, 9, 8, 1, 1, 1, 1, 10, 10, 7, 7, 5, 1, 1, 1, 1, 10, 10, 7, 7, 8, 1, 1, 1, 1, 10, 10, 7, 7, 5, 1, 1, 1, 1, 10, 10, 7, 7},
|
||||
Mode1220: {7, 8, 9, 8, 6, 9, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 5, 6, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 5, 9, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 5, 6, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 5},
|
||||
}
|
||||
|
||||
// reorderBits maps transmitted bit position -> ETS serial bit index per mode.
|
||||
var reorderBits = [numSpeechModes][]int16{
|
||||
Mode0475: {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
23, 24, 25, 26, 27, 28, 48, 49, 61, 62, 82, 83, 47, 46, 45, 44,
|
||||
81, 80, 79, 78, 17, 18, 20, 22, 77, 76, 75, 74, 29, 30, 43, 42,
|
||||
41, 40, 38, 39, 16, 19, 21, 50, 51, 59, 60, 63, 64, 72, 73, 84,
|
||||
85, 93, 94, 32, 33, 35, 36, 53, 54, 56, 57, 66, 67, 69, 70, 87,
|
||||
88, 90, 91, 34, 55, 68, 89, 37, 58, 71, 92, 31, 52, 65, 86,
|
||||
},
|
||||
Mode0515: {
|
||||
7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8,
|
||||
23, 24, 25, 26, 27, 46, 65, 84, 45, 44, 43, 64, 63, 62, 83, 82,
|
||||
81, 102, 101, 100, 42, 61, 80, 99, 28, 47, 66, 85, 18, 41, 60, 79,
|
||||
98, 29, 48, 67, 17, 20, 22, 40, 59, 78, 97, 21, 30, 49, 68, 86,
|
||||
19, 16, 87, 39, 38, 58, 57, 77, 35, 54, 73, 92, 76, 96, 95, 36,
|
||||
55, 74, 93, 32, 51, 33, 52, 70, 71, 89, 90, 31, 50, 69, 88, 37,
|
||||
56, 75, 94, 34, 53, 72, 91,
|
||||
},
|
||||
Mode0590: {
|
||||
0, 1, 4, 5, 3, 6, 7, 2, 13, 15, 8, 9, 11, 12, 14, 10,
|
||||
16, 28, 74, 29, 75, 27, 73, 26, 72, 30, 76, 51, 97, 50, 71, 96,
|
||||
117, 31, 77, 52, 98, 49, 70, 95, 116, 53, 99, 32, 78, 33, 79, 48,
|
||||
69, 94, 115, 47, 68, 93, 114, 46, 67, 92, 113, 19, 21, 23, 22, 18,
|
||||
17, 20, 24, 111, 43, 89, 110, 64, 65, 44, 90, 25, 45, 66, 91, 112,
|
||||
54, 100, 40, 61, 86, 107, 39, 60, 85, 106, 36, 57, 82, 103, 35, 56,
|
||||
81, 102, 34, 55, 80, 101, 42, 63, 88, 109, 41, 62, 87, 108, 38, 59,
|
||||
84, 105, 37, 58, 83, 104,
|
||||
},
|
||||
Mode0670: {
|
||||
0, 1, 4, 3, 5, 6, 13, 7, 2, 8, 9, 11, 15, 12, 14, 10,
|
||||
28, 82, 29, 83, 27, 81, 26, 80, 30, 84, 16, 55, 109, 56, 110, 31,
|
||||
85, 57, 111, 48, 73, 102, 127, 32, 86, 51, 76, 105, 130, 52, 77, 106,
|
||||
131, 58, 112, 33, 87, 19, 23, 53, 78, 107, 132, 21, 22, 18, 17, 20,
|
||||
24, 25, 50, 75, 104, 129, 47, 72, 101, 126, 54, 79, 108, 133, 46, 71,
|
||||
100, 125, 128, 103, 74, 49, 45, 70, 99, 124, 42, 67, 96, 121, 39, 64,
|
||||
93, 118, 38, 63, 92, 117, 35, 60, 89, 114, 34, 59, 88, 113, 44, 69,
|
||||
98, 123, 43, 68, 97, 122, 41, 66, 95, 120, 40, 65, 94, 119, 37, 62,
|
||||
91, 116, 36, 61, 90, 115,
|
||||
},
|
||||
Mode0740: {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 26, 87, 27, 88, 28, 89, 29, 90, 30, 91, 51, 80, 112, 141, 52,
|
||||
81, 113, 142, 54, 83, 115, 144, 55, 84, 116, 145, 58, 119, 59, 120, 21,
|
||||
22, 23, 17, 18, 19, 31, 60, 92, 121, 56, 85, 117, 146, 20, 24, 25,
|
||||
50, 79, 111, 140, 57, 86, 118, 147, 49, 78, 110, 139, 48, 77, 53, 82,
|
||||
114, 143, 109, 138, 47, 76, 108, 137, 32, 33, 61, 62, 93, 94, 122, 123,
|
||||
41, 42, 43, 44, 45, 46, 70, 71, 72, 73, 74, 75, 102, 103, 104, 105,
|
||||
106, 107, 131, 132, 133, 134, 135, 136, 34, 63, 95, 124, 35, 64, 96, 125,
|
||||
36, 65, 97, 126, 37, 66, 98, 127, 38, 67, 99, 128, 39, 68, 100, 129,
|
||||
40, 69, 101, 130,
|
||||
},
|
||||
Mode0795: {
|
||||
8, 7, 6, 5, 4, 3, 2, 14, 16, 9, 10, 12, 13, 15, 11, 17,
|
||||
20, 22, 24, 23, 19, 18, 21, 56, 88, 122, 154, 57, 89, 123, 155, 58,
|
||||
90, 124, 156, 52, 84, 118, 150, 53, 85, 119, 151, 27, 93, 28, 94, 29,
|
||||
95, 30, 96, 31, 97, 61, 127, 62, 128, 63, 129, 59, 91, 125, 157, 32,
|
||||
98, 64, 130, 1, 0, 25, 26, 33, 99, 34, 100, 65, 131, 66, 132, 54,
|
||||
86, 120, 152, 60, 92, 126, 158, 55, 87, 121, 153, 117, 116, 115, 46, 78,
|
||||
112, 144, 43, 75, 109, 141, 40, 72, 106, 138, 36, 68, 102, 134, 114, 149,
|
||||
148, 147, 146, 83, 82, 81, 80, 51, 50, 49, 48, 47, 45, 44, 42, 39,
|
||||
35, 79, 77, 76, 74, 71, 67, 113, 111, 110, 108, 105, 101, 145, 143, 142,
|
||||
140, 137, 133, 41, 73, 107, 139, 37, 69, 103, 135, 38, 70, 104, 136,
|
||||
},
|
||||
Mode1020: {
|
||||
7, 6, 5, 4, 3, 2, 1, 0, 16, 15, 14, 13, 12, 11, 10, 9,
|
||||
8, 26, 27, 28, 29, 30, 31, 115, 116, 117, 118, 119, 120, 72, 73, 161,
|
||||
162, 65, 68, 69, 108, 111, 112, 154, 157, 158, 197, 200, 201, 32, 33, 121,
|
||||
122, 74, 75, 163, 164, 66, 109, 155, 198, 19, 23, 21, 22, 18, 17, 20,
|
||||
24, 25, 37, 36, 35, 34, 80, 79, 78, 77, 126, 125, 124, 123, 169, 168,
|
||||
167, 166, 70, 67, 71, 113, 110, 114, 159, 156, 160, 202, 199, 203, 76, 165,
|
||||
81, 82, 92, 91, 93, 83, 95, 85, 84, 94, 101, 102, 96, 104, 86, 103,
|
||||
87, 97, 127, 128, 138, 137, 139, 129, 141, 131, 130, 140, 147, 148, 142, 150,
|
||||
132, 149, 133, 143, 170, 171, 181, 180, 182, 172, 184, 174, 173, 183, 190, 191,
|
||||
185, 193, 175, 192, 176, 186, 38, 39, 49, 48, 50, 40, 52, 42, 41, 51,
|
||||
58, 59, 53, 61, 43, 60, 44, 54, 194, 179, 189, 196, 177, 195, 178, 187,
|
||||
188, 151, 136, 146, 153, 134, 152, 135, 144, 145, 105, 90, 100, 107, 88, 106,
|
||||
89, 98, 99, 62, 47, 57, 64, 45, 63, 46, 55, 56,
|
||||
},
|
||||
Mode1220: {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 23,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 38, 141, 39,
|
||||
142, 40, 143, 41, 144, 42, 145, 43, 146, 44, 147, 45, 148, 46, 149, 47,
|
||||
97, 150, 200, 48, 98, 151, 201, 49, 99, 152, 202, 86, 136, 189, 239, 87,
|
||||
137, 190, 240, 88, 138, 191, 241, 91, 194, 92, 195, 93, 196, 94, 197, 95,
|
||||
198, 29, 30, 31, 32, 33, 34, 35, 50, 100, 153, 203, 89, 139, 192, 242,
|
||||
51, 101, 154, 204, 55, 105, 158, 208, 90, 140, 193, 243, 59, 109, 162, 212,
|
||||
63, 113, 166, 216, 67, 117, 170, 220, 36, 37, 54, 53, 52, 58, 57, 56,
|
||||
62, 61, 60, 66, 65, 64, 70, 69, 68, 104, 103, 102, 108, 107, 106, 112,
|
||||
111, 110, 116, 115, 114, 120, 119, 118, 157, 156, 155, 161, 160, 159, 165, 164,
|
||||
163, 169, 168, 167, 173, 172, 171, 207, 206, 205, 211, 210, 209, 215, 214, 213,
|
||||
219, 218, 217, 223, 222, 221, 73, 72, 71, 76, 75, 74, 79, 78, 77, 82,
|
||||
81, 80, 85, 84, 83, 123, 122, 121, 126, 125, 124, 129, 128, 127, 132, 131,
|
||||
130, 135, 134, 133, 176, 175, 174, 179, 178, 177, 182, 181, 180, 185, 184, 183,
|
||||
188, 187, 186, 226, 225, 224, 229, 228, 227, 232, 231, 230, 235, 234, 233, 238,
|
||||
237, 236, 96, 199,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 本文件是本项目为 vendored 包**新增**的导出桥,**不是**上游 `v1.0.0` 的内容。
|
||||
// 出处与许可证见同目录 VENDORED.md(上游 = github.com/VoiceBlender/goamr-nb
|
||||
// v1.0.0,Apache-2.0,LICENSE / NOTICE 已逐字保留)。
|
||||
//
|
||||
// 为什么需要它:上游公开 API 以 **RTP 载荷**(RFC 4867 的 octet-aligned /
|
||||
// bandwidth-efficient 打包)为输入;而 `.amr` **存储格式**(RFC 4867 §5)是
|
||||
// 「`#!AMR\n` 魔数 + 每帧『1 字节 ToC + ceil(bits/8) 字节语音位』」,没有 CMR
|
||||
// 字节、也不用 F 位。调用方要按 ToC 分帧就必须知道每种帧类型占多少字节,而这张
|
||||
// 表(RFC 4867 Table 1a)在上游包内是未导出的。
|
||||
//
|
||||
// 两条路:① 在本项目的 codec 里再抄一份规格表(两处表格会各自漂移);
|
||||
// ② 在这里开一个只做转发的薄导出。本包选 ②:全仓只有一份帧长表,且它来自被
|
||||
// vendored 的那份实现本身。
|
||||
package amrnb
|
||||
|
||||
// FrameBytesByFT 返回帧类型 ft(ToC 字节的高 4 位,0..15)在 **octet-aligned**
|
||||
// 打包下占用的字节数(**不含** ToC 字节本身),以及该帧类型是否合法。
|
||||
//
|
||||
// 语音模式返回 ceil(bits/8)(RFC 4867 Table 1a:12/13/15/17/19/20/26/31),
|
||||
// SID(FT 8)返回 5,SPEECH_LOST(FT 14)与 NO_DATA(FT 15)返回 0,
|
||||
// 保留值(FT 9..13)返回 false。
|
||||
func FrameBytesByFT(ft int) (int, bool) { return frameBytesByFT(ft) }
|
||||
@@ -0,0 +1,60 @@
|
||||
package transcode
|
||||
|
||||
import "context"
|
||||
|
||||
// Request 是一次转码请求(design/25 §2.1)。
|
||||
type Request struct {
|
||||
// Data 是源字节。
|
||||
Data []byte
|
||||
// Source 是源格式;可留空,留空时按魔数嗅探,嗅不出即 ErrUnsupportedFormat
|
||||
// (deny-by-default)。
|
||||
Source Format
|
||||
// Target 是目标格式;留空即 ErrUnsupportedFormat。
|
||||
Target Format
|
||||
}
|
||||
|
||||
// Result 是转码结果(design/25 §2.1)。
|
||||
type Result struct {
|
||||
// Data 是目标字节。
|
||||
Data []byte
|
||||
// Format 是目标格式(= Request.Target)。
|
||||
Format Format
|
||||
// Source 是实际识别到的源格式(回给调用方,便于日志与 DTO)。
|
||||
Source Format
|
||||
// SampleRate 是采样率(Hz);能确定时给出,否则 0。
|
||||
SampleRate int
|
||||
// Channels 是声道数;能确定时给出,否则 0。
|
||||
Channels int
|
||||
// DurationMs 是时长;能从容器/帧数**确证**时给出,否则 0(绝不估算,
|
||||
// 与 design/24 §10.4 的 durationMs 纪律同源)。
|
||||
DurationMs int64
|
||||
// Frames 是已解码帧数(AMR-NB 一帧 20ms = 160 样本);不适用时 0。
|
||||
Frames int
|
||||
}
|
||||
|
||||
// Transcoder 是转码能力对外的唯一接口:**进程内、同步、纯本地**
|
||||
// (不触网、不落盘、不查库)。实例由组合根构造并经依赖注入交给调用方,
|
||||
// 绝不设包级全局变量(design/25 §1.3.2 / AGENTS §10 红线)。
|
||||
type Transcoder interface {
|
||||
// Transcode 把 req.Data 从 req.Source 转成 req.Target。失败一律返回
|
||||
// errors.go 的哨兵之一(或包裹它),绝不返回部分结果假装成功。
|
||||
Transcode(ctx context.Context, req Request) (Result, error)
|
||||
// Supports 报告这条路是否可用(能力门;调用方据此决定走原生路径还是诚实拒绝)。
|
||||
Supports(src, dst Format) bool
|
||||
// Formats 报告当前注册的所有格式(发现;顺序无保证)。
|
||||
Formats() []Format
|
||||
}
|
||||
|
||||
// Codec 是一条「源→目标」转换路(design/25 §2.2)。实现必须是**纯函数**:
|
||||
// 给定相同输入产出相同输出,不读写外部状态、不发网络请求、不做缓存
|
||||
// (缓存若需要,由调用方按内容寻址去做)。
|
||||
type Codec interface {
|
||||
// From 返回源格式。
|
||||
From() Format
|
||||
// To 返回目标格式。
|
||||
To() Format
|
||||
// Transcode 转换 in 并返回结果。它必须自己执行「本格式可确证」的上限
|
||||
// (时长、产物字节),并在循环内遵守 ctx;engine 负责的是与格式无关的
|
||||
// 那一层(输入字节上限、并发槽、超时闸、日志)。
|
||||
Transcode(ctx context.Context, in []byte, limits Limits) (Result, error)
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/transcode/amrnb"
|
||||
)
|
||||
|
||||
// 本文件实现 design/25 §3.1 的**唯一必需**那条路:`amr → wav`。
|
||||
//
|
||||
// 为什么必须:QQ OneBot v11 的语音是 AMR(`.amr`),浏览器**播不了**
|
||||
// (Chrome 的 AMR 支持表为 No,只有 ChromeOS 支持 AMR-NB/WB),所以
|
||||
// 「能点开听」在零转码下不可达(design/25 §3.2)。
|
||||
//
|
||||
// 解码器是 vendored 的 github.com/VoiceBlender/goamr-nb v1.0.0(Apache-2.0,
|
||||
// 见 amrnb/VENDORED.md):纯 Go、CGO_ENABLED=0 可构建、非 amd64 走
|
||||
// simd_noasm.go 兜底 ⇒ 交叉编译不破。
|
||||
//
|
||||
// 目标参数 = AMR-NB 的原生参数:**8 kHz、单声道、PCM16,不重采样、不变声道**
|
||||
// (design/25 §3.1/§3.2:重采样到 16 kHz 只会把文件放大一倍且不增加任何信息)。
|
||||
const (
|
||||
// amrStorageHeaderBytes 是 AMR-NB 存储格式的魔数长度(`#!AMR\n`)。
|
||||
amrStorageHeaderBytes = len(amrNBMagic)
|
||||
// amrFrameMillis 是一帧的时长:AMR-NB 一帧固定 20 ms。
|
||||
amrFrameMillis = 20
|
||||
// amrSampleRate / amrChannels / amrFrameSamples 是 AMR-NB 的原生参数,
|
||||
// 取自 vendored 解码器自己的常量,不在这里另抄一份。
|
||||
amrSampleRate = amrnb.SampleRate
|
||||
amrChannels = 1
|
||||
amrFrameSamples = amrnb.FrameSamples
|
||||
// amrPayloadNoCMR 是 octet-aligned 载荷首字节的 CMR = 15「no mode request」。
|
||||
amrPayloadNoCMR = 0xF0
|
||||
// amrToCFrameTypeShift / amrToCFrameTypeMask 抽出 ToC 字节里的帧类型(FT):
|
||||
// ToC 布局为 F(bit7) FT(bits6-3) Q(bit2) P(bits1-0)(RFC 4867 §4.3.1)。
|
||||
amrToCFrameTypeShift = 3
|
||||
amrToCFrameTypeMask = 0x0F
|
||||
// amrRTPHeaderBytes 是每帧送进解码器的 RTP 载荷头长度:CMR 1 字节 + ToC 1 字节。
|
||||
amrRTPHeaderBytes = 2
|
||||
// amrMaxSpeechBytes 是单帧语音位的上界:12.2 kbps 一帧 244 位 ⇒ 31 字节
|
||||
// (RFC 4867 Table 1a)。用常量而非 `amrnb.Mode1220.Bytes()`:后者是方法调用,
|
||||
// 不是常量表达式。
|
||||
amrMaxSpeechBytes = 31
|
||||
// amrFramePayloadBytes 是逐帧复用的载荷缓冲容量(不逐帧分配)。
|
||||
amrFramePayloadBytes = amrRTPHeaderBytes + amrMaxSpeechBytes
|
||||
)
|
||||
|
||||
// amrNBToWAV 是 `amr → wav` 这条转换路。它是**纯函数**:同样的输入产出同样的输出,
|
||||
// 不读写外部状态、不发网络请求、不做缓存(design/25 §2.2)。
|
||||
type amrNBToWAV struct{}
|
||||
|
||||
// From 返回源格式。
|
||||
func (amrNBToWAV) From() Format { return FormatAMR }
|
||||
|
||||
// To 返回目标格式。
|
||||
func (amrNBToWAV) To() Format { return FormatWAV }
|
||||
|
||||
// Transcode 把 AMR-NB 字节解成 8 kHz 单声道 PCM16 的 WAV。
|
||||
//
|
||||
// 三层上限里「格式相关」的两层在这里执行,且都在**解码前**:
|
||||
// - 输入字节上限由 engine 执行(与格式无关,故不在本文件);
|
||||
// - 时长上限:帧数 × 20 ms 是**可确证**的(不是估算),超限即 ErrInputTooLarge;
|
||||
// - 产物字节上限:帧数 × 160 样本 × 2 字节 + 44 字节头是可**精确算出**的,
|
||||
// 超限即 ErrOutputTooLarge,且此时一个样本都还没解(比「边解码边计数」更早、
|
||||
// 更省内存,design/25 §5.1 允许「在解码前」)。
|
||||
//
|
||||
// 结构预检(魔数 / 帧长 / 截断 / 未知帧类型)同样在解码前完成:损坏的字节不会
|
||||
// 解出一半再报错。ctx 在逐帧循环里被查,超时/取消立即返回,不泄漏 goroutine。
|
||||
func (amrNBToWAV) Transcode(ctx context.Context, in []byte, limits Limits) (Result, error) {
|
||||
frames, err := amrFrameCount(in)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
if duration := amrDuration(frames); duration > limits.MaxDuration {
|
||||
return Result{}, fmt.Errorf("transcode: amr -> wav: %w: duration %s > %s",
|
||||
ErrInputTooLarge, duration, limits.MaxDuration)
|
||||
}
|
||||
pcmBytes := frames * amrFrameSamples * wavPCMBytesPerSample
|
||||
if total := wavHeaderBytes + pcmBytes; total > limits.MaxOutputBytes {
|
||||
return Result{}, fmt.Errorf("transcode: amr -> wav: %w: %d bytes > %d",
|
||||
ErrOutputTooLarge, total, limits.MaxOutputBytes)
|
||||
}
|
||||
return amrDecodeToWAV(ctx, in, frames, pcmBytes)
|
||||
}
|
||||
|
||||
// amrDecodeToWAV 逐帧解码并**直接写进目标缓冲**(不是先攒 []int16 再复制一份
|
||||
// WAV):峰值内存 = 输入 + 输出 ≈ 70 MB,与 design/25 §2.5 登记的口径一致。
|
||||
// frames / pcmBytes 由调用方在解码前算定(故这里不再分配不可控的内存)。
|
||||
func amrDecodeToWAV(ctx context.Context, in []byte, frames, pcmBytes int) (Result, error) {
|
||||
hdr, err := wavHeader(pcmBytes, amrSampleRate, amrChannels)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
out := make([]byte, wavHeaderBytes+pcmBytes)
|
||||
copy(out, hdr[:])
|
||||
dec := amrnb.NewDecoder(amrnb.DecoderConfig{OctetAligned: true})
|
||||
payload := make([]byte, 0, amrFramePayloadBytes)
|
||||
off := wavHeaderBytes
|
||||
decoded := 0
|
||||
err = amrWalk(in, func(frame []byte) error {
|
||||
if cerr := ctx.Err(); cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
// 存储帧(ToC + 语音位)= octet-aligned 载荷去掉 CMR 首字节,
|
||||
// 因此补回 CMR 即可直接交给 vendored 解码器。
|
||||
payload = append(payload[:0], amrPayloadNoCMR, frame[0])
|
||||
payload = append(payload, frame[1:]...)
|
||||
samples, derr := dec.Decode(payload)
|
||||
if derr != nil {
|
||||
return fmt.Errorf("transcode: amr frame %d: %w: %v", decoded, ErrDecodeFailed, derr)
|
||||
}
|
||||
if len(samples) != amrFrameSamples {
|
||||
return fmt.Errorf("transcode: amr frame %d: %w: got %d samples, want %d",
|
||||
decoded, ErrDecodeFailed, len(samples), amrFrameSamples)
|
||||
}
|
||||
writePCM16LE(out[off:], samples)
|
||||
off += amrFrameSamples * wavPCMBytesPerSample
|
||||
decoded++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
return Result{
|
||||
Data: out,
|
||||
Format: FormatWAV,
|
||||
Source: FormatAMR,
|
||||
SampleRate: amrSampleRate,
|
||||
Channels: amrChannels,
|
||||
DurationMs: amrDurationMs(frames),
|
||||
Frames: frames,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// amrWalk 逐帧走一遍 AMR-NB 存储格式(RFC 4867 §5):`#!AMR\n` 魔数,随后每帧
|
||||
// 「1 字节 ToC + ceil(bits/8) 字节语音位」。它只分帧、只做结构检查,**不解码**,
|
||||
// 因此可以安全用于「解码前」的帧数 / 时长 / 产物上限判定。
|
||||
//
|
||||
// visit 拿到的是含 ToC 的完整存储帧。visit 返回的错误经 %w 包裹上抛(保持
|
||||
// ctx 超时的 errors.Is 链)。
|
||||
func amrWalk(in []byte, visit func(frame []byte) error) error {
|
||||
if len(in) < amrStorageHeaderBytes {
|
||||
return fmt.Errorf("transcode: amr: %w: header truncated (%d bytes)", ErrDecodeFailed, len(in))
|
||||
}
|
||||
if string(in[:amrStorageHeaderBytes]) != amrNBMagic {
|
||||
return fmt.Errorf("transcode: amr: %w: not an AMR-NB stream (`#!AMR\\n` magic missing)", ErrDecodeFailed)
|
||||
}
|
||||
body := in[amrStorageHeaderBytes:]
|
||||
for index := 0; len(body) > 0; index++ {
|
||||
ft := int(body[0]>>amrToCFrameTypeShift) & amrToCFrameTypeMask
|
||||
size, ok := amrnb.FrameBytesByFT(ft)
|
||||
if !ok {
|
||||
return fmt.Errorf("transcode: amr frame %d: %w: reserved frame type %d", index, ErrDecodeFailed, ft)
|
||||
}
|
||||
if len(body) < 1+size {
|
||||
return fmt.Errorf("transcode: amr frame %d: %w: truncated (need %d bytes, have %d)",
|
||||
index, ErrDecodeFailed, 1+size, len(body))
|
||||
}
|
||||
if err := visit(body[:1+size]); err != nil {
|
||||
return fmt.Errorf("transcode: amr: %w", err)
|
||||
}
|
||||
body = body[1+size:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// amrFrameCount 数出存储流里的帧数(结构预检)。空流(只有魔数、没有帧)按
|
||||
// 「坏的输入」处理:一条语音消息连一帧音频都没有,诚实报 ErrDecodeFailed
|
||||
// 比交出一份 0 样本的 WAV 更不容易被误当成成功。
|
||||
func amrFrameCount(in []byte) (int, error) {
|
||||
frames := 0
|
||||
if err := amrWalk(in, func([]byte) error { frames++; return nil }); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if frames == 0 {
|
||||
return 0, fmt.Errorf("transcode: amr: %w: no audio frames", ErrDecodeFailed)
|
||||
}
|
||||
return frames, nil
|
||||
}
|
||||
|
||||
// amrDuration 由帧数**确证**时长(一帧 20 ms),绝不估算。
|
||||
func amrDuration(frames int) time.Duration {
|
||||
return time.Duration(frames*amrFrameMillis) * time.Millisecond
|
||||
}
|
||||
|
||||
// amrDurationMs 是 amrDuration 的毫秒数形式(DTO 用)。
|
||||
func amrDurationMs(frames int) int64 {
|
||||
return int64(frames) * amrFrameMillis
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 黄金向量(design/25 §4.1 采纳 opencore-amrnb 为正确性基准、§8-T2 的验收判据)。
|
||||
//
|
||||
// 夹具怎么来的(可复现;生成命令原文,2026-10-05 在 Windows + ffmpeg 8.1.1 上执行):
|
||||
//
|
||||
// FF=/d/Bak/PROLIB/ffmpeg-8.1.1-full_build/bin/ffmpeg
|
||||
// SIG="aevalsrc=0.6*sin(2*PI*440*t)*(0.6+0.4*sin(2*PI*3*t))+0.2*sin(2*PI*1200*t):s=8000:d=0.5"
|
||||
// for br in 4750 5150 5900 6700 7400 7950 10200 12200; do
|
||||
// $FF -f lavfi -i "$SIG" -ac 1 -c:a libopencore_amrnb -b:a $br -y mode$br.amr
|
||||
// $FF -c:a libopencore_amrnb -i mode$br.amr -f s16le -ac 1 -ar 8000 -y mode$br.pcm
|
||||
// done
|
||||
// # mixed_modes.amr = 把 8 个文件按帧交错拼接(每帧自带 ToC,故混合模式合法流)
|
||||
//
|
||||
// 样例是**自造的合成信号**(三音正弦 + 幅度包络),不是任何真实语音;参考 PCM 由
|
||||
// 本机 ffmpeg 的 `libopencore_amrnb` 解码器产出(`-f s16le -ar 8000 -ac 1`)。
|
||||
// 夹具体积很小(AMR ≈3 KB + PCM ≈75 KB),因此**测试不需要装 ffmpeg**。
|
||||
//
|
||||
// 断言强度(**逐样本**,不是逐字节——原因见下):
|
||||
//
|
||||
// vendored 的 goamr-nb 是**部分移植**(其 dsp_decoder.go:13 自述
|
||||
// Cb_gain_average 与自适应相位色散未移植),实测与 opencore-amrnb
|
||||
// **逐字节不一致**。因此硬套「逐字节相等」会是一条永远红的假断言。
|
||||
// 改成:帧数 / 样本数 / 时长**精确相等**(这一条仍是逐样本级的强断言),
|
||||
// 再对全部样本逐个做「绝对差 ≤ goldenMaxAbsDiff」+ 全局 SNR / 相关系数 /
|
||||
// 能量比三档下限。实测基线与阈值的余量记录在下面的常量旁。
|
||||
const (
|
||||
// goldenMinSNRDB 实测最差 16.0 dB(mode10200),阈值留 ~25% 余量。
|
||||
goldenMinSNRDB = 12.0
|
||||
// goldenMaxAbsDiff 实测最差 11192(mixed_modes),阈值取满量程的一半。
|
||||
goldenMaxAbsDiff = 16384
|
||||
// goldenMinCorrelation 实测最差 0.9874,阈值 0.97。
|
||||
goldenMinCorrelation = 0.97
|
||||
// goldenMaxRMSDrift 实测最差 |1−rms 比| = 0.021,阈值 0.05。
|
||||
goldenMaxRMSDrift = 0.05
|
||||
)
|
||||
|
||||
// goldenCases 是把夹具名映射到「人类可读说明」的表:8 个编码模式 + 1 个混合模式。
|
||||
var goldenCases = []struct {
|
||||
name string
|
||||
what string
|
||||
}{
|
||||
{"mode4750", "AMR-NB 4.75 kbit/s(MR475)"},
|
||||
{"mode5150", "AMR-NB 5.15 kbit/s(MR515)"},
|
||||
{"mode5900", "AMR-NB 5.90 kbit/s(MR59)"},
|
||||
{"mode6700", "AMR-NB 6.70 kbit/s(MR67)"},
|
||||
{"mode7400", "AMR-NB 7.40 kbit/s(MR74)"},
|
||||
{"mode7950", "AMR-NB 7.95 kbit/s(MR795)"},
|
||||
{"mode10200", "AMR-NB 10.2 kbit/s(MR102)"},
|
||||
{"mode12200", "AMR-NB 12.2 kbit/s(MR122)"},
|
||||
{"mixed_modes", "逐帧交错的混合模式流"},
|
||||
}
|
||||
|
||||
// TestGoldenAgainstOpenCORE 对每个夹具做一次完整转码,再逐样本与参考 PCM 比对。
|
||||
func TestGoldenAgainstOpenCORE(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(DefaultCodecs()...))
|
||||
if err := eng.RegistrationError(); err != nil {
|
||||
t.Fatalf("codec registration: %v", err)
|
||||
}
|
||||
for _, tc := range goldenCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
src := readFixture(t, tc.name+".amr")
|
||||
want := readFixture(t, tc.name+".pcm")
|
||||
res, err := eng.Transcode(context.Background(), Request{
|
||||
Data: src, Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("transcode %s (%s): %v", tc.name, tc.what, err)
|
||||
}
|
||||
wantSamples := len(want) / wavPCMBytesPerSample
|
||||
assertGoldenMeta(t, res, wantSamples)
|
||||
got := wavDataSamples(t, res.Data)
|
||||
if len(got) != wantSamples {
|
||||
t.Fatalf("sample count: got %d, want %d (逐样本对照的前提)", len(got), wantSamples)
|
||||
}
|
||||
assertSamplesClose(t, got, int16Samples(want), tc.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// assertGoldenMeta 断言与参考 PCM 精确一致的那部分元信息:样本数、帧数、时长、
|
||||
// 目标参数。这几项是**逐样本级**的强断言,不掺容差。
|
||||
func assertGoldenMeta(t *testing.T, res Result, wantSamples int) {
|
||||
t.Helper()
|
||||
if res.Format != FormatWAV || res.Source != FormatAMR {
|
||||
t.Errorf("formats: got %s -> %s, want amr -> wav", res.Source, res.Format)
|
||||
}
|
||||
if res.SampleRate != amrSampleRate || res.Channels != amrChannels {
|
||||
t.Errorf("params: got %d Hz / %d ch, want %d Hz / %d ch",
|
||||
res.SampleRate, res.Channels, amrSampleRate, amrChannels)
|
||||
}
|
||||
wantFrames := wantSamples / amrFrameSamples
|
||||
if res.Frames != wantFrames {
|
||||
t.Errorf("frames: got %d, want %d", res.Frames, wantFrames)
|
||||
}
|
||||
if wantMs := int64(wantFrames * amrFrameMillis); res.DurationMs != wantMs {
|
||||
t.Errorf("duration: got %d ms, want %d ms", res.DurationMs, wantMs)
|
||||
}
|
||||
if len(res.Data) != wavHeaderBytes+wantSamples*wavPCMBytesPerSample {
|
||||
t.Fatalf("wav size: got %d, want %d", len(res.Data), wavHeaderBytes+wantSamples*wavPCMBytesPerSample)
|
||||
}
|
||||
}
|
||||
|
||||
// assertSamplesClose 逐样本比对:绝对差上限 + SNR + 相关系数 + 能量比。
|
||||
func assertSamplesClose(t *testing.T, got, want []int16, name string) {
|
||||
t.Helper()
|
||||
stat := comparePCM(got, want)
|
||||
if stat.maxAbsDiff > goldenMaxAbsDiff {
|
||||
t.Errorf("%s: max |diff| = %d > %d", name, stat.maxAbsDiff, goldenMaxAbsDiff)
|
||||
}
|
||||
if stat.snrDB < goldenMinSNRDB {
|
||||
t.Errorf("%s: SNR = %.1f dB < %.1f dB", name, stat.snrDB, goldenMinSNRDB)
|
||||
}
|
||||
if stat.corr < goldenMinCorrelation {
|
||||
t.Errorf("%s: correlation = %.5f < %.5f", name, stat.corr, goldenMinCorrelation)
|
||||
}
|
||||
if math.Abs(1-stat.rmsRatio) > goldenMaxRMSDrift {
|
||||
t.Errorf("%s: rms ratio = %.3f, drift > %.3f", name, stat.rmsRatio, goldenMaxRMSDrift)
|
||||
}
|
||||
t.Logf("%s: frames=%d snr=%.1f dB max|d|=%d corr=%.5f rms_ratio=%.4f",
|
||||
name, len(want)/amrFrameSamples, stat.snrDB, stat.maxAbsDiff, stat.corr, stat.rmsRatio)
|
||||
}
|
||||
|
||||
// pcmStat 是一次比对的结果(逐样本统计量)。
|
||||
type pcmStat struct {
|
||||
snrDB float64
|
||||
maxAbsDiff int
|
||||
corr float64
|
||||
rmsRatio float64
|
||||
}
|
||||
|
||||
// comparePCM 逐样本统计 got 与 want 的差、SNR、相关系数与能量比。
|
||||
func comparePCM(got, want []int16) pcmStat {
|
||||
var sig, noise, dot, gotPower, wantPower float64
|
||||
stat := pcmStat{}
|
||||
for i := range got {
|
||||
g, w := float64(got[i]), float64(want[i])
|
||||
diff := g - w
|
||||
if d := int(math.Abs(diff)); d > stat.maxAbsDiff {
|
||||
stat.maxAbsDiff = d
|
||||
}
|
||||
sig += w * w
|
||||
noise += diff * diff
|
||||
dot += g * w
|
||||
gotPower += g * g
|
||||
wantPower += w * w
|
||||
}
|
||||
stat.snrDB = snrDB(sig, noise)
|
||||
stat.corr = correlation(dot, gotPower, wantPower)
|
||||
stat.rmsRatio = math.Sqrt(gotPower / wantPower)
|
||||
return stat
|
||||
}
|
||||
|
||||
// snrDB 算信号/噪声能量比(dB);噪声为零时返回 +Inf(完全一致就该是 +Inf)。
|
||||
func snrDB(signal, noise float64) float64 {
|
||||
if noise == 0 {
|
||||
return math.Inf(1)
|
||||
}
|
||||
return 10 * math.Log10(signal/noise)
|
||||
}
|
||||
|
||||
// correlation 算归一化相关系数;任一序列零能量时返回 0。
|
||||
func correlation(dot, aPower, bPower float64) float64 {
|
||||
if aPower == 0 || bPower == 0 {
|
||||
return 0
|
||||
}
|
||||
return dot / math.Sqrt(aPower*bPower)
|
||||
}
|
||||
|
||||
// readFixture 读一份夹具,并断言它非空(夹具被清空/截断时测试立刻红)。
|
||||
func readFixture(t *testing.T, name string) []byte {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(filepath.Join("testdata", name))
|
||||
if err != nil {
|
||||
t.Fatalf("fixture %s: %v", name, err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("fixture %s is empty", name)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// wavDataSamples 从一份 WAV 里取出 PCM16 样本(只认本包写出的 44 字节头形态)。
|
||||
func wavDataSamples(t *testing.T, wav []byte) []int16 {
|
||||
t.Helper()
|
||||
if len(wav) < wavHeaderBytes {
|
||||
t.Fatalf("wav too short: %d bytes", len(wav))
|
||||
}
|
||||
if string(wav[wavOffRiffID:wavOffChunkSize]) != wavRiffID || string(wav[wavOffWaveID:wavOffFmtID]) != wavWaveID {
|
||||
t.Fatalf("wav: bad RIFF/WAVE magic")
|
||||
}
|
||||
if string(wav[wavOffFmtID:wavOffFmtSize]) != wavFmtID || string(wav[wavOffDataID:wavOffDataSize]) != wavDataID {
|
||||
t.Fatalf("wav: bad fmt/data chunk ids")
|
||||
}
|
||||
size := int(binary.LittleEndian.Uint32(wav[wavOffDataSize:]))
|
||||
if size != len(wav)-wavHeaderBytes {
|
||||
t.Fatalf("wav: data size %d != payload %d", size, len(wav)-wavHeaderBytes)
|
||||
}
|
||||
out := make([]int16, size/wavPCMBytesPerSample)
|
||||
for i := range out {
|
||||
out[i] = int16(binary.LittleEndian.Uint16(wav[wavHeaderBytes+i*wavPCMBytesPerSample:]))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// int16Samples 把小端 PCM16 字节转成样本。
|
||||
func int16Samples(pcm []byte) []int16 {
|
||||
out := make([]int16, len(pcm)/wavPCMBytesPerSample)
|
||||
for i := range out {
|
||||
out[i] = int16(binary.LittleEndian.Uint16(pcm[i*wavPCMBytesPerSample:]))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// TestAMRRejectsBrokenInput 覆盖 design/25 §8-T2 要求的边界样本:空文件、坏头、
|
||||
// 截断帧、保留帧类型、只有魔数。它们一律 ErrDecodeFailed(「坏了」,与「不会」
|
||||
// 的 ErrUnsupportedFormat 分开),且绝不返回半份结果。
|
||||
func TestAMRRejectsBrokenInput(t *testing.T) {
|
||||
codec := amrNBToWAV{}
|
||||
good := readFixture(t, "mode12200.amr")
|
||||
headerOnly := []byte(amrNBMagic)
|
||||
badMagic := append([]byte("XXXXXX"), good[amrStorageHeaderBytes:]...)
|
||||
truncated := good[:len(good)-1]
|
||||
reservedFT := append([]byte(amrNBMagic), 0x48, 0x00) // FT=9(保留值)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"empty file", nil},
|
||||
{"magic only", headerOnly},
|
||||
{"bad magic", badMagic},
|
||||
{"truncated last frame", truncated},
|
||||
{"reserved frame type", reservedFT},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, err := codec.Transcode(context.Background(), tc.data, DefaultLimits())
|
||||
if !errors.Is(err, ErrDecodeFailed) {
|
||||
t.Fatalf("err = %v, want ErrDecodeFailed", err)
|
||||
}
|
||||
if len(res.Data) != 0 {
|
||||
t.Fatalf("broken input produced %d bytes of output", len(res.Data))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAMRSilentFramesPinned 把「已知边界」钉住:SID(FT 8)与 no-data(FT 15)
|
||||
// 帧在 vendored 解码器里产出 **160 样本静音**(错误隐藏/CNG 未移植,见
|
||||
// amrnb/VENDORED.md)。这条测试不假装它做得好——只是把行为写死,避免悄悄漂移。
|
||||
func TestAMRSilentFramesPinned(t *testing.T) {
|
||||
codec := amrNBToWAV{}
|
||||
// FT=15(no data):ToC = 15<<3 = 0x78,零字节载荷;两帧。
|
||||
noData := append([]byte(amrNBMagic), 0x78, 0x78)
|
||||
res, err := codec.Transcode(context.Background(), noData, DefaultLimits())
|
||||
if err != nil {
|
||||
t.Fatalf("no-data stream: %v", err)
|
||||
}
|
||||
if res.Frames != 2 || res.DurationMs != 2*amrFrameMillis {
|
||||
t.Fatalf("no-data stream: frames=%d duration=%d", res.Frames, res.DurationMs)
|
||||
}
|
||||
samples := wavDataSamples(t, res.Data)
|
||||
if len(samples) != 2*amrFrameSamples {
|
||||
t.Fatalf("no-data stream: %d samples, want %d", len(samples), 2*amrFrameSamples)
|
||||
}
|
||||
for i, s := range samples {
|
||||
if s != 0 {
|
||||
t.Fatalf("no-data stream: sample %d = %d, want silence (CNG 未移植)", i, s)
|
||||
}
|
||||
}
|
||||
// FT=8(SID):ToC = 8<<3 = 0x40 + 5 字节载荷 ⇒ 仍是一帧静音。
|
||||
sid := append([]byte(amrNBMagic), 0x40, 0, 0, 0, 0, 0)
|
||||
res, err = codec.Transcode(context.Background(), sid, DefaultLimits())
|
||||
if err != nil {
|
||||
t.Fatalf("sid stream: %v", err)
|
||||
}
|
||||
if res.Frames != 1 {
|
||||
t.Fatalf("sid stream: frames=%d, want 1", res.Frames)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAMRDeterministic 断言同一输入两次转码逐字节相同(纯函数契约)。
|
||||
func TestAMRDeterministic(t *testing.T) {
|
||||
codec := amrNBToWAV{}
|
||||
in := readFixture(t, "mixed_modes.amr")
|
||||
first, err := codec.Transcode(context.Background(), in, DefaultLimits())
|
||||
if err != nil {
|
||||
t.Fatalf("first pass: %v", err)
|
||||
}
|
||||
second, err := codec.Transcode(context.Background(), in, DefaultLimits())
|
||||
if err != nil {
|
||||
t.Fatalf("second pass: %v", err)
|
||||
}
|
||||
if len(first.Data) != len(second.Data) {
|
||||
t.Fatalf("size drift: %d vs %d", len(first.Data), len(second.Data))
|
||||
}
|
||||
for i := range first.Data {
|
||||
if first.Data[i] != second.Data[i] {
|
||||
t.Fatalf("byte %d drifted: %d vs %d", i, first.Data[i], second.Data[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAMRLimitsRejectedBeforeDecode 断言「格式相关」的两层闸:可确证的时长与
|
||||
// 精确可算的产物字节数都在**解码前**判定(因此报的是上限哨兵,而不是解到一半
|
||||
// 才发现)。
|
||||
func TestAMRLimitsRejectedBeforeDecode(t *testing.T) {
|
||||
codec := amrNBToWAV{}
|
||||
in := readFixture(t, "mode12200.amr") // 26 帧 = 520 ms
|
||||
limits := DefaultLimits()
|
||||
|
||||
t.Run("duration over limit", func(t *testing.T) {
|
||||
l := limits
|
||||
l.MaxDuration = time.Duration(10*amrFrameMillis) * time.Millisecond
|
||||
if _, err := codec.Transcode(context.Background(), in, l); !errors.Is(err, ErrInputTooLarge) {
|
||||
t.Fatalf("err = %v, want ErrInputTooLarge", err)
|
||||
}
|
||||
})
|
||||
t.Run("output over limit", func(t *testing.T) {
|
||||
l := limits
|
||||
l.MaxOutputBytes = wavHeaderBytes + 10*amrFrameSamples*wavPCMBytesPerSample
|
||||
if _, err := codec.Transcode(context.Background(), in, l); !errors.Is(err, ErrOutputTooLarge) {
|
||||
t.Fatalf("err = %v, want ErrOutputTooLarge", err)
|
||||
}
|
||||
})
|
||||
t.Run("deadline respected", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
<-ctx.Done()
|
||||
if _, err := codec.Transcode(ctx, in, limits); !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("err = %v, want context.DeadlineExceeded(由 engine 归为 ErrTimeout)", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package transcode
|
||||
|
||||
// DefaultCodecs 是全仓**唯一**的编解码器注册点(design/25 §6.3):
|
||||
//
|
||||
// 新增一种格式 = 在下面追加一行(+ 一个实现文件 + 一个 Format 常量)。
|
||||
//
|
||||
// 本期只发一条路:`amr → wav`(design/25 §3.1)。`silk` 不做、`amr-wb` 后置、
|
||||
// 编码方向不做、无界重采样/音量归一/降噪不做(§3.3 逐条有理由)。
|
||||
//
|
||||
// 一条单测把它的条数钉死在设计上(见 codecs_test.go 的 designPublishedRoutes),
|
||||
// 防止「加了实现却忘了注册」(或反之)。
|
||||
func DefaultCodecs() []Codec {
|
||||
return []Codec{
|
||||
amrNBToWAV{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package transcode
|
||||
|
||||
import "testing"
|
||||
|
||||
// designPublishedRoutes 是 design/25 §3.1「格式矩阵」里**已发布**的路数:
|
||||
// 本期只有一条 —— `amr → wav`。
|
||||
//
|
||||
// 这条常量刻意写在测试里:谁加了实现却忘了在 DefaultCodecs 注册(或反过来),
|
||||
// 这条断言就会红,逼他回设计里对一次账。这就是 design/25 §6.3 要的「把值钉死在
|
||||
// 设计上」,与只测边界逻辑互补。
|
||||
const designPublishedRoutes = 1
|
||||
|
||||
// TestDefaultCodecsMatchesPublishedMatrix 断言注册点与设计矩阵一致。
|
||||
func TestDefaultCodecsMatchesPublishedMatrix(t *testing.T) {
|
||||
codecs := DefaultCodecs()
|
||||
if len(codecs) != designPublishedRoutes {
|
||||
t.Fatalf("DefaultCodecs() registers %d routes, design/25 §3.1 publishes %d: "+
|
||||
"加了实现要在此处同步注册(并回设计对账)", len(codecs), designPublishedRoutes)
|
||||
}
|
||||
route := codecs[0]
|
||||
if route.From() != FormatAMR || route.To() != FormatWAV {
|
||||
t.Fatalf("DefaultCodecs()[0] = %s -> %s, want amr -> wav", route.From(), route.To())
|
||||
}
|
||||
}
|
||||
|
||||
// TestEnginePublishesOnlyTheDesignRoute 断言引擎对外报告的路也是那一条:
|
||||
// Supports 只认 amr→wav,Formats 只报 amr/wav。
|
||||
func TestEnginePublishesOnlyTheDesignRoute(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(DefaultCodecs()...))
|
||||
if err := eng.RegistrationError(); err != nil {
|
||||
t.Fatalf("registration: %v", err)
|
||||
}
|
||||
if !eng.Supports(FormatAMR, FormatWAV) {
|
||||
t.Fatalf("amr -> wav must be supported")
|
||||
}
|
||||
for _, pair := range [][2]Format{
|
||||
{FormatWAV, FormatAMR}, {FormatAMR, FormatAMR}, {FormatWAV, FormatWAV},
|
||||
{FormatAMRWB, FormatWAV}, {FormatAMR, FormatAMRWB},
|
||||
} {
|
||||
if eng.Supports(pair[0], pair[1]) {
|
||||
t.Errorf("Supports(%q, %q) = true, want false(本期只发 amr -> wav)", pair[0], pair[1])
|
||||
}
|
||||
}
|
||||
formats := eng.Formats()
|
||||
if len(formats) != 2 || formats[0] != FormatAMR || formats[1] != FormatWAV {
|
||||
t.Fatalf("Formats = %v, want [amr wav]", formats)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Engine 是 Transcoder 的默认实现:把「格式无关」的那一层(输入字节上限、
|
||||
// 并发信号量、单次超时、日志)收在一处,把「格式相关」的那一层交给注册进来的
|
||||
// Codec(design/25 §1.3.2 / §2.2)。
|
||||
//
|
||||
// 它是普通结构体,由组合根构造并注入;**没有包级全局变量**
|
||||
// (AGENTS §10 红线)。它可安全并发使用。
|
||||
type Engine struct {
|
||||
limits Limits
|
||||
reg *Registry
|
||||
// sem 是全局并发信号量。容量 = Limits.Concurrency;槽满即 ErrBusy
|
||||
// (快速失败、不入队)。
|
||||
sem chan struct{}
|
||||
log *slog.Logger
|
||||
// codecs 是选项里攒下的待注册列表,在 NewEngine 末尾统一注册(见 WithCodecs)。
|
||||
codecs []Codec
|
||||
// regErr 记录注册期的第一个错误(不该发生:DefaultCodecs 是已知良好的)。
|
||||
// 它不阻塞构造,但绝不静默——组合根可读 RegistrationError 并告警。
|
||||
regErr error
|
||||
}
|
||||
|
||||
// Compile-time assertion that *Engine satisfies the Transcoder interface.
|
||||
var _ Transcoder = (*Engine)(nil)
|
||||
|
||||
// Option 配置 Engine。
|
||||
type Option func(*Engine)
|
||||
|
||||
// WithCodecs 把 codecs 交给引擎(在 NewEngine 的所有选项都应用完之后统一注册,
|
||||
// 这样即使调用方按 design/25 §6.1 的顺序先写 WithCodecs 再写 WithLogger,
|
||||
// 注册失败的告警也能落到正确的日志器上)。注册失败(重复 / 不合法)是程序错误:
|
||||
// 记 Error 日志并留在 RegistrationError 里,绝不静默吞掉。
|
||||
func WithCodecs(codecs ...Codec) Option {
|
||||
return func(e *Engine) {
|
||||
e.codecs = append(e.codecs, codecs...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger 设置引擎日志。日志一律带 source=capability/transcode
|
||||
// (AGENTS §10:日志必须标来源)。
|
||||
func WithLogger(l *slog.Logger) Option {
|
||||
return func(e *Engine) {
|
||||
e.log = loggerFor(l)
|
||||
}
|
||||
}
|
||||
|
||||
// loggerFor 把 nil 日志收敛成一个丢弃日志:本包不因为「没人给日志」而崩,
|
||||
// 也不因为「没人给日志」而少一次调用记录(默认丢弃,责任在组合根)。
|
||||
func loggerFor(l *slog.Logger) *slog.Logger {
|
||||
if l == nil {
|
||||
return slog.New(slog.DiscardHandler)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// NewEngine 构造一个引擎。limits 的零值字段由 DefaultLimits 补足;
|
||||
// 未给 WithCodecs 时注册表为空 ⇒ Supports 恒 false、Transcode 恒
|
||||
// ErrUnsupportedFormat(design/25 §8-T1 的空注册表行为)。
|
||||
func NewEngine(limits Limits, opts ...Option) *Engine {
|
||||
e := &Engine{
|
||||
limits: limits.withDefaults(),
|
||||
reg: NewRegistry(),
|
||||
sem: make(chan struct{}, limits.withDefaults().Concurrency),
|
||||
log: loggerFor(nil),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(e)
|
||||
}
|
||||
}
|
||||
e.registerCodecs()
|
||||
return e
|
||||
}
|
||||
|
||||
// registerCodecs 注册 WithCodecs 攒下的编解码器(在所有选项都生效之后)。
|
||||
func (e *Engine) registerCodecs() {
|
||||
for _, c := range e.codecs {
|
||||
if err := e.reg.Register(c); err != nil {
|
||||
if e.regErr == nil {
|
||||
e.regErr = err
|
||||
}
|
||||
e.log.Error("transcode codec registration skipped", "error", err)
|
||||
}
|
||||
}
|
||||
e.codecs = nil
|
||||
}
|
||||
|
||||
// RegistrationError 返回构造期第一个编解码器注册错误(正常为 nil)。
|
||||
func (e *Engine) RegistrationError() error { return e.regErr }
|
||||
|
||||
// Limits 返回生效的限额(零值已补默认)。
|
||||
func (e *Engine) Limits() Limits { return e.limits }
|
||||
|
||||
// Supports 报告 src→dst 这条路是否可用(能力门)。
|
||||
func (e *Engine) Supports(src, dst Format) bool {
|
||||
_, ok := e.reg.Lookup(src, dst)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Formats 报告当前注册的所有格式(发现)。
|
||||
func (e *Engine) Formats() []Format { return e.reg.Formats() }
|
||||
|
||||
// Transcode 把 req.Data 从 req.Source 转成 req.Target(design/25 §2.1)。
|
||||
//
|
||||
// 失败一律返回 errors.go 的哨兵之一(或包裹它)——ctx 截止返回 ErrTimeout,
|
||||
// 调用方主动取消返回 context.Canceled 的包裹(取消是调用方自己的决定,
|
||||
// 与「转码失败」不是一回事)。进入编解码器之前的每一道闸都在**解码前**:
|
||||
// 未知格式、越限、槽满都不会解出一个字节。
|
||||
//
|
||||
// ctx 为 nil 时按 context.Background() 处理;ctx 超时是硬闸,绝不无界等待,
|
||||
// 也不泄漏 goroutine(本调用全程在调用方 goroutine 里跑完)。
|
||||
func (e *Engine) Transcode(ctx context.Context, req Request) (Result, error) {
|
||||
start := time.Now()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
src, err := e.resolveSource(req)
|
||||
if err != nil {
|
||||
return Result{}, e.reject(ctx, req, src, err, start)
|
||||
}
|
||||
if req.Target == "" {
|
||||
return Result{}, e.reject(ctx, req, src,
|
||||
fmt.Errorf("%w: empty target format", ErrUnsupportedFormat), start)
|
||||
}
|
||||
codec, ok := e.reg.Lookup(src, req.Target)
|
||||
if !ok {
|
||||
return Result{}, e.reject(ctx, req, src,
|
||||
fmt.Errorf("%w: no codec registered", ErrUnsupportedFormat), start)
|
||||
}
|
||||
if len(req.Data) > e.limits.MaxInputBytes {
|
||||
return Result{}, e.reject(ctx, req, src,
|
||||
fmt.Errorf("%w: %d bytes > %d", ErrInputTooLarge, len(req.Data), e.limits.MaxInputBytes), start)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Result{}, e.reject(ctx, req, src, timeoutError(err), start)
|
||||
}
|
||||
if !e.acquire() {
|
||||
return Result{}, e.reject(ctx, req, src,
|
||||
fmt.Errorf("%w: %d slots busy", ErrBusy, e.limits.Concurrency), start)
|
||||
}
|
||||
defer e.release()
|
||||
|
||||
callCtx, cancel := context.WithTimeout(ctx, e.limits.Timeout)
|
||||
defer cancel()
|
||||
res, err := codec.Transcode(callCtx, req.Data, e.limits)
|
||||
if err != nil {
|
||||
return Result{}, e.reject(ctx, req, src, classify(callCtx, err), start)
|
||||
}
|
||||
if len(res.Data) == 0 {
|
||||
return Result{}, e.reject(ctx, req, src,
|
||||
fmt.Errorf("%w: codec returned empty output", ErrEncodeFailed), start)
|
||||
}
|
||||
e.log.Info("transcode ok",
|
||||
"source_format", FormatName(res.Source),
|
||||
"target_format", FormatName(res.Format),
|
||||
"in_bytes", len(req.Data),
|
||||
"out_bytes", len(res.Data),
|
||||
"frames", res.Frames,
|
||||
"duration_ms", res.DurationMs,
|
||||
"elapsed_ms", time.Since(start).Milliseconds())
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// resolveSource 定出这次请求的实际来源格式:显式给出就用它,留空则按魔数嗅探,
|
||||
// 嗅不出即 ErrUnsupportedFormat(deny-by-default)。
|
||||
func (e *Engine) resolveSource(req Request) (Format, error) {
|
||||
if req.Source != "" {
|
||||
return req.Source, nil
|
||||
}
|
||||
sniffed, ok := Sniff(req.Data)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%w: magic not recognized", ErrUnsupportedFormat)
|
||||
}
|
||||
return sniffed, nil
|
||||
}
|
||||
|
||||
// acquire 非阻塞地取一个并发槽。拿不到即 false——绝不排队。
|
||||
func (e *Engine) acquire() bool {
|
||||
select {
|
||||
case e.sem <- struct{}{}:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// release 归还一个并发槽。
|
||||
func (e *Engine) release() { <-e.sem }
|
||||
|
||||
// reject 记一次失败日志并把错误包上**路由上下文**后返回(绝不静默、绝不裸返回):
|
||||
// 最终文案形如 `transcode: amr -> wav: unsupported format: no codec registered`。
|
||||
// 可预期的输入问题(哨兵)记 Warn;不该发生的(非哨兵:编解码器违约)记 Error。
|
||||
func (e *Engine) reject(ctx context.Context, req Request, src Format, err error, start time.Time) error {
|
||||
level := slog.LevelWarn
|
||||
if !isSentinelError(err) {
|
||||
level = slog.LevelError
|
||||
}
|
||||
e.log.Log(ctx, level, "transcode failed",
|
||||
"source_format", FormatName(src),
|
||||
"target_format", FormatName(req.Target),
|
||||
"in_bytes", len(req.Data),
|
||||
"elapsed_ms", time.Since(start).Milliseconds(),
|
||||
"error", err)
|
||||
return fmt.Errorf("transcode: %s: %w", routeLabel(src, req.Target), err)
|
||||
}
|
||||
|
||||
// routeLabel 是错误/日志里的路由可读名;格式未知(嗅探失败 / 目标留空)时用
|
||||
// `<unknown>` 占位,绝不留一段空白让人猜。
|
||||
func routeLabel(src, dst Format) string {
|
||||
from, to := FormatName(src), FormatName(dst)
|
||||
if from == "" {
|
||||
from = "<unknown>"
|
||||
}
|
||||
if to == "" {
|
||||
to = "<unknown>"
|
||||
}
|
||||
return from + " -> " + to
|
||||
}
|
||||
|
||||
// classify 把编解码器返回的错误收敛到 design/25 §2.4 的口径:ctx 截止即
|
||||
// ErrTimeout;调用方取消保持 context.Canceled 的包裹;其余补一层来源后上抛
|
||||
// (编解码器自己用哨兵,engine 不替它猜,但也不裸返回)。
|
||||
func classify(callCtx context.Context, err error) error {
|
||||
switch {
|
||||
case errors.Is(err, context.DeadlineExceeded), errors.Is(callCtx.Err(), context.DeadlineExceeded):
|
||||
return timeoutError(context.DeadlineExceeded)
|
||||
case errors.Is(err, context.Canceled), errors.Is(callCtx.Err(), context.Canceled):
|
||||
return fmt.Errorf("canceled: %w", context.Canceled)
|
||||
default:
|
||||
return fmt.Errorf("codec failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// timeoutError 把 ctx 截止统一成 ErrTimeout 的包裹,并保留原始原因。
|
||||
func timeoutError(cause error) error {
|
||||
return fmt.Errorf("%w: %w", ErrTimeout, cause)
|
||||
}
|
||||
|
||||
// isSentinelError 报告 err 是否属于本包已登记的哨兵(含包内构造期错误)。
|
||||
// 逐条列举而不是查一个包级切片:本包因此**没有任何包级状态**
|
||||
// (AGENTS §10:禁包级全局变量,除 var ErrXxx)。
|
||||
func isSentinelError(err error) bool {
|
||||
return errors.Is(err, ErrUnsupportedFormat) ||
|
||||
errors.Is(err, ErrInputTooLarge) ||
|
||||
errors.Is(err, ErrOutputTooLarge) ||
|
||||
errors.Is(err, ErrDecodeFailed) ||
|
||||
errors.Is(err, ErrEncodeFailed) ||
|
||||
errors.Is(err, ErrTimeout) ||
|
||||
errors.Is(err, ErrBusy) ||
|
||||
errors.Is(err, ErrNoBackend) ||
|
||||
errors.Is(err, ErrDuplicate) ||
|
||||
errors.Is(err, ErrInvalidCodec) ||
|
||||
errors.Is(err, ErrBadArgs)
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
)
|
||||
|
||||
// stubCodec 是 engine 测试用的编解码器桩:它不碰真实音频,只记录调用次数并按
|
||||
// 需要阻塞 / 报错,因此可以精确检验「闸在解码之前」这类顺序性断言。
|
||||
type stubCodec struct {
|
||||
from, to Format
|
||||
calls atomic.Int32
|
||||
// entered 在 Transcode 进入时被关闭(并发用例用来确认槽已被占)。
|
||||
entered chan struct{}
|
||||
// block 非 nil 时,Transcode 会等到它被关闭或 ctx 结束。
|
||||
block chan struct{}
|
||||
// waitCtx 为 true 时,Transcode 会一直等到 ctx 结束(超时用例)。
|
||||
waitCtx bool
|
||||
err error
|
||||
result Result
|
||||
}
|
||||
|
||||
func newStubCodec() *stubCodec { return &stubCodec{from: FormatAMR, to: FormatWAV} }
|
||||
|
||||
func (c *stubCodec) From() Format { return c.from }
|
||||
func (c *stubCodec) To() Format { return c.to }
|
||||
|
||||
func (c *stubCodec) Transcode(ctx context.Context, in []byte, _ Limits) (Result, error) {
|
||||
c.calls.Add(1)
|
||||
if c.entered != nil {
|
||||
select {
|
||||
case c.entered <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
if c.block != nil {
|
||||
select {
|
||||
case <-c.block:
|
||||
case <-ctx.Done():
|
||||
return Result{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
if c.waitCtx {
|
||||
<-ctx.Done()
|
||||
return Result{}, ctx.Err()
|
||||
}
|
||||
if c.err != nil {
|
||||
return Result{}, c.err
|
||||
}
|
||||
res := c.result
|
||||
if res.Data == nil {
|
||||
res.Data = in
|
||||
}
|
||||
if res.Source == "" {
|
||||
res.Source = c.from
|
||||
}
|
||||
if res.Format == "" {
|
||||
res.Format = c.to
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// amrStubBytes 是一段「魔数与目标路都对得上」的最小输入。
|
||||
func amrStubBytes(payload ...byte) []byte {
|
||||
return append([]byte(amrNBMagic), payload...)
|
||||
}
|
||||
|
||||
// TestEngineEmptyRegistry 是 design/25 §8-T1 的空注册表行为:Supports 恒 false、
|
||||
// Transcode 恒 ErrUnsupportedFormat(且 Formats 为空)。
|
||||
func TestEngineEmptyRegistry(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits())
|
||||
if eng.Supports(FormatAMR, FormatWAV) {
|
||||
t.Fatalf("empty registry reports support for amr -> wav")
|
||||
}
|
||||
if got := eng.Formats(); len(got) != 0 {
|
||||
t.Fatalf("empty registry formats = %v, want none", got)
|
||||
}
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if !errors.Is(err, ErrUnsupportedFormat) {
|
||||
t.Fatalf("err = %v, want ErrUnsupportedFormat", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineInputTooLargeDecodesNothing 断言输入字节闸在**编解码器之前**:桩一次都
|
||||
// 没被调用(design/25 §8-T1「越限 ⇒ ErrInputTooLarge 且一行都没解码」)。
|
||||
func TestEngineInputTooLargeDecodesNothing(t *testing.T) {
|
||||
stub := newStubCodec()
|
||||
limits := DefaultLimits()
|
||||
limits.MaxInputBytes = len(amrNBMagic)
|
||||
eng := NewEngine(limits, WithCodecs(stub))
|
||||
data := amrStubBytes(0x3c, 0x00)
|
||||
_, err := eng.Transcode(context.Background(), Request{Data: data, Source: FormatAMR, Target: FormatWAV})
|
||||
if !errors.Is(err, ErrInputTooLarge) {
|
||||
t.Fatalf("err = %v, want ErrInputTooLarge", err)
|
||||
}
|
||||
if n := stub.calls.Load(); n != 0 {
|
||||
t.Fatalf("codec was invoked %d times before the input-size gate", n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineBusyFailsFast 断言并发槽满时**快速失败、不入队**(design/25 §5.2),
|
||||
// 且槽随后会被归还。
|
||||
func TestEngineBusyFailsFast(t *testing.T) {
|
||||
stub := newStubCodec()
|
||||
stub.block = make(chan struct{})
|
||||
stub.entered = make(chan struct{}, 1)
|
||||
limits := DefaultLimits()
|
||||
limits.Concurrency = 1
|
||||
eng := NewEngine(limits, WithCodecs(stub))
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
done <- err
|
||||
}()
|
||||
<-stub.entered
|
||||
|
||||
start := time.Now()
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if !errors.Is(err, ErrBusy) {
|
||||
t.Fatalf("err = %v, want ErrBusy", err)
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed > busyFailBudget {
|
||||
t.Fatalf("busy rejection took %s (should fail fast, not queue)", elapsed)
|
||||
}
|
||||
close(stub.block)
|
||||
if err := <-done; err != nil {
|
||||
t.Fatalf("first call: %v", err)
|
||||
}
|
||||
if _, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
}); err != nil {
|
||||
t.Fatalf("slot not released after the first call: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// busyFailBudget 是「快速失败」的时间预算:ErrBusy 必须来自非阻塞的 select,
|
||||
// 而不是等出来的。取值远大于调度抖动(同一台机器上实测 <1ms)。
|
||||
const busyFailBudget = 500 * time.Millisecond
|
||||
|
||||
// TestEngineTimeout 断言单次 ctx 超时是硬闸:超时即 ErrTimeout,且调用方拿得到
|
||||
// (不无界等待、不泄漏 goroutine)。
|
||||
func TestEngineTimeout(t *testing.T) {
|
||||
stub := newStubCodec()
|
||||
stub.waitCtx = true
|
||||
limits := DefaultLimits()
|
||||
limits.Timeout = 20 * time.Millisecond
|
||||
eng := NewEngine(limits, WithCodecs(stub))
|
||||
start := time.Now()
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if !errors.Is(err, ErrTimeout) {
|
||||
t.Fatalf("err = %v, want ErrTimeout", err)
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed > time.Second {
|
||||
t.Fatalf("timeout took %s, want ~%s", elapsed, limits.Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineRejectsBadRequests 覆盖「进入编解码器之前」的几种诚实拒绝。
|
||||
func TestEngineRejectsBadRequests(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(newStubCodec()))
|
||||
cases := []struct {
|
||||
name string
|
||||
req Request
|
||||
}{
|
||||
{"empty target", Request{Data: amrStubBytes(0x3c), Source: FormatAMR}},
|
||||
{"source not sniffable", Request{Data: []byte("not audio at all"), Target: FormatWAV}},
|
||||
{"no route for sniffed source", Request{Data: []byte("RIFF\x00\x00\x00\x00WAVE"), Target: FormatWAV}},
|
||||
{"unknown source name", Request{Data: amrStubBytes(0x3c), Source: Format("mp3"), Target: FormatWAV}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := eng.Transcode(context.Background(), tc.req)
|
||||
if !errors.Is(err, ErrUnsupportedFormat) {
|
||||
t.Fatalf("err = %v, want ErrUnsupportedFormat", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineSniffsSourceWhenOmitted 断言 Source 留空时按魔数嗅探并回填到 Result。
|
||||
func TestEngineSniffsSourceWhenOmitted(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(newStubCodec()))
|
||||
res, err := eng.Transcode(context.Background(), Request{Data: amrStubBytes(0x3c), Target: FormatWAV})
|
||||
if err != nil {
|
||||
t.Fatalf("transcode: %v", err)
|
||||
}
|
||||
if res.Source != FormatAMR {
|
||||
t.Fatalf("sniffed source = %q, want amr", res.Source)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineCodecContractViolations 断言「不该发生的」被当成失败:空产物 ⇒
|
||||
// ErrEncodeFailed;非哨兵错误 ⇒ 原样上抛(但记 ERROR 级日志,绝不静默)。
|
||||
func TestEngineCodecContractViolations(t *testing.T) {
|
||||
t.Run("empty output", func(t *testing.T) {
|
||||
stub := newStubCodec()
|
||||
stub.result = Result{Data: []byte{}}
|
||||
stub.err = nil
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(stub))
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if !errors.Is(err, ErrEncodeFailed) {
|
||||
t.Fatalf("err = %v, want ErrEncodeFailed", err)
|
||||
}
|
||||
})
|
||||
t.Run("non-sentinel error is not swallowed", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
stub := newStubCodec()
|
||||
stub.err = errBoom
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(stub), WithLogger(testLogger(&buf)))
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if !errors.Is(err, errBoom) {
|
||||
t.Fatalf("err = %v, want the codec error to pass through", err)
|
||||
}
|
||||
assertLogContains(t, &buf, "level=ERROR", "transcode failed", "source=capability/transcode")
|
||||
})
|
||||
}
|
||||
|
||||
// errBoom 是「编解码器违约」用的非哨兵错误。
|
||||
var errBoom = errors.New("boom")
|
||||
|
||||
// testLogger 造一个按组合根口径接线的日志器(source=capability/transcode)。
|
||||
func testLogger(buf *bytes.Buffer) *slog.Logger {
|
||||
return logger.WithSource(slog.New(slog.NewTextHandler(buf, nil)), logSource)
|
||||
}
|
||||
|
||||
// logSource 是本能力的日志来源(design/25 §5.3;组合根必须照此接线)。
|
||||
const logSource = "capability/transcode"
|
||||
|
||||
// assertLogContains 断言日志里出现了给定的每一段子串。
|
||||
func assertLogContains(t *testing.T, buf *bytes.Buffer, wants ...string) {
|
||||
t.Helper()
|
||||
got := buf.String()
|
||||
for _, want := range wants {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("log missing %q; got:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineLogsEveryOutcome 断言 design/25 §5.3 要求的字段与来源都在:
|
||||
// 成功记 Info(含 in/out 字节数、帧数、时长、耗时),失败记 Warn。
|
||||
// 同时断言「只记长度、不记字节内容」。
|
||||
func TestEngineLogsEveryOutcome(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
stub := newStubCodec()
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(stub), WithLogger(testLogger(&buf)))
|
||||
if _, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
}); err != nil {
|
||||
t.Fatalf("transcode: %v", err)
|
||||
}
|
||||
assertLogContains(t, &buf, "level=INFO", "transcode ok", "source=capability/transcode",
|
||||
"source_format=amr", "target_format=wav", "in_bytes=7", "out_bytes=7", "frames=0")
|
||||
})
|
||||
t.Run("unsupported is a warning", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
eng := NewEngine(DefaultLimits(), WithLogger(testLogger(&buf)))
|
||||
if _, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
}); !errors.Is(err, ErrUnsupportedFormat) {
|
||||
t.Fatalf("err = %v", err)
|
||||
}
|
||||
assertLogContains(t, &buf, "level=WARN", "transcode failed", "source=capability/transcode")
|
||||
})
|
||||
}
|
||||
|
||||
// TestEngineRegistrationErrorsAreVisible 断言注册失败不阻塞构造、但**可见**
|
||||
// (绝不静默):nil 注册项、重复注册都会进 RegistrationError,且好的一路仍可用。
|
||||
func TestEngineRegistrationErrorsAreVisible(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
codecs []Codec
|
||||
wantErr error
|
||||
}{
|
||||
{"nil codec", []Codec{nil, newStubCodec()}, ErrInvalidCodec},
|
||||
{"duplicate codec", []Codec{newStubCodec(), newStubCodec()}, ErrDuplicate},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(tc.codecs...), WithLogger(testLogger(&buf)))
|
||||
err := eng.RegistrationError()
|
||||
if !errors.Is(err, tc.wantErr) {
|
||||
t.Fatalf("RegistrationError = %v, want %v", err, tc.wantErr)
|
||||
}
|
||||
if !eng.Supports(FormatAMR, FormatWAV) {
|
||||
t.Fatalf("good codec was not registered")
|
||||
}
|
||||
assertLogContains(t, &buf, "level=ERROR", "transcode codec registration skipped")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineDefaultLimits 断言零值 Limits 被补成 design/25 §2.5 的具名默认,
|
||||
// 且显式给出的值不被覆盖。
|
||||
func TestEngineDefaultLimits(t *testing.T) {
|
||||
got := NewEngine(Limits{}).Limits()
|
||||
want := DefaultLimits()
|
||||
if got != want {
|
||||
t.Fatalf("zero limits = %+v, want defaults %+v", got, want)
|
||||
}
|
||||
custom := Limits{MaxInputBytes: 1, MaxOutputBytes: 2, MaxDuration: 3, Concurrency: 4, Timeout: 5}
|
||||
if got := NewEngine(custom).Limits(); got != custom {
|
||||
t.Fatalf("explicit limits = %+v, want untouched %+v", got, custom)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineNilContextIsSafe 断言 ctx 为 nil 不会 panic(按 Background 处理)。
|
||||
func TestEngineNilContextIsSafe(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(newStubCodec()))
|
||||
if _, err := eng.Transcode(nil, Request{ //nolint:staticcheck // 故意传 nil ctx
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
}); err != nil {
|
||||
t.Fatalf("nil ctx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngineErrorsAreWrapped 断言对外错误都能被 errors.Is 命中哨兵,且带上下文
|
||||
// (不是裸 err):文案形状是 `transcode: <源> -> <目标>: <原因>`。
|
||||
func TestEngineErrorsAreWrapped(t *testing.T) {
|
||||
stub := newStubCodec()
|
||||
stub.err = fmt.Errorf("stub: %w", ErrDecodeFailed)
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(stub))
|
||||
_, err := eng.Transcode(context.Background(), Request{
|
||||
Data: amrStubBytes(0x3c), Source: FormatAMR, Target: FormatWAV,
|
||||
})
|
||||
if !errors.Is(err, ErrDecodeFailed) {
|
||||
t.Fatalf("err = %v, want ErrDecodeFailed", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "stub") {
|
||||
t.Fatalf("wrapped error lost its cause: %v", err)
|
||||
}
|
||||
const wantPrefix = "transcode: amr -> wav: "
|
||||
if !strings.HasPrefix(err.Error(), wantPrefix) {
|
||||
t.Fatalf("err = %q, want the %q prefix(带路由上下文)", err.Error(), wantPrefix)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Package transcode 是 design/25「转码能力」的本地能力包实现:一条
|
||||
// 「字节 + 源格式 + 目标格式 → 字节 + 目标格式 + 元信息」的进程内、同步、
|
||||
// 纯函数式转换(不触网、不落盘、不读数据库)。它被设计为可被其它模块、
|
||||
// 编译期系统插件与工具面(agent / MCP)复用,落地方式见 design/25 §1.3.2
|
||||
// 的「两层注册」。
|
||||
//
|
||||
// 本包的所有失败一律是 errors.go 里的具名哨兵(或包裹它),绝不返回部分
|
||||
// 结果假装成功(design/25 §2.4 / §5.4)。
|
||||
package transcode
|
||||
|
||||
import "errors"
|
||||
|
||||
// design/25 §2.4 的八个哨兵。调用方用 errors.Is 判定;一律 %w 包裹,
|
||||
// 绝不静默吞掉。错误文案是哨兵 + 可读短句:上游/解码器的错误原文只进日志,
|
||||
// 不进用户可见正文(与 channelbridge 的「原因短语不含上游回显」同一条纪律)。
|
||||
var (
|
||||
// ErrUnsupportedFormat 源或目标格式未知 / 魔数嗅不出 / 注册表里没有这条路。
|
||||
// 调用方应诚实提示;按内容发生的失败不熔断。
|
||||
ErrUnsupportedFormat = errors.New("transcode: unsupported format")
|
||||
// ErrInputTooLarge 输入字节超过 MaxInputBytes,或可确证的时长超过
|
||||
// MaxDuration。在解码前拒绝(一行都不解)。
|
||||
ErrInputTooLarge = errors.New("transcode: input too large")
|
||||
// ErrOutputTooLarge 解码产物超过 MaxOutputBytes。在解码前即可确证时先拒绝,
|
||||
// 否则边走边计数、命中即停。
|
||||
ErrOutputTooLarge = errors.New("transcode: output too large")
|
||||
// ErrDecodeFailed 字节损坏 / 头非法 / 帧截断。与「格式不支持」分开:
|
||||
// 一个是「不会」,一个是「坏了」。
|
||||
ErrDecodeFailed = errors.New("transcode: decode failed")
|
||||
// ErrEncodeFailed 目标侧构造失败(如 WAV 头写不出)。
|
||||
ErrEncodeFailed = errors.New("transcode: encode failed")
|
||||
// ErrTimeout ctx 超时被检出。记 WARN、诚实提示、不重试(同步路径由调用方决定)。
|
||||
ErrTimeout = errors.New("transcode: timeout")
|
||||
// ErrBusy 并发槽已满,快速失败、不排队(绝不入无界队列)。
|
||||
ErrBusy = errors.New("transcode: busy")
|
||||
// ErrNoBackend 选了外部后端但主机上找不到 / 不可用。本期默认路径是进程内
|
||||
// 纯 Go,故暂无触发点;哨兵先行登记,供 design/25 §4.3 的可选后端复用。
|
||||
ErrNoBackend = errors.New("transcode: no backend")
|
||||
)
|
||||
|
||||
// 以下两个不是 design/25 §2.4 的八个转码哨兵:它们描述的是「注册表里放进了
|
||||
// 不合法的东西」和「工具面的入参不合法」,属包内自有的构造期/入参错误。
|
||||
var (
|
||||
// ErrDuplicate 同一个「源→目标」被注册了两次(与 registry.Register 的
|
||||
// 「重名报错」纪律同源)。
|
||||
ErrDuplicate = errors.New("transcode: codec already registered")
|
||||
// ErrInvalidCodec 注册项不合法:nil、源或目标格式为空、或源等于目标。
|
||||
ErrInvalidCodec = errors.New("transcode: invalid codec")
|
||||
// ErrBadArgs 工具面入参不合法(非对象、缺字段、类型不对)。
|
||||
ErrBadArgs = errors.New("transcode: bad arguments")
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSentinelInventoryMatchesDesign 把 design/25 §2.4 的**八个**哨兵钉在测试里:
|
||||
// 少一个、多一个、或者文案离开 `transcode: ` 前缀都会红。它是「清单型断言」——
|
||||
// 与只测边界逻辑互补(design/25 §6.3 的同一手法)。
|
||||
func TestSentinelInventoryMatchesDesign(t *testing.T) {
|
||||
designSentinels := []struct {
|
||||
name string
|
||||
err error
|
||||
}{
|
||||
{"ErrUnsupportedFormat", ErrUnsupportedFormat},
|
||||
{"ErrInputTooLarge", ErrInputTooLarge},
|
||||
{"ErrOutputTooLarge", ErrOutputTooLarge},
|
||||
{"ErrDecodeFailed", ErrDecodeFailed},
|
||||
{"ErrEncodeFailed", ErrEncodeFailed},
|
||||
{"ErrTimeout", ErrTimeout},
|
||||
{"ErrBusy", ErrBusy},
|
||||
{"ErrNoBackend", ErrNoBackend},
|
||||
}
|
||||
if len(designSentinels) != 8 {
|
||||
t.Fatalf("design/25 §2.4 是八个哨兵,这里列了 %d 个", len(designSentinels))
|
||||
}
|
||||
const prefix = "transcode: "
|
||||
for _, s := range designSentinels {
|
||||
if s.err == nil {
|
||||
t.Fatalf("%s is nil", s.name)
|
||||
}
|
||||
if !strings.HasPrefix(s.err.Error(), prefix) {
|
||||
t.Errorf("%s = %q, want the %q prefix", s.name, s.err.Error(), prefix)
|
||||
}
|
||||
if !isSentinelError(s.err) {
|
||||
t.Errorf("%s is missing from knownErrors", s.name)
|
||||
}
|
||||
}
|
||||
// 两两不同(防止复制粘贴出的同一个值被当成两个哨兵)。
|
||||
for i := range designSentinels {
|
||||
for j := i + 1; j < len(designSentinels); j++ {
|
||||
if errors.Is(designSentinels[i].err, designSentinels[j].err) {
|
||||
t.Errorf("%s and %s are the same error", designSentinels[i].name, designSentinels[j].name)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 包内自有的构造期/入参错误也必须可见(它们不是 §2.4 的八个)。
|
||||
for _, s := range []error{ErrDuplicate, ErrInvalidCodec, ErrBadArgs} {
|
||||
if !isSentinelError(s) {
|
||||
t.Errorf("%q is missing from knownErrors", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTimeoutErrorKeepsBothCauses 断言超时错误同时能被判为 ErrTimeout、也保留
|
||||
// 原始 ctx 原因(%w 链):调用方既能走哨兵分支,也能看到「为什么」。
|
||||
func TestTimeoutErrorKeepsBothCauses(t *testing.T) {
|
||||
err := timeoutError(errTimeoutCause)
|
||||
if !errors.Is(err, ErrTimeout) || !errors.Is(err, errTimeoutCause) {
|
||||
t.Fatalf("err = %v, want both ErrTimeout and the cause", err)
|
||||
}
|
||||
}
|
||||
|
||||
// errTimeoutCause 是超时链里的原始原因桩。
|
||||
var errTimeoutCause = errors.New("stub deadline")
|
||||
@@ -0,0 +1,63 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format 是小写、无点的音频格式标识("amr" / "wav" / …)。
|
||||
//
|
||||
// 口径与 mediafetch.AudioFormatOf(internal/channel/mediafetch/inline_voice.go:62)
|
||||
// 归一出的格式名、以及 voice.Audio{Format}(internal/runtime/multimodal/voice)
|
||||
// 的格式名**同一份**:全仓的「字符串 ↔ Format」转换只有本文件的 FormatOf /
|
||||
// FormatName 两处,调用方不得各写一份映射(design/25 §6.2)。
|
||||
type Format string
|
||||
|
||||
const (
|
||||
// FormatAMR 是 AMR-NB(窄带),魔数 `#!AMR\n`。
|
||||
FormatAMR Format = "amr"
|
||||
// FormatAMRWB 是 AMR-WB(宽带),魔数 `#!AMR-WB\n`。**本期不实现**
|
||||
// (design/25 §3.3 后置):登记它是为了让嗅探与报错诚实——「认得出但没这条路」
|
||||
// 比「认不出」更能说清问题。
|
||||
FormatAMRWB Format = "amr-wb"
|
||||
// FormatWAV 是 RIFF/WAVE 容器(本期只发 PCM16 一种载荷)。
|
||||
FormatWAV Format = "wav"
|
||||
)
|
||||
|
||||
// 魔数与容器标识。AMR 文件的头来自 RFC 4867 §5(存储格式);RIFF/WAVE 的四块
|
||||
// 结构见 design/25 §0.5 登记的 WAV/RIFF 规范。
|
||||
const (
|
||||
amrNBMagic = "#!AMR\n"
|
||||
amrWBMagic = "#!AMR-WB\n"
|
||||
riffMagic = "RIFF"
|
||||
waveMagic = "WAVE"
|
||||
// riffChunkSizeOffset 是 RIFF 头里 "WAVE" 标识的起始偏移("RIFF" + 4 字节长度)。
|
||||
riffChunkSizeOffset = 8
|
||||
)
|
||||
|
||||
// FormatOf 把外部格式名归一成 Format:去空白、转小写、去掉开头的点
|
||||
// (`.amr` → `amr`)。它不判断格式是否支持——「认不认得」由 Sniff /
|
||||
// FormatOf 决定,「支不支持」由 Registry.Lookup 决定,两件事分开。
|
||||
func FormatOf(name string) Format {
|
||||
return Format(strings.ToLower(strings.TrimPrefix(strings.TrimSpace(name), ".")))
|
||||
}
|
||||
|
||||
// FormatName 返回 Format 的规范字符串形式(= 归一化后的格式名)。
|
||||
func FormatName(f Format) string { return string(f) }
|
||||
|
||||
// Sniff 按魔数嗅探 data 的音频格式。嗅不出即返回 false(deny-by-default:
|
||||
// 类型不明的字节绝不进解码器)。
|
||||
func Sniff(data []byte) (Format, bool) {
|
||||
switch {
|
||||
case bytes.HasPrefix(data, []byte(amrWBMagic)):
|
||||
return FormatAMRWB, true
|
||||
case bytes.HasPrefix(data, []byte(amrNBMagic)):
|
||||
return FormatAMR, true
|
||||
case len(data) >= riffChunkSizeOffset+len(waveMagic) &&
|
||||
bytes.HasPrefix(data, []byte(riffMagic)) &&
|
||||
bytes.HasPrefix(data[riffChunkSizeOffset:], []byte(waveMagic)):
|
||||
return FormatWAV, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package transcode
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestFormatOfNormalizes 断言「字符串 → Format」只有一处转换,且归一化口径稳定:
|
||||
// 去空白、转小写、去前导点(design/25 §6.2:与 mediafetch.AudioFormatOf 同源)。
|
||||
func TestFormatOfNormalizes(t *testing.T) {
|
||||
cases := map[string]Format{
|
||||
"amr": FormatAMR,
|
||||
".amr": FormatAMR,
|
||||
"AMR": FormatAMR,
|
||||
" .AmR": FormatAMR,
|
||||
"wav": FormatWAV,
|
||||
".WAV": FormatWAV,
|
||||
"": Format(""),
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := FormatOf(in); got != want {
|
||||
t.Errorf("FormatOf(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFormatName 断言 FormatName 是 FormatOf 的逆(对已归一的格式名)。
|
||||
func TestFormatName(t *testing.T) {
|
||||
for _, f := range []Format{FormatAMR, FormatAMRWB, FormatWAV} {
|
||||
if got := FormatOf(FormatName(f)); got != f {
|
||||
t.Errorf("FormatOf(FormatName(%q)) = %q, want %q", f, got, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSniff 断言魔数嗅探:认得出(AMR-NB / AMR-WB / WAV)、嗅不出即 false
|
||||
// (deny-by-default)。
|
||||
func TestSniff(t *testing.T) {
|
||||
riffWAV := append([]byte("RIFF\x24\x00\x00\x00WAVE"), make([]byte, 8)...)
|
||||
cases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
want Format
|
||||
ok bool
|
||||
}{
|
||||
{"amr-nb magic", []byte(amrNBMagic), FormatAMR, true},
|
||||
{"amr-nb stream", amrStubBytes(0x3c, 1, 2, 3), FormatAMR, true},
|
||||
{"amr-wb magic", []byte(amrWBMagic), FormatAMRWB, true},
|
||||
{"wav", riffWAV, FormatWAV, true},
|
||||
{"empty", nil, "", false},
|
||||
{"short", []byte("#!AM"), "", false},
|
||||
{"text", []byte("hello world"), "", false},
|
||||
{"riff without wave", []byte("RIFF\x24\x00\x00\x00AVI "), "", false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, ok := Sniff(tc.data)
|
||||
if ok != tc.ok || got != tc.want {
|
||||
t.Fatalf("Sniff = (%q, %v), want (%q, %v)", got, ok, tc.want, tc.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAMRWBMagicIsRecognizedButUnsupported 断言「认得出但没这条路」:
|
||||
// AMR-WB 的魔数能被嗅出(报错因此能说清问题),但注册表里没有它 →
|
||||
// ErrUnsupportedFormat(design/25 §3.3 后置)。
|
||||
func TestAMRWBMagicIsRecognizedButUnsupported(t *testing.T) {
|
||||
eng := NewEngine(DefaultLimits(), WithCodecs(DefaultCodecs()...))
|
||||
if got, ok := Sniff([]byte(amrWBMagic)); !ok || got != FormatAMRWB {
|
||||
t.Fatalf("Sniff(amr-wb) = (%q, %v)", got, ok)
|
||||
}
|
||||
if eng.Supports(FormatAMRWB, FormatWAV) {
|
||||
t.Fatalf("amr-wb -> wav must stay unsupported this round")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package transcode
|
||||
|
||||
import "time"
|
||||
|
||||
// 三层上限 + 并发 + 超时的默认取值(design/25 §2.5,取值照设计,不得自行改)。
|
||||
// 全部具名:本包不存在裸数字。
|
||||
const (
|
||||
// DefaultMaxInputBytes 是单次转码的输入字节上限(5 MiB)。取值与
|
||||
// mediafetch.MaxVoiceBytes 相同但**不共用常量**:一个是「入站内联上限」,
|
||||
// 一个是「转码输入上限」,共用会让「改一个静默改另一个」(design/25 §2.5)。
|
||||
DefaultMaxInputBytes = 5 << 20
|
||||
// DefaultMaxOutputBytes 是解码产物的字节上限(64 MiB)。AMR-NB 是唯一会放大的
|
||||
// 那条路:12.2 kbps ≈ 1525 B/s → 8 kHz 单声道 PCM16 = 16000 B/s ≈ 10.5×,
|
||||
// 加帧头最坏约 12.8×,5 MiB 输入最坏 ⇒ ≈64 MiB。没有这条闸就是
|
||||
// 「上传 5 MB、内存里长到 64 MB」。
|
||||
DefaultMaxOutputBytes = 64 << 20
|
||||
// DefaultMaxDuration 是可确证的时长上限(10 分钟):「正常语音」与「把整段录音
|
||||
// 伪装成语音」的分界。可从 AMR 帧数确证(一帧 20ms),绝不估算。
|
||||
DefaultMaxDuration = 10 * time.Minute
|
||||
// DefaultConcurrency 是全局并发槽数(信号量容量)。
|
||||
DefaultConcurrency = 2
|
||||
// DefaultTimeout 是单次调用的 ctx 超时上限。
|
||||
DefaultTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// Limits 是一次转码的资源约束(design/25 §2.5)。零值字段由 Engine 用上面的
|
||||
// 具名默认补足,因此 `Limits{}` 是安全的「全默认」。
|
||||
type Limits struct {
|
||||
// MaxInputBytes 输入字节上限;命中即 ErrInputTooLarge(解码前拒绝)。
|
||||
MaxInputBytes int
|
||||
// MaxOutputBytes 产物字节上限;命中即 ErrOutputTooLarge。
|
||||
MaxOutputBytes int
|
||||
// MaxDuration 可确证的时长上限;命中即 ErrInputTooLarge。
|
||||
MaxDuration time.Duration
|
||||
// Concurrency 全局并发槽数;槽满即 ErrBusy(快速失败,不排队)。
|
||||
Concurrency int
|
||||
// Timeout 单次调用的超时;命中即 ErrTimeout。
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultLimits 返回一组全默认的限额(design/25 §2.5 的取值)。
|
||||
func DefaultLimits() Limits {
|
||||
return Limits{
|
||||
MaxInputBytes: DefaultMaxInputBytes,
|
||||
MaxOutputBytes: DefaultMaxOutputBytes,
|
||||
MaxDuration: DefaultMaxDuration,
|
||||
Concurrency: DefaultConcurrency,
|
||||
Timeout: DefaultTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// withDefaults 用默认值补足零值字段。负数与零一并按「未配置」处理:限额的
|
||||
// 零值若照字面生效会变成「一字节都不许」,那不是任何调用方想要的行为。
|
||||
func (l Limits) withDefaults() Limits {
|
||||
if l.MaxInputBytes <= 0 {
|
||||
l.MaxInputBytes = DefaultMaxInputBytes
|
||||
}
|
||||
if l.MaxOutputBytes <= 0 {
|
||||
l.MaxOutputBytes = DefaultMaxOutputBytes
|
||||
}
|
||||
if l.MaxDuration <= 0 {
|
||||
l.MaxDuration = DefaultMaxDuration
|
||||
}
|
||||
if l.Concurrency <= 0 {
|
||||
l.Concurrency = DefaultConcurrency
|
||||
}
|
||||
if l.Timeout <= 0 {
|
||||
l.Timeout = DefaultTimeout
|
||||
}
|
||||
return l
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestDefaultLimitsPinnedToDesign 把 design/25 §2.5 的**取值**钉在测试里:
|
||||
// 谁改了默认限额(比如把输入上限从 5 MiB 调大),这条断言就会红,逼他回设计对账。
|
||||
// 设计原文:5 MiB 进 / 64 MiB 出 / 10 分钟 / 并发 2 / 超时 10s。
|
||||
func TestDefaultLimitsPinnedToDesign(t *testing.T) {
|
||||
got := DefaultLimits()
|
||||
if got.MaxInputBytes != 5<<20 {
|
||||
t.Errorf("MaxInputBytes = %d, want %d (5 MiB)", got.MaxInputBytes, 5<<20)
|
||||
}
|
||||
if got.MaxOutputBytes != 64<<20 {
|
||||
t.Errorf("MaxOutputBytes = %d, want %d (64 MiB)", got.MaxOutputBytes, 64<<20)
|
||||
}
|
||||
if got.MaxDuration != 10*time.Minute {
|
||||
t.Errorf("MaxDuration = %s, want %s", got.MaxDuration, 10*time.Minute)
|
||||
}
|
||||
if got.Concurrency != 2 {
|
||||
t.Errorf("Concurrency = %d, want 2", got.Concurrency)
|
||||
}
|
||||
if got.Timeout != 10*time.Second {
|
||||
t.Errorf("Timeout = %s, want %s", got.Timeout, 10*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLimitsWithDefaults 断言零值与负值都被补成默认(限额的零值若照字面生效
|
||||
// 会变成「一字节都不许」,那不是任何调用方想要的行为)。
|
||||
func TestLimitsWithDefaults(t *testing.T) {
|
||||
zero := Limits{}.withDefaults()
|
||||
if zero != DefaultLimits() {
|
||||
t.Fatalf("zero limits = %+v, want %+v", zero, DefaultLimits())
|
||||
}
|
||||
negative := Limits{
|
||||
MaxInputBytes: -1, MaxOutputBytes: -1, MaxDuration: -1, Concurrency: -1, Timeout: -1,
|
||||
}.withDefaults()
|
||||
if negative != DefaultLimits() {
|
||||
t.Fatalf("negative limits = %+v, want %+v", negative, DefaultLimits())
|
||||
}
|
||||
}
|
||||
|
||||
// TestWAVSizeLimitIsBelowOutputLimit 断言 64 MiB 的产物上限落在 44 字节头能如实
|
||||
// 声明的范围内(否则会出现「闸放过了、头写不出」的死角)。
|
||||
func TestWAVSizeLimitIsBelowOutputLimit(t *testing.T) {
|
||||
if uint64(DefaultMaxOutputBytes) > uint64(wavMaxDataBytes) {
|
||||
t.Fatalf("MaxOutputBytes %d exceeds the RIFF limit %d", DefaultMaxOutputBytes, wavMaxDataBytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Registry 承载「哪些 源→目标 可用」(design/25 §1.3.2 第一层)。它是**包内**的
|
||||
// 编解码器注册表,与全局能力注册表(internal/capability/registry)是两件事:
|
||||
// 后者表达「agent 可选用的能力 + 门控策略」,前者表达「本能力认识哪几条路」。
|
||||
//
|
||||
// 新增一种格式 = 往这里多一个注册项(见 codecs.go 的 DefaultCodecs);
|
||||
// 并发安全。
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
codecs map[route]Codec
|
||||
}
|
||||
|
||||
// route 是「源→目标」这一对,作注册表的键。用结构体而非字符串拼接:拼接会让
|
||||
// 格式名里出现分隔符时产生歧义(`a->b` 与 `a-` + `>b`)。
|
||||
type route struct {
|
||||
from Format
|
||||
to Format
|
||||
}
|
||||
|
||||
// NewRegistry 返回一个空且并发安全的编解码器注册表。
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{codecs: make(map[route]Codec)}
|
||||
}
|
||||
|
||||
// Register 注册一条转换路。nil、源/目标为空、源等于目标都返回 ErrInvalidCodec;
|
||||
// 同一对重复注册返回 ErrDuplicate。
|
||||
func (r *Registry) Register(c Codec) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("transcode: register codec: %w: nil codec", ErrInvalidCodec)
|
||||
}
|
||||
from, to := c.From(), c.To()
|
||||
if from == "" || to == "" {
|
||||
return fmt.Errorf("transcode: register codec %q -> %q: %w: empty format", from, to, ErrInvalidCodec)
|
||||
}
|
||||
if from == to {
|
||||
return fmt.Errorf("transcode: register codec %q -> %q: %w: identity route", from, to, ErrInvalidCodec)
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
key := route{from: from, to: to}
|
||||
if _, exists := r.codecs[key]; exists {
|
||||
return fmt.Errorf("transcode: register codec %q -> %q: %w", from, to, ErrDuplicate)
|
||||
}
|
||||
r.codecs[key] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup 返回 from→to 这条路上的编解码器。没有这条路时 ok 为 false。
|
||||
func (r *Registry) Lookup(from, to Format) (Codec, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
c, ok := r.codecs[route{from: from, to: to}]
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// Formats 返回当前注册涉及的全部格式(源与目标去重后的并集,已排序)。
|
||||
// 它是「发现」面:调用方据此知道本能力认识哪些格式名。
|
||||
func (r *Registry) Formats() []Format {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
seen := make(map[Format]struct{}, len(r.codecs)*2)
|
||||
for key := range r.codecs {
|
||||
seen[key.from] = struct{}{}
|
||||
seen[key.to] = struct{}{}
|
||||
}
|
||||
out := make([]Format, 0, len(seen))
|
||||
for f := range seen {
|
||||
out = append(out, f)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
|
||||
return out
|
||||
}
|
||||
|
||||
// Len 返回已注册的「源→目标」路数。供启动期自检与测试断言使用。
|
||||
func (r *Registry) Len() int {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.codecs)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRegistryRegisterAndLookup 断言注册 / 查找 / 发现三件事。
|
||||
func TestRegistryRegisterAndLookup(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
codec := newStubCodec()
|
||||
if err := reg.Register(codec); err != nil {
|
||||
t.Fatalf("register: %v", err)
|
||||
}
|
||||
got, ok := reg.Lookup(FormatAMR, FormatWAV)
|
||||
if !ok || got != Codec(codec) {
|
||||
t.Fatalf("lookup = (%v, %v), want the registered codec", got, ok)
|
||||
}
|
||||
if _, ok := reg.Lookup(FormatWAV, FormatAMR); ok {
|
||||
t.Fatalf("reverse route must not exist")
|
||||
}
|
||||
if n := reg.Len(); n != 1 {
|
||||
t.Fatalf("Len = %d, want 1", n)
|
||||
}
|
||||
if formats := reg.Formats(); len(formats) != 2 || formats[0] != FormatAMR || formats[1] != FormatWAV {
|
||||
t.Fatalf("Formats = %v, want [amr wav](去重且有序)", formats)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistryRejectsBadCodecs 断言不合法注册项被拒(nil / 空格式 / 自我转换),
|
||||
// 且 repeated 注册返回 ErrDuplicate。
|
||||
func TestRegistryRejectsBadCodecs(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
cases := []struct {
|
||||
name string
|
||||
codec Codec
|
||||
}{
|
||||
{"nil", nil},
|
||||
{"empty from", &stubCodec{from: "", to: FormatWAV}},
|
||||
{"empty to", &stubCodec{from: FormatAMR, to: ""}},
|
||||
{"identity", &stubCodec{from: FormatWAV, to: FormatWAV}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := reg.Register(tc.codec); !errors.Is(err, ErrInvalidCodec) {
|
||||
t.Fatalf("err = %v, want ErrInvalidCodec", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
if err := reg.Register(newStubCodec()); err != nil {
|
||||
t.Fatalf("register: %v", err)
|
||||
}
|
||||
if err := reg.Register(newStubCodec()); !errors.Is(err, ErrDuplicate) {
|
||||
t.Fatalf("duplicate err = %v, want ErrDuplicate", err)
|
||||
}
|
||||
if n := reg.Len(); n != 1 {
|
||||
t.Fatalf("Len = %d after a rejected duplicate, want 1", n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistryFormatsDedupe 断言多路共享格式时 Formats 去重。
|
||||
func TestRegistryFormatsDedupe(t *testing.T) {
|
||||
reg := NewRegistry()
|
||||
if err := reg.Register(&stubCodec{from: FormatAMR, to: FormatWAV}); err != nil {
|
||||
t.Fatalf("register: %v", err)
|
||||
}
|
||||
if err := reg.Register(&stubCodec{from: FormatWAV, to: FormatAMRWB}); err != nil {
|
||||
t.Fatalf("register: %v", err)
|
||||
}
|
||||
want := []Format{FormatAMR, FormatAMRWB, FormatWAV}
|
||||
got := reg.Formats()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("Formats = %v, want %v", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("Formats = %v, want %v(有序)", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,129 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
)
|
||||
|
||||
// 本文件是 design/25 §1.3.2 的**第二层注册**:把转码暴露成 agent 可选用的内置
|
||||
// 工具(KindTool),走**既有**的 sdk.Registrar → internal/capability/registry。
|
||||
// 与 vision.RegisterTools / io.Register 逐字同族,因此面板、策略、审计三面零新增
|
||||
// 代码即覆盖;**不新增 Kind**(validKind 闭集不动),策略默认零值 = owner-only。
|
||||
//
|
||||
// ⚠️ 本工具是 design/25 §8-T1 的「RegisterTool 壳」:T4 定义的形态是
|
||||
// `{name, to}`(服务端存储名进、新存储名出,薄壳:读存储 → 调 core → 写存储),
|
||||
// 它依赖 design/24 §10 的音频副本存储与 `GET /api/v1/media/{name}`——那两件**尚未
|
||||
// 落地**。因此本壳用「字节进、字节出」(base64 承载)先把**注册链路**打通并可测,
|
||||
// T4 落地后再换入参形态;这条差异已登记在交接报告里。
|
||||
//
|
||||
// 红线提醒:转码产物是**字节**,不进上下文。因此本壳工具另设一个远小于
|
||||
// MaxOutputBytes 的结果上限(MaxToolResultBytes),让「工具结果」不可能把上下文
|
||||
// 灌爆——要转大文件请走进程内 DI 路径,不要走模型工具面。
|
||||
const (
|
||||
// ToolName 是转码工具在全局能力注册表里的名字。
|
||||
ToolName = "transcode"
|
||||
// MaxToolResultBytes 是本壳工具允许回给模型的产物体积上限(解码后字节数)。
|
||||
// 它**不是** design/25 §2.5 的 MaxOutputBytes(那是能力上限,64 MiB);这里更小
|
||||
// 是因为工具结果会进模型上下文。
|
||||
MaxToolResultBytes = 256 << 10
|
||||
)
|
||||
|
||||
// 入参字段名。抽成常量以免同一个键在三处各写一份字符串。
|
||||
const (
|
||||
toolArgData = "data_base64"
|
||||
toolArgSource = "source"
|
||||
toolArgTarget = "target"
|
||||
)
|
||||
|
||||
// Tool 把 Transcoder 包装成 sdk.Tool,使 agent 能直接调用转码(design/25 §9-2
|
||||
// 默认「暴露,owner-only」)。它可安全并发使用。
|
||||
type Tool struct {
|
||||
tr Transcoder
|
||||
}
|
||||
|
||||
// Compile-time assertion that *Tool satisfies the SDK Tool interface.
|
||||
var _ sdk.Tool = (*Tool)(nil)
|
||||
|
||||
// NewTool 用给定转码器构造工具。
|
||||
func NewTool(tr Transcoder) *Tool { return &Tool{tr: tr} }
|
||||
|
||||
// Name 返回工具标识。
|
||||
func (t *Tool) Name() string { return ToolName }
|
||||
|
||||
// Description 说明该工具做什么。它让模型知道「转码是本地能力、同步、不看身份」。
|
||||
func (t *Tool) Description() string {
|
||||
return "Convert audio bytes between formats locally (e.g. amr -> wav). " +
|
||||
"Input and output are base64-encoded bytes; the result carries format, " +
|
||||
"sample rate, channels, duration and frame count."
|
||||
}
|
||||
|
||||
// Parameters 返回输入 JSON Schema:data_base64(必填)+ target(必填)+ source(可选)。
|
||||
func (t *Tool) Parameters() (map[string]any, error) {
|
||||
return map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
toolArgData: map[string]any{
|
||||
"type": "string",
|
||||
"description": "The source audio bytes, base64-encoded (standard alphabet).",
|
||||
},
|
||||
toolArgTarget: map[string]any{
|
||||
"type": "string",
|
||||
"description": "Target format name, lowercase and without a dot, e.g. \"wav\".",
|
||||
},
|
||||
toolArgSource: map[string]any{
|
||||
"type": "string",
|
||||
"description": "Optional source format name; omitted means sniff by magic bytes.",
|
||||
},
|
||||
},
|
||||
"required": []any{toolArgData, toolArgTarget},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Execute 解码入参、调用转码器,并把结果包成模型可读的对象。
|
||||
// 失败一律返回 errors.go 的哨兵(经 %w 保留),绝不假装成功。
|
||||
func (t *Tool) Execute(ctx context.Context, args any) (any, error) {
|
||||
if t.tr == nil {
|
||||
return nil, fmt.Errorf("transcode: tool %q: %w: nil transcoder", ToolName, ErrUnsupportedFormat)
|
||||
}
|
||||
req, err := toolRequest(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := t.tr.Transcode(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transcode: tool %q: %w", ToolName, err)
|
||||
}
|
||||
if len(res.Data) > MaxToolResultBytes {
|
||||
return nil, fmt.Errorf("transcode: tool %q: %w: result %d bytes > %d (tool face is bounded; use the in-process API for larger audio)",
|
||||
ToolName, ErrOutputTooLarge, len(res.Data), MaxToolResultBytes)
|
||||
}
|
||||
return map[string]any{
|
||||
"format": FormatName(res.Format),
|
||||
"source": FormatName(res.Source),
|
||||
"data_base64": base64.StdEncoding.EncodeToString(res.Data),
|
||||
"sample_rate": res.SampleRate,
|
||||
"channels": res.Channels,
|
||||
"duration_ms": res.DurationMs,
|
||||
"frames": res.Frames,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RegisterTool 经 SDK 辅助函数把转码工具注册到 r(design/25 §1.3.2 第二层)。
|
||||
// 与 vision.RegisterTools 同族:tr 为 nil 时不注册(返回 nil,不制造一个必然
|
||||
// 失败的能力条目)。
|
||||
func RegisterTool(r sdk.Registrar, tr Transcoder) error {
|
||||
if r == nil {
|
||||
return sdk.ErrNilRegistrar
|
||||
}
|
||||
if tr == nil {
|
||||
return nil
|
||||
}
|
||||
tool := NewTool(tr)
|
||||
if err := sdk.RegisterTool(r, tool); err != nil {
|
||||
return fmt.Errorf("transcode: register tool %q: %w", tool.Name(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// 本文件解析工具面入参(sdk.Tool.Execute 的 args)。它与 SDK 的「args 是一个
|
||||
// JSON 对象」约定对齐:非对象、缺字段、类型不对一律 ErrBadArgs,绝不猜测。
|
||||
// 参数名在 tool.go 的常量块里统一定义。
|
||||
|
||||
// toolRequest 把工具入参解成一次转码请求。
|
||||
//
|
||||
// source 可省略(留空 ⇒ 由 engine 按魔数嗅探);data_base64 与 target 必填。
|
||||
// base64 用标准字母表(与 Execute 输出同一份编码,往返无损)。
|
||||
func toolRequest(args any) (Request, error) {
|
||||
m, ok := args.(map[string]any)
|
||||
if !ok {
|
||||
return Request{}, fmt.Errorf("transcode: %w: args must be an object", ErrBadArgs)
|
||||
}
|
||||
data, err := toolDataArg(m)
|
||||
if err != nil {
|
||||
return Request{}, err
|
||||
}
|
||||
target := FormatOf(toolStringArg(m, toolArgTarget))
|
||||
if target == "" {
|
||||
return Request{}, fmt.Errorf("transcode: %w: missing %s", ErrBadArgs, toolArgTarget)
|
||||
}
|
||||
return Request{
|
||||
Data: data,
|
||||
Source: FormatOf(toolStringArg(m, toolArgSource)),
|
||||
Target: target,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toolStringArg 读一个字符串字段;字段缺失或不是字符串都算空串。
|
||||
func toolStringArg(m map[string]any, key string) string {
|
||||
s, _ := m[key].(string)
|
||||
return s
|
||||
}
|
||||
|
||||
// toolDataArg 解出源字节:Base64 标准字母表,空串/非法 Base64/空字节都拒。
|
||||
func toolDataArg(m map[string]any) ([]byte, error) {
|
||||
raw := toolStringArg(m, toolArgData)
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("transcode: %w: missing %s", ErrBadArgs, toolArgData)
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transcode: %w: %s is not valid base64: %v", ErrBadArgs, toolArgData, err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("transcode: %w: %s decodes to zero bytes", ErrBadArgs, toolArgData)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
)
|
||||
|
||||
// stubTranscoder 是工具面测试用的最小 Transcoder(只为验证包装层,不碰真实音频)。
|
||||
type stubTranscoder struct {
|
||||
supports bool
|
||||
res Result
|
||||
err error
|
||||
// lastReq 记录最后一次透传的请求(验证 source/target 归一化)。
|
||||
lastReq Request
|
||||
}
|
||||
|
||||
func (s *stubTranscoder) Transcode(_ context.Context, req Request) (Result, error) {
|
||||
s.lastReq = req
|
||||
if s.err != nil {
|
||||
return Result{}, s.err
|
||||
}
|
||||
return s.res, nil
|
||||
}
|
||||
|
||||
func (s *stubTranscoder) Supports(_, _ Format) bool { return s.supports }
|
||||
func (s *stubTranscoder) Formats() []Format { return []Format{FormatAMR, FormatWAV} }
|
||||
|
||||
// TestToolParameters 断言入参 schema 的形状(对象、必填 data_base64/target)。
|
||||
func TestToolParameters(t *testing.T) {
|
||||
tool := NewTool(&stubTranscoder{})
|
||||
if tool.Name() != ToolName {
|
||||
t.Fatalf("Name = %q, want %q", tool.Name(), ToolName)
|
||||
}
|
||||
if tool.Description() == "" {
|
||||
t.Fatalf("Description must not be empty")
|
||||
}
|
||||
schema, err := tool.Parameters()
|
||||
if err != nil {
|
||||
t.Fatalf("Parameters: %v", err)
|
||||
}
|
||||
if schema["type"] != "object" {
|
||||
t.Fatalf("schema type = %v, want object", schema["type"])
|
||||
}
|
||||
required, ok := schema["required"].([]any)
|
||||
if !ok || len(required) != 2 {
|
||||
t.Fatalf("required = %v, want [data_base64 target]", schema["required"])
|
||||
}
|
||||
props, ok := schema["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("properties missing")
|
||||
}
|
||||
for _, key := range []string{toolArgData, toolArgTarget, toolArgSource} {
|
||||
if _, ok := props[key]; !ok {
|
||||
t.Fatalf("properties missing %q", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolExecuteDecodesArgs 断言入参归一化与结果字段。
|
||||
func TestToolExecuteDecodesArgs(t *testing.T) {
|
||||
stub := &stubTranscoder{supports: true, res: Result{
|
||||
Data: []byte("wav-bytes"), Format: FormatWAV, Source: FormatAMR,
|
||||
SampleRate: amrSampleRate, Channels: amrChannels, DurationMs: 40, Frames: 2,
|
||||
}}
|
||||
out, err := NewTool(stub).Execute(context.Background(), map[string]any{
|
||||
toolArgData: base64.StdEncoding.EncodeToString([]byte("amr-bytes")),
|
||||
toolArgTarget: ".WAV",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
if string(stub.lastReq.Data) != "amr-bytes" || stub.lastReq.Target != FormatWAV {
|
||||
t.Fatalf("request = %+v, want the decoded bytes and normalized target", stub.lastReq)
|
||||
}
|
||||
if stub.lastReq.Source != "" {
|
||||
t.Fatalf("source = %q, want empty(留空即嗅探)", stub.lastReq.Source)
|
||||
}
|
||||
res, ok := out.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("result type = %T, want map[string]any", out)
|
||||
}
|
||||
encoded, _ := res["data_base64"].(string)
|
||||
if decoded, err := base64.StdEncoding.DecodeString(encoded); err != nil || string(decoded) != "wav-bytes" {
|
||||
t.Fatalf("data_base64 round trip failed: %q (%v)", encoded, err)
|
||||
}
|
||||
if res["format"] != "wav" || res["source"] != "amr" || res["frames"] != 2 || res["duration_ms"] != int64(40) {
|
||||
t.Fatalf("result fields = %v", res)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolRejectsBadArgs 断言入参不合法一律 ErrBadArgs,绝不猜测。
|
||||
func TestToolRejectsBadArgs(t *testing.T) {
|
||||
tool := NewTool(&stubTranscoder{supports: true})
|
||||
cases := []struct {
|
||||
name string
|
||||
args any
|
||||
}{
|
||||
{"not an object", "data"},
|
||||
{"nil args", nil},
|
||||
{"missing data", map[string]any{toolArgTarget: "wav"}},
|
||||
{"missing target", map[string]any{toolArgData: base64.StdEncoding.EncodeToString([]byte("x"))}},
|
||||
{"bad base64", map[string]any{toolArgData: "!!!not base64!!!", toolArgTarget: "wav"}},
|
||||
{"empty base64 payload", map[string]any{toolArgData: "", toolArgTarget: "wav"}},
|
||||
{"wrong type", map[string]any{toolArgData: 42, toolArgTarget: "wav"}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if _, err := tool.Execute(context.Background(), tc.args); !errors.Is(err, ErrBadArgs) {
|
||||
t.Fatalf("err = %v, want ErrBadArgs", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolPropagatesSentinels 断言工具面不吞哨兵(%w 链保持)。
|
||||
func TestToolPropagatesSentinels(t *testing.T) {
|
||||
stub := &stubTranscoder{err: ErrUnsupportedFormat}
|
||||
_, err := NewTool(stub).Execute(context.Background(), map[string]any{
|
||||
toolArgData: base64.StdEncoding.EncodeToString([]byte("x")),
|
||||
toolArgTarget: "wav",
|
||||
})
|
||||
if !errors.Is(err, ErrUnsupportedFormat) {
|
||||
t.Fatalf("err = %v, want ErrUnsupportedFormat", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolResultCap 断言工具结果体量有上限(产物是字节,不进上下文的红线:
|
||||
// 要转大文件请走进程内 DI,不要走模型工具面)。
|
||||
func TestToolResultCap(t *testing.T) {
|
||||
big := make([]byte, MaxToolResultBytes+1)
|
||||
_, err := NewTool(&stubTranscoder{res: Result{Data: big}}).Execute(context.Background(), map[string]any{
|
||||
toolArgData: base64.StdEncoding.EncodeToString([]byte("x")),
|
||||
toolArgTarget: "wav",
|
||||
})
|
||||
if !errors.Is(err, ErrOutputTooLarge) {
|
||||
t.Fatalf("err = %v, want ErrOutputTooLarge", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolNilTranscoderFailsLoudly 断言没接转码器时不假装成功。
|
||||
func TestToolNilTranscoderFailsLoudly(t *testing.T) {
|
||||
_, err := NewTool(nil).Execute(context.Background(), map[string]any{
|
||||
toolArgData: base64.StdEncoding.EncodeToString([]byte("x")),
|
||||
toolArgTarget: "wav",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("nil transcoder must fail")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterToolIntoGlobalRegistry 是「两层注册」第二层的落点验收:走**既有**
|
||||
// 全局能力注册表,进去的是 KindTool、策略是零值(fail-closed = owner-only),
|
||||
// **不新增 Kind**(validKind 闭集不动)。
|
||||
func TestRegisterToolIntoGlobalRegistry(t *testing.T) {
|
||||
caps := registry.New()
|
||||
if err := RegisterTool(caps, NewEngine(DefaultLimits(), WithCodecs(DefaultCodecs()...))); err != nil {
|
||||
t.Fatalf("RegisterTool: %v", err)
|
||||
}
|
||||
cap, err := caps.Get(ToolName)
|
||||
if err != nil {
|
||||
t.Fatalf("Get(%q): %v", ToolName, err)
|
||||
}
|
||||
if cap.Kind != registry.KindTool {
|
||||
t.Fatalf("kind = %q, want %q(不为转码新增 Kind)", cap.Kind, registry.KindTool)
|
||||
}
|
||||
if cap.Policy.AllowInGroup || cap.Policy.AllowInStranger {
|
||||
t.Fatalf("policy = %+v, want zero value (owner-only, fail-closed)", cap.Policy)
|
||||
}
|
||||
if _, ok := cap.Value.(*Tool); !ok {
|
||||
t.Fatalf("value type = %T, want *Tool", cap.Value)
|
||||
}
|
||||
if len(caps.List(registry.KindTool)) != 1 {
|
||||
t.Fatalf("tool listing did not include the transcode tool")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterToolNilArgs 断言注册入口的 nil 纪律(与 vision.RegisterTools 同族)。
|
||||
func TestRegisterToolNilArgs(t *testing.T) {
|
||||
if err := RegisterTool(nil, &stubTranscoder{}); !errors.Is(err, sdk.ErrNilRegistrar) {
|
||||
t.Fatalf("nil registrar err = %v, want sdk.ErrNilRegistrar", err)
|
||||
}
|
||||
caps := registry.New()
|
||||
if err := RegisterTool(caps, nil); err != nil {
|
||||
t.Fatalf("nil transcoder must be a no-op, got %v", err)
|
||||
}
|
||||
if len(caps.List(registry.KindTool)) != 0 {
|
||||
t.Fatalf("nil transcoder registered a capability")
|
||||
}
|
||||
}
|
||||
|
||||
// TestStubTranscoderSatisfiesInterface 断言测试桩确实实现了接口(防止接口漂移
|
||||
// 时测试静默失去意义)。
|
||||
func TestStubTranscoderSatisfiesInterface(t *testing.T) {
|
||||
var _ Transcoder = (*stubTranscoder)(nil)
|
||||
var _ sdk.Tool = (*Tool)(nil)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// WAV/RIFF 的 44 字节规范头(design/25 §0.5 登记的 WAV/RIFF 规范)。
|
||||
// 四块:RIFF + WAVE + fmt + data。本期只发 PCM16 一种载荷,且**不引入音频库**
|
||||
// ——头是手写的,只用 encoding/binary。
|
||||
//
|
||||
// 之所以目标容器选 WAV 而不是 mp3/ogg:WAV 是「只要解码器、不要编码器」的一条路
|
||||
// (PCM + 44 字节头),而纯 Go 的 mp3 编码器生态远不如解码器成熟(design/25 §3.1)。
|
||||
const (
|
||||
// wavHeaderBytes 是规范头的字节数。
|
||||
wavHeaderBytes = 44
|
||||
// wavFormatPCM 是 fmt 块里的音频格式号:1 = PCM(线性量化)。
|
||||
wavFormatPCM = 1
|
||||
// wavBitsPerSample 是每样本位数:AMR-NB 解码出来就是 16 位定点。
|
||||
wavBitsPerSample = 16
|
||||
// wavFmtChunkBytes 是 fmt 子块的长度(PCM 的规范值 16)。
|
||||
wavFmtChunkBytes = 16
|
||||
// wavPCMBytesPerSample 是单个样本占的字节数。
|
||||
wavPCMBytesPerSample = wavBitsPerSample / 8
|
||||
|
||||
// riffChunkSizeBase 是 RIFF 长度字段里除 data 之外的部分:
|
||||
// chunkSize = riffChunkSizeBase + dataBytes。
|
||||
riffChunkSizeBase = wavHeaderBytes - 8
|
||||
|
||||
// 四块的标识与头内各字段的偏移(规范里定死的值,具名以免裸数字)。
|
||||
wavRiffID = "RIFF"
|
||||
wavWaveID = "WAVE"
|
||||
wavFmtID = "fmt "
|
||||
wavDataID = "data"
|
||||
|
||||
wavOffRiffID = 0
|
||||
wavOffChunkSize = 4
|
||||
wavOffWaveID = 8
|
||||
wavOffFmtID = 12
|
||||
wavOffFmtSize = 16
|
||||
wavOffAudioFormat = 20
|
||||
wavOffChannels = 22
|
||||
wavOffSampleRate = 24
|
||||
wavOffByteRate = 28
|
||||
wavOffBlockAlign = 32
|
||||
wavOffBitsPerSamp = 34
|
||||
wavOffDataID = 36
|
||||
wavOffDataSize = 40
|
||||
)
|
||||
|
||||
// wavMaxDataBytes 是 RIFF 的 32 位长度字段能表达的 data 上限。超出即无法用
|
||||
// 44 字节头如实描述 ⇒ ErrEncodeFailed(绝不写出一个长度撒谎的头)。
|
||||
const wavMaxDataBytes = math.MaxUint32 - riffChunkSizeBase
|
||||
|
||||
// wavHeader 装配一个 44 字节的 RIFF/WAVE 头,声明 dataBytes 字节的 PCM16 载荷。
|
||||
func wavHeader(dataBytes, sampleRate, channels int) ([wavHeaderBytes]byte, error) {
|
||||
var hdr [wavHeaderBytes]byte
|
||||
if sampleRate <= 0 || channels <= 0 {
|
||||
return hdr, fmt.Errorf("transcode: wav header: %w: sample_rate=%d channels=%d",
|
||||
ErrEncodeFailed, sampleRate, channels)
|
||||
}
|
||||
if dataBytes < 0 || dataBytes > wavMaxDataBytes {
|
||||
return hdr, fmt.Errorf("transcode: wav header: %w: %d data bytes exceed the RIFF limit",
|
||||
ErrEncodeFailed, dataBytes)
|
||||
}
|
||||
blockAlign := channels * wavPCMBytesPerSample
|
||||
copy(hdr[wavOffRiffID:], wavRiffID)
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffChunkSize:], uint32(riffChunkSizeBase+dataBytes))
|
||||
copy(hdr[wavOffWaveID:], wavWaveID)
|
||||
copy(hdr[wavOffFmtID:], wavFmtID)
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffFmtSize:], wavFmtChunkBytes)
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffAudioFormat:], wavFormatPCM)
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffChannels:], uint16(channels))
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffSampleRate:], uint32(sampleRate))
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffByteRate:], uint32(sampleRate*blockAlign))
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffBlockAlign:], uint16(blockAlign))
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffBitsPerSamp:], wavBitsPerSample)
|
||||
copy(hdr[wavOffDataID:], wavDataID)
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffDataSize:], uint32(dataBytes))
|
||||
return hdr, nil
|
||||
}
|
||||
|
||||
// writePCM16LE 把样本按小端 PCM16 写进 dst(dst 必须够长:2 字节/样本)。
|
||||
func writePCM16LE(dst []byte, samples []int16) {
|
||||
for i, s := range samples {
|
||||
binary.LittleEndian.PutUint16(dst[i*wavPCMBytesPerSample:], uint16(s))
|
||||
}
|
||||
}
|
||||
|
||||
// encodePCM16WAV 把样本装成一份完整 WAV(44 字节头 + PCM16 数据)。
|
||||
// 主路径(codec_amr_nb.go)不用它:那条路直接写进目标缓冲,避免「先攒一份 PCM、
|
||||
// 再复制一份 WAV」把峰值内存翻倍(design/25 §2.5 的瞬时上限)。
|
||||
func encodePCM16WAV(samples []int16, sampleRate, channels int) ([]byte, error) {
|
||||
dataBytes := len(samples) * wavPCMBytesPerSample
|
||||
hdr, err := wavHeader(dataBytes, sampleRate, channels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]byte, wavHeaderBytes+dataBytes)
|
||||
copy(out, hdr[:])
|
||||
writePCM16LE(out[wavHeaderBytes:], samples)
|
||||
return out, nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestWAVHeaderFields 逐字段断言 44 字节头(design/25 §3.1:不重采样、8 kHz、
|
||||
// 单声道、PCM16)。
|
||||
func TestWAVHeaderFields(t *testing.T) {
|
||||
const samples = 160
|
||||
dataBytes := samples * wavPCMBytesPerSample
|
||||
hdr, err := wavHeader(dataBytes, amrSampleRate, amrChannels)
|
||||
if err != nil {
|
||||
t.Fatalf("wavHeader: %v", err)
|
||||
}
|
||||
if len(hdr) != wavHeaderBytes {
|
||||
t.Fatalf("header length = %d, want %d", len(hdr), wavHeaderBytes)
|
||||
}
|
||||
if string(hdr[wavOffRiffID:wavOffChunkSize]) != wavRiffID ||
|
||||
string(hdr[wavOffWaveID:wavOffFmtID]) != wavWaveID ||
|
||||
string(hdr[wavOffFmtID:wavOffFmtSize]) != wavFmtID ||
|
||||
string(hdr[wavOffDataID:wavOffDataSize]) != wavDataID {
|
||||
t.Fatalf("chunk ids wrong: %q %q %q %q",
|
||||
hdr[wavOffRiffID:wavOffChunkSize], hdr[wavOffWaveID:wavOffFmtID],
|
||||
hdr[wavOffFmtID:wavOffFmtSize], hdr[wavOffDataID:wavOffDataSize])
|
||||
}
|
||||
checks := []struct {
|
||||
name string
|
||||
got uint32
|
||||
want uint32
|
||||
}{
|
||||
{"RIFF size", binary.LittleEndian.Uint32(hdr[wavOffChunkSize:]), uint32(riffChunkSizeBase + dataBytes)},
|
||||
{"fmt size", binary.LittleEndian.Uint32(hdr[wavOffFmtSize:]), wavFmtChunkBytes},
|
||||
{"sample rate", binary.LittleEndian.Uint32(hdr[wavOffSampleRate:]), uint32(amrSampleRate)},
|
||||
{"byte rate", binary.LittleEndian.Uint32(hdr[wavOffByteRate:]), uint32(amrSampleRate * wavPCMBytesPerSample)},
|
||||
{"data size", binary.LittleEndian.Uint32(hdr[wavOffDataSize:]), uint32(dataBytes)},
|
||||
}
|
||||
for _, c := range checks {
|
||||
if c.got != c.want {
|
||||
t.Errorf("%s = %d, want %d", c.name, c.got, c.want)
|
||||
}
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(hdr[wavOffAudioFormat:]); got != wavFormatPCM {
|
||||
t.Errorf("audio format = %d, want %d (PCM)", got, wavFormatPCM)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(hdr[wavOffChannels:]); got != amrChannels {
|
||||
t.Errorf("channels = %d, want %d", got, amrChannels)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(hdr[wavOffBlockAlign:]); got != wavPCMBytesPerSample {
|
||||
t.Errorf("block align = %d, want %d", got, wavPCMBytesPerSample)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint16(hdr[wavOffBitsPerSamp:]); got != wavBitsPerSample {
|
||||
t.Errorf("bits per sample = %d, want %d", got, wavBitsPerSample)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEncodePCM16WAVRoundTrip 断言样本 → WAV → 样本往返无损(小端正确)。
|
||||
func TestEncodePCM16WAVRoundTrip(t *testing.T) {
|
||||
samples := []int16{0, 1, -1, math.MaxInt16, math.MinInt16, 1234, -4321}
|
||||
wav, err := encodePCM16WAV(samples, amrSampleRate, amrChannels)
|
||||
if err != nil {
|
||||
t.Fatalf("encodePCM16WAV: %v", err)
|
||||
}
|
||||
got := wavDataSamples(t, wav)
|
||||
if len(got) != len(samples) {
|
||||
t.Fatalf("round trip samples = %d, want %d", len(got), len(samples))
|
||||
}
|
||||
for i := range samples {
|
||||
if got[i] != samples[i] {
|
||||
t.Fatalf("sample %d = %d, want %d", i, got[i], samples[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWAVHeaderRejectsImpossibleDeclarations 断言不可能的头参数被拒(不写出
|
||||
// 一个长度撒谎的头)。
|
||||
func TestWAVHeaderRejectsImpossibleDeclarations(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
dataBytes, rate, chans int
|
||||
}{
|
||||
{"zero rate", 2, 0, 1},
|
||||
{"negative rate", 2, -1, 1},
|
||||
{"zero channels", 2, amrSampleRate, 0},
|
||||
{"negative data", -1, amrSampleRate, 1},
|
||||
{"data beyond RIFF limit", int(wavMaxDataBytes) + 1, amrSampleRate, 1},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if _, err := wavHeader(tc.dataBytes, tc.rate, tc.chans); !errors.Is(err, ErrEncodeFailed) {
|
||||
t.Fatalf("err = %v, want ErrEncodeFailed", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWritePCM16LEIsLittleEndian 断言字节序:-2 = 0xFE 0xFF。
|
||||
func TestWritePCM16LEIsLittleEndian(t *testing.T) {
|
||||
dst := make([]byte, 2*wavPCMBytesPerSample)
|
||||
writePCM16LE(dst, []int16{-2, 258})
|
||||
if dst[0] != 0xFE || dst[1] != 0xFF {
|
||||
t.Fatalf("-2 写成 %#x %#x, want 0xfe 0xff", dst[0], dst[1])
|
||||
}
|
||||
if dst[2] != 0x02 || dst[3] != 0x01 {
|
||||
t.Fatalf("258 写成 %#x %#x, want 0x02 0x01", dst[2], dst[3])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user