238 lines
5.5 KiB
Go
238 lines
5.5 KiB
Go
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
|
|
}
|