From 269202c767f372933b6f62ed57421739043aaf8d Mon Sep 17 00:00:00 2001 From: Hamdan Anwar Sayeed Date: Sat, 17 Aug 2024 14:01:55 -0500 Subject: [PATCH] feat(cmd): implemented multicast audio publishing --- README.md | 14 ++++++++++++++ cmd/lk/join.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/lk/room.go | 32 +++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/README.md b/README.md index cafb9a88..ee6aed6c 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,20 @@ lk room join --identity bot \ ``` +### Publish audio from UDP Multicast + +It's possible to publish audio in the Opus codec from a multicast packet stream over a UDP socket. `lk` can act as a multicast listener and publish receiving audio packets. For example, if you want to publish audio packets that are being sent to multicast address `225.8.11.101` and port `9001` on a network connection `en0`, + +Run `lk` like this: + +```shell +lk room join --identity bot \ + --publish-multicast \ + --multicast-endpoint 225.8.11.101:9001 \ + --multicast-network-name en0 \ + +``` + ### Publish streams from your application Using unix sockets, it's also possible to publish streams from your application. The tracks need to be encoded into diff --git a/cmd/lk/join.go b/cmd/lk/join.go index cfc761b9..da2906eb 100644 --- a/cmd/lk/join.go +++ b/cmd/lk/join.go @@ -28,6 +28,7 @@ import ( "github.com/pion/rtcp" "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v3/pkg/media" "github.com/urfave/cli/v3" provider2 "github.com/livekit/livekit-cli/pkg/provider" @@ -270,6 +271,57 @@ func publishFile(room *lksdk.Room, return err } +func publishMulticast(room *lksdk.Room, + multicastEndpoint string, + networkName string, + packetDuration int64, +) error { + track, err := lksdk.NewLocalTrack(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}) + if err != nil { + return err + } + + _, err = room.LocalParticipant.PublishTrack(track, &lksdk.TrackPublicationOptions{ + Source: livekit.TrackSource_MICROPHONE, + }) + if err != nil { + return err + } + + addr, err := net.ResolveUDPAddr("udp4", multicastEndpoint) + if err != nil { + return err + } + iface, err := net.InterfaceByName(networkName) + if err != nil { + return err + } + + conn, err := net.ListenMulticastUDP("udp4", iface, addr) + if err != nil { + return err + } + go publishMulticastPackets(conn, track, packetDuration) + return nil +} + +func publishMulticastPackets(conn *net.UDPConn, + track *lksdk.LocalTrack, + packetDuration int64, +) { + buff := make([]byte, 1500) + for { + n, _, err := conn.ReadFromUDP(buff) + if err != nil { + logger.Errorw("Read from UDP error:", err) + } + + if err = track.WriteSample(media.Sample{Data: buff[:n], Duration: time.Millisecond * time.Duration(packetDuration)}, &lksdk.SampleWriteOptions{}); err != nil { + logger.Errorw("Write sample error:", err) + } + } +} + func parseSocketFromName(name string) (string, string, string, error) { // Extract mime type, socket type, and address // e.g. h264://192.168.0.1:1234 (tcp) diff --git a/cmd/lk/room.go b/cmd/lk/room.go index 99c476d2..f34c1f83 100644 --- a/cmd/lk/room.go +++ b/cmd/lk/room.go @@ -145,6 +145,10 @@ var ( Name: "publish-demo", Usage: "Publish demo video as a loop", }, + &cli.BoolFlag{ + Name: "publish-multicast", + Usage: "Publish multicast audio in Opus codec", + }, &cli.StringSliceFlag{ Name: "publish", TakesFile: true, @@ -160,6 +164,19 @@ var ( Name: "exit-after-publish", Usage: "When publishing, exit after file or stream is complete", }, + &cli.StringFlag{ + Name: "multicast-endpoint", + Usage: "Multicast `ENDPOINT` to open the UDP socket. Should be of the format ", + }, + &cli.StringFlag{ + Name: "multicast-network-name", + Usage: "`NETWORK NAME` on which the UDP socket should be opened for multicast audio publishing", + }, + &cli.IntFlag{ + Name: "multiast-packet-duration", + Usage: "Audio packet `DURATION` in milliseconds to be published from multicast", + Value: 20, + }, }, }, { @@ -814,6 +831,21 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error { signal.Notify(done, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + if cmd.Bool("publish-multicast") { + multicastEndpoint := cmd.String("multicast-endpoint") + if multicastEndpoint == "" { + return fmt.Errorf("no multicast endpoint provided name provided") + } + networkName := cmd.String("network-name") + if networkName == "" { + return fmt.Errorf("no network name provided") + } + packetDuration := cmd.Int("packet-duration") + if err = publishMulticast(room, multicastEndpoint, networkName, packetDuration); err != nil { + return err + } + } + if cmd.Bool("publish-demo") { if err = publishDemo(room); err != nil { return err