streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

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:
Acmd/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) + } + } +}