rvoip-codec-core 0.3.8

G.711 and optional G.729/Opus/AMR audio codec implementations for RVOIP
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
# AMR-NB / AMR-WB Implementation Plan

Status: **Draft for review** — branch `feat/amr-codecs`
Owner: TBD
Last updated: 2026-08-08

Goal: add AMR narrowband (AMR-NB) and AMR wideband (AMR-WB / ITU-T G.722.2) to rvoip so
the stack can carry HD voice on ordinary telephony interconnects (VoLTE, IMS, mobile
carrier SBCs), where AMR-WB — not Opus — is the codec that is actually offered.

---

## 1. Executive summary

### 1.1 What "adding AMR" actually means

Three separable pieces of work, in increasing order of cost:

| Layer | What it is | Independent of codec kernel? | Rough size |
|---|---|---|---|
| **L1 — RTP payload format** | RFC 4867 packetization: CMR, ToC, F/FT/Q bits, bandwidth-efficient vs octet-aligned, interleaving, CRC | Yes | ~2–3 weeks |
| **L2 — SDP / signalling** | `mode-set`, `octet-align`, `mode-change-*`, `crc`, `robust-sorting`, dynamic PT allocation, offer/answer intersection, CMR-driven rate adaptation | Yes | ~2–3 weeks |
| **L3 — Codec kernel** | The actual ACELP encoder/decoder for 8 NB modes + 9 WB modes, VAD/DTX/CNG, error concealment | No | months (see §7) |

**L1 + L2 alone let rvoip act as a signalling-aware SBC/B2BUA that *relays* AMR-WB
end-to-end without transcoding** — useful, and delivered at ~week 6 as a by-product. But
transcoding is a day-one requirement here (§1.2), so L3 is on the critical path and is
where essentially all of the cost lives. L1 also carries the AMR file-storage-format reader
that loads test vectors, which is why it is sequenced first.

### 1.2 Decided approach

Four decisions are now fixed for this branch:

- **Pure Rust in the shipped crate.** No FFI backend, not even an opt-in one. Everything
  that ships is Rust written from the specification text, with no C dependency, no
  `build.rs`, and no change to the workspace's cross-compilation or `cargo deny` posture.
- **Transcoding is required from day one.** Pass-through/relay alone is not sufficient;
  AMR must bridge to G.711 and Opus.
- **A two-tier development-time oracle generates per-stage reference vectors offline.**
  The **3GPP reference C** (TS 26.073 / 26.173) is the authority — in-house use for product
  design is expressly permitted, though it may not be redistributed, so it is fetched and
  never committed (§2.3). `opencore-amr` and `vo-amrwbenc` (Apache-2.0) supply
  redistribution-safe fixtures. Neither tier is linked into the shipped crate or appears in
  the workspace dependency graph. See §1.2.1.
- **AMR-WB is built first.** WB is the deliverable that matters (HD voice); NB follows.

  An earlier revision of this plan put NB first as a de-risking warm-up: simpler
  quantisers, no resampler, no high-band synthesis, so a bit-exactness bug costs days
  instead of weeks. **That argument is largely bought out by the oracle decision above.**
  It assumed hard, unaided debugging — but `opencore-amr` gives per-stage ground truth for
  the WB decoder, which is where most of the new machinery (order-16 ISP, S-MSVQ, 12.8 kHz
  resampling, high-band synthesis) first appears. WB-first does not cost much *total* time
  (the shared `common/` work happens either way, just under a harder first consumer); it
  front-loads the difficulty and delivers the goal roughly 2–3 months sooner.

  The residual exposure is the WB **encoder**: its oracle (`vo-amrwbenc`) is the one that
  may not be bit-exact, and WB-first moves it from last phase to mid-project. This raises
  the stakes on Phase 0 oracle qualification — see the caveat below.

Order of work: **L1 (payload format) → L2 (SDP) → `common/` DSP layer + oracle harness →
WB decoder → WB encoder → NB decoder → NB encoder → transcoding + interop.** L1 comes
first not for its own sake but because the AMR file-storage-format reader it contains is
what loads the 3GPP test sequences and the oracle dumps — it is test infrastructure for
everything after it.

> **Transcoding requires both an encoder and a decoder**, so AMR-WB ↔ Opus — the HD-voice
> goal — lands around month 4–5, with AMR-NB following at month 6–7. Phases 1–2 ship relay
> capability at ~week 6, but relay is not the goal here. See the timeline table in §8.

### 1.2.1 Oracle strategy

Bit-exact ACELP is tractable when you can diff each DSP stage against a known-good
implementation, and intractable when you can only compare final bitstreams. The oracle is
what buys that, turning "debug a 244-bit mismatch" into "diff stage 6".

**Multi-oracle, three tiers.** Deliberately more oracles than strictly needed for
debugging, for three distinct reasons: N-version cross-validation finds spec ambiguities
that any single oracle would hide; different licenses permit different depths of use; and
a documented record of validation against several mutually independent implementations is
itself provenance evidence (§2.4).

| Tier | Oracle | Covers | How we may use it | Vendored? |
|---|---|---|---|---|
| **1 — authority** | **TS 26.073** (NB fixed), **TS 26.173** (WB fixed) | NB enc+dec, WB enc+dec | **Instrument + per-stage dumps.** The normative definition of bit-exactness | **No — fetched, git-ignored** (§2.3) |
| 2 — redistributable | `opencore-amr` (Apache-2.0) | NB enc+dec, WB dec | Instrument + per-stage dumps; **output is committable** | Yes |
| 2 — redistributable | `vo-amrwbenc` (Apache-2.0) | WB enc | Instrument + per-stage dumps; output committable | Yes |
| ~~3~~ | ~~TS 26.104 (NB float), TS 26.204 (WB float)~~ | — | **REMOVED.** Not bit-exact with the fixed-point specs, so they cannot confirm bit-exactness and a disagreement does not say which side is wrong | — |
| 3 — black-box only | FFmpeg native `amrnbdec` / `amrwbdec` (LGPL) | NB dec, WB dec | **Run the binary only — never read the source.** LGPL restricts copying, not execution | No |

Coverage per path: NB decode and WB decode get **3 independent oracles**; NB encode and WB
encode get **3**.

**The floating-point references are removed from the roster.** They are not
bit-exact with the fixed-point specs by design, which makes them useless for the
only question that matters: they cannot confirm bit-exactness, and a
disagreement does not identify which side is wrong. Keeping them invites
treating agreement as evidence when it is not.

The same reasoning bounds FFmpeg. Running it costs nothing legally — execution
is not a derivative work — but it is an independent implementation, not a
bit-exact one, so it belongs to interop testing rather than bit-exactness
verification. Tier 3 compares whole frames only, since stage boundaries cannot
be instrumented without reading source.

**Cross-validation is the point, not redundancy.** The interesting signal is disagreement:

- All oracles agree, we differ → **we are wrong.** Fix our stage.
- Oracles disagree with each other → **a genuine spec ambiguity.** These are precisely the
  places a from-spec implementation is most likely to go wrong, and a single oracle would
  have silently picked one interpretation for us. Escalate to the spec text and the
  conformance sequences, and record the resolution in the module docs.
- Tier 1 disagrees with tier 2/3 → tier 1 wins; the other implementation has a bug or a
  deliberate deviation. Note it so its dumps are not trusted for that stage.

Phase 0 qualification therefore records, per path and per oracle, whether it is bit-exact
with tier 1. Where a tier-2 library qualifies, commit its fixtures and treat them as
authoritative. Where it does not — most likely `vo-amrwbenc`, which is not
reference-derived — fall back to tier-1 dumps held locally, pending IP-2 q2.

**Architecture — out-of-tree, vector-generating.** The oracle lives in a standalone Cargo
project *excluded from the workspace*, at `crates/media/codec-core/tools/amr-oracle/`. It
**vendors** the Apache-2.0 sources and **fetches** the 3GPP reference (a `make fetch` step
that downloads TS 26.073 / 26.173 and unpacks them into a git-ignored directory — nothing
3GPP-copyrighted is ever committed). `build.rs` compiles both with `cc` and applies
instrumentation patches that dump each stage's inputs and outputs. It runs offline, on a
developer machine, and writes compact fixtures into
`crates/media/codec-core/tests/vectors/amr/`. **The committed test suite reads only those
checked-in fixtures** — a normal `cargo test` needs no C toolchain and no 3GPP download,
and the shipped crate's dependency graph is unchanged.

This is why the pure-Rust decision and the oracle decision are not in tension: the oracle
is build-time-only tooling that produces data, not a runtime component.

**What this resolves.** The previous revision's largest open risk was that `vo-amrwbenc`
might not be bit-exact, leaving the AMR-WB encoder — the HD-voice milestone, and now
Phase 5 — without a trustworthy per-stage oracle. **TS 26.173 removes that risk entirely**:
the WB encoder now has a normative oracle by definition. Risk R8 is downgraded accordingly,
and the 2–3 week fallback budget on Phase 5 is no longer expected to be needed.

**Retained regardless of oracle quality** — cheap, independent of any implementation's
reading of the spec, and the fallback if oracle qualification disappoints:

1. **Homing frames (EHF/DHF)** — the spec's own testing mechanism. Special inband frames
   force every bit-exactly-defined function into a predefined home state, and consecutive
   homing frames must produce homing frames at the output. Gives self-synchronising
   checkpoints, so a conformance sequence can be entered mid-stream and a failure
   localised to a frame range instead of poisoning everything downstream.
2. **Brute-force reference searches** in test code — the ACELP codebook searches are fast
   heuristics approximating an optimum that can be computed exhaustively for a single
   subframe. The slow version is the oracle for the fast one. This remains necessary even
   with a good oracle, because it validates the *search* rather than one implementation's
   result.
3. **A reduced float reference model**, scoped to wherever oracle qualification comes back
   weak — expected to be the WB encoder's ISF quantisation and codebook search, and
   nothing else. The full float-model-first approach from the previous revision is
   dropped: with a bit-exact oracle it is redundant, and it cost 2–3 weeks.

### 1.3 The one thing to get right before writing code

**Patent expiry and source-code copyright are two different things, and only one of them
has expired.** See §2. The AMR patents appear to have lapsed; the copyright on the
reference implementations has not.

The useful distinction — and one an earlier revision of this plan got wrong by being too
cautious — is **verification versus distribution**. 3GPP expressly permits "in-house copies
… for product design purposes", so building and instrumenting the reference C (TS 26.073 /
26.173) to validate our implementation is allowed, and it is the best oracle available.
What is prohibited is distributing "verbatim copies of source code (or lightly modified
copies)". So:

- **Verify against the reference freely.** Fetch it, build it, instrument it — locally.
- **Distribute nothing derived from it.** It never enters the repository, and our Rust must
  be structured from the spec's block diagram rather than transliterated from the C, or it
  risks being a "lightly modified copy" of something we may not redistribute.

Full detail and the quoted terms are in §2.3; the consequences shape §1.2.1, §6 and §9.2.

---

## 2. Legal / IP position

> Not legal advice. This section records what the research found so counsel can sign off
> quickly; it is not a substitute for that sign-off.

### 2.1 Patents — consistent with "safe to implement"

- AMR-NB was standardised in 1999. Core essential patents are reported to have lapsed
  around 2019–2020.
- AMR-WB was standardised in 2001–2002 and published as ITU-T G.722.2 in 2003. Wikipedia
  states flatly that *"The patent for AMR expired in 2024"* and describes the technology
  as now royalty-free. Licensing was previously administered by VoiceAge (pool launched
  Feb 2010; members included Nokia, Ericsson, NTT, VoiceAge).
- No public VoiceAge announcement formally winding down the programme was found. Absence
  of an announcement is not evidence of live patents — pools generally go quiet rather
  than issue a press release — but it does mean we are relying on expiry arithmetic
  rather than an affirmative grant.

**Action IP-1 (blocking for Phase 3+, i.e. before any codec-kernel work; not for Phases
0–2):** have counsel confirm expiry
of the AMR-WB/G.722.2 essential-patent list published at
`voiceage.com/Patent-Portfolio-Essential.html`. Phases 0–2 implement only IETF RFC 4867
framing, which is not covered by the speech-coding patents, so they can proceed in
parallel.

### 2.2 What each implementation permits

Patents aside (§2.1), the practical question is what we may *do* with each existing
implementation. Running any of them is unrestricted; the columns below concern reading and
copying. §2.4 sets out how these permissions map onto what we actually do.

| Source | License | May we read / copy it? |
|---|---|---|
| 3GPP TS 26.073 / 26.104 (NB C), TS 26.173 / 26.204 (WB C) | 3GPP copyright. **In-house use for product design is expressly permitted; redistribution is not** — see §2.3 | **Run and instrument locally: yes. Redistribute or transliterate: no** |
| `pschatzmann/codec-amr` | Wraps the above; author states license "unclear" — and it *redistributes* 3GPP source, which §2.3 indicates requires permission | **No** |
| FFmpeg `libavcodec/amrnbdec.c`, `amrwbdec.c` (native decoders) | LGPL-2.1-or-later | **No** — LGPL is not on this workspace's `deny.toml` allow-list, and porting from it creates derivative-work exposure |
| Wireshark `epan/dissectors/packet-amr.c` | GPL-2.0 | **No** |
| rtpengine (AMR transcoding + CMR logic) | GPL-3.0 | **No** |
| `opencore-amr` (AMR-NB enc+dec, AMR-WB dec; ex-AOSP/PacketVideo) | **Apache-2.0** | **Yes** — allow-listed in `deny.toml` |
| `mstorsjo/vo-amrwbenc` (AMR-WB encoder, ex-VisualOn/Android) | **Apache-2.0** | **Yes** |
| ITU-T G.722.2 recommendation text, 3GPP TS text | Spec text | **Yes, as the normative source to implement from** |

The workspace publishes under **MIT** (`Cargo.toml:113`) and `deny.toml` allows
MIT / Apache-2.0 / BSD / ISC / Zlib / 0BSD / BSL-1.0 / MPL-2.0 / CDLA-Permissive-2.0 /
CC0-1.0 — **no (L)GPL**. The copyleft implementations above (FFmpeg, Wireshark, rtpengine)
are therefore the genuinely off-limits ones: their code cannot enter the tree, and we should
avoid studying their source at all. Run their *binaries* freely — `ffmpeg` as a black-box
cross-check and Wireshark as a packet dissector are both fine and useful — but do not read
them.

### 2.3 The 3GPP reference code — usable as an oracle, not redistributable

An earlier revision of this plan treated the 3GPP reference C as entirely off-limits,
following the "license unclear" note on `pschatzmann/codec-amr`. **That was too
conservative.** 3GPP's published terms draw a sharp and useful line:

> "Other than for **in-house copies for the purpose of further development of the 3GPP
> standard or for product design purposes**, you may not reproduce any part of a 3GPP TS
> or TR without seeking permission from 3GPP."

> "You must not provide **verbatim copies of source code (or lightly modified copies)**
> without seeking permission from 3GPP."

So:

- **Downloading TS 26.073 / 26.173, building them, running them, and instrumenting them
  locally to validate our implementation is squarely "product design purposes" — expressly
  permitted.** Verifying an independent implementation is what the reference code is for.
- **Redistributing that source is not.** Nothing 3GPP-copyrighted enters the repository;
  the oracle harness **fetches rather than vendors** (§1.2.1).
- **A line-by-line Rust transliteration of the 3GPP C would plausibly be a "lightly
  modified copy"** and is therefore off the table. This is a sharper and more useful
  constraint than "unclear license": it restricts what we *distribute*, not how we
  *verify*.

This is also why `pschatzmann/codec-amr` stays unusable — not because its terms are unclear
in the abstract, but because it redistributes 3GPP source, which the second clause above
indicates requires permission it does not claim to hold.

**Working rule for the port:** implement from TS 26.090 / TS 26.190 (and the companion
specs in §3). Verify against the full oracle roster (§1.2.1), the conformance sequences, and
the spec-defined homing-frame outputs. **Run** the copyleft implementations (FFmpeg,
Wireshark, rtpengine) freely as black-box oracles and interop counterparties — execution
carries no license obligation — but do not read their sources, since this workspace is MIT
and there is no need to create the argument.

Structure the Rust from the **spec's block diagram**, not from any C implementation's file
layout or identifiers. That is the operative discipline: the risk is not "we looked at the
reference" (permitted, and normal) but "what we ship reads as a lightly modified copy of it"
(prohibited). Only the second failure mode matters. See §2.4.

### 2.4 What we actually do with each library, and why each is permitted

Three distinct activities get conflated easily. Only one of them is a copyright question at
all, and it is not one we engage in.

| Activity | Copyright implication | What we do |
|---|---|---|
| **Running an implementation** — as an oracle, or as the far end of an interop test | **None.** Execution is not reproduction. This holds for every license, including GPL and LGPL | Every oracle in §1.2.1, plus the cross-implementation round-trip matrix (§9.2b) |
| **Reading an implementation** to understand the standard | Permitted where the license allows it: Apache-2.0 expressly, 3GPP for in-house product design (§2.3). Not permitted for us on copyleft sources — not because reading is unlawful, but because this workspace is MIT and we choose not to create the argument | Instrument `opencore-amr`, `vo-amrwbenc`, and the 3GPP reference. Never open FFmpeg / Wireshark / rtpengine sources |
| **Copying expression** into what we ship | The only prohibited act | We do not do this |

**Interop testing is not copying, and neither is oracle use.** Encoding with our Rust
implementation and decoding with FFmpeg tells us our bitstream is correct; it involves no
FFmpeg code entering our tree and no FFmpeg source being read. The same is true of every
entry in the round-trip matrix. This is ordinary conformance engineering — it is precisely
what reference implementations and conformance suites exist for.

**Learning from permissively licensed implementations is likewise legitimate.**
`opencore-amr` and `vo-amrwbenc` are Apache-2.0, which grants the right to use, study, and
even copy outright subject to attribution. The 3GPP reference is expressly available for
in-house product design use. Nothing about consulting these to understand how a standard
is realised is excluded — reading them is one of the things they are published for.

So the position, stated plainly: **we implemented AMR in Rust from the 3GPP specifications,
and validated it against multiple independent implementations that are either permissively
licensed or expressly available for this purpose.** That is a normal and defensible way to
build a codec.

**The one discipline worth keeping.** The only way this position weakens is if what we ship
turns out to be a mechanical transliteration of some C implementation — because a
line-by-line translation into another language is generally still treated as a derivative
work, translation being a classic derivative form. This is not a reason to avoid the
oracles; it is a reason to write the Rust from the spec's block diagram and let it look
like Rust. Three things make that the natural outcome anyway:

- Much of a codec's "expression" is dictated by the spec — the tables, the equations, the
  block structure — which is thin protection under merger and scènes-à-faire reasoning.
- The parts that *are* freely expressible (module decomposition, naming, error handling,
  iterator use, type design, `Result` propagation) are exactly where idiomatic Rust
  diverges from C without anyone trying.
- The house idiom is already set by the G.729 modules, and it is not C-shaped.

**Validating against five mutually independent implementations supports this.** Code shaped
to agree with all of them is shaped by the specification they share, not by any one
codebase — you cannot have transliterated all five. Worth being accurate about the weight
this carries: copying is ultimately shown by similarity of expression in the output, so the
oracle roster is corroborating evidence rather than the whole answer. It is real, it is
free, and combined with the language difference and the record below it makes for a strong
position.

**(c) A documented development record.** Cheap to maintain and the most directly useful
artefact if the question is ever asked. Each module's doc header states which spec section
it implements (e.g. "TS 26.190 §5.2.3") and which oracles validated it. Where oracles
disagreed, record the ambiguity and how the spec text resolved it. This is also just good
engineering: the same header tells the next maintainer where to look. The G.729 modules
already do a version of this with their Q-format headers.

None of this is legal advice; it is the position for counsel to confirm alongside IP-1/2.

**Action IP-2 (do during Phase 0):** two related questions.

1. May the 3GPP **test sequences** (TS 26.074 / 26.174) be vendored into the repo? If not,
   use the opt-in external-fixture pattern in §9.3.
2. May **per-stage vectors generated by running** the reference code be committed? Program
   output is generally not a derivative work of the program, and 3GPP itself publishes
   reference-codec output as the conformance sequences — but confirm rather than assume.
   Until answered, prefer `opencore-amr`-derived fixtures for committed data (Apache-2.0,
   unambiguous) and keep 3GPP-derived dumps local.

**Action IP-3:** the oracle harness vendors Apache-2.0 C (`opencore-amr`,
`vo-amrwbenc`) under `tools/amr-oracle/`. Even though it is build-time-only tooling that
never ships in the crate, add `THIRD_PARTY_NOTICES.md` entries and retain the upstream
`LICENSE` / `NOTICE` files in the vendored tree — Apache-2.0 §4 attribution applies to
distribution of the source repository, not just to binaries. The 3GPP reference needs no
such entry precisely because it is never redistributed.

---

## 3. Normative specification set

Everything below is what an implementer actually has to read. Get all of them before
starting Phase 4.

### AMR-NB

| Spec | Title | Why we need it |
|---|---|---|
| TS 26.071 | AMR speech codec; General description | Orientation, mode list |
| **TS 26.090** | **AMR speech codec; Transcoding functions** | **The normative encoder/decoder algorithm** |
| TS 26.091 | Error concealment of lost frames | Decoder PLC behaviour |
| TS 26.092 | Comfort noise aspects | CNG |
| TS 26.093 | Source-controlled rate operation | DTX / SID cadence |
| TS 26.094 | Voice Activity Detector (VAD) | VAD option 1 and option 2 |
| TS 26.101 | AMR frame structure | Bit ordering, class A/B/C sorting, IF1/IF2 |
| **TS 26.073** | **AMR ANSI-C source (fixed point)** | **Primary oracle** — fetch, build, instrument locally; never redistribute (§2.3) |
| TS 26.104 | ANSI-C source (floating point) | **Not used** — not bit-exact, see §1.2.1 |
| TS 26.074 | Test sequences | Conformance vectors |

### AMR-WB

| Spec | Title | Why we need it |
|---|---|---|
| TS 26.171 | AMR-WB speech codec; General description | Orientation |
| **TS 26.190** | **AMR-WB speech codec; Transcoding functions** (= ITU-T G.722.2) | **The normative algorithm** |
| TS 26.191 | Error concealment | Decoder PLC |
| TS 26.192 | Comfort noise aspects | CNG |
| TS 26.193 | Source-controlled rate operation | DTX |
| TS 26.194 | Voice Activity Detector | VAD |
| TS 26.201 | AMR-WB frame structure | Bit ordering |
| **TS 26.173** | **AMR-WB ANSI-C source (fixed point)** | **Primary oracle** — normative for WB encode; same terms (§2.3) |
| TS 26.204 | ANSI-C source (floating point) | **Not used** — not bit-exact, see §1.2.1 |
| TS 26.174 | Test sequences | Conformance vectors |

### Transport

| Spec | Title |
|---|---|
| **RFC 4867** | RTP Payload Format and File Storage Format for AMR and AMR-WB |
| RFC 3551 | RTP A/V profile (static PT context; AMR uses dynamic PTs) |
| TS 26.114 | IMS multimedia telephony — media handling (carrier-profile behaviour: mode-set, ptime, CMR policy in real VoLTE deployments) |

TS 26.114 is not strictly required but is what carriers actually test against; read it
before the interop phase.

---

## 4. Codec facts (verified)

These numbers are load-bearing for L1/L2 and are verified against RFC 4867. Per-parameter
bit allocations (how the 244 bits of MR122 split across LSP / lag / pulses / gains) are
**deliberately not reproduced here** — read them from TS 26.090 Table 8 and TS 26.190
Table 7 during Phase 4/6 rather than trusting a secondary source.

### 4.1 AMR-NB

- 8 kHz mono, 20 ms frames = **160 samples**, 4 subframes of 40 samples.
- LPC order 10; LSP representation; ACELP fixed codebook; DTX/VAD/CNG available.
- Mode 7 (12.2 kbit/s) is bit-allocation-identical to GSM-EFR.

| FT | Mode | kbit/s | Bits/frame | Class A bits | Octet-aligned bytes |
|---|---|---|---|---|---|
| 0 | MR475 | 4.75 | 95 | 42 | 12 |
| 1 | MR515 | 5.15 | 103 | 49 | 13 |
| 2 | MR59 | 5.90 | 118 | 55 | 15 |
| 3 | MR67 | 6.70 | 134 | 58 | 17 |
| 4 | MR74 | 7.40 | 148 | 61 | 19 |
| 5 | MR795 | 7.95 | 159 | 75 | 20 |
| 6 | MR102 | 10.2 | 204 | 65 | 26 |
| 7 | MR122 | 12.2 | 244 | 81 | 31 |
| 8 | AMR SID | — | 39 | 39 | 5 |

FT 9–14 are reserved for AMR-NB; **FT 15 = NO_DATA**. Per RFC 4867 a ToC entry with
FT 9–14 (AMR) means the whole packet SHOULD be discarded.

> Note: several secondary sources (including Wikipedia's AMR page) swap the 6.70 and 7.40
> frame sizes. RFC 4867 is authoritative: **6.70 → 134 bits, 7.40 → 148 bits.**

### 4.2 AMR-WB

- 16 kHz mono in/out, **internally resampled to 12.8 kHz**; 20 ms frames = 320 samples at
  16 kHz / 256 at 12.8 kHz, 4 subframes of 64 samples (12.8 kHz).
- Encodes 50–6400 Hz with ACELP; the 6400–7000 Hz high band is synthesised at the decoder.
- LPC order 16; ISP/ISF representation quantised with split-multistage VQ (S-MSVQ).
- 5 ms look-ahead; ~0.9375 ms extra one-way delay from the bandsplitting filter.

| FT | kbit/s | Bits/frame | Octet-aligned bytes |
|---|---|---|---|
| 0 | 6.60 | 132 | 17 |
| 1 | 8.85 | 177 | 23 |
| 2 | 12.65 | 253 | 32 |
| 3 | 14.25 | 285 | 36 |
| 4 | 15.85 | 317 | 40 |
| 5 | 18.25 | 365 | 46 |
| 6 | 19.85 | 397 | 50 |
| 7 | 23.05 | 461 | 58 |
| 8 | 23.85 | 477 | 60 |
| 9 | AMR-WB SID | — (40 bits) | 5 |

FT 10–13 reserved (discard packet); **FT 14 = SPEECH_LOST** (AMR-WB only);
**FT 15 = NO_DATA**. AMR-WB SID carries 40 class A bits.

Mode 8 (23.85) is mode 7 (23.05) plus **16 bits = 4 bits × 4 subframes of transmitted
high-band gain** — 477 − 461 = 16 confirms this. It is the only mode that transmits HB
gain rather than deriving it.

### 4.3 RTP payload format (RFC 4867)

Two framings, and we must support both — carriers differ, and getting this wrong is the
classic AMR interop failure:

- **Bandwidth-efficient** (default when `octet-align` is absent or `0`): 4-bit CMR, then
  6-bit ToC entries (`F | FT[4] | Q`), then speech bits packed with no intermediate
  padding; only the whole payload is octet-aligned.
- **Octet-aligned** (`octet-align=1`): 1–2 octet header (CMR + 4 reserved bits, plus
  ILL/ILP when interleaving), 1-octet ToC entries (`F | FT[4] | Q | PP`), each speech
  frame individually zero-padded to an octet boundary.

Fields: **CMR** (4 bits, requested mode index, `15` = no request), **F** (1 = another
frame follows), **FT** (4 bits), **Q** (0 = frame severely damaged → SPEECH_BAD/SID_BAD).

Octet-aligned-only extras: **frame CRC** (8-bit over class A bits, polynomial
`1 + x² + x³ + x⁴ + x⁸`), **robust sorting**, **interleaving** (ILL = length − 1 in
frame-blocks, ILP = index). `crc=1` and `robust-sorting=1` each imply `octet-align=1`.

Media types: `audio/AMR` @ **8000 Hz** clock (160 samples/frame-block);
`audio/AMR-WB` @ **16000 Hz** clock (320 samples/frame-block). Both use dynamic payload
types and must always be distinct PTs. Storage-format magic: `#!AMR\n`, `#!AMR-WB\n`
(and `#!AMR_MC1.0\n` / `#!AMR-WB_MC1.0\n` multi-channel).

SDP parameters: `octet-align` (0/1, dflt 0), `mode-set` (subset of 0–7 / 0–8, dflt all),
`mode-change-period` (1|2, dflt 1), `mode-change-capability` (1|2, dflt 1),
`mode-change-neighbor` (0/1, dflt 0), `crc` (0/1, dflt 0), `robust-sorting` (0/1, dflt 0),
`interleaving`, `ptime`, `maxptime`.

---

## 5. Reference-implementation survey

| Project | Scope | License | Use for us |
|---|---|---|---|
| `opencore-amr` (SourceForge; AOSP-derived) | AMR-NB enc+dec, AMR-WB **dec only** | Apache-2.0 | **Development-time oracle** for NB encode/decode and WB decode (§1.2.1). Instrumented out-of-tree to dump per-stage vectors; never linked into the shipped crate |
| `mstorsjo/vo-amrwbenc` | AMR-WB **enc** | Apache-2.0 | **Development-time oracle** for WB encode. VisualOn-derived, so bit-exactness with TS 26.173 is **unverified** — qualify in Phase 0 |
| 3GPP TS 26.073 / 26.173 | NB + WB, fixed point, normative | Unclear | Do not use |
| `pschatzmann/codec-amr` (the repo you found) | C++ wrapper over 3GPP C, NB+WB, enc+dec, Arduino-oriented | **Author says license unclear** | Do not use. Useful only as a pointer to the 3GPP sources |
| FFmpeg native `amrnbdec.c` / `amrwbdec.c` | NB+WB decoders written from spec | LGPL-2.1+ | **Tier-3 black-box oracle** (§1.2.1) — run the `ffmpeg` binary and compare whole frames. LGPL restricts copying, not execution. **Never read the source.** Also proof that a from-spec decoder is achievable at reasonable size |
| FFmpeg `rtpdec_amr.c` | RFC 4867 depacketizer | LGPL-2.1+ | **Run** `ffmpeg` against our RTP output as a depacketizer cross-check; don't read the source |
| Wireshark `packet-amr.c` | RFC 4867 dissector | GPL-2.0 | **Use the built tool** to validate our packets on the wire; don't read the source |
| rtpengine (sipwise) | Production AMR/AMR-WB transcoding, CMR-interval logic, mode-set handling | GPL-3.0 | Read its **documentation** (`docs/transcoding.md`) for behavioural requirements — it documents real-world CMR and octet-align pitfalls. Don't read the source |
| `traud/asterisk-amr` | Asterisk transcoding module over opencore/vo-amrwbenc | Asterisk terms | Interop counterparty for Phase 8 |
| crates.io `amr` | Unrelated ("Applied Mind Radio"), GPL-3.0 | — | Not an audio codec. Name is taken on crates.io |

**Confirmed: there is no pure-Rust AMR-NB/AMR-WB codec in existence.** Searches of
crates.io, docs.rs, and GitHub found nothing. If we complete L3 this would be the first,
which is a meaningful piece of ecosystem value beyond rvoip itself.

Behavioural notes worth stealing from rtpengine's docs (requirements, not code):

- On receiving a CMR, switch encoder bitrate **unconditionally**, even upward past the
  locally preferred rate.
- Its defaults are AMR 6.70 and AMR-WB 14.25 — sensible defaults for us too.
- A `CMR-interval` timer that tracks which FTs actually arrived and requests at most the
  *next-highest* unused allowed mode, at most once per interval, avoids CMR thrash.
- Answer SDP must preserve the offered `octet-align` variant; failing to do so is a
  recurring, widely-reported interop bug. Add an explicit regression test for it.

---

## 6. Architecture decision

**Decided: pure-Rust from-spec port in the shipped crate, with Apache-2.0 C
implementations used out-of-tree as a development-time oracle** (§1.2, §1.2.1). The
alternatives are recorded below so the decision is reviewable, not to reopen it.

### 6.1 Chosen — pure-Rust from-spec port, oracle-assisted

Mirrors what this repo already did for G.729A (`crates/media/codec-core/src/codecs/g729/`,
~120 files of fixed-point Rust with per-module Q-format documentation). No C toolchain in
the build, no `build.rs`, `no_std`-able later, cross-compiles everywhere, MIT-clean, and
the first such implementation in the Rust ecosystem.

The key structural insight is that **"pure Rust" is a property of the shipped artefact,
not of the development process.** An oracle that runs offline and emits data files gives
the full per-stage debugging benefit while leaving the dependency graph, the license
posture, and the cross-compilation story untouched. This is strictly better than either
extreme: shipping FFI (§6.2) or developing blind (§6.4).

### 6.2 Rejected — FFI to `opencore-amr` + `vo-amrwbenc` as a shipped backend

Would have given working AMR-NB/WB in ~1–2 weeks, and both are Apache-2.0 and
allow-listed by `deny.toml`. Rejected as a *runtime* component because it introduces a C
build dependency into a workspace that has almost none (only the optional `opus` crate and
`env-libvpx-sys` in `rvoip-webrtc`), complicates cross-compilation and `cargo deny`, and
adds Apache-2.0 attribution obligations to an MIT project. The same libraries are adopted
as build-time tooling instead (§6.1), and remain the recovery option if the port stalls
(risk R10).

### 6.3 Rejected — payload format only, no codec kernel

Cheap and clean-room, but transcoding is a day-one requirement (§1.2), so a kernel-free
build does not meet the goal. Phases 1–2 still deliver this capability as a by-product at
~week 6.

### 6.4 Rejected — float-model-first, no external oracle

The previous revision of this plan front-loaded a full `f64` reference model (~3–4 weeks,
no shippable artefact) to serve as the per-stage oracle. Superseded: a real bit-exact
oracle does the same job better and for less. A **reduced** float model survives, scoped
only to wherever Phase 0 oracle qualification comes back weak — expected to be the WB
encoder's ISF quantisation and codebook search, and nothing else.

### 6.5 Considered and not taken — direct port of `opencore-amr` C to Rust

Worth recording because it is legally available and much faster than a from-spec
implementation. Apache-2.0 permits derivative works, so transliterating the C into Rust
would be a lawful route to a pure-Rust codec, subject to retaining copyright notices,
stating changes, and adding a `THIRD_PARTY_NOTICES.md` entry.

Not taken because it would make parts of `codec-core` Apache-2.0-derived rather than MIT,
inherit C-shaped code structure into a codebase with an established idiom (see the G.729
module layout), and produce something harder to reason about than a spec-derived
implementation. **If schedule pressure becomes acute, this is a more attractive fallback
than the FFI backend** — it preserves the pure-Rust property and costs only license
attribution. Flagged for the Phase 4 checkpoint (risk R10).

Note the asymmetry with the 3GPP reference: the same transliteration applied to TS 26.073 /
26.173 is **not** available, because 3GPP's terms prohibit distributing "verbatim copies of
source code (or lightly modified copies)" (§2.3). The Apache-2.0 route is lawful precisely
because Apache-2.0 grants what 3GPP withholds.

---

## 7. Repo integration map

Every touchpoint below was located in the current tree; this is the concrete surface a PR
has to cover. For comparison, adding G.729 touched ~30 files across five crates.

### 7.1 `crates/media/codec-core` — the codec kernel

```
src/types.rs                      CodecType::{AmrNb, AmrWb}; AmrParameters; CodecConfig
                                  helpers; bitrate_range()/payload_type()/quality_score()/
                                  supported_sample_rates() arms
src/codecs/mod.rs                 CodecFactory::create / create_by_name /
                                  create_by_payload_type; supported_codecs();
                                  CodecCapabilities::get_all()
src/codecs/amr/                   NEW — see §7.5 for the module tree
tests/vectors/amr/                NEW — checked-in per-stage oracle fixtures (§1.2.1)
tools/amr-oracle/                 NEW — standalone Cargo project, EXCLUDED from the
                                  workspace. Vendors opencore-amr + vo-amrwbenc, builds
                                  them with cc, dumps per-stage vectors. Never a
                                  dependency of the shipped crate
Cargo.toml                        features: amr-nb, amr-wb, amr = [both],
                                  all-codecs += amr. No FFI feature (§6.2).
                                  [workspace] exclude = ["…/tools/amr-oracle"]
```

**Blocking API gap.** The `AudioCodec` trait (`src/types.rs:13`) is
`encode(&[i16]) -> Vec<u8>` / `decode(&[u8]) -> Vec<i16>` with a single fixed
`frame_size()`. It has nowhere to express:

- the **mode** a decoded frame arrived in, or the mode to encode the next frame in;
- a **CMR** received from the peer;
- SID / NO_DATA / SPEECH_LOST frame types distinct from "0 bytes";
- the frame-quality (`Q`) bit driving PLC.

G.729 dodged this by encoding frame type in the output length (10 / 2 / 0 bytes). AMR
cannot: several modes share byte lengths across NB and WB, and mode selection is an
*input*, not just an output.

**Decision required:** add a codec-agnostic extension trait rather than widening
`AudioCodec` (which would break `G711Codec` / `G729Codec` / `OpusCodec`):

```rust
/// Codecs whose per-frame bitrate is negotiated and can change frame to frame.
pub trait VariableRateCodec: AudioCodec {
    fn set_mode(&mut self, mode: u8) -> Result<()>;
    fn current_mode(&self) -> u8;
    fn allowed_modes(&self) -> &[u8];
    fn encode_frame(&mut self, samples: &[i16]) -> Result<CodedFrame>;
    fn decode_frame(&mut self, frame: &CodedFrame) -> Result<Vec<i16>>;
}

pub struct CodedFrame {
    pub frame_type: FrameType,   // Speech(mode) | Sid | NoData | SpeechLost
    pub quality_ok: bool,        // maps to RFC 4867 Q bit
    pub bits: Vec<u8>,
}
```

Per the workspace convention on migrations, this is a *new* trait implemented alongside
the existing one — `AudioCodec` keeps working for fixed-rate codecs and AMR implements
both, with the `AudioCodec` impl using the currently-selected mode.

### 7.2 `crates/media/rtp-core` — RFC 4867 payload format

```
src/payload/amr.rs                NEW — AmrPayloadFormat implementing PayloadFormat,
                                  plus the typed packer/depacker
src/payload/mod.rs                module registration + re-exports
src/payload/registry.rs           dynamic PT registration for AMR / AMR-WB
```

The existing `PayloadFormat` trait (`src/payload/traits.rs`) is
`pack(&[u8], u32) -> Bytes` / `unpack(&[u8], u32) -> Bytes` — byte-slices in, byte-slices
out, with no way to carry CMR, ToC, or multiple frames per packet. Implement a **typed
API first** and adapt it to `PayloadFormat` for the trivial single-frame case:

```rust
pub struct AmrPacketizer { variant: AmrVariant, octet_aligned: bool, /* … */ }
pub struct AmrPacket { pub cmr: Option<u8>, pub frames: Vec<AmrToCFrame> }
```

Multi-frame-per-packet (ptime 40/60/80) and interleaving are the reason the typed API is
non-negotiable.

### 7.3 `crates/media/media-core` — pipeline plumbing

```
src/codec/audio/amr.rs            NEW — mirrors codec/audio/g729.rs (423 lines)
src/codec/audio/mod.rs            registration
src/codec/factory.rs              construction
src/codec/mapping.rs              CodecMapper: dynamic PT ↔ "AMR"/"AMR-WB". Note it
                                  currently special-cases Opus via OpusConfig; AMR needs
                                  the same treatment (AmrConfig: variant, octet_align,
                                  mode_set) because one codec name maps to several
                                  negotiated shapes
src/codec/transcoding.rs          AMR ↔ PCMU/PCMA/Opus paths (incl. NB↔WB resampling)
src/rtp_processing/codec/sdp.rs   fmtp emit/parse for AMR parameters
src/rtp_processing/codec/registry.rs
src/rtp_processing/payload/registry.rs
src/relay/controller/codec_fallback.rs, codec_runtime.rs
src/engine/media_engine.rs
src/types/mod.rs, src/api/types.rs
Cargo.toml                        amr-nb/amr-wb/amr features → rvoip-codec-core
```

### 7.4 `crates/sip` — SDP negotiation

```
crates/sip/sip-core/src/sdp/attributes/fmtp.rs     AMR fmtp parameter parsing
crates/sip/sip-core/src/sdp/attributes/rtpmap.rs   AMR/8000, AMR-WB/16000
crates/sip/rvoip-sip/src/adapters/media_adapter.rs offer/answer, PT selection
crates/sip/rvoip-sip/Cargo.toml                    amr features → rvoip-media-core
crates/rvoip/Cargo.toml                            facade features → rvoip-sip
```

**Concrete blocker found at `media_adapter.rs:379`:**

```rust
96..=127 => cfg!(feature = "opus") && mapping_matches("opus", 48_000, &[1, 2]),
```

The entire dynamic payload-type range is currently hardcoded to Opus. `rtpmap_for_pt`
(:254), `fmtp_for_pt_with_g729_annex_b` (:274), `payload_codec_available` (:345) and
`codec_name_for_payload` (:330) are likewise fixed `match` tables over known PTs. AMR
requires PT→codec to become a **negotiated map built from the offer's `a=rtpmap`**, not a
constant table. This refactor is a prerequisite for Phase 2 and should be sized as its own
work item — it also unblocks any future dynamic-PT codec.

AMR fmtp negotiation rules to implement (RFC 4867 §8.3):

- `mode-set` is **not** a preference list — it is a hard restriction. The answer's active
  set is the intersection; if empty, the payload type must be rejected.
- Absent `mode-set` means all modes allowed.
- `octet-align` must match on both sides; the answer must not silently flip it.
- If offering both framings, offer them as **two distinct payload types**.
- `mode-change-period=2` and `mode-change-neighbor=1` constrain the *encoder's* mode
  trajectory — the encoder needs a mode-change policy object, not just a current mode.

### 7.5 Proposed `codecs/amr/` module tree

Follows the existing `codecs/g729/impls/` decomposition (small files, one concern each,
Q-format documented in the module header).

```
src/codecs/amr/
├── mod.rs                    AudioCodec / AudioCodecExt / VariableRateCodec adapter
├── common/                   shared by NB and WB
│   ├── basicop.rs            ETSI basic operators: add, sub, mult, L_mac, L_msu, shr,
│   │                         shl, norm_l, norm_s, div_s, round, saturate  (Q15/Q31)
│   ├── bits.rs               bit pack/unpack, class A/B/C reordering
│   ├── homing.rs             EHF/DHF detection + home-state reset (TS 26.071/26.101)
│   └── dsp/                  autocorrelation, lag windowing, Levinson-Durbin,
│                             A(z)↔LSP/ISP, interpolation, residual + synthesis filters,
│                             weighting filter, correlation helpers
├── reference/                CONDITIONAL, TEST-ONLY float model — only if Phase 0 oracle
│                             qualification comes back weak, and scoped to the affected
│                             stages (expected: WB encoder ISF quant + codebook search).
│                             Never shipped. See §9.2d
├── nb/
│   ├── tables/               windows, LSP codebooks (MA-predicted split VQ + SMQ for
│   │                         MR122), gain codebooks, grids, bit-ordering tables
│   ├── preproc.rs            HP filter + downscaling
│   ├── lp/                   windowing → autocorr → Levinson → LSP → interpolation
│   ├── lsp_quant/            split VQ w/ MA prediction (MR475..MR102); SMQ (MR122)
│   ├── pitch/                open-loop (per-mode cadence), closed-loop (1/6 and 1/3
│   │                         fractional), lag encode/decode
│   ├── fixed_cb/             per-mode algebraic codebook search + decode
│   │                         (2/3/4/8/10-pulse variants)
│   ├── gain/                 per-mode gain VQ, MA log-energy gain prediction
│   ├── dtx/                  VAD (26.094 opt.1 & opt.2), DTX (26.093), SID/CNG (26.092)
│   ├── conceal.rs            error concealment (26.091)
│   ├── postfilter/           adaptive postfilter (formant, tilt, AGC), HP, upscale
│   ├── bitstream/            TS 26.101 frame structure; IF1 / IF2 / RTP orderings
│   └── codec/                encoder.rs, decoder.rs, mode-switch state machine
└── wb/
    ├── resample.rs           16 kHz ↔ 12.8 kHz decimation/interpolation, pre/de-emphasis
    ├── tables/               ISF codebooks (S-MSVQ), gain tables, bit ordering
    ├── lp/                   order-16 analysis, ISP/ISF conversion + interpolation
    ├── isf_quant/            split + multistage VQ (mode-dependent bit budgets)
    ├── pitch/                open-loop, closed-loop (1/4 and 1/2 resolution)
    ├── fixed_cb/             4 tracks × 16 positions; 1–6 pulses/track by mode
    ├── gain/                 pitch gain + fixed-codebook gain-correction VQ
    ├── highband.rs           6.4–7 kHz synthesis; transmitted HB gain in mode 8 only
    ├── dtx/, conceal.rs, postproc/, bitstream/, codec/   (as NB)
```

`common/basicop.rs` is worth building and testing **first and in isolation** — every
subsequent bit-exactness bug traces back to it, and the G.729 port already proves the
pattern works in this codebase.

`reference/` is the one structural departure from the G.729 layout, and it exists because
this port has no external oracle (§6.1, §9.2a). Gate it so it cannot reach a release
build.

---

## 8. Phased plan

Sizing assumes one engineer familiar with the existing G.729 code. Multiply generously if
not; DSP bit-exactness work is notoriously spiky.

### Phase 0 — Foundations (1 week)

- [ ] Acquire and archive all specs from §3.
- [ ] Legal actions **IP-1** and **IP-2** opened; IP-2 answered in this phase.
- [ ] **Oracle qualification.** Stand up the full roster (§1.2.1): fetch and build the
      3GPP fixed-point references (TS 26.073 / 26.173) and floating-point references
      build `opencore-amr` and `vo-amrwbenc`; install FFmpeg. Run all
      of them over a shared corpus and record, per path (NB enc, NB dec, WB enc, WB dec),
      which are bit-exact with tier 1. Determines which fixtures can be committed directly
      and which need the external-fixture pattern.
- [ ] **Cross-validation baseline.** Diff the oracles against *each other* on the same
      corpus and log every disagreement. Each one is a candidate spec ambiguity, and
      knowing them before writing code is worth more than finding them during Phase 4.
- [ ] `CodecType::AmrNb` / `CodecType::AmrWb`, `AmrParameters`, feature flags wired
      end-to-end (`rvoip` → `rvoip-sip` → `media-core` → `codec-core`) with a stub codec
      that returns `feature_not_enabled`.
- [ ] ADR recorded for the `VariableRateCodec` trait (§7.1).

**Exit:** `cargo build --all-features` green; AMR appears in `supported_codecs()` behind
its feature; oracle bit-exactness recorded per path; no codec behaviour yet.

### Phase 1 — RFC 4867 payload format + file storage format (2–3 weeks) ← *test infrastructure*

This comes first because the **AMR file storage format reader is what loads the 3GPP test
sequences and the oracle's bitstream dumps**. Everything from Phase 3 onward depends on it.
The wire-format work is a by-product that happens to also enable relay.


- [ ] `AmrPacketizer` / `AmrDepacketizer`: bandwidth-efficient **and** octet-aligned.
- [ ] CMR, ToC chains, F/FT/Q, multi-frame packets, NO_DATA / SPEECH_LOST.
- [ ] Frame CRC (poly `1 + x² + x³ + x⁴ + x⁸` over class A bits); robust sorting; interleaving.
- [ ] AMR file storage format (`#!AMR\n` / `#!AMR-WB\n`) — needed for test fixtures anyway.
- [ ] `PayloadFormat` adapter + dynamic PT registration in `rtp-core`.

**Tests:** exhaustive round-trip over every (variant × mode × framing × frames-per-packet)
combination; malformed-input rejection (reserved FT, truncated payload, F-bit runaway,
CMR out of range); byte-for-byte comparison against captured real-world AMR pcaps
dissected with Wireshark; `cargo fuzz` target in the existing `crates/media/fuzz`.

**Exit:** bit-exact packetization both ways, validated against third-party captures.

### Phase 2 — SDP negotiation + relay path (2–3 weeks) ← *can run in parallel with Phase 3+*

Independent of the codec kernel, so a second engineer can own this concurrently with the
DSP work. If staffed by one person, it can also be deferred until after Phase 5 without
blocking anything — but doing it now means every later phase has a real call path to test
against instead of a synthetic harness.


- [ ] Refactor `media_adapter.rs` dynamic-PT handling off the hardcoded Opus arm (§7.4).
- [ ] fmtp parse/emit for all §4.3 parameters.
- [ ] Offer/answer: `mode-set` intersection, `octet-align` matching, dual-PT offers,
      reject-on-empty-intersection.
- [ ] Mode-change policy object honouring `mode-change-period` / `mode-change-neighbor`.
- [ ] CMR send/receive state machine with a `CMR-interval`-style damper.
- [ ] Pass-through/relay path: AMR in → AMR out with no codec kernel.

**Exit:** rvoip completes an AMR-WB call as a relaying B2BUA against Asterisk and against
Kamailio+rtpengine, in both framings, with a mode switch observed mid-call.

### Phase 3 — `common/` layer + oracle harness (2–3 weeks)

Produces no shippable artefact. It is the foundation everything else is debugged against,
and skipping or rushing it is the single most likely cause of Phases 4–7 overrunning.
Because WB comes first, build `common/` order-16- and ISP-capable from the outset rather
than growing it from an order-10 LSP-only base.

- [ ] `common/basicop.rs` — the full ETSI basic-operator set, with the exhaustive tests in
      §9.1. **Nothing else starts until this is green.**
- [ ] `common/dsp/` — autocorrelation, lag windowing, Levinson-Durbin, A(z)↔LSP **and**
      A(z)↔ISP, interpolation, residual/synthesis/weighting filters, correlation helpers.
- [ ] `common/bits.rs` — bit pack/unpack, class A/B/C reordering.
- [ ] `common/homing.rs` — EHF/DHF per TS 26.071/26.101, plus the checkpoint/resynchronise
      test helper described in §1.2.1.
- [ ] **Oracle harness** at `tools/amr-oracle/` (excluded from the workspace): vendored
      `opencore-amr` + `vo-amrwbenc`; a `make fetch` step that downloads the 3GPP reference
      (TS 26.073 / 26.173) into a **git-ignored** directory — never committed (§2.3);
      `build.rs` with `cc`; instrumentation patches that dump per-stage inputs/outputs; and
      a generator that writes fixtures into `tests/vectors/amr/`.
- [ ] **Stage-diff harness** in the test suite: replays a checked-in fixture through one
      Rust stage and reports the first divergence by frame and stage.

**Exit:** basic operators exhaustively tested; oracle builds and emits per-stage fixtures
for at least one WB decode sequence; stage-diff harness demonstrated on a deliberately
injected bug; `cargo test` green with no C toolchain present.

### Phase 4 — AMR-WB decoder, fixed point (5–7 weeks)

Order: bit unpacking + frame structure (TS 26.201) → ISF dequant (S-MSVQ) → adaptive
codebook → fixed codebook decode → gain dequant → synthesis filter → de-emphasis →
12.8→16 kHz interpolation → high-band synthesis (incl. mode-8 transmitted HB gain) →
post-processing → PLC (26.191) → CNG (26.192).

The resampler and the high-band synthesis are the two places where "close enough" passes
casual listening and fails bit-exactness. Diff them against the oracle aggressively.

One stage at a time, green against the oracle before the next. Do not batch.

Carries the first-codec tax: the `common/` layer gets shaken out here rather than in a
simpler NB pass, which is the cost of WB-first (§1.2).

**Exit:** bit-exact against TS 26.174 decoder sequences, all 9 modes + SID + NO_DATA +
SPEECH_LOST + the erroneous-frame sequences. Cross-implementation matrix (§9.2b) wired up
in the inbound direction: our decoder correctly decodes bitstreams from the 3GPP reference,
`opencore-amr`, and `vo-amrwbenc`.

### Phase 5 — AMR-WB encoder, fixed point (6–8 weeks) ← *the HD-voice milestone*

Order: preproc (HP filter, 16→12.8 kHz decimation, pre-emphasis) → order-16 LP analysis →
ISF quantisation (S-MSVQ) → open-loop pitch → closed-loop pitch (1/4 and 1/2 resolution) →
algebraic codebook search (4 tracks × 16 positions, 1–6 pulses/track by mode) → gain
quantisation → bitstream → VAD (26.194) / DTX (26.193) / SID.

The codebook searches are the bulk of the work. Validate each against a brute-force
exhaustive search over a single subframe (§9.2) before trusting it on a sequence.

Oracle: **TS 26.173 is normative for WB encode**, so this phase has an authoritative
per-stage reference. If Phase 0 found `vo-amrwbenc` not bit-exact, that only affects which
fixtures can be committed (use reference-generated dumps via the external-fixture pattern),
not whether the phase is debuggable.

**Exit:** bit-exact against TS 26.174 encoder sequences, all 9 modes, DTX on and off.
Cross-implementation matrix completed in the outbound direction: **FFmpeg's native AMR-WB
decoder, `opencore-amr`, and the 3GPP reference all decode our encoder's output to correct
audio** (§9.2b). **AMR-WB ↔ Opus/G.711 transcoding — the HD-voice goal — is reachable
here.**

### Phase 6 — AMR-NB decoder, fixed point (2–3 weeks)

Order: bit unpacking + frame structure (TS 26.101) → LSP dequant → adaptive codebook →
fixed codebook decode → gain dequant → synthesis filter → postfilter → PLC (26.091) →
CNG (26.092).

Substantially cheaper than the same phase would have been first: `common/`, the oracle
harness, the stage-diff workflow, and the bitstream machinery are all proven by now, and
NB is the simpler codec (order-10 LSP, no resampler, no high band).

**Exit:** bit-exact against TS 26.074 decoder sequences, all 8 modes + SID + NO_DATA + the
erroneous-frame sequences; inbound cross-implementation matrix green.

### Phase 7 — AMR-NB encoder, fixed point (3–5 weeks)

Order: preproc → LP analysis (two analyses per frame in MR122) → LSP quant (split VQ with
MA prediction for MR475–MR102; SMQ for MR122) → open-loop pitch → closed-loop pitch (1/6
and 1/3 fractional) → per-mode fixed codebook search → gain quant → bitstream →
VAD (26.094) / DTX (26.093) / SID.

Eight per-mode algebraic codebook searches are the bulk of the work; MR122's 10-pulse
search is the hardest single item. `opencore-amr` is a 3GPP-reference-derived oracle here,
so this phase should be the best-supported of the four.

**Exit:** bit-exact against TS 26.074 encoder sequences, all 8 modes, DTX on and off;
outbound cross-implementation matrix green (FFmpeg native AMR-NB decoder, `opencore-amr`,
3GPP reference). **AMR-NB ↔ G.711/Opus transcoding complete.**

### Phase 8 — Transcoding, interop, performance, hardening (3–4 weeks)

- [ ] Transcoding matrix wired through `media-core/src/codec/transcoding.rs`:
      AMR-NB ↔ PCMU/PCMA, AMR-WB ↔ Opus, AMR-NB ↔ AMR-WB (incl. 8↔16 kHz resampling).
- [ ] Interop matrix: Asterisk (`traud/asterisk-amr`), Kamailio + rtpengine, FreeSWITCH,
      a commercial SBC if available, and at least one real VoLTE/IMS trunk.
- [ ] Benchmarks + profiling per `crates/sip/rvoip-sip/docs/PROFILING.md`.
- [ ] Fuzzing of decoder and depacketizer.
- [ ] Docs, `CHANGELOG.md`, README codec table, release-gate evidence.

### Timeline summary

Single engineer familiar with the existing G.729 code, with Phase 2 parallelised onto a
second person (it is the only phase that parallelises cleanly — Phases 3–7 are one
dependency chain).

| Milestone | Phase | Earliest |
|---|---|---|
| AMR-WB relay / pass-through calls (no transcoding) | 1–2 | ~week 6 |
| `common/` + oracle harness + stage-diff workflow | 3 | ~week 8 |
| AMR-WB decode, bit-exact | 4 | ~month 3–4 |
| **AMR-WB ↔ Opus transcoding — the HD-voice goal** | **5** | **~month 4–5** |
| AMR-NB decode, bit-exact | 6 | ~month 5–6 |
| **AMR-NB ↔ G.711 transcoding** | 7 | ~month 6–7 |
| Interop, transcoding matrix, hardening | 8 | ~month 7–8 |

Add 2–3 weeks to Phase 5 if Phase 0 finds `vo-amrwbenc` is not bit-exact (§1.2.1) — the
largest single schedule uncertainty.

**Effect of the two revisions to this plan.** WB-first moves the HD-voice milestone from
~month 9–11 to ~month 4–5. Roughly half of that comes from reordering (WB no longer waits
behind NB) and roughly half from the oracle decision (per-stage debugging, and dropping
the 3–4 week float-model phase). Total time to *both* codecs is only modestly reduced —
~7–8 months against ~8–11 — because the work is largely the same; what changed is which
codec lands first and how fast each phase debugs.

---

## 9. Test & validation strategy

This repo is deliberately careful about not overclaiming conformance — see the header of
`src/codecs/g711/tests/itu_validation_tests.rs`, which explicitly disclaims evidence from
files not in the tree. Keep that discipline: **a test named `*_conformance` must actually
run normative vectors, or be renamed.**

### 9.1 Layer 0 — basic operators

Every ETSI basic operator gets an exhaustive or boundary-exhaustive unit test:
saturation at `i16`/`i32` limits, rounding direction, `div_s` domain restrictions,
`norm_l`/`norm_s` on zero. `i16`-domain operators can be tested exhaustively over all
2³² input pairs where feasible, otherwise property-tested against a slow-but-obvious
`i64` model. **Do this before anything else** — it is cheap and eliminates a whole class
of downstream bugs.

### 9.2 Layer 1 — per-stage differential testing (the workhorse)

This is what makes bit-exactness tractable: a failure localises to one stage instead of one
frame. Five mechanisms, roughly in descending order of how much load they carry.

**(a) Per-stage oracle vectors.** The out-of-tree harness (§1.2.1) runs the instrumented C
implementations over a speech corpus and dumps every stage's inputs and outputs. Those
dumps become compact fixtures; each Rust module gets a test that replays its stage against
them. Because every side is a fixed-point implementation of the same spec, comparison is
**exact equality**, not tolerance-based — which is precisely why this beats the float model
it replaced.

Weight by tier. A mismatch against the **3GPP fixed-point reference** (TS 26.073 / 26.173)
is dispositive — conformance is *defined* as agreement with it, so there is no judgement
call. A mismatch against `opencore-amr` or `vo-amrwbenc` is dispositive only where Phase 0
found that library bit-exact with tier 1; otherwise it is a lead to investigate.

Fixture provenance follows IP-2 q2 (§2.3): prefer Apache-2.0-derived dumps for committed
data, and hold reference-derived dumps locally (external-fixture pattern, §9.3) until the
question is answered.

Keep fixtures small (a few hundred KB total) — sample a diverse subset of frames
(voiced / unvoiced / onset / silence / DTMF / music / clipping) rather than whole files.

**(a′) Whole-frame consensus across all oracles.** Separately from per-stage dumps, run the
full roster (§1.2.1) over the corpus and compare whole encoded frames / decoded PCM. Cheap,
needs no instrumentation, and covers the tier-3 oracles that cannot be instrumented at all.
Three outcomes, each actionable:

- Consensus, we differ → we are wrong. Bisect with per-stage dumps.
- **Oracles disagree with each other** → a spec ambiguity. Resolve from the spec text and
  the conformance sequences, then record the resolution in the module doc header (§2.4c) so
  the reasoning survives. Phase 0 establishes the baseline set of these before any code is
  written.
- Consensus including us → strong signal, though only the tier-1 comparison and the 3GPP
  conformance sequences are *evidence* of conformance (§9.3).

The floating-point references (TS 26.104 / 26.204) are **not** part of this. Being
non-bit-exact by design, they cannot confirm the only property under test, and a
disagreement with them would not say which side is wrong.

**(b) Cross-implementation round-trip matrix — how third-party decoders validate our
encoder.**

FFmpeg has **native AMR-NB and AMR-WB decoders but no native AMR encoder** — it encodes
only through `libopencore-amrnb` / `libvo-amrwbenc`. So FFmpeg cannot serve as an
independent *encoder* oracle. What it can do is more useful: **decode our encoder's output
with an implementation that shares no code with anything we validated against**, and tell
us whether the result is correct audio.

Generalised, that is an N×N matrix run over the corpus:

| Direction | What it proves |
|---|---|
| **Our encoder → each third-party decoder** (FFmpeg native, `opencore-amr`, 3GPP reference) | Everyone can decode our bitstream, and the audio they get is right |
| **Each third-party encoder → our decoder** (3GPP reference, `opencore-amr`, `vo-amrwbenc`) | We correctly decode bitstreams produced by every implementation in the field |

Why this earns its own layer rather than folding into (a):

- **It catches a bug class per-stage dumps structurally cannot.** If every DSP stage is
  correct but bits are packed in the wrong order — wrong bit ordering per TS 26.101/26.201,
  wrong class A/B/C sorting, an off-by-one in the mode field — every per-stage comparison
  passes and cross-decoding fails immediately and loudly. Bitstream assembly is exactly the
  part the stage dumps skip.
- **It measures interoperability, which is the deployment bar, and bit-exactness is the
  conformance bar.** These are different, and the difference matters most for encoders. A
  bit-exact decoder is interoperable by construction; an encoder that is *not* bit-exact can
  still be perfectly interoperable and produce good audio. If Phase 5 or 7 stalls on the
  last few bits of some mode, this matrix is what tells us whether we have a shipping
  blocker or a conformance footnote.
- **It needs no instrumentation and no source access**, so it works with the tier-3 oracles
  and with any binary we can obtain — including, later, real SBCs and handsets.

Wire it up as soon as each direction becomes possible: our decoder against third-party
encoders from the Phase 4 exit, our encoder against third-party decoders from the Phase 5
exit. Quality is measured, not eyeballed — segmental SNR plus a spectral-distance check
against the same input decoded by the reference.

**(c) Homing frames as checkpoints.** TS 26.071/26.101 define encoder and decoder homing
frames that drive every bit-exactly-specified function into a predefined home state, and
consecutive homing frames must produce homing frames at the output. Two uses:

- A standalone conformance test: feed homing frames, assert the exact specified output.
  Cheap, spec-mandated, catches whole classes of state bugs with no test vectors at all,
  and available from Phase 3 before any oracle fixtures are generated.
- Resynchronisation: injecting a homing frame mid-sequence resets state, so a failure at
  frame 900 can be isolated without replaying frames 1–899 and without one early bug
  poisoning every subsequent comparison.

**(c) Brute-force reference searches.** The ACELP codebook searches are fast heuristics
approximating a well-defined optimum. For a single subframe the optimum is computable
exhaustively. Test the fast search against the slow one over a corpus of subframes; the
same technique applies to the LSP/ISF VQ codebook searches. This remains necessary even
with a good oracle, because it validates the *search* rather than one implementation's
result. Useful independently of oracle quality, and cheap.

**(d) Reduced float model — now unlikely to be needed.** It existed as insurance against
having no trustworthy WB-encode oracle. TS 26.173 supplies one, so this drops to a
contingency: build it only if a specific stage resists diagnosis, scoped to that stage. The
previous revisions' float-model-first approach for the whole codec is dropped as redundant
(§6.4).

### 9.3 Layer 2 — 3GPP conformance sequences (the proof)

TS 26.074 (NB) and TS 26.174 (WB) are the normative vectors and the only acceptable
evidence for a bit-exactness claim. Pending **IP-2**, use the opt-in external-fixture
pattern:

```
AMR_TEST_VECTORS=/path/to/26074 cargo test -p rvoip-codec-core --all-features -- --ignored
```

Tests `#[ignore]` by default and skip loudly with a message when the env var is unset, so
a green local run is never mistaken for conformance evidence. Wire the vectors into the
release gate as a required job once licensing is resolved.

### 9.4 Layer 3 — payload format

Round-trip over the full cross-product, malformed-input rejection, third-party pcap
comparison via Wireshark, and a `cargo fuzz` target. Explicit regression test for the
"answer must preserve the offered `octet-align` variant" bug documented by rtpengine.

### 9.5 Layer 4 — SDP / negotiation

Table-driven offer/answer cases: `mode-set` intersection (including empty → reject),
missing `mode-set` (= all), `octet-align` mismatch, dual-PT offers, `crc`/`robust-sorting`
implying octet-align, `mode-change-period=2` and `mode-change-neighbor=1` constraining the
encoder trajectory.

### 9.6 Layer 5 — end-to-end and interop

Full SIP calls through `rvoip-sip` with AMR-WB, both framings, mid-call mode switching,
packet loss with PLC, DTX gaps, and re-INVITE renegotiation. Interop matrix per Phase 8.

### 9.7 Layer 6 — quality, performance, robustness

- Objective quality (segmental SNR now; PESQ/POLQA if a licensed tool is available) —
  a *sanity* signal, never a substitute for bit-exactness.
- Criterion benchmarks per mode; target real-time factor well under 0.05 per channel per
  core so a single core carries ≥ 20 concurrent AMR-WB legs.
- Fuzzing of decoder and depacketizer; the decoder must never panic on arbitrary input.
- Long-run soak: 24 h continuous encode/decode checking for state drift and leaks.

### 9.8 CI

Per the workspace convention, **all codec validation runs with `--all-features`** —
default `cargo test` silently skips feature-gated targets and gives a false green.

```bash
cargo test -p rvoip-codec-core --all-features
```

Add AMR jobs to `.github/workflows/pr-gate.yml` and `main-ci.yml`. These must run **with no
C toolchain assumptions** — they consume checked-in fixtures only, which is the property
that keeps the oracle out of the shipped build. Add a `nightly-interop.yml` job that
rebuilds the oracle, regenerates fixtures, and fails on any diff against what is committed;
that is what catches instrumentation drift (risk R11). Interop runs go there too.

---

## 10. Risks

| # | Risk | Impact | Mitigation |
|---|---|---|---|
| R1 | Patent expiry relied on secondary sources | Legal | **IP-1** before Phase 3; Phases 0–2 are RFC-only and unaffected |
| R2 | 3GPP reference C or copyleft source is accidentally redistributed | Legal | 3GPP reference is fetched into a git-ignored dir, never committed (§2.3); no copyleft source read at all; see also R13 on structural similarity |
| R3 | 3GPP test sequences can't be vendored | Weakens conformance claim | **IP-2**; opt-in external fixtures (§9.3) meanwhile |
| R4 | Bit-exactness proves harder than G.729A — 17 modes, two codecs | Schedule | Per-stage oracle vectors + homing frames + brute-force searches (§9.2); decoder before encoder within each codec; one stage green before the next |
| R5 | `AudioCodec` trait can't express variable rate | Blocks everything downstream | `VariableRateCodec` extension trait decided in Phase 0 (§7.1) |
| R6 | Dynamic-PT handling hardcoded to Opus (`media_adapter.rs:379`) | Blocks Phase 2 | Size the refactor as its own item; it unblocks future dynamic-PT codecs too |
| R7 | Bandwidth-efficient vs octet-aligned interop bugs | Field failures | Both framings from Phase 1; explicit regression tests; Wireshark validation |
| R8 | `vo-amrwbenc` is not bit-exact with TS 26.173, so its WB-encode stage dumps cannot be trusted | **Measured, and real but narrow.** `vo-amrwbenc` reproduces TS 26.173 at seven of the nine WB rates and **not at 12.65 or 14.25 kbit/s**; `opencore-amr` reproduces TS 26.073 at **all eight** NB rates. So an NB-encode fixture from the Apache-2.0 oracle is ground truth outright, and a WB-encode one is ground truth everywhere except those two adjacent mid-range rates, where it is a lead rather than an answer. The decoders are unaffected — both were validated against the 3GPP references directly | Done. `src/codecs/amr/qualification.rs` pins the result, and pins the divergent set exactly rather than bounding it, so a rate that starts or stops agreeing is read before it is accepted |
| R13 | Our implementation ends up structurally close enough to a reference C implementation to read as a "lightly modified copy" | **Low.** Oracle use and interop testing carry no copyright implication at all (§2.4); the exposure is only in shipped expression, and three factors compound against it — different language and not a transliteration, five independent oracles, documented per-module spec provenance. Retained because a language change alone is not a defence: mechanical translation is still a derivative work | Structure modules from the spec's block diagram, not any C file layout; do not carry over identifiers; follow the G.729 module idiom. Record spec section + validating oracles in each module header. Review at each phase exit |
| R9 | CMR thrash causing audible mode oscillation | Quality | `CMR-interval` damper (§5); soak test with an adversarial peer |
| R10 | Effort underestimated; port stalls mid-way with no transcoding delivered | Sunk cost — transcoding is a day-one requirement and does not arrive until ~month 4–5 | Two recovery options, in preference order: (a) direct Rust port of the Apache-2.0 C (§6.5) — keeps the pure-Rust property, costs only license attribution; (b) the FFI backend (§6.2), ~1–2 weeks from zero. Decide at the Phase 4 exit checkpoint |
| R11 | Oracle instrumentation patches drift from upstream, or dumps encode a subtly different stage boundary than the Rust code | Wasted debugging on a phantom mismatch | Pin the vendored upstream revision; keep instrumentation patches minimal and reviewed; when a mismatch resists explanation, check the stage boundary before the arithmetic |
| R12 | WB-first means `common/` is shaken out under the harder codec, inflating Phase 4 | Schedule | Accepted cost of WB-first (§1.2), already priced into Phase 4's 5–7 weeks. Build `common/` order-16/ISP-capable from the start rather than retrofitting |

---

## 11. Decisions taken and questions still open

### Resolved

1. **Strategy** — ✅ **Pure Rust in the shipped crate.** No FFI backend, not even opt-in.
2. **Scope** — ✅ **Transcoding required from day one.** The codec kernel is on the
   critical path; relay is a by-product, not the goal.
3. **Oracle** — ✅ **Two-tier, out-of-tree, vector-generating.** The **3GPP reference C**
   (TS 26.073 / 26.173) is the authority: in-house use for product design is expressly
   permitted, so it is fetched and instrumented locally but never committed (§2.3).
   `opencore-amr` + `vo-amrwbenc` (Apache-2.0) supply redistribution-safe fixtures. The
   shipped dependency graph is unchanged. The float reference model is dropped as
   redundant.
4. **Ordering** — ✅ **WB first, NB second.** WB is the priority and the HD-voice
   deliverable. The earlier NB-first recommendation was premised on WB bugs being expensive
   to debug unaided; the oracle decision largely removes that premise, and WB-first moves
   the goal milestone from ~month 9–11 to ~month 4–5 (§1.2, §8).

### Still open

5. **IP-2** — two parts, both Phase 0 (§2.3): (a) may the 3GPP test sequences be vendored?
   (b) may per-stage vectors *generated by running* the reference code be committed?
   Neither blocks the work — the external-fixture pattern covers both — but the answers
   determine how much of the test suite runs unaided in CI.

   **IP-2b is now cheaper to answer the wrong way than it was.** Oracle qualification
   (§1.2.1, risk R8) found `opencore-amr` bit-exact with TS 26.073 at every narrowband
   rate, so narrowband fixtures can be regenerated from an Apache-2.0 source if the
   3GPP-derived ones have to go. Wideband has the same escape at seven of its nine
   rates. Every build script reproduces its fixtures bit-identically from a clean
   fetch, so the change would be mechanical either way.

6. **Crate placement** — keep AMR inside `rvoip-codec-core` alongside G.711/G.729/Opus, or
   split it into a standalone publishable crate? A standalone pure-Rust AMR crate would be
   the first in the Rust ecosystem and has value beyond rvoip; note the name `amr` is
   already taken on crates.io by an unrelated GPL-3.0 project.

7. **Staffing** — Phase 2 parallelises cleanly onto a second engineer and pulls the
   HD-voice milestone in by ~2–3 weeks. Phases 3–7 are one dependency chain and do not. Is
   a second person available?

---

## 12. Sources

- [RFC 4867 — RTP Payload Format and File Storage Format for AMR and AMR-WB](https://www.rfc-editor.org/rfc/rfc4867.txt)
- [3GPP TS 26.071 — AMR Speech Codec; General description](https://www.arib.or.jp/english/html/overview/doc/STD-T63v9_60/5_Appendix/Rel4/26/26071-400.pdf)
- [TS 26.171 — AMR-WB Speech Codec: General Description (tech-invite index)](https://www.tech-invite.com/3m26/tinv-3gpp-26-171.html)
- [3GPP TS 26.190 — AMR-WB Transcoding functions](https://tec.gov.in/pdf/3gpp/TSDSI_Doc_1657/rel18/TS-26.190%20V1.0.0.pdf)
- [Wikipedia — Adaptive Multi-Rate audio codec](https://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec)
- [Wikipedia — Adaptive Multi-Rate Wideband](https://en.wikipedia.org/wiki/Adaptive_Multi-Rate_Wideband)
- [Patent expiry dates and software for AMR, AMR-WB, and AMR-WB+ (HydrogenAudio)](https://hydrogenaudio.org/index.php/topic,114506.0.html)
- [VoiceAge — AMR-WB essential patent portfolio](https://voiceage.com/Patent-Portfolio-Essential.html)
- [VoiceAge AMR-WB/G.722.2 patent pool launch (2010)](https://www.prnewswire.com/news-releases/voiceage-announces-the-launch-of-the-amr-wbg7222-speech-compression-standards-patent-pool-83263272.html)
- [3GPP FAQs — copyright and reproduction terms](https://www.3gpp.org/about-us/3gpp-faqs) ("in-house copies … for product design purposes"; "verbatim copies of source code (or lightly modified copies)")
- [3GPP Terms of Use](https://www.3gpp.org/terms-of-use)
- [3GPP Legal Matters](https://www.3gpp.org/about-us/legal-matters)
- [opencore-amr (SourceForge)](https://sourceforge.net/projects/opencore-amr/)
- [mstorsjo/vo-amrwbenc — VisualOn AMR-WB encoder](https://github.com/mstorsjo/vo-amrwbenc)
- [pschatzmann/codec-amr](https://github.com/pschatzmann/codec-amr)
- [traud/asterisk-amr](https://github.com/traud/asterisk-amr)
- [rtpengine transcoding documentation](https://github.com/sipwise/rtpengine/blob/master/docs/transcoding.md)
- [rtpengine issue #784 — AMR/AMR-WB bandwidth-efficient by default](https://github.com/sipwise/rtpengine/issues/784)
- [FFmpeg general documentation — AMR support and licensing](https://ffmpeg.org/general.html)
- [Wireshark packet-amr.c dissector](https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-amr.c)
- [Library of Congress — AMR-WB format description](https://www.loc.gov/preservation/digital/formats/fdd/fdd000255.shtml)
- [Library of Congress — AMR format description](https://www.loc.gov/preservation/digital/formats/fdd/fdd000254.shtml)
- [ETSI TS 126 071 V5.0.0 — AMR general description (homing frames / EHF / DHF)](https://www.etsi.org/deliver/etsi_ts/126000_126099/126071/05.00.00_60/ts_126071v050000p.pdf)