golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
2.7 KiB
132 lines
2.7 KiB
package rtmputils |
|
|
|
import ( |
|
"fmt" |
|
"time" |
|
|
|
"github.com/aler9/gortsplib" |
|
"github.com/notedit/rtmp/av" |
|
"github.com/notedit/rtmp/codec/h264" |
|
"github.com/notedit/rtmp/format/flv/flvio" |
|
"github.com/notedit/rtmp/format/rtmp" |
|
) |
|
|
|
const ( |
|
codecH264 = 7 |
|
codecAAC = 10 |
|
) |
|
|
|
func readMetadata(rconn *rtmp.Conn) (flvio.AMFMap, error) { |
|
pkt, err := rconn.ReadPacket() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if pkt.Type != av.Metadata { |
|
return nil, fmt.Errorf("first packet must be metadata") |
|
} |
|
|
|
arr, err := flvio.ParseAMFVals(pkt.Data, false) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if len(arr) != 1 { |
|
return nil, fmt.Errorf("invalid metadata") |
|
} |
|
|
|
ma, ok := arr[0].(flvio.AMFMap) |
|
if !ok { |
|
return nil, fmt.Errorf("invalid metadata") |
|
} |
|
|
|
return ma, nil |
|
} |
|
|
|
// Metadata extracts track informations from a RTMP connection that is publishing. |
|
func Metadata(conn ConnPair, readTimeout time.Duration) ( |
|
*gortsplib.Track, *gortsplib.Track, error) { |
|
var videoTrack *gortsplib.Track |
|
var audioTrack *gortsplib.Track |
|
|
|
// configuration must be completed within readTimeout |
|
conn.NConn.SetReadDeadline(time.Now().Add(readTimeout)) |
|
|
|
md, err := readMetadata(conn.RConn) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
hasVideo := false |
|
if v, ok := md.GetFloat64("videocodecid"); ok { |
|
switch v { |
|
case codecH264: |
|
hasVideo = true |
|
case 0: |
|
default: |
|
return nil, nil, fmt.Errorf("unsupported video codec %v", v) |
|
} |
|
|
|
} |
|
|
|
hasAudio := false |
|
if v, ok := md.GetFloat64("audiocodecid"); ok { |
|
switch v { |
|
case codecAAC: |
|
hasAudio = true |
|
case 0: |
|
default: |
|
return nil, nil, fmt.Errorf("unsupported audio codec %v", v) |
|
} |
|
} |
|
|
|
if !hasVideo && !hasAudio { |
|
return nil, nil, fmt.Errorf("stream has no tracks") |
|
} |
|
|
|
for { |
|
var pkt av.Packet |
|
pkt, err = conn.RConn.ReadPacket() |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
switch pkt.Type { |
|
case av.H264DecoderConfig: |
|
if !hasVideo { |
|
return nil, nil, fmt.Errorf("unexpected video packet") |
|
} |
|
if videoTrack != nil { |
|
return nil, nil, fmt.Errorf("video track setupped twice") |
|
} |
|
|
|
codec, err := h264.FromDecoderConfig(pkt.Data) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
videoTrack, err = gortsplib.NewTrackH264(96, codec.SPS[0], codec.PPS[0]) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
case av.AACDecoderConfig: |
|
if !hasAudio { |
|
return nil, nil, fmt.Errorf("unexpected audio packet") |
|
} |
|
if audioTrack != nil { |
|
return nil, nil, fmt.Errorf("audio track setupped twice") |
|
} |
|
|
|
audioTrack, err = gortsplib.NewTrackAAC(96, pkt.Data) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
} |
|
|
|
if (!hasVideo || videoTrack != nil) && |
|
(!hasAudio || audioTrack != nil) { |
|
return videoTrack, audioTrack, nil |
|
} |
|
} |
|
}
|
|
|