commit 97693535acd9ae096cffa7e635491aa97264c4a1
parent 5e057162d97599b2803923f2450d38d26c9833f0
Author: Oliver Lowe <o@olowe.co>
Date: Thu, 1 Aug 2024 18:44:56 +1000
cmd/autx: import audio transmit program
This is a fun test of our RTP and SDP packages. From the docs:
"Command autx transmits audio over the network via RTP."
Diffstat:
| A | cmd/autx/autx.go | | | 110 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 110 insertions(+), 0 deletions(-)
diff --git a/cmd/autx/autx.go b/cmd/autx/autx.go
@@ -0,0 +1,110 @@
+// Command autx transmits audio over the network via RTP.
+// It reads raw audio from the standard input
+// and transmits it to the provided address.
+// Audio must be single-channel (mono),
+// signed 16-bit linear PCM data in big-endian byte order.
+// The sample rate must be 44.1KHz.
+//
+// Its usage is:
+//
+// autx address
+//
+// Address is in addr:port format.
+//
+// # Example
+//
+// Transmit audio from the file "test.pcm" to 2001:db8::1 port 9999:
+//
+// autx [2001:db8::1]:9999 < test.pcm
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "time"
+
+ "github.com/untangledco/streaming/rtp"
+ "github.com/untangledco/streaming/sdp"
+)
+
+// 1441kbps in bytes per 20 milliseconds
+const bufSize int = 1411 / 8 * 20
+const packetInterval = 20 * time.Millisecond // from RFC 3xxx
+
+const usage string = "usage: autx address"
+
+func main() {
+ if len(os.Args) != 2 {
+ fmt.Fprintln(os.Stderr, usage)
+ os.Exit(1)
+ }
+
+ session, err := rtp.Dial("udp", os.Args[1])
+ if err != nil {
+ log.Fatal(err)
+ }
+ session.Clock = 44100 // 44.1KHz
+
+ description := sdp.Session{
+ Origin: sdp.Origin{
+ Username: sdp.NoUsername,
+ ID: sdp.Now(),
+ Version: sdp.Now(),
+ AddressType: "IP6",
+ Address: "[::1]",
+ },
+ Name: "test",
+ Media: []sdp.Media{
+ {
+ Type: sdp.MediaTypeAudio,
+ Port: 9999,
+ Transport: sdp.ProtoRTP,
+ Format: []string{fmt.Sprintf("%d", rtp.PayloadL16)},
+ Attributes: []string{
+ fmt.Sprintf("rtpmap:%d", rtp.PayloadL16),
+ fmt.Sprintf("L16/%d", session.Clock),
+ },
+ },
+ },
+ }
+ fmt.Fprintln(os.Stderr, description)
+
+ out := make(chan rtp.Packet, 100)
+ go func() {
+ buf := &bytes.Buffer{}
+ p := rtp.Packet{
+ Header: rtp.Header{
+ Type: rtp.PayloadL16,
+ },
+ }
+ for {
+ buf.Reset()
+ _, err := io.CopyN(buf, os.Stdin, int64(bufSize))
+ if errors.Is(err, io.EOF) {
+ p.Payload = buf.Bytes()
+ out <- p
+ close(out)
+ return
+ } else if err != nil {
+ log.Println(err)
+ }
+ p.Payload = buf.Bytes()
+ out <- p
+ }
+ }()
+
+ ticker := time.NewTicker(packetInterval)
+ for range ticker.C {
+ p, ok := <-out
+ if !ok {
+ return
+ }
+ if err := session.Transmit(&p); err != nil {
+ log.Println(err)
+ }
+ }
+}