autx.go (2645B)
1 // Command autx transmits audio over the network via RTP. 2 // It reads raw audio from the standard input 3 // and transmits it to the provided address every 30 milliseconds. 4 // Audio must be single-channel (mono), 5 // signed 16-bit linear PCM data in big-endian byte order. 6 // The sample rate must be 22.05KHz. 7 // 8 // Its usage is: 9 // 10 // autx address 11 // 12 // Address is in addr:port format. 13 // 14 // # Example 15 // 16 // Transmit audio from the file "test.pcm" to 2001:db8::1 port 9999: 17 // 18 // autx [2001:db8::1]:9999 < test.pcm 19 package main 20 21 import ( 22 "bytes" 23 "errors" 24 "fmt" 25 "io" 26 "log" 27 "net" 28 "net/netip" 29 "os" 30 "strconv" 31 "strings" 32 "time" 33 34 "github.com/untangledco/streaming/rtp" 35 "github.com/untangledco/streaming/sdp" 36 ) 37 38 const sampleRate = 22050 39 const bitsPerSample = 16 40 const tick = 30 // milliseconds 41 const packetInterval = tick * time.Millisecond 42 const bufSize = sampleRate * bitsPerSample / 8 / 1000 * tick 43 44 const usage string = "usage: autx address" 45 46 func main() { 47 if len(os.Args) != 2 { 48 fmt.Fprintln(os.Stderr, usage) 49 os.Exit(1) 50 } 51 52 session, err := rtp.Dial("udp", os.Args[1]) 53 if err != nil { 54 log.Fatal(err) 55 } 56 session.Clock = rtp.ClockPCMAudio // not the audio sample rate. 57 58 origin := sdp.Origin{ 59 ID: sdp.Now(), 60 Version: sdp.Now(), 61 Address: netip.AddrFrom4([4]byte{127, 0, 0, 1}), 62 } 63 if strings.HasPrefix(os.Args[1], "[") { 64 origin.Address = netip.IPv6Loopback() 65 } 66 _, port, err := net.SplitHostPort(os.Args[1]) 67 if err != nil { 68 fmt.Fprintln(os.Stderr, err) 69 os.Exit(1) 70 } 71 nport, err := strconv.Atoi(port) 72 if err != nil { 73 fmt.Fprintln(os.Stderr, "parse port:", err) 74 os.Exit(1) 75 } 76 77 description := sdp.Session{ 78 Origin: origin, 79 Name: "test", 80 Media: []sdp.Media{ 81 { 82 Type: sdp.MediaTypeAudio, 83 Port: nport, 84 Transport: sdp.ProtoRTP, 85 Format: []string{fmt.Sprintf("%d", rtp.PayloadType(11))}, 86 Attributes: []string{ 87 fmt.Sprintf("rtpmap:%d", rtp.PayloadType(11)), 88 fmt.Sprintf("L16/%d", sampleRate), 89 }, 90 }, 91 }, 92 } 93 fmt.Fprintln(os.Stderr, description) 94 95 out := make(chan rtp.Packet, 100) 96 go func() { 97 buf := &bytes.Buffer{} 98 p := rtp.Packet{ 99 Header: rtp.Header{ 100 Type: rtp.PayloadType(11), 101 }, 102 } 103 for { 104 buf.Reset() 105 _, err := io.CopyN(buf, os.Stdin, int64(bufSize)) 106 if errors.Is(err, io.EOF) { 107 p.Payload = buf.Bytes() 108 out <- p 109 close(out) 110 return 111 } else if err != nil { 112 log.Println(err) 113 } 114 p.Payload = bytes.Clone(buf.Bytes()) 115 out <- p 116 } 117 }() 118 119 for range time.NewTicker(packetInterval).C { 120 p, ok := <-out 121 if !ok { 122 return 123 } 124 if err := session.Transmit(&p); err != nil { 125 log.Println(err) 126 } 127 } 128 }