package main import ( "encoding/binary" "fmt" "os" "time" "git.metznet.ca/MetzNet/pnyx" "github.com/gen2brain/malgo" "github.com/google/uuid" "github.com/hraban/opus" ) var decoders = map[pnyx.PeerID]chan[]byte{} var encoder *opus.Encoder var sample_rate int = 0 var mic = make(chan []byte, 0) var speaker = make(chan []byte, 0) func set_sample_rate(new_sample_rate int) error { sample_rate = new_sample_rate var err error fmt.Printf("Creating encoder with sample_rate %d\n", new_sample_rate) encoder, err = opus.NewEncoder(new_sample_rate, 1, opus.AppVoIP) if err != nil { return err } for peer_id, decoder_chan := range(decoders) { if decoder_chan != nil { decoder_chan <- nil } new_chan := make(chan[]byte, 1000) decoders[peer_id] = new_chan go handle_peer_decode(peer_id, decoders[peer_id], sample_rate) } return nil } func handle_peer_decode(peer_id pnyx.PeerID, decode_chan chan[]byte, sample_rate int){ fmt.Printf("Starting decoder routine for %x with sample_rate %d\n", peer_id, sample_rate) decoder, err := opus.NewDecoder(sample_rate, 1) if err != nil { panic(err) } running := true for running { select { case <-time.After(20*time.Millisecond): pcm := make([]int16, sample_rate/50) err := decoder.DecodePLC(pcm) if err != nil { panic(err) } pcm_bytes := make([]byte, sample_rate/50*2) for i := 0; i < sample_rate/50; i++ { binary.LittleEndian.PutUint16(pcm_bytes[i*2:], uint16(pcm[i])) } speaker <- pcm_bytes case data := <-decode_chan: if data == nil { running = false } else { pcm := make([]int16, sample_rate/50*2) written, err := decoder.Decode(data, pcm) if err != nil { panic(err) } pcm_bytes := make([]byte, written*2) for i := 0; i < written; i++ { binary.LittleEndian.PutUint16(pcm_bytes[i*2:], uint16(pcm[i])) } speaker <- pcm_bytes } } } fmt.Printf("Stopping decoder routine for %x with sample_rate %d\n", peer_id, sample_rate) } func main() { ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, nil) if err != nil { panic(err) } defer ctx.Free() defer ctx.Uninit() infos, err := ctx.Devices(malgo.Playback) if err != nil { panic(err) } var playback_device *malgo.DeviceInfo = nil for _, info := range infos { if info.IsDefault != 0 { full, err := ctx.DeviceInfo(malgo.Playback, info.ID, malgo.Shared) if err != nil { panic(err) } playback_device = &full } } if playback_device == nil { panic("No default playback device") } infos, err = ctx.Devices(malgo.Capture) if err != nil { panic(err) } var capture_device *malgo.DeviceInfo = nil for _, info := range infos { if info.IsDefault != 0 { full, err := ctx.DeviceInfo(malgo.Capture, info.ID, malgo.Shared) if err != nil { panic(err) } capture_device = &full } } if capture_device == nil { panic("No default capture device") } inDeviceConfig := malgo.DefaultDeviceConfig(malgo.Capture) inDeviceConfig.Capture.Format = malgo.FormatS16 inDeviceConfig.Capture.Channels = 1 inDeviceConfig.Capture.DeviceID = capture_device.ID.Pointer() inDeviceConfig.SampleRate = 48000 inDeviceConfig.PeriodSizeInFrames = 960 inDeviceConfig.Alsa.NoMMap = 1 inDeviceConfig.Capture.ShareMode = malgo.Shared outDeviceConfig := malgo.DefaultDeviceConfig(malgo.Playback) outDeviceConfig.Playback.Format = malgo.FormatS16 outDeviceConfig.Playback.Channels = 1 outDeviceConfig.Playback.DeviceID = playback_device.ID.Pointer() outDeviceConfig.SampleRate = 48000 outDeviceConfig.PeriodSizeInFrames = 960 outDeviceConfig.Alsa.NoMMap = 1 outDeviceConfig.Playback.ShareMode = malgo.Shared onSendFrames := func(output_samples []byte, input_samples []byte, framecount uint32) { select { case data := <- speaker: copy(output_samples, data) default: } } playbackCallbacks := malgo.DeviceCallbacks{ Data: onSendFrames, } fmt.Printf("Creating playback device %+v with format %+v\n", playback_device, playback_device.Formats[0]) outDevice, err := malgo.InitDevice(ctx.Context, outDeviceConfig, playbackCallbacks) if err != nil { panic(err) } fmt.Printf("Starting playback\n") err = outDevice.Start() if err != nil { panic(err) } defer outDevice.Uninit() defer outDevice.Stop() onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) { if encoder != nil { pcm := make([]int16, len(input_samples)/2) for i := 0; i < len(input_samples)/2; i++ { pcm[i] = int16(binary.LittleEndian.Uint16(input_samples[2*i:])) } data := make([]byte, len(input_samples)) written, err := encoder.Encode(pcm, data) if err != nil { panic(err) } select { case mic <- data[:written]: default: } } } captureCallbacks := malgo.DeviceCallbacks{ Data: onRecvFrames, } fmt.Printf("Creating capture device %+v with format %+v\n", capture_device, capture_device.Formats[0]) inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks) if err != nil { panic(err) } fmt.Printf("Starting capture device\n") err = inDevice.Start() if err != nil { panic(err) } defer inDevice.Uninit() defer inDevice.Stop() fmt.Printf("Starting pnyx client\n") client, err := pnyx.NewClient(nil, os.Args[1]) if err != nil { panic(err) } fmt.Printf("Started pnyx client\n") go func() { var buf [1024]byte for true { read, _, err := client.Connection.ReadFromUDP(buf[:]) if err != nil { fmt.Printf("Read Error %s\n", err) break } data, err := pnyx.ParseSessionData(&client.Session, buf[pnyx.COMMAND_LENGTH + pnyx.SESSION_ID_LENGTH:read]) if err != nil { fmt.Printf("ParseSessionData Error %s\n", err) continue } packet, err := pnyx.ParsePacket(data) if err != nil { fmt.Printf("ParsePacket Error %s - %x\n", err, data) continue } switch packet := packet.(type) { case pnyx.ChannelCommandPacket: if packet.Channel == pnyx.ChannelID(0) { if packet.Mode == pnyx.MODE_AUDIO { if packet.Command == pnyx.AUDIO_SET_SAMPLE_RATE { fmt.Printf("GOT NEW SAMPLE RATE 0x%02x\n", packet.Data) var new_sample_rate int switch packet.Data[0] { case byte(pnyx.SAMPLE_RATE_24KHZ): new_sample_rate = 24000 case byte(pnyx.SAMPLE_RATE_48KHZ): new_sample_rate = 48000 default: continue } err := set_sample_rate(new_sample_rate) if err != nil { panic(err) } } } } case pnyx.ChannelPeerPacket: if packet.Channel == pnyx.ChannelID(0) { decode_chan, exists := decoders[packet.Peer] if exists == false { if sample_rate != 0 { decode_chan = make(chan[]byte, 1000) decoders[packet.Peer] = decode_chan go handle_peer_decode(packet.Peer, decoders[packet.Peer], sample_rate) decode_chan <- packet.Data } else { decoders[packet.Peer] = nil } } else if decode_chan != nil { decode_chan <- packet.Data } } default: fmt.Printf("Unhandled packet type: %s\n", packet) } } }() add_packet, _ := pnyx.NewServerCommandPacket(pnyx.SERVER_COMMAND_ADD_CHANNEL, []byte{0xFF}) err = client.Send(add_packet) if err != nil { panic(err) } join_packet := pnyx.NewChannelCommandPacket(uuid.New(), pnyx.ChannelID(0), pnyx.MODE_CHANNEL, pnyx.CHANNEL_JOIN, nil) err = client.Send(join_packet) if err != nil { panic(err) } get_sample_rate_packet := pnyx.NewChannelCommandPacket(uuid.New(), pnyx.ChannelID(0), pnyx.MODE_AUDIO, pnyx.AUDIO_SET_SAMPLE_RATE, []byte{byte(pnyx.SAMPLE_RATE_48KHZ)}) err = client.Send(get_sample_rate_packet) if err != nil { panic(err) } for true { data := <- mic err = client.Send(pnyx.NewChannelDataPacket(pnyx.ChannelID(0), pnyx.MODE_AUDIO, data)) if err != nil { panic(err) } } }