Audible output from AMBE3000

pull/1/head
Wijnand Modderman-Lenstra 9 years ago
parent 174b460919
commit ff51bb1dbd

@ -0,0 +1,28 @@
package main
import (
"crypto/rand"
"fmt"
)
func main() {
var raw = make([]byte, 4)
rand.Read(raw)
fmt.Printf("raw = %02x%02x%02x%02x\n", raw[0], raw[1], raw[2], raw[3])
for i := 0; i < 4*8; i++ {
b := i / 8
o := byte(7 - i%8)
fmt.Printf("%02x b=%d, o=%d, r=%d\n", raw[b], b, o, raw[b]>>o)
}
for i := 0; i < 4*8; i++ {
b := i / 8
o := byte(7 - i%8)
if (raw[b]>>o)&0x01 == 0x01 {
fmt.Print("1")
} else {
fmt.Print("0")
}
}
fmt.Println("")
}

@ -10,270 +10,16 @@ import (
"gopkg.in/yaml.v2"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"github.com/gordonklaus/portaudio"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/dmr"
"github.com/tehmaze/go-dmr/dmr/repeater"
"github.com/tehmaze/go-dmr/homebrew"
"github.com/tehmaze/go-dmr/ipsc"
"github.com/tehmaze/go-dsd"
)
var (
bursttypes = map[uint32]string{
0: "pi header",
1: "voice lc header",
2: "terminator with lc",
3: "csbk",
4: "csbk",
5: "appended mbc",
6: "data header",
7: "rate ½ data continuation",
8: "rate ¾ data continuation",
9: "idle",
}
headertypes = []string{
"UDT ", "Resp", "UDat", "CDat", "Hdr4", "Hdr5", "Hdr6", "Hdr7",
"Hdr8", "Hdr9", "Hdr10", "Hdr11", "Hdr12", "DSDa", "RSDa", "Prop",
}
saptypes = []string{
"UDT", "1", "TCP HC", "UDP HC", "IP Pkt", "ARP Pkt", "6", "7",
"8", "Prop Pkt", "Short Data", "11", "12", "13", "14", "15",
}
fidMap = [256]byte{
0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
fidName = map[uint8]string{
0: "Unknown",
1: "Standard",
2: "Flyde Micro",
3: "PROD-EL SPA",
4: "Motorola Connect+",
5: "RADIODATA GmbH",
6: "Hyteria (8)",
7: "Motorola Capacity+",
8: "EMC S.p.A (19)",
9: "EMC S.p.A (28)",
10: "Radio Activity Srl (51)",
11: "Radio Activity Srl (60)",
12: "Tait Electronics",
13: "Hyteria (104)",
14: "Vertex Standard",
}
)
func dumpFCLO(payload []byte, fid uint8) {
fmt.Printf(" fid: %s (%d)\n", fidName[fid], fid)
if fid == 0 || fid == 16 { // MotoTRBO Capacity+
fmt.Printf(" fclo: -\n")
}
}
func dumpDataHeader(header []byte) {
dh, err := dmr.ParseDataHeader(header, false)
if err != nil {
fmt.Printf(" data header error: %v\n", err)
return
}
dc := dh.CommonHeader()
fmt.Printf(" service accesspt: %d\n", dc.ServiceAccessPoint)
fmt.Printf(" src id -> dst id: %d -> %d\n", dc.SrcID, dc.DstID)
switch d := dh.(type) {
case dmr.UDTDataHeader:
fmt.Printf(" format..........: %s (%#02x)\n", dmr.UDTFormatName[d.Format], d.Format)
fmt.Printf(" pad nibble......: %d\n", d.PadNibble)
fmt.Printf(" appended blocks.: %d\n", d.AppendedBlocks)
fmt.Printf(" supplementary f.: %b\n", d.SupplementaryFlag)
fmt.Printf(" opcode..........: %d\n", d.OPCode)
case dmr.UnconfirmedDataHeader:
fmt.Printf(" pad octet count.: %d\n", d.PadOctetCount)
fmt.Printf(" full message....: %b\n", d.FullMessage)
fmt.Printf(" blocks to follow: %d\n", d.BlocksToFollow)
fmt.Printf(" fragment seq.no.: %d\n", d.FragmentSequenceNumber)
}
}
func dumpData(data bit.Bits) {
var out = make([][]byte, 7)
for y := 0; y < 7; y++ {
out[y] = make([]byte, 80)
for x := 0; x < 80; x++ {
if x == 0 || x == 18 || x == 21 || x == 30 || x == 33 || x == 51 {
out[y][x] = '|'
} else {
out[y][x] = ' '
}
}
}
// Info first half
for i := 0; i < 98; i++ {
var x = 1 + (i % 16)
var y = (i / 16)
if x > 8 {
x++
}
if data[i] > 0 {
out[y][x] = '1'
} else {
out[y][x] = '0'
}
}
// Slot type first half
for i := 98; i < 108; i++ {
var x = 19 + ((i - 98) % 2)
var y = (i - 98) / 2
if data[i] > 0 {
out[y][x] = '1'
} else {
out[y][x] = '0'
}
}
// SYNC
for i := 108; i < 156; i++ {
var x = 22 + ((i - 108) % 8)
var y = (i - 108) / 8
if data[i] > 0 {
out[y][x] = '1'
} else {
out[y][x] = '0'
}
}
// Slot type second half
for i := 156; i < 166; i++ {
var x = 31 + ((i - 156) % 2)
var y = (i - 156) / 2
if data[i] > 0 {
out[y][x] = '1'
} else {
out[y][x] = '0'
}
}
// Info second half
for i := 166; i < 264; i++ {
var x = 34 + ((i - 166) % 16)
var y = ((i - 166) / 16)
if x > 41 {
x++
}
if data[i] > 0 {
out[y][x] = '1'
} else {
out[y][x] = '0'
}
}
fmt.Println("|------INFO1------|ST|--SYNC--|ST|------INFO2------|")
for _, row := range out {
fmt.Println(string(row))
}
}
func dumpSync(p *ipsc.Packet, desc string) {
sync := dmr.ExtractSyncBits(p.PayloadBits)
patt := dmr.SyncPattern(sync)
fmt.Printf("dmr[%d->%d]: ts%d got %s:\n", p.SrcID, p.DstID, p.Timeslot+1, desc)
fmt.Printf(" sync pattern: %s\n", dmr.SyncPatternName[patt])
if patt == dmr.SyncPatternUnknown {
fmt.Print(hex.Dump(sync.Bytes()))
for i, b := range sync {
sync[i] = b ^ 1
}
fmt.Print(hex.Dump(sync.Bytes()))
}
/*
slot := dmr.ExtractSlotType(p.PayloadBits)
var (
codeword uint32
bursttype uint32
payload = make([]byte, 12)
)
codeword = (uint32(slot[0]) << 11) | (uint32(slot[1]) << 3) | uint32(slot[2])>>5
bursttype = uint32(slot[0]) & 7
fmt.Printf("codeword: %#08x, bursttype: %#04x\n", codeword, bursttype)
fec.Golay_23_12_Correct(&codeword)
fmt.Printf("codeword: %#08x (after correcting)\n", codeword)
codeword &= 0x0f
fmt.Printf("codeword: %#08x (after masking)\n", codeword)
bursttype ^= codeword
var errors int
if bursttype&1 > 0 {
errors++
}
if bursttype&2 > 0 {
errors++
}
if bursttype&4 > 0 {
errors++
}
if bursttype&8 > 0 {
errors++
}
bursttype = codeword
//fmt.Printf("%d errors detected, burstype: %08x (%d)\n", errors, bursttype, bursttype)
fmt.Printf(" burst type: %s (%d), %d errors\n", bursttypes[bursttype], bursttype, errors)
if bursttype < 7 {
if err := bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
fmt.Printf(" payload error: %v\n", err)
return
}
fmt.Printf(" payload: (%d bytes)\n", len(payload))
fmt.Print(" " + hex.Dump(payload))
}
fid := payload[1]
fidm := fidMap[fid]
switch bursttype {
case 1, 2:
dumpFCLO(payload, fidm)
case 6:
dumpDataHeader(payload)
}
/*
if err := fec.Golay_20_8_Check(slot); err != nil {
fmt.Printf("%v\n", err)
fmt.Print(hex.Dump(slot.Bytes()))
return
}
cc := (slot[0] << 3) | (slot[1] << 2) | (slot[2] << 1) | slot[3]
dt := (slot[4] << 3) | (slot[5] << 2) | (slot[6] << 1) | slot[7]
fmt.Printf("cc: %d, data type: %d\n", cc, dt)
*/
}
func rc() *homebrew.RepeaterConfiguration {
return &homebrew.RepeaterConfiguration{
Callsign: "PI1BOL",
@ -291,52 +37,70 @@ func rc() *homebrew.RepeaterConfiguration {
}
}
func dumpRaw(raw []byte) {
fmt.Printf("dump raw frame of %d bytes\n", len(raw))
p, err := homebrew.ParseData(raw)
if err != nil {
fmt.Printf(" parse error: %v\n", err)
return
}
dumpPacket(p)
}
func dumpPacket(p *ipsc.Packet) {
fmt.Print(p.Dump())
dumpData(p.PayloadBits)
switch p.SlotType {
case ipsc.VoiceLCHeader:
dumpSync(p, "voice LC header")
case ipsc.TerminatorWithLC:
dumpSync(p, "terminator with LC")
case ipsc.CSBK:
dumpSync(p, "CSBK")
case ipsc.VoiceDataA:
dumpSync(p, "voice A")
case ipsc.VoiceDataB:
dumpSync(p, "voice B")
case ipsc.VoiceDataC:
dumpSync(p, "voice C")
case ipsc.VoiceDataD:
dumpSync(p, "voice D")
case ipsc.VoiceDataE:
dumpSync(p, "voice E")
case ipsc.VoiceDataF:
dumpSync(p, "voice F")
}
fmt.Print("\n---\n\n")
}
func main() {
dumpFile := flag.String("dump", "", "dump file")
pcapFile := flag.String("pcap", "", "PCAP file")
liveFile := flag.String("live", "", "live configuration file")
showRaw := flag.Bool("raw", false, "show raw frames")
audioTS := flag.Int("audiots", 0, "play audio from time slot (1 or 2)")
flag.Parse()
if *audioTS > 2 {
log.Fatalf("invalid time slot %d\n", *audioTS)
return
}
r := repeater.New()
if *liveFile != "" {
if *audioTS > 0 {
ambeframe := make(chan float32)
vs := dsd.NewAMBEVoiceStream(3)
r.VoiceFrameFunc = func(p *ipsc.Packet, bits bit.Bits) {
var in = make([]byte, len(bits))
for i, b := range bits {
in[i] = byte(b)
}
samples, err := vs.Decode(in)
if err != nil {
log.Printf("error decoding AMBE3000 frame: %v\n", err)
return
}
fmt.Printf("%v\n", samples)
for _, sample := range samples {
ambeframe <- sample
}
}
portaudio.Initialize()
defer portaudio.Terminate()
h, err := portaudio.DefaultHostApi()
if err != nil {
panic(err)
}
p := portaudio.LowLatencyParameters(nil, h.DefaultOutputDevice)
p.SampleRate = 8000
p.Output.Channels = 1
stream, err := portaudio.OpenStream(p, func(out []float32) {
for i := range out {
out[i] = <-ambeframe
}
})
if err != nil {
log.Printf("error streaming: %v\n", err)
return
}
defer stream.Close()
if err := stream.Start(); err != nil {
log.Printf("error streaming: %v\n", err)
return
}
}
switch {
case *liveFile != "":
log.Printf("going in live mode using %q\n", *liveFile)
f, err := os.Open(*liveFile)
if err != nil {
@ -362,8 +126,7 @@ func main() {
protocol.Dump = true
panic(protocol.Run())
} else {
case *dumpFile != "":
i, err := os.Open(*dumpFile)
if err != nil {
log.Fatal(err)
@ -394,5 +157,33 @@ func main() {
panic(err)
}
}
case *pcapFile != "":
var (
handle *pcap.Handle
err error
)
if handle, err = pcap.OpenOffline(*pcapFile); err != nil {
panic(err)
}
defer handle.Close()
dec := gopacket.DecodersByLayerName["Ethernet"]
source := gopacket.NewPacketSource(handle, dec)
for packet := range source.Packets() {
raw := packet.ApplicationLayer().Payload()
if *showRaw {
fmt.Println("raw packet:")
fmt.Print(hex.Dump(raw))
}
p, err := homebrew.ParseData(raw)
if err != nil {
fmt.Printf(" parse error: %v\n", err)
continue
}
r.Stream(p)
}
}
}

@ -0,0 +1,8 @@
package repeater
// DataFrame is a decoded frame with data
type DataFrame struct {
SrcID, DstID uint32
Timeslot uint8
Data []byte
}

@ -5,6 +5,7 @@ import (
"log"
"time"
"github.com/tehmaze/go-dmr/bit"
"github.com/tehmaze/go-dmr/ipsc"
)
@ -39,7 +40,9 @@ type Slot struct {
}
type Repeater struct {
Slot []*Slot
Slot []*Slot
DataFrameFunc func(*ipsc.Packet, bit.Bits)
VoiceFrameFunc func(*ipsc.Packet, bit.Bits)
}
func New() *Repeater {

@ -76,13 +76,19 @@ func (r *Repeater) HandleVoiceFrame(p *ipsc.Packet) error {
)
if emb, err = dmr.ParseEMB(dmr.ExtractEMBBitsFromSyncBits(sync)); err != nil {
fmt.Println("unknown sync pattern, no EMB")
return err
}
fmt.Printf("EMB LCSS %d\n", emb.LCSS)
if emb != nil {
fmt.Printf("EMB LCSS %d\n", emb.LCSS)
// TODO(maze): implement VBPTC matrix handling
switch emb.LCSS {
}
}
// TODO(maze): implement VBPTC matrix handling
switch emb.LCSS {
// Extract the three embedded AMBE frames if we have a callback function to process them.
if r.VoiceFrameFunc != nil {
r.VoiceFrameFunc(p, dmr.ExtractVoiceBits(p.PayloadBits))
}
return nil

@ -0,0 +1,10 @@
package dmr
import "github.com/tehmaze/go-dmr/bit"
func ExtractVoiceBits(payload bit.Bits) bit.Bits {
var b = make(bit.Bits, VoiceBits)
copy(b[:VoiceHalfBits], payload[:VoiceHalfBits])
copy(b[VoiceHalfBits:], payload[VoiceHalfBits+SyncBits:])
return b
}
Loading…
Cancel
Save