10 changed files with 878 additions and 636 deletions
@ -0,0 +1,724 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"log" |
||||||
|
"net" |
||||||
|
"net/url" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"rtsp-server/rtsp" |
||||||
|
|
||||||
|
"gortc.io/sdp" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
errTeardown = errors.New("teardown") |
||||||
|
errPlay = errors.New("play") |
||||||
|
errRecord = errors.New("record") |
||||||
|
) |
||||||
|
|
||||||
|
func interleavedChannelToTrack(channel int) (trackFlow, int) { |
||||||
|
if (channel % 2) == 0 { |
||||||
|
return _TRACK_FLOW_RTP, (channel / 2) |
||||||
|
} |
||||||
|
return _TRACK_FLOW_RTCP, ((channel - 1) / 2) |
||||||
|
} |
||||||
|
|
||||||
|
func trackToInterleavedChannel(flow trackFlow, id int) int { |
||||||
|
if flow == _TRACK_FLOW_RTP { |
||||||
|
return id * 2 |
||||||
|
} |
||||||
|
return (id * 2) + 1 |
||||||
|
} |
||||||
|
|
||||||
|
type transportHeader map[string]struct{} |
||||||
|
|
||||||
|
func newTransportHeader(in string) transportHeader { |
||||||
|
th := make(map[string]struct{}) |
||||||
|
for _, t := range strings.Split(in, ";") { |
||||||
|
th[t] = struct{}{} |
||||||
|
} |
||||||
|
return th |
||||||
|
} |
||||||
|
|
||||||
|
func (th transportHeader) getKeyValue(key string) string { |
||||||
|
prefix := key + "=" |
||||||
|
for t := range th { |
||||||
|
if strings.HasPrefix(t, prefix) { |
||||||
|
return t[len(prefix):] |
||||||
|
} |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func (th transportHeader) getClientPorts() (int, int) { |
||||||
|
val := th.getKeyValue("client_port") |
||||||
|
if val == "" { |
||||||
|
return 0, 0 |
||||||
|
} |
||||||
|
|
||||||
|
ports := strings.Split(val, "-") |
||||||
|
if len(ports) != 2 { |
||||||
|
return 0, 0 |
||||||
|
} |
||||||
|
|
||||||
|
port1, err := strconv.ParseInt(ports[0], 10, 64) |
||||||
|
if err != nil { |
||||||
|
return 0, 0 |
||||||
|
} |
||||||
|
|
||||||
|
port2, err := strconv.ParseInt(ports[1], 10, 64) |
||||||
|
if err != nil { |
||||||
|
return 0, 0 |
||||||
|
} |
||||||
|
|
||||||
|
return int(port1), int(port2) |
||||||
|
} |
||||||
|
|
||||||
|
type client struct { |
||||||
|
p *program |
||||||
|
rconn *rtsp.Conn |
||||||
|
state string |
||||||
|
ip net.IP |
||||||
|
streamSdpText []byte // filled only if publisher
|
||||||
|
streamSdpParsed *sdp.Message // filled only if publisher
|
||||||
|
streamProtocol streamProtocol |
||||||
|
streamTracks []*track |
||||||
|
} |
||||||
|
|
||||||
|
func newRtspClient(p *program, nconn net.Conn) *client { |
||||||
|
c := &client{ |
||||||
|
p: p, |
||||||
|
rconn: rtsp.NewConn(nconn), |
||||||
|
state: "STARTING", |
||||||
|
} |
||||||
|
|
||||||
|
c.p.mutex.Lock() |
||||||
|
c.p.clients[c] = struct{}{} |
||||||
|
c.p.mutex.Unlock() |
||||||
|
|
||||||
|
return c |
||||||
|
} |
||||||
|
|
||||||
|
func (c *client) close() error { |
||||||
|
// already deleted
|
||||||
|
if _, ok := c.p.clients[c]; !ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
delete(c.p.clients, c) |
||||||
|
c.rconn.Close() |
||||||
|
|
||||||
|
if c.p.publisher == c { |
||||||
|
c.p.publisher = nil |
||||||
|
|
||||||
|
// if the publisher has disconnected
|
||||||
|
// close all other connections
|
||||||
|
for oc := range c.p.clients { |
||||||
|
oc.close() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *client) log(format string, args ...interface{}) { |
||||||
|
format = "[RTSP client " + c.rconn.RemoteAddr().String() + "] " + format |
||||||
|
log.Printf(format, args...) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *client) run() { |
||||||
|
defer c.log("disconnected") |
||||||
|
defer func() { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
c.close() |
||||||
|
}() |
||||||
|
|
||||||
|
ipstr, _, _ := net.SplitHostPort(c.rconn.RemoteAddr().String()) |
||||||
|
c.ip = net.ParseIP(ipstr) |
||||||
|
|
||||||
|
c.log("connected") |
||||||
|
|
||||||
|
for { |
||||||
|
req, err := c.rconn.ReadRequest() |
||||||
|
if err != nil { |
||||||
|
if err != io.EOF { |
||||||
|
c.log("ERR: %s", err) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
c.log(req.Method) |
||||||
|
|
||||||
|
res, err := c.handleRequest(req) |
||||||
|
|
||||||
|
switch err { |
||||||
|
// normal response
|
||||||
|
case nil: |
||||||
|
err = c.rconn.WriteResponse(res) |
||||||
|
if err != nil { |
||||||
|
c.log("ERR: %s", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// TEARDOWN, close connection silently
|
||||||
|
case errTeardown: |
||||||
|
return |
||||||
|
|
||||||
|
// PLAY: first write response, then set state
|
||||||
|
// otherwise, in case of TCP connections, RTP packets could be written
|
||||||
|
// before the response
|
||||||
|
// then switch to RTP if TCP
|
||||||
|
case errPlay: |
||||||
|
err = c.rconn.WriteResponse(res) |
||||||
|
if err != nil { |
||||||
|
c.log("ERR: %s", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
c.log("is receiving %d %s via %s", len(c.streamTracks), func() string { |
||||||
|
if len(c.streamTracks) == 1 { |
||||||
|
return "track" |
||||||
|
} |
||||||
|
return "tracks" |
||||||
|
}(), c.streamProtocol) |
||||||
|
|
||||||
|
c.p.mutex.Lock() |
||||||
|
c.state = "PLAY" |
||||||
|
c.p.mutex.Unlock() |
||||||
|
|
||||||
|
// when protocol is TCP, the RTSP connection becomes a RTP connection
|
||||||
|
// receive RTP feedback, do not parse it, wait until connection closes
|
||||||
|
if c.streamProtocol == _STREAM_PROTOCOL_TCP { |
||||||
|
buf := make([]byte, 2048) |
||||||
|
for { |
||||||
|
_, err := c.rconn.Read(buf) |
||||||
|
if err != nil { |
||||||
|
if err != io.EOF { |
||||||
|
c.log("ERR: %s", err) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RECORD: switch to RTP if TCP
|
||||||
|
case errRecord: |
||||||
|
err = c.rconn.WriteResponse(res) |
||||||
|
if err != nil { |
||||||
|
c.log("ERR: %s", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
c.p.mutex.Lock() |
||||||
|
c.state = "RECORD" |
||||||
|
c.p.mutex.Unlock() |
||||||
|
|
||||||
|
c.log("is publishing %d %s via %s", len(c.streamTracks), func() string { |
||||||
|
if len(c.streamTracks) == 1 { |
||||||
|
return "track" |
||||||
|
} |
||||||
|
return "tracks" |
||||||
|
}(), c.streamProtocol) |
||||||
|
|
||||||
|
// when protocol is TCP, the RTSP connection becomes a RTP connection
|
||||||
|
// receive RTP data and parse it
|
||||||
|
if c.streamProtocol == _STREAM_PROTOCOL_TCP { |
||||||
|
buf := make([]byte, 2048) |
||||||
|
for { |
||||||
|
channel, n, err := c.rconn.ReadInterleavedFrame(buf) |
||||||
|
if err != nil { |
||||||
|
if _, ok := err.(*net.OpError); ok { |
||||||
|
} else if err == io.EOF { |
||||||
|
} else { |
||||||
|
c.log("ERR: %s", err) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
trackFlow, trackId := interleavedChannelToTrack(channel) |
||||||
|
|
||||||
|
if trackId >= len(c.streamTracks) { |
||||||
|
c.log("ERR: invalid track id '%d'", trackId) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
c.p.mutex.RLock() |
||||||
|
c.p.forwardTrack(trackFlow, trackId, buf[:n]) |
||||||
|
c.p.mutex.RUnlock() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// error: write and exit
|
||||||
|
default: |
||||||
|
c.log("ERR: %s", err) |
||||||
|
|
||||||
|
if cseq, ok := req.Headers["CSeq"]; ok { |
||||||
|
c.rconn.WriteResponse(&rtsp.Response{ |
||||||
|
StatusCode: 400, |
||||||
|
Status: "Bad Request", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} else { |
||||||
|
c.rconn.WriteResponse(&rtsp.Response{ |
||||||
|
StatusCode: 400, |
||||||
|
Status: "Bad Request", |
||||||
|
}) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *client) handleRequest(req *rtsp.Request) (*rtsp.Response, error) { |
||||||
|
cseq, ok := req.Headers["CSeq"] |
||||||
|
if !ok { |
||||||
|
return nil, fmt.Errorf("cseq missing") |
||||||
|
} |
||||||
|
|
||||||
|
ur, err := url.Parse(req.Path) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("unable to parse path '%s'", req.Path) |
||||||
|
} |
||||||
|
|
||||||
|
switch req.Method { |
||||||
|
case "OPTIONS": |
||||||
|
// do not check state, since OPTIONS can be requested
|
||||||
|
// in any state
|
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Public": strings.Join([]string{ |
||||||
|
"DESCRIBE", |
||||||
|
"ANNOUNCE", |
||||||
|
"SETUP", |
||||||
|
"PLAY", |
||||||
|
"PAUSE", |
||||||
|
"RECORD", |
||||||
|
"TEARDOWN", |
||||||
|
}, ", "), |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
case "DESCRIBE": |
||||||
|
if c.state != "STARTING" { |
||||||
|
return nil, fmt.Errorf("client is in state '%s'", c.state) |
||||||
|
} |
||||||
|
|
||||||
|
sdp, err := func() ([]byte, error) { |
||||||
|
c.p.mutex.RLock() |
||||||
|
defer c.p.mutex.RUnlock() |
||||||
|
|
||||||
|
if c.p.publisher == nil { |
||||||
|
return nil, fmt.Errorf("no one is streaming") |
||||||
|
} |
||||||
|
|
||||||
|
return c.p.publisher.streamSdpText, nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Content-Base": ur.String(), |
||||||
|
"Content-Type": "application/sdp", |
||||||
|
}, |
||||||
|
Content: sdp, |
||||||
|
}, nil |
||||||
|
|
||||||
|
case "ANNOUNCE": |
||||||
|
if c.state != "STARTING" { |
||||||
|
return nil, fmt.Errorf("client is in state '%s'", c.state) |
||||||
|
} |
||||||
|
|
||||||
|
ct, ok := req.Headers["Content-Type"] |
||||||
|
if !ok { |
||||||
|
return nil, fmt.Errorf("Content-Type header missing") |
||||||
|
} |
||||||
|
|
||||||
|
if ct != "application/sdp" { |
||||||
|
return nil, fmt.Errorf("unsupported Content-Type '%s'", ct) |
||||||
|
} |
||||||
|
|
||||||
|
sdpParsed, err := func() (*sdp.Message, error) { |
||||||
|
s, err := sdp.DecodeSession(req.Content, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
m := &sdp.Message{} |
||||||
|
d := sdp.NewDecoder(s) |
||||||
|
err = d.Decode(m) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return m, nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("invalid SDP: %s", err) |
||||||
|
} |
||||||
|
|
||||||
|
err = func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if c.p.publisher != nil { |
||||||
|
return fmt.Errorf("another client is already streaming") |
||||||
|
} |
||||||
|
|
||||||
|
c.p.publisher = c |
||||||
|
c.streamSdpText = req.Content |
||||||
|
c.streamSdpParsed = sdpParsed |
||||||
|
c.state = "ANNOUNCE" |
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
case "SETUP": |
||||||
|
transportstr, ok := req.Headers["Transport"] |
||||||
|
if !ok { |
||||||
|
return nil, fmt.Errorf("transport header missing") |
||||||
|
} |
||||||
|
|
||||||
|
th := newTransportHeader(transportstr) |
||||||
|
|
||||||
|
if _, ok := th["unicast"]; !ok { |
||||||
|
return nil, fmt.Errorf("transport header does not contain unicast") |
||||||
|
} |
||||||
|
|
||||||
|
switch c.state { |
||||||
|
// play
|
||||||
|
case "STARTING", "PRE_PLAY": |
||||||
|
err := func() error { |
||||||
|
c.p.mutex.RLock() |
||||||
|
defer c.p.mutex.RUnlock() |
||||||
|
|
||||||
|
if c.p.publisher == nil { |
||||||
|
return fmt.Errorf("no one is streaming") |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// play via UDP
|
||||||
|
if _, ok := th["RTP/AVP"]; ok { |
||||||
|
rtpPort, rtcpPort := th.getClientPorts() |
||||||
|
if rtpPort == 0 || rtcpPort == 0 { |
||||||
|
return nil, fmt.Errorf("transport header does not have valid client ports (%s)", transportstr) |
||||||
|
} |
||||||
|
|
||||||
|
err = func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP { |
||||||
|
return fmt.Errorf("client want to send tracks with different protocols") |
||||||
|
} |
||||||
|
|
||||||
|
if len(c.streamTracks) >= len(c.p.publisher.streamSdpParsed.Medias) { |
||||||
|
return fmt.Errorf("all the tracks have already been setup") |
||||||
|
} |
||||||
|
|
||||||
|
c.streamProtocol = _STREAM_PROTOCOL_UDP |
||||||
|
c.streamTracks = append(c.streamTracks, &track{ |
||||||
|
rtpPort: rtpPort, |
||||||
|
rtcpPort: rtcpPort, |
||||||
|
}) |
||||||
|
|
||||||
|
c.state = "PRE_PLAY" |
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Transport": strings.Join([]string{ |
||||||
|
"RTP/AVP", |
||||||
|
"unicast", |
||||||
|
fmt.Sprintf("client_port=%d-%d", rtpPort, rtcpPort), |
||||||
|
// use two fake server ports, since we do not want to receive feedback
|
||||||
|
// from the client
|
||||||
|
fmt.Sprintf("server_port=%d-%d", c.p.rtpPort+2, c.p.rtcpPort+2), |
||||||
|
"ssrc=1234ABCD", |
||||||
|
}, ";"), |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
// play via TCP
|
||||||
|
} else if _, ok := th["RTP/AVP/TCP"]; ok { |
||||||
|
err = func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP { |
||||||
|
return fmt.Errorf("client want to send tracks with different protocols") |
||||||
|
} |
||||||
|
|
||||||
|
if len(c.streamTracks) >= len(c.p.publisher.streamSdpParsed.Medias) { |
||||||
|
return fmt.Errorf("all the tracks have already been setup") |
||||||
|
} |
||||||
|
|
||||||
|
c.streamProtocol = _STREAM_PROTOCOL_TCP |
||||||
|
c.streamTracks = append(c.streamTracks, &track{ |
||||||
|
rtpPort: 0, |
||||||
|
rtcpPort: 0, |
||||||
|
}) |
||||||
|
|
||||||
|
c.state = "PRE_PLAY" |
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
interleaved := fmt.Sprintf("%d-%d", ((len(c.streamTracks) - 1) * 2), ((len(c.streamTracks)-1)*2)+1) |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Transport": strings.Join([]string{ |
||||||
|
"RTP/AVP/TCP", |
||||||
|
"unicast", |
||||||
|
fmt.Sprintf("interleaved=%s", interleaved), |
||||||
|
}, ";"), |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
} else { |
||||||
|
return nil, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transportstr) |
||||||
|
} |
||||||
|
|
||||||
|
// record
|
||||||
|
case "ANNOUNCE", "PRE_RECORD": |
||||||
|
if _, ok := th["mode=record"]; !ok { |
||||||
|
return nil, fmt.Errorf("transport header does not contain mode=record") |
||||||
|
} |
||||||
|
|
||||||
|
// record via UDP
|
||||||
|
if _, ok := th["RTP/AVP/UDP"]; ok { |
||||||
|
rtpPort, rtcpPort := th.getClientPorts() |
||||||
|
if rtpPort == 0 || rtcpPort == 0 { |
||||||
|
return nil, fmt.Errorf("transport header does not have valid client ports (%s)", transportstr) |
||||||
|
} |
||||||
|
|
||||||
|
err = func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP { |
||||||
|
return fmt.Errorf("client want to send tracks with different protocols") |
||||||
|
} |
||||||
|
|
||||||
|
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) { |
||||||
|
return fmt.Errorf("all the tracks have already been setup") |
||||||
|
} |
||||||
|
|
||||||
|
c.streamProtocol = _STREAM_PROTOCOL_UDP |
||||||
|
c.streamTracks = append(c.streamTracks, &track{ |
||||||
|
rtpPort: rtpPort, |
||||||
|
rtcpPort: rtcpPort, |
||||||
|
}) |
||||||
|
|
||||||
|
c.state = "PRE_RECORD" |
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Transport": strings.Join([]string{ |
||||||
|
"RTP/AVP", |
||||||
|
"unicast", |
||||||
|
fmt.Sprintf("client_port=%d-%d", rtpPort, rtcpPort), |
||||||
|
fmt.Sprintf("server_port=%d-%d", c.p.rtpPort, c.p.rtcpPort), |
||||||
|
"ssrc=1234ABCD", |
||||||
|
}, ";"), |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
// record via TCP
|
||||||
|
} else if _, ok := th["RTP/AVP/TCP"]; ok { |
||||||
|
var interleaved string |
||||||
|
err = func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP { |
||||||
|
return fmt.Errorf("client want to send tracks with different protocols") |
||||||
|
} |
||||||
|
|
||||||
|
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) { |
||||||
|
return fmt.Errorf("all the tracks have already been setup") |
||||||
|
} |
||||||
|
|
||||||
|
interleaved = th.getKeyValue("interleaved") |
||||||
|
if interleaved == "" { |
||||||
|
return fmt.Errorf("transport header does not contain interleaved field") |
||||||
|
} |
||||||
|
|
||||||
|
expInterleaved := fmt.Sprintf("%d-%d", 0+len(c.streamTracks)*2, 1+len(c.streamTracks)*2) |
||||||
|
if interleaved != expInterleaved { |
||||||
|
return fmt.Errorf("wrong interleaved value, expected '%s', got '%s'", expInterleaved, interleaved) |
||||||
|
} |
||||||
|
|
||||||
|
c.streamProtocol = _STREAM_PROTOCOL_TCP |
||||||
|
c.streamTracks = append(c.streamTracks, &track{ |
||||||
|
rtpPort: 0, |
||||||
|
rtcpPort: 0, |
||||||
|
}) |
||||||
|
|
||||||
|
c.state = "PRE_RECORD" |
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Transport": strings.Join([]string{ |
||||||
|
"RTP/AVP/TCP", |
||||||
|
"unicast", |
||||||
|
fmt.Sprintf("interleaved=%s", interleaved), |
||||||
|
}, ";"), |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
} else { |
||||||
|
return nil, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transportstr) |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
return nil, fmt.Errorf("client is in state '%s'", c.state) |
||||||
|
} |
||||||
|
|
||||||
|
case "PLAY": |
||||||
|
if c.state != "PRE_PLAY" { |
||||||
|
return nil, fmt.Errorf("client is in state '%s'", c.state) |
||||||
|
} |
||||||
|
|
||||||
|
err := func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if len(c.streamTracks) != len(c.p.publisher.streamSdpParsed.Medias) { |
||||||
|
return fmt.Errorf("not all tracks have been setup") |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, errPlay |
||||||
|
|
||||||
|
case "PAUSE": |
||||||
|
if c.state != "PLAY" { |
||||||
|
return nil, fmt.Errorf("client is in state '%s'", c.state) |
||||||
|
} |
||||||
|
|
||||||
|
c.log("paused") |
||||||
|
|
||||||
|
c.p.mutex.Lock() |
||||||
|
c.state = "PRE_PLAY" |
||||||
|
c.p.mutex.Unlock() |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
|
||||||
|
case "RECORD": |
||||||
|
if c.state != "PRE_RECORD" { |
||||||
|
return nil, fmt.Errorf("client is in state '%s'", c.state) |
||||||
|
} |
||||||
|
|
||||||
|
err := func() error { |
||||||
|
c.p.mutex.Lock() |
||||||
|
defer c.p.mutex.Unlock() |
||||||
|
|
||||||
|
if len(c.streamTracks) != len(c.streamSdpParsed.Medias) { |
||||||
|
return fmt.Errorf("not all tracks have been setup") |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
}() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &rtsp.Response{ |
||||||
|
StatusCode: 200, |
||||||
|
Status: "OK", |
||||||
|
Headers: map[string]string{ |
||||||
|
"CSeq": cseq, |
||||||
|
"Session": "12345678", |
||||||
|
}, |
||||||
|
}, errRecord |
||||||
|
|
||||||
|
case "TEARDOWN": |
||||||
|
return nil, errTeardown |
||||||
|
|
||||||
|
default: |
||||||
|
return nil, fmt.Errorf("unhandled method '%s'", req.Method) |
||||||
|
} |
||||||
|
} |
@ -1,540 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"log" |
|
||||||
"net" |
|
||||||
"net/url" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"rtsp-server/rtsp" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
errTeardown = errors.New("teardown") |
|
||||||
errPlay = errors.New("play") |
|
||||||
errRecord = errors.New("record") |
|
||||||
) |
|
||||||
|
|
||||||
type rtspClient struct { |
|
||||||
p *program |
|
||||||
rconn *rtsp.Conn |
|
||||||
state string |
|
||||||
IP net.IP |
|
||||||
rtpProto string |
|
||||||
rtpPort int |
|
||||||
rtcpPort int |
|
||||||
} |
|
||||||
|
|
||||||
func newRtspClient(p *program, nconn net.Conn) *rtspClient { |
|
||||||
c := &rtspClient{ |
|
||||||
p: p, |
|
||||||
rconn: rtsp.NewConn(nconn), |
|
||||||
state: "STARTING", |
|
||||||
} |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.p.clients[c] = struct{}{} |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return c |
|
||||||
} |
|
||||||
|
|
||||||
func (c *rtspClient) close() error { |
|
||||||
// already deleted
|
|
||||||
if _, ok := c.p.clients[c]; !ok { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
delete(c.p.clients, c) |
|
||||||
c.rconn.Close() |
|
||||||
|
|
||||||
if c.p.streamAuthor == c { |
|
||||||
c.p.streamAuthor = nil |
|
||||||
c.p.streamSdp = nil |
|
||||||
|
|
||||||
// if the publisher has disconnected
|
|
||||||
// close all other connections
|
|
||||||
for oc := range c.p.clients { |
|
||||||
oc.close() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *rtspClient) log(format string, args ...interface{}) { |
|
||||||
format = "[RTSP client " + c.rconn.RemoteAddr().String() + "] " + format |
|
||||||
log.Printf(format, args...) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *rtspClient) run() { |
|
||||||
defer c.log("disconnected") |
|
||||||
defer func() { |
|
||||||
c.p.mutex.Lock() |
|
||||||
defer c.p.mutex.Unlock() |
|
||||||
c.close() |
|
||||||
}() |
|
||||||
|
|
||||||
ipstr, _, _ := net.SplitHostPort(c.rconn.RemoteAddr().String()) |
|
||||||
c.IP = net.ParseIP(ipstr) |
|
||||||
|
|
||||||
c.log("connected") |
|
||||||
|
|
||||||
for { |
|
||||||
req, err := c.rconn.ReadRequest() |
|
||||||
if err != nil { |
|
||||||
if err != io.EOF { |
|
||||||
c.log("ERR: %s", err) |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
c.log(req.Method) |
|
||||||
|
|
||||||
res, err := c.handleRequest(req) |
|
||||||
|
|
||||||
switch err { |
|
||||||
// normal response
|
|
||||||
case nil: |
|
||||||
err = c.rconn.WriteResponse(res) |
|
||||||
if err != nil { |
|
||||||
c.log("ERR: %s", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// TEARDOWN, close connection silently
|
|
||||||
case errTeardown: |
|
||||||
return |
|
||||||
|
|
||||||
// PLAY: first write response, then set state
|
|
||||||
// otherwise, in case of TCP connections, RTP packets could be written
|
|
||||||
// before the response
|
|
||||||
// then switch to RTP if TCP
|
|
||||||
case errPlay: |
|
||||||
err = c.rconn.WriteResponse(res) |
|
||||||
if err != nil { |
|
||||||
c.log("ERR: %s", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
c.log("is receiving (via %s)", c.rtpProto) |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.state = "PLAY" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
// when rtp protocol is TCP, the RTSP connection becomes a RTP connection
|
|
||||||
// receive RTP feedback, do not parse it, wait until connection closes
|
|
||||||
if c.rtpProto == "tcp" { |
|
||||||
buf := make([]byte, 2048) |
|
||||||
for { |
|
||||||
_, err := c.rconn.Read(buf) |
|
||||||
if err != nil { |
|
||||||
if err != io.EOF { |
|
||||||
c.log("ERR: %s", err) |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// RECORD: switch to RTP if TCP
|
|
||||||
case errRecord: |
|
||||||
err = c.rconn.WriteResponse(res) |
|
||||||
if err != nil { |
|
||||||
c.log("ERR: %s", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.state = "RECORD" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
c.log("is publishing (via %s)", c.rtpProto) |
|
||||||
|
|
||||||
// when rtp protocol is TCP, the RTSP connection becomes a RTP connection
|
|
||||||
// receive RTP data and parse it
|
|
||||||
if c.rtpProto == "tcp" { |
|
||||||
buf := make([]byte, 2048) |
|
||||||
for { |
|
||||||
channel, n, err := c.rconn.ReadInterleavedFrame(buf) |
|
||||||
if err != nil { |
|
||||||
if err != io.EOF { |
|
||||||
c.log("ERR: %s", err) |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
switch channel { |
|
||||||
case 0: |
|
||||||
c.p.handleRtp(buf[:n]) |
|
||||||
|
|
||||||
case 1: |
|
||||||
c.p.handleRtcp(buf[:n]) |
|
||||||
|
|
||||||
default: |
|
||||||
c.log("ERR: unsupported channel '%d'", channel) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// error: write and exit
|
|
||||||
default: |
|
||||||
c.log("ERR: %s", err) |
|
||||||
|
|
||||||
if cseq, ok := req.Headers["CSeq"]; ok { |
|
||||||
c.rconn.WriteResponse(&rtsp.Response{ |
|
||||||
StatusCode: 400, |
|
||||||
Status: "Bad Request", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} else { |
|
||||||
c.rconn.WriteResponse(&rtsp.Response{ |
|
||||||
StatusCode: 400, |
|
||||||
Status: "Bad Request", |
|
||||||
}) |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *rtspClient) handleRequest(req *rtsp.Request) (*rtsp.Response, error) { |
|
||||||
cseq, ok := req.Headers["CSeq"] |
|
||||||
if !ok { |
|
||||||
return nil, fmt.Errorf("cseq missing") |
|
||||||
} |
|
||||||
|
|
||||||
ur, err := url.Parse(req.Path) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("unable to parse path '%s'", req.Path) |
|
||||||
} |
|
||||||
|
|
||||||
switch req.Method { |
|
||||||
case "OPTIONS": |
|
||||||
// do not check state, since OPTIONS can be requested
|
|
||||||
// in any state
|
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Public": strings.Join([]string{ |
|
||||||
"DESCRIBE", |
|
||||||
"ANNOUNCE", |
|
||||||
"SETUP", |
|
||||||
"PLAY", |
|
||||||
"PAUSE", |
|
||||||
"RECORD", |
|
||||||
"TEARDOWN", |
|
||||||
}, ", "), |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
case "DESCRIBE": |
|
||||||
if c.state != "STARTING" { |
|
||||||
return nil, fmt.Errorf("client is in state '%s'", c.state) |
|
||||||
} |
|
||||||
|
|
||||||
sdp, err := func() ([]byte, error) { |
|
||||||
c.p.mutex.RLock() |
|
||||||
defer c.p.mutex.RUnlock() |
|
||||||
|
|
||||||
if len(c.p.streamSdp) == 0 { |
|
||||||
return nil, fmt.Errorf("no one is streaming") |
|
||||||
} |
|
||||||
|
|
||||||
return c.p.streamSdp, nil |
|
||||||
}() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Content-Base": ur.String(), |
|
||||||
"Content-Type": "application/sdp", |
|
||||||
}, |
|
||||||
Content: sdp, |
|
||||||
}, nil |
|
||||||
|
|
||||||
case "ANNOUNCE": |
|
||||||
if c.state != "STARTING" { |
|
||||||
return nil, fmt.Errorf("client is in state '%s'", c.state) |
|
||||||
} |
|
||||||
|
|
||||||
ct, ok := req.Headers["Content-Type"] |
|
||||||
if !ok { |
|
||||||
return nil, fmt.Errorf("Content-Type header missing") |
|
||||||
} |
|
||||||
|
|
||||||
if ct != "application/sdp" { |
|
||||||
return nil, fmt.Errorf("unsupported Content-Type '%s'", ct) |
|
||||||
} |
|
||||||
|
|
||||||
err := func() error { |
|
||||||
c.p.mutex.Lock() |
|
||||||
defer c.p.mutex.Unlock() |
|
||||||
|
|
||||||
if c.p.streamAuthor != nil { |
|
||||||
return fmt.Errorf("another client is already streaming") |
|
||||||
} |
|
||||||
|
|
||||||
c.p.streamAuthor = c |
|
||||||
c.p.streamSdp = req.Content |
|
||||||
return nil |
|
||||||
}() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.state = "ANNOUNCE" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
case "SETUP": |
|
||||||
transport, ok := req.Headers["Transport"] |
|
||||||
if !ok { |
|
||||||
return nil, fmt.Errorf("transport header missing") |
|
||||||
} |
|
||||||
|
|
||||||
transports := make(map[string]struct{}) |
|
||||||
for _, t := range strings.Split(transport, ";") { |
|
||||||
transports[t] = struct{}{} |
|
||||||
} |
|
||||||
|
|
||||||
if _, ok := transports["unicast"]; !ok { |
|
||||||
return nil, fmt.Errorf("transport header does not contain unicast") |
|
||||||
} |
|
||||||
|
|
||||||
getPorts := func() (int, int) { |
|
||||||
for t := range transports { |
|
||||||
if !strings.HasPrefix(t, "client_port=") { |
|
||||||
continue |
|
||||||
} |
|
||||||
t = t[len("client_port="):] |
|
||||||
|
|
||||||
ports := strings.Split(t, "-") |
|
||||||
if len(ports) != 2 { |
|
||||||
return 0, 0 |
|
||||||
} |
|
||||||
|
|
||||||
port1, err := strconv.ParseInt(ports[0], 10, 64) |
|
||||||
if err != nil { |
|
||||||
return 0, 0 |
|
||||||
} |
|
||||||
|
|
||||||
port2, err := strconv.ParseInt(ports[1], 10, 64) |
|
||||||
if err != nil { |
|
||||||
return 0, 0 |
|
||||||
} |
|
||||||
|
|
||||||
return int(port1), int(port2) |
|
||||||
} |
|
||||||
return 0, 0 |
|
||||||
} |
|
||||||
|
|
||||||
switch c.state { |
|
||||||
// play
|
|
||||||
case "STARTING": |
|
||||||
// UDP
|
|
||||||
if _, ok := transports["RTP/AVP"]; ok { |
|
||||||
clientPort1, clientPort2 := getPorts() |
|
||||||
if clientPort1 == 0 || clientPort2 == 0 { |
|
||||||
return nil, fmt.Errorf("transport header does not have valid client ports (%s)", transport) |
|
||||||
} |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.rtpProto = "udp" |
|
||||||
c.rtpPort = clientPort1 |
|
||||||
c.rtcpPort = clientPort2 |
|
||||||
c.state = "PRE_PLAY" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Transport": strings.Join([]string{ |
|
||||||
"RTP/AVP", |
|
||||||
"unicast", |
|
||||||
fmt.Sprintf("client_port=%d-%d", clientPort1, clientPort2), |
|
||||||
// use two fake server ports, since we do not want to receive feedback
|
|
||||||
// from the client
|
|
||||||
fmt.Sprintf("server_port=%d-%d", c.p.rtpPort+2, c.p.rtcpPort+2), |
|
||||||
"ssrc=1234ABCD", |
|
||||||
}, ";"), |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
// TCP
|
|
||||||
} else if _, ok := transports["RTP/AVP/TCP"]; ok { |
|
||||||
c.p.mutex.Lock() |
|
||||||
c.rtpProto = "tcp" |
|
||||||
c.state = "PRE_PLAY" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Transport": strings.Join([]string{ |
|
||||||
"RTP/AVP/TCP", |
|
||||||
"unicast", |
|
||||||
"destination=127.0.0.1", |
|
||||||
"source=127.0.0.1", |
|
||||||
"interleaved=0-1", |
|
||||||
}, ";"), |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
} else { |
|
||||||
return nil, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transport) |
|
||||||
} |
|
||||||
|
|
||||||
// record
|
|
||||||
case "ANNOUNCE": |
|
||||||
if _, ok := transports["mode=record"]; !ok { |
|
||||||
return nil, fmt.Errorf("transport header does not contain mode=record") |
|
||||||
} |
|
||||||
|
|
||||||
if _, ok := transports["RTP/AVP/UDP"]; ok { |
|
||||||
clientPort1, clientPort2 := getPorts() |
|
||||||
if clientPort1 == 0 || clientPort2 == 0 { |
|
||||||
return nil, fmt.Errorf("transport header does not have valid client ports (%s)", transport) |
|
||||||
} |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.rtpProto = "udp" |
|
||||||
c.rtpPort = clientPort1 |
|
||||||
c.rtcpPort = clientPort2 |
|
||||||
c.state = "PRE_RECORD" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Transport": strings.Join([]string{ |
|
||||||
"RTP/AVP", |
|
||||||
"unicast", |
|
||||||
fmt.Sprintf("client_port=%d-%d", clientPort1, clientPort2), |
|
||||||
fmt.Sprintf("server_port=%d-%d", c.p.rtpPort, c.p.rtcpPort), |
|
||||||
"ssrc=1234ABCD", |
|
||||||
}, ";"), |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
} else if _, ok := transports["RTP/AVP/TCP"]; ok { |
|
||||||
if _, ok := transports["interleaved=0-1"]; !ok { |
|
||||||
return nil, fmt.Errorf("transport header does not contain interleaved=0-1") |
|
||||||
} |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.rtpProto = "tcp" |
|
||||||
c.state = "PRE_RECORD" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Transport": strings.Join([]string{ |
|
||||||
"RTP/AVP/TCP", |
|
||||||
"unicast", |
|
||||||
"destination=127.0.0.1", |
|
||||||
"source=127.0.0.1", |
|
||||||
}, ";"), |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
} else { |
|
||||||
return nil, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP or RTP/AVP/TCP) (%s)", transport) |
|
||||||
} |
|
||||||
|
|
||||||
default: |
|
||||||
return nil, fmt.Errorf("client is in state '%s'", c.state) |
|
||||||
} |
|
||||||
|
|
||||||
case "PLAY": |
|
||||||
if c.state != "PRE_PLAY" { |
|
||||||
return nil, fmt.Errorf("client is in state '%s'", c.state) |
|
||||||
} |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, errPlay |
|
||||||
|
|
||||||
case "PAUSE": |
|
||||||
if c.state != "PLAY" { |
|
||||||
return nil, fmt.Errorf("client is in state '%s'", c.state) |
|
||||||
} |
|
||||||
|
|
||||||
c.log("paused receiving") |
|
||||||
|
|
||||||
c.p.mutex.Lock() |
|
||||||
c.state = "PRE_PLAY" |
|
||||||
c.p.mutex.Unlock() |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, nil |
|
||||||
|
|
||||||
case "RECORD": |
|
||||||
if c.state != "PRE_RECORD" { |
|
||||||
return nil, fmt.Errorf("client is in state '%s'", c.state) |
|
||||||
} |
|
||||||
|
|
||||||
return &rtsp.Response{ |
|
||||||
StatusCode: 200, |
|
||||||
Status: "OK", |
|
||||||
Headers: map[string]string{ |
|
||||||
"CSeq": cseq, |
|
||||||
"Session": "12345678", |
|
||||||
}, |
|
||||||
}, errRecord |
|
||||||
|
|
||||||
case "TEARDOWN": |
|
||||||
return nil, errTeardown |
|
||||||
|
|
||||||
default: |
|
||||||
return nil, fmt.Errorf("unhandled method '%s'", req.Method) |
|
||||||
} |
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"log" |
|
||||||
"net" |
|
||||||
) |
|
||||||
|
|
||||||
type udpListener struct { |
|
||||||
nconn *net.UDPConn |
|
||||||
logPrefix string |
|
||||||
cb func([]byte) |
|
||||||
} |
|
||||||
|
|
||||||
func newUdpListener(port int, logPrefix string, cb func([]byte)) (*udpListener, error) { |
|
||||||
nconn, err := net.ListenUDP("udp", &net.UDPAddr{ |
|
||||||
Port: port, |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
l := &udpListener{ |
|
||||||
nconn: nconn, |
|
||||||
logPrefix: logPrefix, |
|
||||||
cb: cb, |
|
||||||
} |
|
||||||
|
|
||||||
l.log("opened on :%d", port) |
|
||||||
return l, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (l *udpListener) log(format string, args ...interface{}) { |
|
||||||
log.Printf("["+l.logPrefix+" listener] "+format, args...) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *udpListener) run() { |
|
||||||
buf := make([]byte, 2048) // UDP MTU is 1400
|
|
||||||
|
|
||||||
for { |
|
||||||
n, _, err := l.nconn.ReadFromUDP(buf) |
|
||||||
if err != nil { |
|
||||||
l.log("ERR: %s", err) |
|
||||||
break |
|
||||||
} |
|
||||||
|
|
||||||
l.cb(buf[:n]) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,90 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"log" |
||||||
|
"net" |
||||||
|
) |
||||||
|
|
||||||
|
type udpListener struct { |
||||||
|
p *program |
||||||
|
nconn *net.UDPConn |
||||||
|
flow trackFlow |
||||||
|
} |
||||||
|
|
||||||
|
func newUdpListener(p *program, port int, flow trackFlow) (*udpListener, error) { |
||||||
|
nconn, err := net.ListenUDP("udp", &net.UDPAddr{ |
||||||
|
Port: port, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
l := &udpListener{ |
||||||
|
p: p, |
||||||
|
nconn: nconn, |
||||||
|
flow: flow, |
||||||
|
} |
||||||
|
|
||||||
|
l.log("opened on :%d", port) |
||||||
|
return l, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *udpListener) log(format string, args ...interface{}) { |
||||||
|
var label string |
||||||
|
if l.flow == _TRACK_FLOW_RTP { |
||||||
|
label = "RTP" |
||||||
|
} else { |
||||||
|
label = "RTCP" |
||||||
|
} |
||||||
|
log.Printf("["+label+" listener] "+format, args...) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *udpListener) run() { |
||||||
|
buf := make([]byte, 2048) // UDP MTU is 1400
|
||||||
|
|
||||||
|
for { |
||||||
|
n, addr, err := l.nconn.ReadFromUDP(buf) |
||||||
|
if err != nil { |
||||||
|
l.log("ERR: %s", err) |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
func() { |
||||||
|
l.p.mutex.RLock() |
||||||
|
defer l.p.mutex.RUnlock() |
||||||
|
|
||||||
|
if l.p.publisher == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if l.p.publisher.streamProtocol != _STREAM_PROTOCOL_UDP { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if !l.p.publisher.ip.Equal(addr.IP) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// get track id by using client port
|
||||||
|
trackId := func() int { |
||||||
|
for i, t := range l.p.publisher.streamTracks { |
||||||
|
if l.flow == _TRACK_FLOW_RTP { |
||||||
|
if t.rtpPort == addr.Port { |
||||||
|
return i |
||||||
|
} |
||||||
|
} else { |
||||||
|
if t.rtcpPort == addr.Port { |
||||||
|
return i |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
}() |
||||||
|
if trackId < 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
l.p.forwardTrack(l.flow, trackId, buf[:n]) |
||||||
|
}() |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue