2024-04-03 18:52:04 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-04-24 19:09:17 -06:00
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/pem"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.metznet.ca/MetzNet/pnyx"
|
|
|
|
"github.com/gen2brain/malgo"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/hraban/opus"
|
2024-04-03 18:52:04 -06:00
|
|
|
)
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
var decoders = map[pnyx.PeerID]chan []byte{}
|
2024-04-12 18:06:57 -06:00
|
|
|
var encoder *opus.Encoder
|
|
|
|
var sample_rate int = 0
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
func set_sample_rate(audio_data chan []int16, new_sample_rate int) error {
|
2024-04-12 18:06:57 -06:00
|
|
|
sample_rate = new_sample_rate
|
|
|
|
|
|
|
|
var err error
|
|
|
|
encoder, err = opus.NewEncoder(new_sample_rate, 1, opus.AppVoIP)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
for peer_id, decoder_chan := range decoders {
|
2024-04-12 18:06:57 -06:00
|
|
|
if decoder_chan != nil {
|
|
|
|
decoder_chan <- nil
|
|
|
|
}
|
2024-04-24 19:09:17 -06:00
|
|
|
new_chan := make(chan []byte, 1000)
|
2024-04-12 18:06:57 -06:00
|
|
|
decoders[peer_id] = new_chan
|
2024-04-23 16:38:08 -06:00
|
|
|
go handle_peer_decode(audio_data, peer_id, decoders[peer_id], sample_rate)
|
2024-04-12 18:06:57 -06:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_chan chan []byte, sample_rate int) {
|
2024-04-12 18:06:57 -06:00
|
|
|
decoder, err := opus.NewDecoder(sample_rate, 1)
|
2024-04-08 17:23:55 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:06:57 -06:00
|
|
|
running := true
|
2024-04-13 11:44:41 -06:00
|
|
|
missed := 0
|
2024-04-12 18:06:57 -06:00
|
|
|
for running {
|
|
|
|
select {
|
2024-04-24 19:09:17 -06:00
|
|
|
case <-time.After(20 * time.Millisecond):
|
2024-04-13 11:44:41 -06:00
|
|
|
missed += 1
|
|
|
|
|
|
|
|
if missed > 100 {
|
2024-04-24 19:09:17 -06:00
|
|
|
decode_chan <- <-decode_chan
|
2024-04-13 11:44:41 -06:00
|
|
|
}
|
|
|
|
|
2024-04-12 18:06:57 -06:00
|
|
|
pcm := make([]int16, sample_rate/50)
|
|
|
|
err := decoder.DecodePLC(pcm)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-16 15:36:32 -06:00
|
|
|
audio_data <- pcm
|
2024-04-12 18:06:57 -06:00
|
|
|
|
|
|
|
case data := <-decode_chan:
|
2024-04-13 11:44:41 -06:00
|
|
|
missed = 0
|
2024-04-12 18:06:57 -06:00
|
|
|
if data == nil {
|
|
|
|
running = false
|
|
|
|
} else {
|
|
|
|
pcm := make([]int16, sample_rate/50*2)
|
|
|
|
written, err := decoder.Decode(data, pcm)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-16 15:36:32 -06:00
|
|
|
audio_data <- pcm[:written]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mixer(data_chan chan []int16, speaker_chan chan []int16) {
|
|
|
|
var samples []int16 = nil
|
|
|
|
for true {
|
|
|
|
if samples == nil {
|
2024-04-24 19:09:17 -06:00
|
|
|
samples = <-data_chan
|
2024-04-16 15:36:32 -06:00
|
|
|
} else {
|
|
|
|
select {
|
2024-04-24 19:09:17 -06:00
|
|
|
case new_samples := <-data_chan:
|
|
|
|
for i, sample := range new_samples {
|
2024-04-16 15:36:32 -06:00
|
|
|
samples[i] += sample
|
2024-04-12 18:06:57 -06:00
|
|
|
}
|
2024-04-16 15:36:32 -06:00
|
|
|
case speaker_chan <- samples:
|
|
|
|
samples = nil
|
2024-04-12 18:06:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext, *malgo.Device, *malgo.Device) {
|
2024-04-08 11:28:52 -06:00
|
|
|
ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, nil)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
infos, err := ctx.Devices(malgo.Playback)
|
|
|
|
if err != nil {
|
2024-04-08 11:32:39 -06:00
|
|
|
panic(err)
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:32:39 -06:00
|
|
|
|
2024-04-08 11:56:34 -06:00
|
|
|
var playback_device *malgo.DeviceInfo = nil
|
2024-04-24 19:09:17 -06:00
|
|
|
for _, info := range infos {
|
2024-04-08 11:46:43 -06:00
|
|
|
if info.IsDefault != 0 {
|
2024-04-08 11:56:34 -06:00
|
|
|
full, err := ctx.DeviceInfo(malgo.Playback, info.ID, malgo.Shared)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
playback_device = &full
|
2024-04-08 11:46:43 -06:00
|
|
|
}
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:32:39 -06:00
|
|
|
|
2024-04-08 11:56:34 -06:00
|
|
|
if playback_device == nil {
|
|
|
|
panic("No default playback device")
|
|
|
|
}
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
infos, err = ctx.Devices(malgo.Capture)
|
|
|
|
if err != nil {
|
2024-04-08 11:32:39 -06:00
|
|
|
panic(err)
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:32:39 -06:00
|
|
|
|
2024-04-08 11:56:34 -06:00
|
|
|
var capture_device *malgo.DeviceInfo = nil
|
2024-04-24 19:09:17 -06:00
|
|
|
for _, info := range infos {
|
2024-04-08 11:46:43 -06:00
|
|
|
if info.IsDefault != 0 {
|
2024-04-08 11:56:34 -06:00
|
|
|
full, err := ctx.DeviceInfo(malgo.Capture, info.ID, malgo.Shared)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
capture_device = &full
|
2024-04-08 11:46:43 -06:00
|
|
|
}
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:32:39 -06:00
|
|
|
|
2024-04-08 11:56:34 -06:00
|
|
|
if capture_device == nil {
|
|
|
|
panic("No default capture device")
|
|
|
|
}
|
|
|
|
|
2024-04-08 11:28:52 -06:00
|
|
|
inDeviceConfig := malgo.DefaultDeviceConfig(malgo.Capture)
|
2024-04-24 19:09:17 -06:00
|
|
|
inDeviceConfig.Capture.Format = malgo.FormatS16
|
|
|
|
inDeviceConfig.Capture.Channels = 1
|
2024-04-08 11:47:56 -06:00
|
|
|
inDeviceConfig.Capture.DeviceID = capture_device.ID.Pointer()
|
2024-04-24 19:09:17 -06:00
|
|
|
inDeviceConfig.SampleRate = 48000
|
2024-04-08 17:26:43 -06:00
|
|
|
inDeviceConfig.PeriodSizeInFrames = 960
|
2024-04-24 19:09:17 -06:00
|
|
|
inDeviceConfig.Alsa.NoMMap = 1
|
2024-04-08 11:28:52 -06:00
|
|
|
inDeviceConfig.Capture.ShareMode = malgo.Shared
|
|
|
|
|
|
|
|
outDeviceConfig := malgo.DefaultDeviceConfig(malgo.Playback)
|
2024-04-24 19:09:17 -06:00
|
|
|
outDeviceConfig.Playback.Format = malgo.FormatS16
|
|
|
|
outDeviceConfig.Playback.Channels = 1
|
2024-04-08 11:47:56 -06:00
|
|
|
outDeviceConfig.Playback.DeviceID = playback_device.ID.Pointer()
|
2024-04-24 19:09:17 -06:00
|
|
|
outDeviceConfig.SampleRate = 48000
|
2024-04-08 17:26:43 -06:00
|
|
|
outDeviceConfig.PeriodSizeInFrames = 960
|
2024-04-24 19:09:17 -06:00
|
|
|
outDeviceConfig.Alsa.NoMMap = 1
|
2024-04-08 11:28:52 -06:00
|
|
|
outDeviceConfig.Playback.ShareMode = malgo.Shared
|
|
|
|
|
|
|
|
onSendFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
|
|
|
|
select {
|
2024-04-24 19:09:17 -06:00
|
|
|
case pcm := <-speaker:
|
2024-04-13 11:44:41 -06:00
|
|
|
for i := 0; i < sample_rate/50; i++ {
|
|
|
|
binary.LittleEndian.PutUint16(output_samples[i*2:], uint16(pcm[i]))
|
|
|
|
}
|
2024-04-08 11:59:36 -06:00
|
|
|
default:
|
2024-04-08 11:28:52 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
playbackCallbacks := malgo.DeviceCallbacks{
|
|
|
|
Data: onSendFrames,
|
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
|
|
|
outDevice, err := malgo.InitDevice(ctx.Context, outDeviceConfig, playbackCallbacks)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = outDevice.Start()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
|
2024-04-12 18:06:57 -06:00
|
|
|
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:]))
|
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-12 18:06:57 -06:00
|
|
|
data := make([]byte, len(input_samples))
|
|
|
|
written, err := encoder.Encode(pcm, data)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case mic <- data[:written]:
|
|
|
|
default:
|
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
}
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
captureCallbacks := malgo.DeviceCallbacks{
|
|
|
|
Data: onRecvFrames,
|
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks)
|
|
|
|
if err != nil {
|
2024-04-08 11:28:52 -06:00
|
|
|
panic(err)
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
err = inDevice.Start()
|
|
|
|
if err != nil {
|
2024-04-08 11:28:52 -06:00
|
|
|
panic(err)
|
2024-04-24 19:09:17 -06:00
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-23 14:16:25 -06:00
|
|
|
return ctx, outDevice, inDevice
|
|
|
|
}
|
2024-04-18 20:10:01 -06:00
|
|
|
|
2024-04-23 14:16:25 -06:00
|
|
|
func get_private_key(path string, generate bool) ed25519.PrivateKey {
|
|
|
|
key_file_bytes, err := os.ReadFile(path)
|
2024-04-18 20:10:01 -06:00
|
|
|
if err == nil {
|
|
|
|
key_pem, _ := pem.Decode(key_file_bytes)
|
|
|
|
if key_pem.Type != "PRIVATE KEY" {
|
|
|
|
panic("Key file has wrong PEM format")
|
|
|
|
}
|
|
|
|
|
|
|
|
private_key, err := x509.ParsePKCS8PrivateKey(key_pem.Bytes)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ok bool
|
2024-04-23 14:16:25 -06:00
|
|
|
key, ok := private_key.(ed25519.PrivateKey)
|
2024-04-18 20:10:01 -06:00
|
|
|
if ok == false {
|
|
|
|
panic("Private key is not ed25519.PrivateKey")
|
|
|
|
}
|
2024-04-23 14:16:25 -06:00
|
|
|
return key
|
|
|
|
} else if generate {
|
|
|
|
_, key, err := ed25519.GenerateKey(rand.Reader)
|
2024-04-18 20:10:01 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
key_pkcs8, err := x509.MarshalPKCS8PrivateKey(key)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
key_pem := pem.EncodeToMemory(&pem.Block{
|
2024-04-24 19:09:17 -06:00
|
|
|
Type: "PRIVATE KEY",
|
2024-04-18 20:10:01 -06:00
|
|
|
Bytes: key_pkcs8,
|
|
|
|
})
|
|
|
|
|
2024-04-23 14:16:25 -06:00
|
|
|
err = os.WriteFile(path, key_pem, 0o600)
|
2024-04-18 20:10:01 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-23 14:16:25 -06:00
|
|
|
return key
|
|
|
|
} else {
|
|
|
|
panic(fmt.Sprintf("Failed to read key from %s", path))
|
2024-04-18 20:10:01 -06:00
|
|
|
}
|
2024-04-23 14:16:25 -06:00
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-24 19:09:17 -06:00
|
|
|
type ChannelState struct {
|
|
|
|
ID pnyx.ChannelID
|
|
|
|
Name string
|
|
|
|
Members []pnyx.PeerID
|
|
|
|
Modes []pnyx.ModeID
|
|
|
|
}
|
|
|
|
|
|
|
|
type PeerState struct {
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
type UIState struct {
|
2024-04-24 19:27:38 -06:00
|
|
|
Username string
|
2024-04-24 19:09:17 -06:00
|
|
|
SelectedBody int
|
|
|
|
SelectedPeer int
|
|
|
|
SelectedChannel int
|
|
|
|
SelectedArea int
|
|
|
|
Channels []ChannelState
|
|
|
|
Peers map[pnyx.PeerID]PeerState
|
|
|
|
}
|
|
|
|
|
2024-09-12 14:55:12 -06:00
|
|
|
func main_loop(client *pnyx.Client, audio_data chan []int16, packet_chan chan pnyx.Payload) {
|
2024-04-23 16:38:08 -06:00
|
|
|
for client.Active() {
|
2024-04-23 14:16:25 -06:00
|
|
|
select {
|
|
|
|
case packet := <-packet_chan:
|
2024-04-09 17:08:46 -06:00
|
|
|
switch packet := packet.(type) {
|
2024-04-12 18:06:57 -06:00
|
|
|
case pnyx.ChannelCommandPacket:
|
2024-04-09 17:08:46 -06:00
|
|
|
if packet.Channel == pnyx.ChannelID(0) {
|
2024-04-12 18:06:57 -06:00
|
|
|
if packet.Mode == pnyx.MODE_AUDIO {
|
|
|
|
if packet.Command == pnyx.AUDIO_SET_SAMPLE_RATE {
|
|
|
|
var new_sample_rate int
|
2024-04-16 15:06:53 -06:00
|
|
|
switch pnyx.SampleRate(packet.Data[0]) {
|
|
|
|
case pnyx.SAMPLE_RATE_24KHZ:
|
2024-04-12 18:06:57 -06:00
|
|
|
new_sample_rate = 24000
|
2024-04-16 15:06:53 -06:00
|
|
|
case pnyx.SAMPLE_RATE_48KHZ:
|
2024-04-12 18:06:57 -06:00
|
|
|
new_sample_rate = 48000
|
|
|
|
default:
|
|
|
|
continue
|
|
|
|
}
|
2024-04-23 16:38:08 -06:00
|
|
|
|
|
|
|
err := set_sample_rate(audio_data, new_sample_rate)
|
2024-04-09 17:08:46 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-12 18:06:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-16 15:06:53 -06:00
|
|
|
case pnyx.PeerPacket:
|
2024-04-12 18:06:57 -06:00
|
|
|
if packet.Channel == pnyx.ChannelID(0) {
|
|
|
|
decode_chan, exists := decoders[packet.Peer]
|
|
|
|
if exists == false {
|
|
|
|
if sample_rate != 0 {
|
2024-04-24 19:09:17 -06:00
|
|
|
decode_chan = make(chan []byte, 1000)
|
2024-04-12 18:06:57 -06:00
|
|
|
decoders[packet.Peer] = decode_chan
|
2024-04-23 16:38:08 -06:00
|
|
|
go handle_peer_decode(audio_data, packet.Peer, decoders[packet.Peer], sample_rate)
|
2024-04-12 18:06:57 -06:00
|
|
|
decode_chan <- packet.Data
|
|
|
|
} else {
|
|
|
|
decoders[packet.Peer] = nil
|
|
|
|
}
|
|
|
|
} else if decode_chan != nil {
|
|
|
|
decode_chan <- packet.Data
|
2024-04-09 17:08:46 -06:00
|
|
|
}
|
2024-04-08 17:23:55 -06:00
|
|
|
}
|
|
|
|
}
|
2024-04-23 14:16:25 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
func process_mic(client *pnyx.Client, mic chan []byte) {
|
2024-04-23 14:16:25 -06:00
|
|
|
for true {
|
2024-04-24 19:09:17 -06:00
|
|
|
data := <-mic
|
2024-04-23 14:16:25 -06:00
|
|
|
err := client.Send(pnyx.NewDataPacket(pnyx.ChannelID(0), pnyx.MODE_AUDIO, data))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2024-04-08 11:28:52 -06:00
|
|
|
}
|
2024-04-23 14:16:25 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
key_file_arg := flag.String("key", "${HOME}/.pnyx.key", "Path to the private key file to load/save")
|
|
|
|
generate_key_arg := flag.Bool("genkey", false, "Set to generate a key if none exists")
|
|
|
|
flag.Parse()
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
var audio_data = make(chan []int16, 0)
|
|
|
|
var speaker = make(chan []int16, 0)
|
|
|
|
|
|
|
|
go mixer(audio_data, speaker)
|
|
|
|
|
|
|
|
var mic = make(chan []byte, 0)
|
|
|
|
ctx, outDevice, inDevice := setup_audio(mic, speaker)
|
2024-04-23 14:16:25 -06:00
|
|
|
|
|
|
|
defer ctx.Free()
|
|
|
|
defer ctx.Uninit()
|
|
|
|
defer outDevice.Uninit()
|
|
|
|
defer outDevice.Stop()
|
|
|
|
defer inDevice.Uninit()
|
|
|
|
defer inDevice.Stop()
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
packet_chan := make(chan pnyx.Payload, 1024)
|
|
|
|
|
|
|
|
key := get_private_key(os.ExpandEnv(*key_file_arg), *generate_key_arg)
|
|
|
|
client, err := pnyx.NewClient(key, flag.Arg(0), func(payload pnyx.Payload) error {
|
|
|
|
select {
|
|
|
|
case packet_chan <- payload:
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Channel overflow")
|
|
|
|
}
|
|
|
|
})
|
2024-04-23 14:16:25 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2024-04-24 19:09:17 -06:00
|
|
|
|
2024-04-16 15:06:53 -06:00
|
|
|
join_packet := pnyx.NewChannelCommandPacket(uuid.New(), pnyx.ChannelID(0), pnyx.MODE_CHANNEL, pnyx.CHANNEL_COMMAND_JOIN, nil)
|
2024-04-09 17:08:46 -06:00
|
|
|
err = client.Send(join_packet)
|
2024-04-08 17:23:55 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:06:57 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
go process_mic(client, mic)
|
2024-09-12 14:55:12 -06:00
|
|
|
go main_loop(client, audio_data, packet_chan)
|
2024-04-13 14:00:56 -06:00
|
|
|
|
2024-04-18 20:10:01 -06:00
|
|
|
os_sigs := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(os_sigs, syscall.SIGINT, syscall.SIGABRT)
|
2024-04-13 14:00:56 -06:00
|
|
|
|
2024-04-18 20:10:01 -06:00
|
|
|
<-os_sigs
|
2024-04-23 16:38:08 -06:00
|
|
|
client.Close()
|
2024-04-03 18:52:04 -06:00
|
|
|
}
|