Added RTSP Server functionality
This commit is contained in:
parent
822fbbf6e8
commit
4f0f595aa0
3 changed files with 199 additions and 7 deletions
24
actioncam.go
24
actioncam.go
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/jonas-koeritz/actioncam/libipcamera"
|
"github.com/jonas-koeritz/actioncam/libipcamera"
|
||||||
|
"github.com/jonas-koeritz/actioncam/rtsp"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -140,6 +141,28 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rtsp = &cobra.Command{
|
||||||
|
Use: "rtsp [Cameras IP Address]",
|
||||||
|
Short: "Start an RTSP-Server serving the cameras preview.",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
rtspServer := rtsp.CreateServer("127.0.0.1", 8554, camera)
|
||||||
|
log.Printf("Created RTSP Server\n")
|
||||||
|
err := rtspServer.ListenAndServe()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR starting RTSP Server: %s\n", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
camera = connectAndLogin(net.ParseIP(args[0]), int(port), username, password, verbose)
|
||||||
|
},
|
||||||
|
PostRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
camera.Disconnect()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var cmd = &cobra.Command{
|
var cmd = &cobra.Command{
|
||||||
Use: "cmd [RAW Command] [Cameras IP Address]",
|
Use: "cmd [RAW Command] [Cameras IP Address]",
|
||||||
Short: "Send a raw command to the camera",
|
Short: "Send a raw command to the camera",
|
||||||
|
|
@ -201,6 +224,7 @@ func main() {
|
||||||
rootCmd.AddCommand(fetch)
|
rootCmd.AddCommand(fetch)
|
||||||
rootCmd.AddCommand(record)
|
rootCmd.AddCommand(record)
|
||||||
rootCmd.AddCommand(firmware)
|
rootCmd.AddCommand(firmware)
|
||||||
|
rootCmd.AddCommand(rtsp)
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ type RTPRelay struct {
|
||||||
close bool
|
close bool
|
||||||
targetIP net.IP
|
targetIP net.IP
|
||||||
targetPort int
|
targetPort int
|
||||||
|
listener net.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRTPRelay creates a UDP listener that handles live data
|
// CreateRTPRelay creates a UDP listener that handles live data
|
||||||
// from the camera and forwards it as an RTP stream
|
// from the camera and forwards it as an RTP stream
|
||||||
func CreateRTPRelay(targetAddress net.IP, targetPort int) RTPRelay {
|
func CreateRTPRelay(targetAddress net.IP, targetPort int) *RTPRelay {
|
||||||
conn, err := net.ListenPacket("udp", ":6669")
|
conn, err := net.ListenPacket("udp", ":6669")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -28,6 +29,7 @@ func CreateRTPRelay(targetAddress net.IP, targetPort int) RTPRelay {
|
||||||
relay := RTPRelay{
|
relay := RTPRelay{
|
||||||
targetIP: targetAddress,
|
targetIP: targetAddress,
|
||||||
targetPort: targetPort,
|
targetPort: targetPort,
|
||||||
|
listener: conn,
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERROR: %s\n", err)
|
log.Printf("ERROR: %s\n", err)
|
||||||
|
|
@ -35,7 +37,7 @@ func CreateRTPRelay(targetAddress net.IP, targetPort int) RTPRelay {
|
||||||
|
|
||||||
go handleCameraStream(relay, conn)
|
go handleCameraStream(relay, conn)
|
||||||
|
|
||||||
return relay
|
return &relay
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCameraStream(relay RTPRelay, conn net.PacketConn) {
|
func handleCameraStream(relay RTPRelay, conn net.PacketConn) {
|
||||||
|
|
@ -47,8 +49,11 @@ func handleCameraStream(relay RTPRelay, conn net.PacketConn) {
|
||||||
IP: relay.targetIP,
|
IP: relay.targetIP,
|
||||||
Port: relay.targetPort,
|
Port: relay.targetPort,
|
||||||
}
|
}
|
||||||
rtpSource, _ := net.ResolveUDPAddr("udp", "127.0.0.1:5000")
|
rtpSource, _ := net.ResolveUDPAddr("udp", "127.0.0.1")
|
||||||
rtpConn, _ := net.DialUDP("udp", rtpSource, &rtpTarget)
|
rtpConn, err := net.DialUDP("udp", rtpSource, &rtpTarget)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR creating RTP sender: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
var sequenceNumber uint16
|
var sequenceNumber uint16
|
||||||
var elapsed uint32
|
var elapsed uint32
|
||||||
|
|
@ -56,6 +61,11 @@ func handleCameraStream(relay RTPRelay, conn net.PacketConn) {
|
||||||
frameBuffer := []byte{}
|
frameBuffer := []byte{}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
if relay.close {
|
||||||
|
rtpConn.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
conn.ReadFrom(buffer)
|
conn.ReadFrom(buffer)
|
||||||
packetReader := bytes.NewReader(buffer)
|
packetReader := bytes.NewReader(buffer)
|
||||||
binary.Read(packetReader, binary.BigEndian, &header)
|
binary.Read(packetReader, binary.BigEndian, &header)
|
||||||
|
|
@ -94,13 +104,12 @@ func handleCameraStream(relay RTPRelay, conn net.PacketConn) {
|
||||||
log.Printf("Received Unknown Message: %+v\n", header)
|
log.Printf("Received Unknown Message: %+v\n", header)
|
||||||
log.Printf("Payload:\n%s\n", hex.Dump(payload))
|
log.Printf("Payload:\n%s\n", hex.Dump(payload))
|
||||||
}
|
}
|
||||||
if relay.close {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
rtpConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops listening for packets
|
// Stop stops listening for packets
|
||||||
func (r *RTPRelay) Stop() {
|
func (r *RTPRelay) Stop() {
|
||||||
|
r.listener.Close()
|
||||||
r.close = true
|
r.close = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
159
rtsp/RTSPServer.go
Normal file
159
rtsp/RTSPServer.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
package rtsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jonas-koeritz/actioncam/libipcamera"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server implements the RTSP protocol to serve a H.264 stream
|
||||||
|
type Server struct {
|
||||||
|
localIP string
|
||||||
|
localPort int
|
||||||
|
listener net.Listener
|
||||||
|
remoteRTPPort int
|
||||||
|
remoteIP string
|
||||||
|
rtpRelay *libipcamera.RTPRelay
|
||||||
|
camera *libipcamera.Camera
|
||||||
|
sdp string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateServer creates a new Server instance
|
||||||
|
func CreateServer(localIP string, port int, camera *libipcamera.Camera) *Server {
|
||||||
|
server := &Server{
|
||||||
|
localIP: localIP,
|
||||||
|
localPort: port,
|
||||||
|
camera: camera,
|
||||||
|
remoteRTPPort: 0,
|
||||||
|
remoteIP: "",
|
||||||
|
sdp: "v=0\r\ns=ActionCamera\r\nm=video 0 RTP/AVP 99\r\na=rtpmap:99 H264/90000",
|
||||||
|
}
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe starts listening for connections and handles them
|
||||||
|
func (s *Server) ListenAndServe() error {
|
||||||
|
log.Printf("%+v\n", *s)
|
||||||
|
listener, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", s.localIP, s.localPort))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.listener = listener
|
||||||
|
|
||||||
|
log.Printf("RTSP Server waiting for connections on %s:%d\n", s.localIP, s.localPort)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR accepting connection: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Accepted new RTSP Client %s\n", conn.RemoteAddr().String())
|
||||||
|
|
||||||
|
go s.handleClient(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleClient(conn net.Conn) error {
|
||||||
|
packet := make([]string, 0)
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if len(line) > 0 {
|
||||||
|
packet = append(packet, line)
|
||||||
|
} else {
|
||||||
|
s.handleRequest(packet, conn)
|
||||||
|
packet = make([]string, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleRequest(packet []string, conn net.Conn) {
|
||||||
|
fmt.Printf("C->S:\n%s\n", packet)
|
||||||
|
|
||||||
|
request := strings.Split(packet[0], " ")
|
||||||
|
if len(request) != 3 {
|
||||||
|
log.Printf("Received invalid request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
method := request[0]
|
||||||
|
headers := make(map[string]string, 0)
|
||||||
|
for _, header := range packet[1:] {
|
||||||
|
parts := strings.Split(header, ":")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
headers[parts[0]] = strings.Join(parts[1:], ":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case "OPTIONS":
|
||||||
|
writeStatus(conn, 200, "OK")
|
||||||
|
replyCSeq(conn, headers)
|
||||||
|
conn.Write([]byte("Public: DESCRIBE, SETUP, PLAY, PAUSE, RECORD\r\n\r\n"))
|
||||||
|
case "DESCRIBE":
|
||||||
|
writeStatus(conn, 200, "OK")
|
||||||
|
replyCSeq(conn, headers)
|
||||||
|
writeHeader(conn, "Content-Type", "application/sdp")
|
||||||
|
writeHeader(conn, "Content-Length", fmt.Sprintf("%d", len(s.sdp)))
|
||||||
|
conn.Write([]byte(fmt.Sprintf("\r\n%s\r\n", s.sdp)))
|
||||||
|
case "SETUP":
|
||||||
|
transportDescription := strings.Split(headers["Transport"], ";")
|
||||||
|
rtpDescription := transportDescription[len(transportDescription)-1]
|
||||||
|
remoteRTPPort, err := strconv.ParseInt(strings.Split(strings.Split(rtpDescription, "=")[1], "-")[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR Parsing RTP description: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.remoteRTPPort = int(remoteRTPPort)
|
||||||
|
s.remoteIP = (conn.RemoteAddr().(*net.TCPAddr)).IP.String()
|
||||||
|
|
||||||
|
log.Printf("Preparing to Stream to %s:%d\n", s.remoteIP, s.remoteRTPPort)
|
||||||
|
|
||||||
|
writeStatus(conn, 200, "OK")
|
||||||
|
replyCSeq(conn, headers)
|
||||||
|
writeHeader(conn, "Transport", headers["Transport"]+";ssrc=0")
|
||||||
|
writeHeader(conn, "Session", "1")
|
||||||
|
conn.Write([]byte("\r\n"))
|
||||||
|
|
||||||
|
case "PLAY":
|
||||||
|
s.rtpRelay = libipcamera.CreateRTPRelay(net.ParseIP(s.remoteIP), s.remoteRTPPort)
|
||||||
|
s.camera.StartPreviewStream()
|
||||||
|
|
||||||
|
writeStatus(conn, 200, "OK")
|
||||||
|
replyCSeq(conn, headers)
|
||||||
|
writeHeader(conn, "Session", "1")
|
||||||
|
writeHeader(conn, "RTP-Info", "url="+request[1]+";seq=0;rtptime=0")
|
||||||
|
conn.Write([]byte("\r\n"))
|
||||||
|
case "TEARDOWN":
|
||||||
|
s.rtpRelay.Stop()
|
||||||
|
writeStatus(conn, 200, "OK")
|
||||||
|
replyCSeq(conn, headers)
|
||||||
|
conn.Write([]byte("\r\n"))
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStatus(conn net.Conn, status int, statusWord string) {
|
||||||
|
conn.Write([]byte(fmt.Sprintf("RTSP/1.0 %d %s\r\n", status, statusWord)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func replyCSeq(conn net.Conn, headers map[string]string) {
|
||||||
|
writeHeader(conn, "CSeq", headers["CSeq"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeHeader(conn net.Conn, key, value string) {
|
||||||
|
conn.Write([]byte(fmt.Sprintf("%s: %s\r\n", key, value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops listening for connections
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
s.listener.Close()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue