streaming

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

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 }